commit eab2e7a4132a620f1cc4c5deead3443b7b517844 Author: lzyme.dev@gmail.com Date: Sat Aug 29 16:41:55 2026 +0800 重构拆分 diff --git a/JHS-origin.js b/JHS-origin.js new file mode 100644 index 0000000..040d4c8 --- /dev/null +++ b/JHS-origin.js @@ -0,0 +1,10962 @@ +// ==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://raw.githubusercontent.com/Tampermonkey/utils/refs/heads/main/requires/gh_2215_make_GM_xhr_more_parallel_again.js +// @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 \n
\n \n
\n
${movie.number} ${movie.origin_title}
\n
\n
\n
\n ${movie.release_date}\n
\n
\n ${movie.has_cnsub ? '含中字磁鏈' : movie.magnets_count > 0 ? '含磁鏈' : '无磁鏈'}\n ${movie.new_magnets ? '今日新種' : ""}\n
\n
\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 = $('\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 \n \n \n \n \n \n \n \n \n \n \n
115匹配
名称大小时间播放
\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 \n \n \n \n \n `)).join("")}\n \n
名称大小时间播放
${match.name}${this.formatSize(match.size)}${match.createTime}\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
\n
\n ${this.actressSvg}    手动同步演员\n ${this.newSvg}    手动检测最新作品\n ${this.settingSvg}    配置\n \n
\n
\n \n \n ${this.refreshSvg}    刷新\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, 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 ${allNamesStr}\n \n\n
\n \n
${data.name}
\n
\n
${allNamesStr}
\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 ${this.editSvg}\n ${this.deleteSvg}\n ${this.checkSvg}\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 Avatar Preview\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 `; + 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 ${imageItems}\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(); +}(); + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..a125100 --- /dev/null +++ b/README.md @@ -0,0 +1,228 @@ +# JHS-enhance + +> Jav-鉴黄师 油猴脚本 — 工程化重构版本 + +基于 `vite-plugin-monkey` 脚手架,将原 10,000+ 行单体脚本拆分为 49 个可维护的 ES Module 模块,支持本地开发热更新、一键构建。 + +--- + +## 功能概览 + +- **列表页增强**:作品状态标签(屏蔽/收藏/已下载/已观看)、一键打开待鉴定/已收藏作品、新作品检测、演员黑名单过滤 +- **详情页增强**:磁力链接高亮、预览视频(DMM 多画质)、标题翻译、字幕搜索(SubTitleCat / 迅雷)、演员信息、相关清单 +- **数据管理**:IndexedDB 本地存储、云盘备份/恢复(阿里云盘 / 115 网盘 / WebDAV)、跨 Tab 数据同步 +- **自动化任务**:自动翻页、新作品检测、收藏演员同步、黑名单更新 +- **UI 优化**:分类折叠、热榜 Top250、免 VIP 查看热播、以图识图、截图功能 +- **多站点支持**:JavDB / JavBus / JavSee / SeeJav / FC2 / JavTrailers / SubTitleCat + +--- + +## 项目结构 + +``` +JHS-enhance/ +├── src/ +│ ├── main.js # 入口文件:全局初始化、插件注册、页面启动 +│ ├── core/ # 基础设施层 +│ │ ├── constants.js # 全局常量、CSS 样式、页面类型判断 +│ │ ├── base-plugin.js # 插件基类(含 SVG 图标、页面信息解析) +│ │ ├── plugin-manager.js # 插件管理器(注册、CSS/插件生命周期) +│ │ ├── globals.js # 全局依赖挂载(jQuery、localforage 等) +│ │ ├── storage.js # IndexedDB 存储管理器(localforage 封装) +│ │ ├── utils.js # 工具函数集(DOM 操作、弹窗、快捷键等) +│ │ └── gmHttp.js # GM_xmlhttpRequest 封装(带重试/并发控制) +│ ├── api/ # API 层 +│ │ ├── javdb.js # JavDB API 封装(签名、搜索、评论等) +│ │ └── dmm.js # DMM 预览视频获取(多画质) +│ └── plugins/ # 插件层(38 个功能插件) +│ ├── list-page.js # 列表页过滤与状态标签 +│ ├── list-page-button.js # 列表页功能按钮(待鉴定/已收藏/新作品/黑名单) +│ ├── detail-page.js # 详情页数据加载 +│ ├── detail-page-button.js# 详情页操作按钮 +│ ├── setting.js # 设置面板(Tabulator 表格) +│ ├── translate.js # 标题翻译(Google 翻译 API) +│ ├── preview-video.js # DMM 预览视频 +│ ├── highlight-magnet.js # 磁力链接高亮匹配 +│ ├── auto-page.js # 自动翻页 / 云盘备份 +│ ├── blacklist.js # 黑名单管理(演员/番号) +│ ├── favorite-actresses.js# 收藏演员同步 +│ ├── new-video.js # 新作品检测 +│ ├── task.js # 后台任务调度 +│ ├── history.js # 浏览历史 +│ ├── wangpan-115.js # 115 网盘匹配 +│ ├── wangpan-115-task.js # 115 网盘任务处理 +│ ├── aliyun-pan.js # 阿里云盘 +│ ├── fc2.js # FC2 页面处理 +│ ├── fc2-by123av.js # FC2 通过 123AV 搜索 +│ ├── actress-info.js # 演员信息展示 +│ ├── related.js # 相关清单 +│ ├── review.js # 评论展示 +│ ├── cover-button.js # 封面按钮 +│ ├── fold-category.js # 分类折叠 +│ ├── hit-show.js # 热播榜单 +│ ├── top250.js # Top250 排行榜 +│ ├── nav-bar.js # JavDB 导航栏增强 +│ ├── bus-nav-bar.js # JavBus 导航栏增强 +│ ├── bus-detail-page.js # JavBus 详情页 +│ ├── bus-img.js # JavBus 图片处理 +│ ├── bus-preview-video.js # JavBus 预览视频 +│ ├── image-recognition.js # 以图识图 +│ ├── screenshot.js # 截图功能 +│ ├── magnet-hub.js # MagnetHub 磁力搜索 +│ ├── other-site.js # 第三方站点链接 +│ ├── jav-trailers.js # JavTrailers 站点 +│ ├── subtitle-cat.js # SubTitleCat 字幕搜索 +│ ├── filter-title-keyword.js # 标题关键词过滤 +│ └── want-watched.js # 想看/已看视频列表 +├── vite.config.mjs # Vite + vite-plugin-monkey 配置 +├── package.json +└── JHS-origin.js # 原始单体脚本(参考) +``` + +--- + +## 技术栈 + +| 类别 | 技术 | +|------|------| +| 构建工具 | Vite 6 + vite-plugin-monkey 5 | +| 模块系统 | ES Module | +| DOM 操作 | jQuery 3.7 | +| 数据存储 | localforage (IndexedDB) | +| 表格组件 | Tabulator 6 | +| 弹窗组件 | layui-layer | +| Toast 通知 | Toastify | +| 图片查看 | ViewerJS | +| 二维码 | QRCodeJS | +| 哈希 | blueimp-md5 | +| 代码风格 | 纯 JavaScript(无 TypeScript) | + +--- + +## 快速开始 + +### 安装依赖 + +```bash +npm install +``` + +### 开发模式 + +```bash +npm run dev +``` + +启动后会在终端输出一个本地代理服务器地址,将该地址填入 Tampermonkey 即可实现热更新开发。 + +### 生产构建 + +```bash +npm run build +``` + +构建产物输出到 `dist/jhs-enhance.user.js`,可直接安装到 Tampermonkey。 + +--- + +## 架构设计 + +### 三层架构 + +``` +main.js (入口) + │ + ├── core/ 基础设施层 + │ ├── 全局常量、CSS 样式 + │ ├── 插件基类、插件管理器 + │ ├── 存储、网络、工具函数 + │ └── 全局依赖挂载 + │ + ├── api/ API 层 + │ ├── JavDB API(签名、搜索) + │ └── DMM 视频 API + │ + └── plugins/ 插件层 + ├── 列表页插件(按站点分) + ├── 详情页插件(按站点分) + ├── 功能插件(翻译/黑名单/备份等) + └── 第三方站点插件 +``` + +### 插件机制 + +每个插件继承 `BasePlugin`,实现以下生命周期方法: + +- `getName()` — 返回插件唯一标识 +- `initCss()` — 返回插件专属 CSS(可选) +- `handle()` — 插件主逻辑 + +插件通过 `PluginManager` 统一注册和管理,按站点类型(JavDB / JavBus)分两组注册,确保不同站点加载正确的插件。 + +### 页面类型判断 + +``` +isDetailPage → 作品详情页 +isListPage → 作品列表页 +isFc2Page → FC2 搜索结果页 +isSearchPage → 搜索页 +``` + +插件在 `handle()` 中通过 `window.isDetailPage` 等全局变量判断当前页面类型,决定是否执行逻辑。 + +--- + +## 开发指南 + +### 添加新插件 + +1. 在 `src/plugins/` 下创建 `my-plugin.js` +2. 继承 `BasePlugin`,实现 `getName()` 和 `handle()` +3. 在 `src/main.js` 中 import 并注册: + +```javascript +import { MyPlugin } from './plugins/my-plugin.js'; +// 在对应站点分支中注册 +pluginManager.register(MyPlugin); +``` + +### 访问全局服务 + +插件中可通过以下方式访问全局服务: + +```javascript +await storageManager.getSetting('key'); // 存储 +await storageManager.getCarList(); // 车辆列表 +this.getBean('OtherPluginName'); // 获取其他插件实例 +window.utils.q(event, '确认?', callback); // 确认弹窗 +window.show.ok('操作成功'); // Toast 通知 +``` + +### 样式注入 + +- **全局样式**:定义在 `core/constants.js` 的 `mainCss` 中,启动时自动注入 +- **站点样式**:`javBusStyle` / `javdbStyle` 按站点条件注入 +- **插件样式**:通过 `initCss()` 方法返回,由 PluginManager 统一注入 + +--- + +## 构建配置 + +`vite-plugin-monkey` 在 `vite.config.mjs` 中配置了: + +- **match/include**:脚本匹配的 URL 模式 +- **require**:CDN 外部依赖(jQuery、Tabulator 等) +- **connect**:GM_xmlhttpRequest 允许的跨域域名 +- **grant**:GM_* API 权限声明 + +--- + +## 版本历史 + +- **v3.3.6** — 工程化重构版本,模块化拆分,支持 HMR 开发 + +--- + +## License + +MIT \ No newline at end of file diff --git a/REDESIGN.md b/REDESIGN.md new file mode 100644 index 0000000..528c346 --- /dev/null +++ b/REDESIGN.md @@ -0,0 +1,251 @@ +好的,让我先全面分析这个 11000 行的脚本。我会分段读取,理解它的整体结构和功能模块。 + + + + +The user wants me to analyze the JHS-origin.js file (about 11,000 lines) and provide a detailed report on how to modularize it. Let me first read through the entire file to understand its structure, then provide a comprehensive refactoring plan. + +Since the file is 10,962 lines, I need to read it in chunks. Let me start by reading several sections to understand the code structure. + +Let me read the file in multiple chunks to understand: +1. The beginning (already read some) +2. Key sections throughout +3. The end + +I'll read multiple sections in parallel. + +Let me continue reading more sections to get a complete picture of the codebase. + +Let me also read the end of the file to see the initialization/entry point. + +Now I have a very comprehensive understanding of the codebase. Let me also read the very end to see the initialization. + +Now I have a comprehensive understanding of the entire codebase. Let me write a detailed analysis report. + +The code is approximately 10,962 lines, with a well-defined plugin architecture: +- PluginManager: manages plugins +- BasePlugin: base class for all plugins +- ~40 plugins extending BasePlugin + +The code also contains: +- Utility classes (Utils, gmHttp, StorageManager, show, clog, etc.) +- CSS styles +- Constants and configuration +- API interfaces (JavDbApi, etc.) +- A plugin system with registration and execution + +Let me now write a comprehensive refactoring report. +现在我已经完整分析了整个代码库。以下是详细的结构分析报告: + +--- + +## 🔍 JHS-enhance 代码结构分析报告 + +### 📊 总体概况 + +| 指标 | 数据 | +|------|------| +| 总行数 | **10,962 行** | +| 核心类/插件 | **40+ 个** | +| 支持站点 | JavDB、JavBus、JavTrailers、SubTitleCat、阿里云盘、115网盘 | +| 外部依赖 | jQuery、Tabulator、layui-layer、MD5、Toastify、localForage、ViewerJS、QRCodeJS | + +--- + +### 🏗️ 现有架构 + +代码已经有一个**不错的插件架构**,只是全部写在一个文件里: + +``` +入口(最后100行) + ├── 初始化 pluginManager + ├── 检测当前页面类型(isDetailPage / isListPage / isFc2Page) + ├── 按站点注册插件 + ├── pluginManager.processCss() → 加载所有插件CSS + └── pluginManager.processPlugins() → 执行所有插件 +``` + +插件基类 `BasePlugin` 提供了: +- `getName()` — 插件名 +- `initCss()` — 返回CSS字符串 +- `handle()` — 主逻辑入口 +- `getPageInfo()` — 获取当前页面信息 +- `getBean(name)` — 获取其他插件实例 + +--- + +### 📦 代码功能模块划分 + +我将 11000 行代码按功能分为以下 **8 大模块**,建议拆分为独立文件: + +--- + +#### 模块一:🛠️ 基础设施层(~1500 行) +**建议目录:`src/core/`** + +| 文件 | 内容 | 说明 | +|------|------|------| +| `utils.js` | `Utils` 类(~300行) | 通用工具函数:下载、滚动、剪贴板、URL解析、排序等 | +| `gmHttp.js` | `gmHttp` 类(~200行) | 基于 `GM_xmlhttpRequest` 的 HTTP 封装 | +| `storage.js` | `StorageManager` 类(~500行) | IndexedDB 存储管理:收藏、屏蔽、设置、演员等 | +| `show.js` | `show` / `clog` 对象(~100行) | Toast 消息提示、控制台日志 | +| `constants.js` | 所有常量(~200行) | 状态枚举、画质选项、翻译映射表、标签样式等 | +| `styles.js` | CSS 样式(~300行) | JavDB/JavBus 站点样式、全局样式、滚动条样式 | + +--- + +#### 模块二:🔌 插件系统(~200 行) +**建议目录:`src/core/`** + +| 文件 | 内容 | 说明 | +|------|------|------| +| `plugin-manager.js` | `PluginManager` 类 | 插件注册、CSS加载、插件执行 | +| `base-plugin.js` | `BasePlugin` 类 | 插件基类,提供 `getName()`、`handle()`、`initCss()` 等 | + +--- + +#### 模块三:🌐 API 接口层(~200 行) +**建议目录:`src/api/`** + +| 文件 | 内容 | 说明 | +|------|------|------| +| `javdb-api.js` | `javDbApi` 对象 | JavDB API:获取评论、搜索电影、电影详情、磁力链接 | + +--- + +#### 模块四:📋 列表页插件(~2500 行) +**建议目录:`src/plugins/list/`** + +| 文件 | 类名 | 功能 | +|------|------|------| +| `list-page.js` | `ListPagePlugin` | 列表页核心:状态标签渲染、屏蔽过滤、演员屏蔽、排序 | +| `list-page-button.js` | `ListPageButtonPlugin` | 列表页工具栏按钮:一键屏蔽、收藏、随机打开等 | +| `auto-page.js` | `AutoPagePlugin` | 自动翻页 | +| `fold-category.js` | `FoldCategoryPlugin` | 折叠/展开分类标签区域 | +| `cover-button.js` | `CoverButtonPlugin` | 封面图上的操作按钮(屏蔽、收藏等) | +| `history.js` | `HistoryPlugin` | 鉴定记录面板 | +| `filter-title-keyword.js` | `FilterTitleKeywordPlugin` | 标题关键词屏蔽 | +| `blacklist.js` | `BlacklistPlugin` | 黑名单管理(屏蔽演员/标签) | +| `hit-show.js` | `HitShowPlugin` | 热播榜 | +| `top250.js` | `TOP250Plugin` | TOP250 排行榜 | + +--- + +#### 模块五:🎬 详情页插件(~2000 行) +**建议目录:`src/plugins/detail/`** + +| 文件 | 类名 | 功能 | +|------|------|------| +| `detail-page.js` | `DetailPagePlugin` | 详情页核心:FC2/普通影片信息展示 | +| `detail-page-button.js` | `DetailPageButtonPlugin` | 详情页按钮:屏蔽、收藏、已下载、已观看、磁力搜索、字幕 | +| `bus-detail-page.js` | `BusDetailPagePlugin` | JavBus 详情页 | +| `review.js` | `ReviewPlugin` | 评论查看 | +| `related.js` | `RelatedPlugin` | 相关清单 | +| `highlight-magnet.js` | `HighlightMagnetPlugin` | 磁力链接高亮过滤 | +| `translate.js` | `TranslatePlugin` | 标题翻译(日→中) | +| `want-watched.js` | `WantAndWatchedVideosPlugin` | 想看/已看视频 | + +--- + +#### 模块六:🎥 视频与媒体插件(~1500 行) +**建议目录:`src/plugins/media/`** + +| 文件 | 类名 | 功能 | +|------|------|------| +| `preview-video.js` | `PreviewVideoPlugin` | JavDB 预览视频播放 | +| `bus-preview-video.js` | `BusPreviewVideoPlugin` | JavBus 预览视频播放 | +| `screen-shot.js` | `ScreenShotPlugin` | 缩略图加载 | +| `bus-img.js` | `BusImgPlugin` | JavBus 图片处理 | +| `image-recognition.js` | `ImageRecognitionPlugin` | 以图识图 | + +--- + +#### 模块七:🔗 外部站点与工具插件(~2000 行) +**建议目录:`src/plugins/external/`** + +| 文件 | 类名 | 功能 | +|------|------|------| +| `magnet-hub.js` | `MagnetHubPlugin` | 多站点磁力搜索聚合 | +| `other-site.js` | `OtherSitePlugin` | 其他站点入口(JavStore、MissAV等) | +| `fc2.js` | `Fc2Plugin` | FC2 影片页面 | +| `fc2-123av.js` | `Fc2By123AvPlugin` | 123AV 的 FC2 页面 | +| `wangpan-115.js` | `WangPan115Plugin` / `WangPan115TaskPlugin` / `WangPan115MatchPlugin` | 115网盘离线下载 | +| `aliyun-pan.js` | `AliyunPanPlugin` | 阿里云盘 | +| `jav-trailers.js` | `JavTrailersPlugin` | JavTrailers 站点 | +| `subtitle-cat.js` | `SubTitleCatPlugin` | SubTitleCat 字幕搜索 | + +--- + +#### 模块八:👤 演员与任务插件(~2000 行) +**建议目录:`src/plugins/actress/`** + +| 文件 | 类名 | 功能 | +|------|------|------| +| `actress-info.js` | `ActressInfoPlugin` | 演员信息面板 | +| `favorite-actresses.js` | `FavoriteActressesPlugin` | 收藏演员管理 | +| `new-video.js` | `NewVideoPlugin` | 新作品检测 | +| `task.js` | `TaskPlugin` | 定时任务:检测黑名单、同步演员、新作品检测 | +| `nav-bar.js` | `NavBarPlugin` / `BusNavBarPlugin` | 导航栏增强 | +| `setting.js` | `SettingPlugin` | 设置面板 | + +--- + +### 🎯 建议的项目目录结构 + +``` +JHS-enhance/ +├── src/ +│ ├── main.js # 入口:初始化 + 插件注册 +│ ├── core/ +│ │ ├── utils.js # 通用工具函数 +│ │ ├── gmHttp.js # GM_xmlhttpRequest 封装 +│ │ ├── storage.js # IndexedDB 存储管理 +│ │ ├── show.js # Toast 消息提示 +│ │ ├── constants.js # 所有常量 +│ │ ├── styles.js # CSS 样式 +│ │ ├── plugin-manager.js # 插件管理器 +│ │ └── base-plugin.js # 插件基类 +│ ├── api/ +│ │ └── javdb-api.js # JavDB API 接口 +│ ├── plugins/ +│ │ ├── list/ # 列表页插件 (10个) +│ │ ├── detail/ # 详情页插件 (8个) +│ │ ├── media/ # 视频/媒体插件 (5个) +│ │ ├── external/ # 外部站点插件 (8个) +│ │ └── actress/ # 演员/任务插件 (6个) +│ └── styles/ +│ └── ... (可选,如果CSS拆出来) +├── vite.config.mjs +├── package.json +└── JHS-origin.js # 保留原始文件作为参考 +``` + +--- + +### ⚠️ 拆分时的注意事项 + +1. **全局变量依赖**:当前代码大量使用 `unsafeWindow.xxx = window.xxx`,拆分为 ES Module 后需要改为 `export`/`import`,同时通过 `window` 暴露给油猴全局作用域 + +2. **插件间依赖**:插件通过 `this.getBean(name)` 互相调用,拆分后需确保 `PluginManager` 先初始化 + +3. **CDN 外部依赖**:jQuery、Tabulator 等通过 `@require` 加载,在模块中直接使用 `$`、`Tabulator` 等全局变量即可 + +4. **CSS 注入**:`insertStyle()` 和 `initCss()` 需要 `document.head` 可用后才能执行 + +5. **循环依赖**:注意 `TaskPlugin` ↔ `BlacklistPlugin`、`ListPagePlugin` ↔ `BlacklistPlugin` 等之间的双向引用 + +--- + +### 🚀 建议的实施顺序 + +| 优先级 | 步骤 | 预计工作量 | +|--------|------|-----------| +| P0 | 拆分 `core/` 基础设施(utils, gmHttp, storage, show, constants, styles) | 1-2小时 | +| P1 | 拆分插件系统(plugin-manager, base-plugin)和入口 `main.js` | 30分钟 | +| P2 | 拆分 API 层(javdb-api) | 15分钟 | +| P3 | 分批拆分插件,每批 3-5 个,逐个验证构建 | 3-5小时 | +| P4 | 清理全局变量,统一为 ES Module 导入导出 | 1小时 | + +--- + +需要我开始执行拆分吗?建议从 **P0(基础设施层)** 开始,这是最独立、最不容易出错的模块。 \ No newline at end of file diff --git a/fix-imports.js b/fix-imports.js new file mode 100644 index 0000000..b0d0d71 --- /dev/null +++ b/fix-imports.js @@ -0,0 +1,49 @@ +const fs = require('fs'); +const path = require('path'); + +const allConstants = [ + 'isJavBus', 'isJavDb', 'isSearchPage', 'currentHref', + 'Status_RUNNING', 'Status_SUCCESS', 'Status_FAIL', 'Status_LOADING', + 'Status_FILTER', 'Status_FAVORITE', 'Status_HAS_DOWN', 'Status_HAS_WATCH', + 'NO', 'YES' +]; + +const pluginsDir = 'src/plugins'; +const files = fs.readdirSync(pluginsDir).filter(f => f.endsWith('.js')); + +let fixed = 0; + +for (const file of files) { + const filePath = path.join(pluginsDir, file); + let content = fs.readFileSync(filePath, 'utf8'); + + const needed = allConstants.filter(c => new RegExp('\\b' + c + '\\b').test(content)); + if (needed.length === 0) continue; + + const importMatch = content.match(/import\s*\{([^}]+)\}\s*from\s*['"][^'"]*constants[^'"]*['"]/); + + if (importMatch) { + const existing = importMatch[1].split(',').map(s => s.trim()).filter(Boolean); + const missing = needed.filter(c => !existing.includes(c)); + + if (missing.length > 0) { + const newImports = [...existing, ...missing].join(', '); + const newImportLine = `import { ${newImports} } from '../core/constants.js';`; + content = content.replace(importMatch[0], newImportLine); + fs.writeFileSync(filePath, content); + console.log(`Fixed: ${file} +${missing.join(', ')}`); + fixed++; + } + } else { + const newImportLine = `import { ${needed.join(', ')} } from '../core/constants.js';`; + const baseImportMatch = content.match(/import\s*\{[^}]*\}\s*from\s*['"][^'"]*base-plugin[^'"]*['"]/); + if (baseImportMatch) { + content = content.replace(baseImportMatch[0], baseImportMatch[0] + '\n' + newImportLine); + fs.writeFileSync(filePath, content); + console.log(`Fixed: ${file} +${needed.join(', ')} (new import)`); + fixed++; + } + } +} + +console.log(`\nTotal fixed: ${fixed} files`); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..85b822c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1628 @@ +{ + "name": "jhs-enhance", + "version": "3.3.6", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "jhs-enhance", + "version": "3.3.6", + "license": "MIT", + "dependencies": { + "blueimp-md5": "^2.19.0", + "jquery": "^4.0.0", + "layui-layer": "^1.0.9", + "localforage": "^1.10.0", + "qrcodejs": "^1.0.0", + "tabulator-tables": "^6.5.2", + "toastify-js": "^1.12.0", + "viewerjs": "^1.12.0" + }, + "devDependencies": { + "vite": "^6.0.0", + "vite-plugin-monkey": "^5.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmmirror.com/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/blueimp-md5": { + "version": "2.19.0", + "resolved": "https://registry.npmmirror.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz", + "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", + "license": "MIT" + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/default-browser": { + "version": "5.5.1", + "resolved": "https://registry.npmmirror.com/default-browser/-/default-browser-5.5.1.tgz", + "integrity": "sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/default-browser-id/-/default-browser-id-5.0.1.tgz", + "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmmirror.com/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/is-wsl/-/is-wsl-3.1.1.tgz", + "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jquery": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/jquery/-/jquery-4.0.0.tgz", + "integrity": "sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg==", + "license": "MIT" + }, + "node_modules/layui-layer": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/layui-layer/-/layui-layer-1.0.9.tgz", + "integrity": "sha512-CzPVtG0D4IvmISUyLTzzbz2Uxfaoqch+87lHqVmO53ie8ZKOfOkENbBl7ssiLna1lAnv4M1SycmH62NbtndtDQ==", + "license": "ISC", + "dependencies": { + "jquery": "^3.1.1" + } + }, + "node_modules/layui-layer/node_modules/jquery": { + "version": "3.7.1", + "resolved": "https://registry.npmmirror.com/jquery/-/jquery-3.7.1.tgz", + "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", + "license": "MIT" + }, + "node_modules/lie": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/lie/-/lie-3.1.1.tgz", + "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, + "node_modules/localforage": { + "version": "1.10.0", + "resolved": "https://registry.npmmirror.com/localforage/-/localforage-1.10.0.tgz", + "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", + "license": "Apache-2.0", + "dependencies": { + "lie": "3.1.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmmirror.com/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/qrcodejs": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/qrcodejs/-/qrcodejs-1.0.0.tgz", + "integrity": "sha512-67rj3mMBhSBepaD57qENnltO+r8rSYlqM7HGThks/BiyDAkc86sLvkKqjkqPS5v13f7tvnt6dbEf3qt7zq+BCg==" + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.1.0", + "resolved": "https://registry.npmmirror.com/run-applescript/-/run-applescript-7.1.0.tgz", + "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/systemjs": { + "version": "6.15.1", + "resolved": "https://registry.npmmirror.com/systemjs/-/systemjs-6.15.1.tgz", + "integrity": "sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tabulator-tables": { + "version": "6.5.2", + "resolved": "https://registry.npmmirror.com/tabulator-tables/-/tabulator-tables-6.5.2.tgz", + "integrity": "sha512-cRL3xsaaf5RzND8KPbn4C9Ce5tzyiQUE01E/10zN50MjdRKl/Gl5nXkzLN6cvEyRfWHCPuqBF92y50CBw20WYA==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/toastify-js": { + "version": "1.12.0", + "resolved": "https://registry.npmmirror.com/toastify-js/-/toastify-js-1.12.0.tgz", + "integrity": "sha512-HeMHCO9yLPvP9k0apGSdPUWrUbLnxUKNFzgUoZp1PHCLploIX/4DSQ7V8H25ef+h4iO9n0he7ImfcndnN6nDrQ==", + "license": "MIT" + }, + "node_modules/viewerjs": { + "version": "1.12.0", + "resolved": "https://registry.npmmirror.com/viewerjs/-/viewerjs-1.12.0.tgz", + "integrity": "sha512-eQrV7FvqXj2Ysavd9LINI6bFAvXtw5YbdlMtdIo6Bmrv25hEOBeXoOX9E7cJwYuFAOPFzmppFvDn02QNHB120w==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmmirror.com/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-monkey": { + "version": "5.0.9", + "resolved": "https://registry.npmmirror.com/vite-plugin-monkey/-/vite-plugin-monkey-5.0.9.tgz", + "integrity": "sha512-Q9K8AHOFWCBeWR7WENy4eDiOHV8OOGCKgbtP8B/Z4D5wXHloHqgO2yyEvvePHzTDwk8zqJFXLXLPs+q2upoCAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn-walk": "^8.3.4", + "cross-spawn": "^7.0.6", + "htmlparser2": "^9.1.0", + "import-meta-resolve": "^4.1.0", + "magic-string": "^0.30.15", + "mrmime": "^2.0.0", + "open": "^10.1.0", + "picocolors": "^1.1.1", + "systemjs": "^6.15.1" + }, + "peerDependencies": { + "vite": "^5.4.11 || ^6.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wsl-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmmirror.com/wsl-utils/-/wsl-utils-0.1.0.tgz", + "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..db8bfa0 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "jhs-enhance", + "version": "3.3.6", + "description": "Jav-鉴黄师 油猴脚本 - 工程化版本", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "license": "MIT", + "devDependencies": { + "vite": "^6.0.0", + "vite-plugin-monkey": "^5.0.0" + }, + "dependencies": { + "blueimp-md5": "^2.19.0", + "jquery": "^4.0.0", + "layui-layer": "^1.0.9", + "localforage": "^1.10.0", + "qrcodejs": "^1.0.0", + "tabulator-tables": "^6.5.2", + "toastify-js": "^1.12.0", + "viewerjs": "^1.12.0" + } +} diff --git a/src/api/dmm.js b/src/api/dmm.js new file mode 100644 index 0000000..803b96b --- /dev/null +++ b/src/api/dmm.js @@ -0,0 +1,181 @@ +import { qualityOptions } from '../core/constants.js'; +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(); + +export { DmmVideoFetcher, getDmmVideo, selectDefaultQuality }; diff --git a/src/core/globals.js b/src/core/globals.js new file mode 100644 index 0000000..b54325e --- /dev/null +++ b/src/core/globals.js @@ -0,0 +1,33 @@ +import $ from 'jquery'; +import localforage from 'localforage'; +import Toastify from 'toastify-js'; +import md5 from 'blueimp-md5'; +import { TabulatorFull as Tabulator } from 'tabulator-tables'; +import Viewer from 'viewerjs'; +import QRCode from 'qrcodejs'; +import { currentHref, isJavDb, isJavBus, isSearchPage, Status_RUNNING, Status_SUCCESS, Status_FAIL, Status_LOADING, Status_FILTER, Status_FAVORITE, Status_HAS_DOWN, Status_HAS_WATCH, NO, YES } from './constants.js'; + +window.$ = window.jQuery = $; +window.localforage = localforage; +window.Toastify = Toastify; +window.md5 = md5; +window.Tabulator = Tabulator; +window.Viewer = Viewer; +window.QRCode = QRCode; + +window.currentHref = currentHref; +window.isJavDb = isJavDb; +window.isJavBus = isJavBus; +window.isSearchPage = isSearchPage; +window.Status_RUNNING = Status_RUNNING; +window.Status_SUCCESS = Status_SUCCESS; +window.Status_FAIL = Status_FAIL; +window.Status_LOADING = Status_LOADING; +window.Status_FILTER = Status_FILTER; +window.Status_FAVORITE = Status_FAVORITE; +window.Status_HAS_DOWN = Status_HAS_DOWN; +window.Status_HAS_WATCH = Status_HAS_WATCH; +window.NO = NO; +window.YES = YES; + +import 'layui-layer'; \ No newline at end of file diff --git a/vite.config.mjs b/vite.config.mjs new file mode 100644 index 0000000..48ce65b --- /dev/null +++ b/vite.config.mjs @@ -0,0 +1,108 @@ +import { defineConfig } from 'vite'; +import monkey from 'vite-plugin-monkey'; + +export default defineConfig({ + plugins: [ + monkey({ + entry: 'src/main.js', + 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/*', + 'https://www.javbus.com/*', + ], + include: [ + 'https://javdb*.com/*', + 'https://*javbus*/*', + 'https://*javsee*/*', + 'https://*seejav*/*', + 'https://javtrailers.com/*', + 'https://subtitlecat.com/*', + 'https://www.aliyundrive.com/*', + 'https://www.alipan.com/*', + 'https://115.com/*', + ], + exclude: [ + 'https://*javbus*/forum/*', + 'https://*javbus*/*actresses', + 'https://*javsee*/forum/*', + 'https://*javsee*/*actresses', + 'https://*seejav*/forum/*', + 'https://*seejav*/*actresses', + ], + require: [ + 'https://raw.githubusercontent.com/Tampermonkey/utils/refs/heads/main/requires/gh_2215_make_GM_xhr_more_parallel_again.js', + 'https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js', + 'https://cdn.jsdelivr.net/npm/tabulator-tables@6.3.1/dist/js/tabulator.min.js', + 'https://cdn.jsdelivr.net/npm/layui-layer@1.0.9/dist/layer.min.js', + 'https://cdn.jsdelivr.net/npm/blueimp-md5@2.19.0/js/md5.min.js', + 'https://cdn.jsdelivr.net/npm/toastify-js@1.12.0/src/toastify.min.js', + 'https://cdn.jsdelivr.net/npm/localforage@1.10.0/dist/localforage.min.js', + 'https://cdn.jsdelivr.net/npm/viewerjs@1.11.1/dist/viewer.min.js', + 'https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js', + ], + connect: [ + 'xunlei.com', + 'geilijiasu.com', + 'aliyundrive.com', + 'aliyundrive.net', + 'ja.wikipedia.org', + 'beta.magnet.pics', + 'jdforrepam.com', + 'cc3001.dmm.co.jp', + 'cc3001.dmm.com', + 'www.dmm.co.jp', + 'www.dmm.com', + 'api.dmm.com', + 'special.dmm.co.jp', + 'adult.contents.fc2.com', + 'fc2ppvdb.com', + '123av.com', + 'u3c3.com', + 'u9a9.com', + 'btsow.lol', + 'sukebei.nyaa.si', + 'javstore.net', + 'javbest.net', + 'missav.live', + 'jable.tv', + 'www.av.gl', + 'jav.rs', + 'javtrailers.com', + 'javdb.com', + 'javbus.com', + 'supjav.com', + '115.com', + 'jianguoyun.com', + '127.0.0.1', + '*', + ], + grant: [ + 'GM_xmlhttpRequest', + 'GM_openInTab', + 'unsafeWindow', + ], + 'run-at': 'document-idle', + }, + build: { + fileName: 'jhs-enhance.user.js', + externalGlobals: { + 'jquery': '$', + 'localforage': 'localforage', + 'toastify-js': 'Toastify', + 'blueimp-md5': 'md5', + 'tabulator-tables': 'Tabulator', + 'viewerjs': 'Viewer', + 'qrcodejs': 'QRCode', + 'layui-layer': 'layer', + }, + }, + }), + ], +}); \ No newline at end of file