// ==UserScript== // @name JHS-enhance // @namespace JHS-enhance // @version 3.3.7 // @author xie bro,fireinrain // @description Jav-鉴黄师 增强脚本。列表页:作品状态标签、一键筛选、新作品检测、演员黑名单过滤;详情页:磁力链接高亮、DMM 多画质预览视频、标题翻译、整合字幕搜索(迅雷+SubTitleCat)并支持 115 直传、多源预览图(javfree/projectjav/javstore)含来源切换;数据:115 网盘目录匹配与多目录选择、云盘备份/恢复、跨 Tab 同步;其他:自动翻页、分类折叠、Top250、以图识图、热门榜单、评论查看、相关清单。支持 JavDB / JavBus / JavSee / SeeJav / FC2 / JavTrailers // @license MIT // @icon https://www.google.com/s2/favicons?sz=64&domain=javdb.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/* // @match https://javdb.com/* // @match https://www.javbus.com/* // @exclude https://*javbus*/forum/* // @exclude https://*javbus*/*actresses // @exclude https://*javsee*/forum/* // @exclude https://*javsee*/*actresses // @exclude https://*seejav*/forum/* // @exclude https://*seejav*/*actresses // @require https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js // @require https://cdn.jsdelivr.net/npm/tabulator-tables@6.5.2/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_info // @grant GM_openInTab // @grant GM_xmlhttpRequest // @grant unsafeWindow // @grant window.close // @run-at document-idle // ==/UserScript== (function ($$1, localforage$1, Toastify$1, md5$1, Viewer$1, QRCode$1) { 'use strict'; var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __defProp2 = Object.defineProperty, __typeError = (msg) => { throw TypeError(msg); }, __publicField2 = (obj, key, value) => ((obj2, key2, value2) => key2 in obj2 ? __defProp2(obj2, key2, { enumerable: true, configurable: true, writable: true, value: value2 }) : obj2[key2] = value2)(obj, "symbol" != typeof key ? key + "" : key, value), __privateMethod = (obj, member, method) => (((obj2, member2, msg) => { member2.has(obj2) || __typeError("Cannot " + msg); })(obj, member, "access private method"), method); const currentHref$1 = window.location.href, isJavDb$1 = currentHref$1.includes("javdb"), isJavBus$1 = currentHref$1.includes("javbus") || currentHref$1.includes("seejav") || currentHref$1.includes("bus") || currentHref$1.includes("javsee") || "javbus" === (() => { try { const _alt = $(".hidden-xs").attr("alt"); return _alt ? _alt.trim().toLowerCase() : void 0; } catch (_e) { return void 0; } })(), isSearchPage = currentHref$1.includes("/search?q") || currentHref$1.includes("/search/") || currentHref$1.includes("/users/"), Status_RUNNING = "RUNNING", Status_SUCCESS = "SUCCESS", Status_FAIL = "FAIL", Status_LOADING = "LOADING", Status_FILTER$1 = "filter", Status_FAVORITE$1 = "favorite", Status_HAS_DOWN$1 = "hasDown", Status_HAS_WATCH$1 = "hasWatch", NO = "no", YES = "yes", qualityOptions = [{ id: "video-mhb", quality: "dmb_w", text: "旧视频源-中画质宽版 (404p)", canSelect: false }, { id: "video-mhb", quality: "sm_s", text: "旧视频源-低画质 (240p)", canSelect: false }, { id: "video-mhb", quality: "dm_s", text: "旧视频源-中画质 (360p)", canSelect: false }, { id: "video-mhb", quality: "dmb_s", text: "旧视频源-中画质 (480p)", canSelect: false }, { id: "video-mhb", quality: "mhb_w", text: "旧视频源-高画质宽版 (404p)", canSelect: false }, { id: "video-mmb", quality: "mmb", text: "中画质 (432p)", canSelect: true }, { id: "video-mhb", quality: "mhb", text: "高画质 (576p)", canSelect: true }, { id: "video-hmb", quality: "hmb", text: "HD (720p)", canSelect: true }, { id: "video-hhb", quality: "hhb", text: "FullHD (1080p)", canSelect: true }, { id: "video-hhbs", quality: "hhbs", text: "FullHD (1080p60fps)", canSelect: true }, { id: "video-4k", quality: "4k", text: "4K (2160p)", canSelect: true }, { id: "video-4ks", quality: "4ks", text: "4K (2160p60fps)", canSelect: true }]; 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 = ` `; 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 = ` `; const mainCss = ` `; const subtitleModalCss = ` `; function insertStyle(css) { if (css) if (css.includes("\n" ); insertStyle(mainCss); insertStyle(subtitleModalCss); window.$ = window.jQuery = $$1; if (typeof $$1.trim !== "function") { $$1.trim = function (text) { return text == null ? "" : (text + "").trim(); }; } window.localforage = localforage$1; window.Toastify = Toastify$1; window.md5 = md5$1; window.Viewer = Viewer$1; window.QRCode = QRCode$1; window.currentHref = currentHref$1; window.isJavDb = isJavDb$1; window.isJavBus = isJavBus$1; 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$1; window.Status_FAVORITE = Status_FAVORITE$1; window.Status_HAS_DOWN = Status_HAS_DOWN$1; window.Status_HAS_WATCH = Status_HAS_WATCH$1; window.NO = NO; window.YES = YES; const HAS_GM = typeof GM !== "undefined"; const NEW_GM = ((scope, GM2) => { if (typeof GM_info === "undefined" || GM_info.scriptHandler !== "Tampermonkey" || compareVersions(GM_info.version, "5.3.2") < 0) return; const GM_xmlhttpRequestOrig = GM_xmlhttpRequest; const GM_xmlHttpRequestOrig = GM2.xmlHttpRequest; function compareVersions(v1, v2) { const parts1 = v1.split(".").map(Number); const parts2 = v2.split(".").map(Number); const length = Math.max(parts1.length, parts2.length); for (let i = 0; i < length; i++) { const num1 = parts1[i] || 0; const num2 = parts2[i] || 0; if (num1 > num2) return 1; if (num1 < num2) return -1; } return 0; } function GM_xmlhttpRequestWrapper(odetails) { if (odetails.redirect !== void 0) { return GM_xmlhttpRequestOrig(odetails); } if (odetails.onprogress || odetails.fetch === false) { console.warn("Fetch mode does not support onprogress in the background."); } const { onload, onloadend, onerror, onabort, ontimeout, ...details } = odetails; const handleRedirects = (initialDetails) => { const request = GM_xmlhttpRequestOrig({ ...initialDetails, redirect: "manual", onload: function (response) { if (response.status >= 300 && response.status < 400) { const m = response.responseHeaders.match(/Location:\s*(\S+)/i); const redirectUrl = m && m[1]; if (redirectUrl) { const absoluteUrl = new URL(redirectUrl, initialDetails.url).href; handleRedirects({...initialDetails, url: absoluteUrl}); return; } } if (onload) onload.call(this, response); if (onloadend) onloadend.call(this, response); }, onerror: function (response) { if (onerror) onerror.call(this, response); if (onloadend) onloadend.call(this, response); }, onabort: function (response) { if (onabort) onabort.call(this, response); if (onloadend) onloadend.call(this, response); }, ontimeout: function (response) { if (ontimeout) ontimeout.call(this, response); if (onloadend) onloadend.call(this, response); } }); return request; }; return handleRedirects(details); } function GM_xmlHttpRequestWrapper(odetails) { let abort; const p = new Promise((resolve, reject) => { const {onload, ontimeout, onerror, ...send} = odetails; send.onerror = function (r) { if (onerror) { resolve(r); onerror.call(this, r); } else { reject(r); } }; send.ontimeout = function (r) { if (ontimeout) { resolve(r); ontimeout.call(this, r); } else { reject(r); } }; send.onload = function (r) { resolve(r); if (onload) onload.call(this, r); }; const a = GM_xmlhttpRequestWrapper(send).abort; if (abort === true) { a(); } else { abort = a; } }); p.abort = () => { if (typeof abort === "function") { abort(); } else { abort = true; } }; return p; } GM_xmlhttpRequest = GM_xmlhttpRequestWrapper; scope.GM_xmlhttpRequestOrig = GM_xmlhttpRequestOrig; const gopd = Object.getOwnPropertyDescriptor(GM2, "xmlHttpRequest"); if (gopd && gopd.configurable === false) { return { __proto__: GM2, xmlHttpRequest: GM_xmlHttpRequestWrapper, xmlHttpRequestOrig: GM_xmlHttpRequestOrig }; } else { GM2.xmlHttpRequest = GM_xmlHttpRequestWrapper; GM2.xmlHttpRequestOrig = GM_xmlHttpRequestOrig; } })(globalThis, HAS_GM ? GM : {}); if (HAS_GM && NEW_GM) GM = NEW_GM; var _StorageManager_instances, setItem_fn, saveFilterItem_fn; _StorageManager_instances = /* @__PURE__ */ 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; __publicField2(this, "car_list_key", "car_list"); __publicField2(this, "filter_keyword_title_key", "filter_keyword_title"); __publicField2(this, "filter_keyword_review_key", "filter_keyword_review"); __publicField2(this, "setting_key", "setting"); __publicField2(this, "blacklist_key", "blacklist"); __publicField2(this, "blacklist_car_list_key", "blacklist_car_list"); __publicField2(this, "favorite_actresses_key", "favorite_actresses"); __publicField2(this, "highlighted_tags_key", "highlighted_tags"); __publicField2(this, "forage", localforage.createInstance({ driver: localforage.INDEXEDDB, name: "JAV-JHS", version: 1, storeName: "appData" })); __publicField2(this, "cacheCarList", null); __publicField2(this, "cacheBlacklistCarList", null); __publicField2(this, "cacheFavoriteActressList", null); __publicField2(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, names, actionType, publishTime, 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, names, status: "", createDate: nowStr, updateDate: nowStr, 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, names, actionType, publishTime, 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 = false; 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 = true; } 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 false; } await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.car_list_key, updatedList); return true; } 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, name: name2, allName, role, movieType, 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, name: name2, allName: allName || [name2], createTime: utils.getNowStr(), role, movieType, 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 = false, 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 = true; 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, name: name2, allName, avatar, lastCheckTime, lastPublishTime, 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, name: name2, allName, avatar, lastCheckTime, lastPublishTime, createDate: nowStr, updateDate: nowStr, 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 false; } await __privateMethod(this, _StorageManager_instances, setItem_fn).call(this, this.favorite_actresses_key, updatedList); return true; } async updateFavoriteActress(actress) { const favoriteActressesInfoList = await this.getFavoriteActressList(), { starId, name: name2, allName, avatar, lastCheckTime, newVideoList, lastPublishTime, actressType, remark } = actress; if (!starId) throw new Error("缺失starId"); let favoriteActresses = favoriteActressesInfoList.find(((item) => item.starId === starId)); if (!favoriteActresses) { clog.error("未找到演员信息", starId, name2); return false; } 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 = false; 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 = true; } 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 }; }, 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, markDataListHtml: (movies) => { let moviesHtml = ""; movies.forEach(((movie) => { moviesHtml += `
${movie.number} ${movie.origin_title}
${movie.release_date}
${movie.has_cnsub ? '含中字磁鏈' : movie.magnets_count > 0 ? '含磁鏈' : '无磁鏈'} ${movie.new_magnets ? '今日新種' : ""}
`; })); return moviesHtml; } }; var _GM_openInTab = /* @__PURE__ */ (() => typeof GM_openInTab != "undefined" ? GM_openInTab : void 0)(); var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)(); class Utils { constructor() { __publicField2(this, "intervalContainer", {}); __publicField2(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" }); __publicField2(this, "timers", /* @__PURE__ */ new Map()); __publicField2(this, "insertStyle", ((css) => { if (css) { -1 === css.indexOf(""); $("head").append(css); } })); __publicField2(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 ?? true; 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, content: finalUrl, scrollbar: false, shadeClose, area: this.getResponsiveArea(["85%", "90%"]), isOutAnim: false, 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 = false; if ($layer.find(".viewer-container").length > 0) viewerExists = true; else { const iframe = $layer.find(`#layui-layer-iframe${topLayerIndex}`)[0]; if (iframe && iframe.contentDocument) try { $(iframe.contentDocument).find(".viewer-container").length > 0 && (viewerExists = true); } 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 = true) { const uuid = Math.random(), start = (/* @__PURE__ */ new Date()).getTime(), stopAndRun = (shouldRun) => { clearInterval(this.intervalContainer[uuid]); shouldRun && after && after(); delete this.intervalContainer[uuid]; }; this.intervalContainer[uuid] = setInterval((() => { const timeElapsed = (/* @__PURE__ */ new Date()).getTime() - start; condition() ? stopAndRun(true) : 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, shade: 0, zIndex: 999999991 }, (function() { yesFun && yesFun(); layer.close(confirmIndex); })); } getNowStr(dateSplitStr = "-", timeSplitStr = ":", dateString = null) { let now; now = dateString ? new Date(dateString) : /* @__PURE__ */ 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 < 0.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 true; } catch (_) { return false; } } 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 = 604800, path = "/", domain = "", secure = false, 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, precision }); } sleep(ms = 1e3) { return new Promise(((resolve) => setTimeout(resolve, ms))); } genericSort(arr, sortRules, emptyLast = true) { 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, 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); } } class AsyncQueue { constructor() { this.queue = Promise.resolve(); } addTask(fun) { this.queue = this.queue.then((() => fun())).catch(((e) => { console.error("执行异步队列任务失败:", e); })); } async waitAllFinished() { return this.queue; } } class GmHttp { 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 Content-Disposition: form-data; name="${key}"\r \r ${value}\r `)).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, 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, 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 = false) { 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, url, headers, timeout: httpTimeout, 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); } } class PluginManager { constructor() { this.plugins = /* @__PURE__ */ new Map(); } register(pluginClass) { if ("function" != typeof pluginClass) throw new Error("插件必须是一个类"); const instance = new pluginClass(); instance.pluginManager = this; const lowerName = instance.getName(); if (this.plugins.has(lowerName)) throw new Error(`插件"${name}"已注册`); this.plugins.set(lowerName, instance); } getBean(name2) { return this.plugins.get(name2); } async processCss() { const failedCssLoads = (await Promise.allSettled(Array.from(this.plugins).map((async ([name2, instance]) => { try { if ("function" == typeof instance.initCss) { const css = await instance.initCss(); css && utils.insertStyle(css); return { name: name2, status: "fulfilled" }; } return { name: name2, status: "skipped" }; } catch (e) { console.error(`插件 ${name2} 加载 CSS 失败`, e); return { name: name2, status: "rejected", error: e }; } })))).filter(((r) => "rejected" === r.status)); failedCssLoads.length && console.error("以下插件的 CSS 加载失败:", failedCssLoads.map(((p) => p.value.name))); } async processPlugins() { const failedPlugins = (await Promise.allSettled(Array.from(this.plugins).map((async ([name2, instance]) => { try { if ("function" == typeof instance.handle) { await instance.handle(); return { name: name2, status: "fulfilled" }; } } catch (e) { clog.error(`插件 ${name2} 执行失败`, e); return { name: name2, status: "rejected", error: e }; } })))).filter(((r) => "rejected" === r.status)); failedPlugins.length && console.error("以下插件执行失败:", failedPlugins.map(((p) => p.value.name))); } } class BasePlugin { constructor() { __publicField2(this, "pluginManager", null); __publicField2(this, "settingSvg", ''); __publicField2(this, "editSvg", ''); __publicField2(this, "deleteSvg", ''); __publicField2(this, "checkSvg", ''); __publicField2(this, "actressSvg", ''); __publicField2(this, "newSvg", ''); __publicField2(this, "refreshSvg", ''); __publicField2(this, "blacklistSvg", ''); __publicField2(this, "removeSvg", ''); __publicField2(this, "copySvg", ''); __publicField2(this, "titleSvg", ''); __publicField2(this, "carNumSvg", ''); __publicField2(this, "downSvg", ''); __publicField2(this, "handleSvg", ''); __publicField2(this, "siteSvg", ''); __publicField2(this, "videoSvg", ''); __publicField2(this, "screenSvg", ''); __publicField2(this, "recoveryVideoSvg", ''); __publicField2(this, "markDeleteSvg", ''); } getName() { throw new Error(`${this.constructor.name} 未显示getName()`); } getBean(name2) { return this.pluginManager.getBean(name2); } async initCss() { return ""; } async handle() { } getPageInfo() { let carNum2, url, actress, actors, publishTime, currentHref2 = window.location.href; if (isJavDb) { carNum2 = $('a[title="複製番號"]').attr("data-clipboard-text"); url = currentHref2.split("?")[0].split("#")[0]; actress = $(".female").prev().map(((i, el) => $(el).text())).get().join(" "); actors = $(".male").prev().map(((i, el) => $(el).text())).get().join(" "); publishTime = $('strong:contains("日期:")').parent(".panel-block").find(".value").text().trim(); } if (isJavBus) { url = currentHref2.split("?")[0]; carNum2 = url.split("/").filter(Boolean).pop().replace(/_\d{4}-\d{2}-\d{2}$/, ""); actress = $('span[onmouseover*="star_"] a').map(((i, el) => $(el).text())).get().join(" "); actors = ""; publishTime = $('span.header:contains("發行日期:")').parent("p").text().trim().replace("發行日期:", "").trim(); } return { carNum: carNum2, url, actress, actors, publishTime }; } getActressId() { const match = currentHref.match(/\/actors\/([^/?]+)/); return match && match.length > 1 ? match[1] : null; } getActressPageInfo() { let currentHref2 = window.location.href; if (!currentHref2.includes("/actors/") && !currentHref2.includes("/star/")) throw new Error("接口调用错误, 非演员详情页"); let nameList = [], $actor = isJavDb ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10"); $actor.length && $actor.text().trim().split(",").forEach(((name2) => { nameList.push(name2.trim()); })); let $sectionMeta = $(".section-meta:not(:contains('影片'))"); $sectionMeta.length && $sectionMeta.text().trim().split(",").forEach(((name2) => { nameList.push(name2.trim()); })); let role = $(".section-meta:contains('男優')").length > 0 ? "actor" : "actress", movieType = "censored"; nameList.some(((n) => n.includes("無碼"))) && (movieType = "uncensored"); currentHref2.includes("uncensored") && (movieType = "uncensored"); let blacklistUrl = null, starId = null; const urlObj = new URL(currentHref2); if (isJavDb) { starId = urlObj.pathname.split("/").filter(((segment) => "" !== segment.trim())).pop(); const params = urlObj.searchParams; params.delete("sort_type"); params.delete("page"); blacklistUrl = urlObj.toString(); } else if (isJavBus) { const splitStr = "/star/", parts = currentHref2.split(splitStr); if (parts.length < 2) throw new Error("提取演员url失败"); const host = parts[0]; starId = parts[1].split("/")[0]; blacklistUrl = host + splitStr + starId; } return { starId, name: nameList[0], allName: nameList, role, movieType, blacklistUrl }; } getSelector(type) { const key = type || (isJavDb ? "javdb" : isJavBus ? "javbus" : null), selectors = { javdb: { boxSelector: ".movie-list", itemSelector: ".movie-list .item", coverImgSelector: ".cover img", requestDomItemSelector: ".movie-list .item", nextPageSelector: ".pagination-next" }, javbus: { boxSelector: ".masonry", itemSelector: ".masonry .item", coverImgSelector: ".masonry .movie-box .photo-frame img", requestDomItemSelector: "#waterfall .item", nextPageSelector: "#next" } }; if (!key || !selectors[key]) throw new Error("类型错误: 无法确定选择器类型 (JavDb 或 JavBus)"); return selectors[key]; } parseMovieId(href) { return href.split("/").pop().split(/[?#]/)[0]; } getBoxCarInfo($box2) { var _a2, _b, _c; const $aLink = $box2.find("a"), url = $aLink.attr("href"); let carNum2 = null, title = null, publishTime = null; const $videoTitleEle = $box2.find(".video-title"); if ($videoTitleEle.length > 0) { const $strongEle = $videoTitleEle.find("strong"); $strongEle.length > 0 && (carNum2 = $strongEle.text().trim()); title = null == (_a2 = $aLink.attr("title")) ? void 0 : _a2.trim(); if (!title) { const fullTitleText = $videoTitleEle.text().trim(); title = carNum2 && fullTitleText.includes(carNum2) ? fullTitleText.replace(carNum2, "").trim() : fullTitleText; } publishTime = $box2.find(".meta").text().trim(); } if (!carNum2) { const $imgEle = $box2.find("img"); $imgEle.length > 0 && (title = (null == (_b = $imgEle.attr("title")) ? void 0 : _b.trim()) || (null == (_c = $imgEle.attr("data-title")) ? void 0 : _c.trim())); const dateElements = $box2.find("date").map(((i, el) => $(el).text().trim())).get(), isDate = (s) => /^\d{4}-\d{1,2}-\d{1,2}$/.test(s); publishTime = dateElements.find(isDate) || null; carNum2 = dateElements.find(((i) => !isDate(i))) || null; } if (!carNum2) { const msg = "提取番号信息失败: carNum 为空"; console.error("Error in getBoxCarInfo:", msg, "Box Element:", $box2.get(0)); show.error(msg); throw new Error(msg); } return { carNum: carNum2, url: url || "", aHref: url || "", title: title || "", publishTime: publishTime || "" }; } getBoxCarInfoList($boxList = null) { $boxList || ($boxList = $(this.getSelector().itemSelector)); if (0 === $boxList.length) { clog.error("获取当前列表页所有item的番号信息失败!"); return []; } const results = []; $boxList.each(((index, element) => { const $box2 = $(element); try { const carInfo = this.getBoxCarInfo($box2); results.push(carInfo); } catch (error) { clog.error("[getBoxCarInfoList] 提取单个 boxCar 信息失败:", error.message, "元素索引:", index); } })); return results; } checkDuplicateCarNumbers(currentList, newList) { if (!isJavDb) return false; if (!currentList || 0 === currentList.length || !newList || 0 === newList.length) return false; const currentCarNums = new Set(currentList.map(((item) => item.carNum)).filter(((n) => n))); if (0 === currentCarNums.size) return false; let consecutiveDuplicates = 0; for (let i = 0; i < newList.length; i++) { const newCarNum = newList[i] ? newList[i].carNum : null; if (newCarNum && currentCarNums.has(newCarNum)) { consecutiveDuplicates++; if (consecutiveDuplicates >= 2) { clog.warn("警告: 检测到连续番号信息重复, 该类别可能已被限制页码。"); return true; } } else consecutiveDuplicates = 0; } return false; } } const DMM_GRAPHQL_URL = "https://api.video.dmm.co.jp/graphql"; const dmmGraphQLKeyword = (code) => { const match = String(code || "").trim().toUpperCase().match(/^([A-Z0-9]{2,10})-(\d{1,6})$/); if (!match) return ""; return `${match[1].toLowerCase()}${match[2].padStart(5, "0")}`; }; const dmmGraphQLRequest = (query) => { return new Promise((resolve, reject) => { _GM_xmlhttpRequest({ method: "POST", url: DMM_GRAPHQL_URL, data: JSON.stringify({query}), timeout: 12e3, headers: { Accept: "application/json", "Content-Type": "application/json", Referer: "https://video.dmm.co.jp/", "Fanza-Device": "BROWSER", "Cache-Control": "no-cache" }, onload: (response) => { if (response.status < 200 || response.status >= 400) { reject(new Error(`DMM GraphQL HTTP ${response.status || 0}`)); return; } try { const json = JSON.parse(response.responseText || "{}"); if (Array.isArray(json.errors) && json.errors.length) { reject(new Error(json.errors[0].message || "DMM GraphQL error")); return; } resolve(json.data || {}); } catch (e) { reject(new Error(`DMM GraphQL JSON parse error: ${e.message}`)); } }, onerror: () => reject(new Error("DMM GraphQL network error")), ontimeout: () => reject(new Error("DMM GraphQL timeout")) }); }); }; const searchDmmContentIdsByGraphQL = async (code) => { const keyword = dmmGraphQLKeyword(code); if (!keyword) return []; const carNumNoHyphen = String(code || "").replace(/-/g, "").toLowerCase(); const results = []; const searchKeywords = [.../* @__PURE__ */ new Set([keyword, `${keyword}#`])]; for (const searchKeyword of searchKeywords) { const query = `{ legacySearchPPV(limit: 10, offset: 0, sort: SALES_RANK_SCORE, floor: AV, queryWord: ${JSON.stringify(searchKeyword)}) { result { contents { contentId serviceCode floorCode } } } }`; const data = await dmmGraphQLRequest(query); const contents = Array.isArray(data && data.legacySearchPPV && data.legacySearchPPV.result && data.legacySearchPPV.result.contents) ? data.legacySearchPPV.result.contents : []; for (const item of contents) { const contentId = String(item.contentId || ""); if (contentId.replace(/[^a-z0-9]/gi, "").toLowerCase().includes(carNumNoHyphen)) { results.push({ serviceCode: item.serviceCode || "digital", floorCode: item.floorCode || "AV", contentId }); } } if (results.length > 0) break; } return results; }; const getDmmGraphQLVideoUrls = async (code) => { const contentItems = await searchDmmContentIdsByGraphQL(code); if (!contentItems.length) return null; const results = await Promise.allSettled( contentItems.map((item) => extractTrailerLinks(item)) ); for (const result of results) { if (result.status === "fulfilled" && result.value) { return result.value; } } return null; }; const extractTrailerLinks = (item) => { const {contentId, serviceCode, floorCode} = item; return new Promise((resolve, reject) => { const trailerPageUrl = `https://www.dmm.co.jp/service/digitalapi/-/html5_player/=/cid=${contentId}/mtype=AhRVShI_/service=${serviceCode}/floor=${floorCode}/mode=/`; _GM_xmlhttpRequest({ method: "GET", url: trailerPageUrl, timeout: 15e3, headers: { "accept-language": "ja-JP,ja;q=0.9", Cookie: "age_check_done=1" }, onload: (response) => { const htmlContent = response.responseText || ""; if (htmlContent.includes("このサービスはお住まいの地域からは")) { reject(new Error("节点不可用,请将DMM域名分流到日本ip")); return; } const match = htmlContent.match(/const\s+args\s+=\s+(.*);/); if (!match) { reject(new Error("未在脚本中找到 const args = ... 变量")); return; } let bitrates; try { bitrates = JSON.parse(match[1]).bitrates; } catch (e) { reject(new Error(`解析播放器脚本 JSON 失败: ${e.message}`)); return; } if (!Array.isArray(bitrates)) { reject(new Error("解析画质链接失败: bitrates 字段不是一个数组或不存在")); return; } const qualityKeys = [ "4ks", "4k", "hhbs", "hhb", "hmb", "mhb", "mmb", "dmb_w", "dmb_s", "dm_s", "sm_s", "mhb_w" ]; const qualityNameRegex = new RegExp(`(${qualityKeys.join("|")})\\.mp4$`); const finalQualityMap = {}; for (const bitrate of bitrates) { const url = bitrate && bitrate.src; if (!url || typeof url !== "string" || !url.endsWith(".mp4")) continue; const qualityMatch = url.match(qualityNameRegex); let qualityKey = ""; if (qualityMatch && qualityMatch[1]) qualityKey = qualityMatch[1]; if (qualityKey && !finalQualityMap[qualityKey]) { finalQualityMap[qualityKey] = url; } } if (Object.keys(finalQualityMap).length === 0) { reject(new Error("未找到匹配要求的预览画质视频")); return; } resolve(finalQualityMap); }, onerror: () => reject(new Error("DMM 播放页请求网络错误")), ontimeout: () => reject(new Error("DMM 播放页请求超时")) }); }); }; 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 = true) { 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, 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 }).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, 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, 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, serviceCode, 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} = 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 _searchViaGraphQL() { clog.debug("尝试通过 GraphQL API 搜索视频..."); const videoMap = await getDmmGraphQLVideoUrls(this.carNum); if (videoMap && Object.keys(videoMap).length > 0) { this._updateCache(videoMap); clog.debug("GraphQL 成功解析出预览视频:", videoMap); const dmmCacheKey = "jhs_other_site_dmm"; const dmmCacheData = localStorage.getItem(dmmCacheKey) ? JSON.parse(localStorage.getItem(dmmCacheKey)) : {}; dmmCacheData[this.carNum] = { type: "graphql", url: `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}` }; localStorage.setItem(dmmCacheKey, JSON.stringify(dmmCacheData)); return videoMap; } return null; } 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); } if (!contentItems || 0 === contentItems.length) { clog.warn("Affiliate API 未找到视频,尝试 GraphQL API..."); const graphQLResult = await this._searchViaGraphQL(); if (graphQLResult) return graphQLResult; const $btn = $("#fanzaBtn"); $btn.attr("href", `https://www.dmm.co.jp/search/=/searchstr=${this.carNum}`); $btn.attr("title", "未查询到, 点击前往搜索页"); $btn.css("backgroundColor", "#de3333"); 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]; clog.error(`Affiliate 解析失败: ${errors[0] && errors[0].message || errors[0]}`, errors); clog.warn("尝试 GraphQL API 作为 fallback..."); const graphQLResult = await this._searchViaGraphQL(); if (graphQLResult) return graphQLResult; if (errors.some(((err) => err.message.includes("节点不可用")))) this.showErrorMessages && show.error("节点不可用,请将DMM域名分流到日本ip"); else { const displayError = errors[0].message || errors[0]; 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 = true) => new DmmVideoFetcher(carNum2, showErrorMessages).fetchVideo(); class AutoPagePlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "preloadDistance", 500); __publicField2(this, "currentPage", this.getInitialPageNumber()); __publicField2(this, "pageItems", []); } getName() { return "AutoPagePlugin"; } async initCss() { return "\n \n "; } async handle() { this.waterfall().then(); } getInitialPageNumber() { if (isJavBus$1) { const match = currentHref$1.match(/\/(page|star\/[^/]+)\/(\d+)/); return match ? parseInt(match[2], 10) : 1; } if (isJavDb$1) { const match = currentHref$1.match(/[?&]page=(\d+)/); return match ? parseInt(match[1], 10) : 1; } return 1; } async waterfall() { if (await this.shouldDisablePaging()) return; const selector = this.getSelector(); this.container = document.querySelector(selector.boxSelector); if (!this.container) { console.error("没有找到容器节点,停止瀑布流!"); return; } this.loader = document.createElement("div"); this.loader.className = "jhs-scroll"; this.container.parentNode.insertBefore(this.loader, this.container.nextSibling); this.pageItems.push({ page: this.currentPage, top: 0, url: window.location.href }); this.loader.addEventListener("click", (() => { this.loader.classList.contains("waterfall-error") && this.loadNextPage().then(); })); window.addEventListener("scroll", (() => { this.checkLoad(); this.checkScrollPosition(); })); const nextLink = document.querySelector(selector.nextPageSelector); this.nextUrl = null == nextLink ? void 0 : nextLink.href; this.hasMore = !!this.nextUrl; setTimeout((() => { this.checkLoad(); }), 1e3); this.hasMore || this.setState("waterfall-no-more", "已经到底了"); } async loadNextPage() { var _a2; if (await storageManager.getSetting("autoPage", YES) === NO) { this.setState("waterfall-loading", ""); return; } if (this.isLoading || !this.nextUrl) return; this.isLoading = true; this.setState("waterfall-loading", "加载中..."); const selector = this.getSelector(); try { const pageNum = utils.getUrlParam(this.nextUrl, "page"); let maxPage = 60; currentHref$1.includes("c11") && (maxPage = 30); if (isJavDb$1 && pageNum > maxPage || currentHref$1.includes("month")) { const beyond60Plugin = this.getBean("Beyond60Plugin"); if (beyond60Plugin) { const { html: html2, nextUrl, hasMore } = await beyond60Plugin.handleBeyond60(this.nextUrl); if (html2) { const pageTop2 = this.container.scrollHeight; this.pageItems.push({ page: this.currentPage + 1, top: pageTop2, url: this.nextUrl }); $(".movie-list").append(html2); } this.hasMore = hasMore; this.nextUrl = nextUrl; const $ul = beyond60Plugin.createPagination(pageNum, hasMore); $(".pagination").html($ul); this.setState("waterfall-loading", ""); this.hasMore || this.setState("waterfall-no-more", "已经到底了"); return; } } const html = await gmHttp.get(this.nextUrl); clog.log("请求下一页内容:", this.nextUrl); const $dom = utils.htmlTo$dom(html); isJavBus$1 && $dom.find(".avatar-box").length > 0 && $dom.find(".avatar-box").parent().remove(); let itemList = $dom.find(this.getSelector().requestDomItemSelector); const currentBoxCarInfoList = this.getBoxCarInfoList(), nextPageBoxCarInfoList = this.getBoxCarInfoList(itemList); if (this.checkDuplicateCarNumbers(currentBoxCarInfoList, nextPageBoxCarInfoList)) { this.nextUrl = null; this.hasMore = false; this.setState("waterfall-error", "翻页内容出现重复数据, 可能首页已更新了新视频 或 页码受JavDB限制, 停止瀑布流"); return; } const pageTop = this.container.scrollHeight; this.pageItems.push({ page: this.currentPage + 1, top: pageTop, url: this.nextUrl }); const listPagePlugin = this.getBean("ListPagePlugin"); let coverImgNodeList = $dom.find(this.getSelector().coverImgSelector); listPagePlugin.replaceHdImg(coverImgNodeList); $(this.getSelector().boxSelector).append(itemList); this.nextUrl = null == (_a2 = $dom.find(selector.nextPageSelector)) ? void 0 : _a2.attr("href"); this.hasMore = !!this.nextUrl; let pagination = $dom.find(".pagination"); $(".pagination").replaceWith(pagination); this.setState("waterfall-loading", ""); this.hasMore || this.setState("waterfall-no-more", "已经到底了"); } catch (e) { clog.error("加载失败:", e); this.setState("waterfall-error", "加载失败,点击重试"); } finally { this.isLoading = false; } } checkScrollPosition() { const scrollPosition = window.scrollY; for (let i = this.pageItems.length - 1; i >= 0; i--) { const page = this.pageItems[i]; if (scrollPosition >= page.top) { if (this.currentPage !== page.page) { this.currentPage = page.page; this.updatePageUrl(page.url); } break; } } } checkLoad() { if (!this.loader) return; this.loader.getBoundingClientRect().top < window.innerHeight + this.preloadDistance && this.loadNextPage().then(); } async shouldDisablePaging() { if (!window.isListPage) return true; await storageManager.getSetting("autoPage", YES); return ["search?q", "handlePlayback=1", "handleTop=1", "/want_watch_videos", "/watched_videos", "/advanced_search?type=100"].some(((path) => currentHref$1.includes(path))); } updatePageUrl_old(href) { window.history.pushState({}, "", href); if (isJavBus$1) { const match = href.match(/\/(page|star\/.*?)\/(\d+)/), pageNumber = match ? parseInt(match[2], 10) : null; document.title = document.title.replace(/第\d+頁/, "第" + pageNumber + "頁"); } } updatePageUrl(url) { window.history.replaceState({}, "", url); isJavBus$1 && (document.title = document.title.replace(/第\d+頁/, `第${this.currentPage}頁`)); } setState(state, text) { this.loader.className = `jhs-scroll ${state}`; this.loader.textContent = text; } } class AliyunApi { constructor(refresh_token) { this.baseApiUrl = "https://api.aliyundrive.com"; this.refresh_token = refresh_token; this.authorization = null; this.default_drive_id = null; this.backupFolderId = null; } async getDefaultDriveId() { if (this.default_drive_id) return this.default_drive_id; this.userInfo = await this.getUserInfo(); this.default_drive_id = this.userInfo.default_drive_id; return this.default_drive_id; } async getHeaders() { if (this.authorization) return { authorization: this.authorization }; this.authorization = await this.getAuthorization(); return { authorization: this.authorization }; } async getAuthorization() { let url = this.baseApiUrl + "/v2/account/token", data = { refresh_token: this.refresh_token, grant_type: "refresh_token" }; try { return "Bearer " + (await gmHttp.post(url, data)).access_token; } catch (e) { throw e.message.includes("is not valid") ? new Error("refresh_token无效, 请重新填写并保存") : e; } } async getUserInfo() { const headers = await this.getHeaders(); let url = this.baseApiUrl + "/v2/user/get"; return await gmHttp.post(url, {}, headers); } async deleteFile(file_id, drive_id = null) { if (!file_id) throw new Error("未传入file_id"); drive_id || (drive_id = await this.getDefaultDriveId()); let data = { file_id, drive_id }, url = this.baseApiUrl + "/v2/recyclebin/trash"; const headers = await this.getHeaders(); await gmHttp.post(url, data, headers); return {}; } async createFolder(name2, drive_id = null, parent_folder_id = "root") { drive_id || (drive_id = await this.getDefaultDriveId()); let url = this.baseApiUrl + "/adrive/v2/file/createWithFolders", data = { name: name2, type: "folder", parent_file_id: parent_folder_id, check_name_mode: "auto_rename", content_hash_name: "sha1", drive_id }; const headers = await this.getHeaders(); return await gmHttp.post(url, data, headers); } async getFileList(parent_folder_id = "root", drive_id = null) { drive_id || (drive_id = await this.getDefaultDriveId()); let url = this.baseApiUrl + "/adrive/v3/file/list"; const data = { drive_id, parent_file_id: parent_folder_id, limit: 200, all: false, url_expire_sec: 14400, image_thumbnail_process: "image/resize,w_256/format,avif", image_url_process: "image/resize,w_1920/format,avif", video_thumbnail_process: "video/snapshot,t_120000,f_jpg,m_lfit,w_256,ar_auto,m_fast", fields: "*", order_by: "updated_at", order_direction: "DESC" }, headers = await this.getHeaders(); return (await gmHttp.post(url, data, headers)).items; } async uploadFile(folder_id, fileName, uploadContent, drive_id = null) { show.info("请求存储空间中..."); let createFileUrl = this.baseApiUrl + "/adrive/v2/file/createWithFolders"; drive_id || (drive_id = await this.getDefaultDriveId()); let data = { drive_id, part_info_list: [{ part_number: 1 }], parent_file_id: folder_id, name: fileName, type: "file", check_name_mode: "auto_rename" }; const headers = await this.getHeaders(), createFileResult = await gmHttp.post(createFileUrl, data, headers), upload_id = createFileResult.upload_id, upload_file_id = createFileResult.file_id, upload_url = createFileResult.part_info_list[0].upload_url; show.info("开始上传文件..."); await this._doUpload(upload_url, uploadContent); await gmHttp.post("https://api.aliyundrive.com/v2/file/complete", data = { drive_id, file_id: upload_file_id, upload_id }, headers); } _doUpload(upload_url, uploadContent) { return new Promise(((resolve, reject) => { $.ajax({ type: "PUT", url: upload_url, data: uploadContent, contentType: " ", processData: false, success: (res, status, xhr) => { 200 === xhr.status ? resolve({}) : reject(xhr); }, error: (xhr) => { clog.error("上传失败", xhr.responseText); reject(xhr); } }); })); } async getDownloadUrl(file_id, drive_id = null) { drive_id || (drive_id = await this.getDefaultDriveId()); let url = this.baseApiUrl + "/v2/file/get_download_url"; const headers = await this.getHeaders(); let data = { file_id, drive_id }; return (await gmHttp.post(url, data, headers)).url; } async _createBackupFolder(folderName) { const fileList = await this.getFileList(); let folderObj = null; for (let i = 0; i < fileList.length; i++) { let file = fileList[i]; if (file.name === folderName) { folderObj = file; break; } } if (!folderObj) { show.info("不存在备份目录, 进行创建"); folderObj = await this.createFolder(folderName); } this.backupFolderId = folderObj.file_id; } async backup(folderName, fileName, uploadContent) { if (this.backupFolderId) await this.uploadFile(this.backupFolderId, fileName, uploadContent); else { await this._createBackupFolder(folderName); await this.uploadFile(this.backupFolderId, fileName, uploadContent); } } async getBackupList(folderName) { let dataList; if (this.backupFolderId) dataList = await this.getFileList(this.backupFolderId); else { await this._createBackupFolder(folderName); dataList = await this.getFileList(this.backupFolderId); } const fileList = []; dataList.forEach(((data) => { fileList.push({ name: data.name, fileId: data.file_id, createTime: data.created_at, size: data.size }); })); return fileList; } } class WebDavApi { constructor(davUrl, username, password) { this.davUrl = davUrl.endsWith("/") ? davUrl : davUrl + "/"; this.username = username; this.password = password; this.folderName = null; } _getAuthHeaders() { return { Authorization: `Basic ${btoa(`${this.username}:${this.password}`)}`, Depth: "1" }; } _sendRequest(method, path, headers = {}, data) { return new Promise(((resolve, reject) => { const url = this.davUrl + path, allHeaders = { ...this._getAuthHeaders(), ...headers }; _GM_xmlhttpRequest({ method, url, headers: allHeaders, data, onload: (response) => { if (response.status >= 200 && response.status < 300) resolve(response); else { console.error(response); reject(new Error(`请求失败 ${response.status}: ${response.statusText}`)); } }, onerror: (response) => { console.error("请求WebDav发生错误:", response); reject(new Error("请求WebDav失败, 请检查服务是否启动, 凭证是否正确")); } }); })); } async backup(folderName, fileName, uploadContent) { await this._sendRequest("MKCOL", folderName); const path = folderName + "/" + fileName; await this._sendRequest("PUT", path, { "Content-Type": "text/plain" }, uploadContent); } async getFileList(folderName) { var _a2, _b, _c; const xmlResponse = (await this._sendRequest("PROPFIND", folderName, { "Content-Type": "application/xml" }, '\n \n \n \n \n \n \n \n \n \n ')).responseText, items = new DOMParser().parseFromString(xmlResponse, "text/xml").getElementsByTagNameNS("DAV:", "response"), fileList = []; for (let i = 0; i < items.length; i++) { if (0 === i) continue; let item = items[i]; console.log(item); const name2 = item.getElementsByTagNameNS("DAV:", "displayname")[0].textContent, size = (null == (_a2 = item.getElementsByTagNameNS("DAV:", "getcontentlength")[0]) ? void 0 : _a2.textContent) || "0", createTime = (null == (_b = item.getElementsByTagNameNS("DAV:", "creationdate")[0]) ? void 0 : _b.textContent) || (null == (_c = item.getElementsByTagNameNS("DAV:", "getlastmodified")[0]) ? void 0 : _c.textContent) || ""; "0" !== size && fileList.push({ fileId: name2, name: name2, size: Number(size), createTime }); } fileList.reverse(); return fileList; } async deleteFile(fileId) { let path = this.folderName + "/" + encodeURI(fileId); await this._sendRequest("DELETE", path, { "Cache-Control": "no-cache" }); } async getBackupList(folderName) { this.folderName = folderName; await this._sendRequest("MKCOL", folderName); return this.getFileList(folderName); } async getFileContent(filePath) { let path = this.folderName + "/" + filePath; return (await this._sendRequest("GET", path, { Accept: "application/octet-stream" })).responseText; } } const SALT = "x7k9p3"; function simpleEncrypt(str) { return (SALT + str + SALT).split("").map(((char) => { const code = char.codePointAt(0); return String.fromCodePoint(code + 5); })).join(""); } class SettingPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "folderName", "JHS-数据备份"); __publicField2(this, "cacheItems", [{ key: "jhs_dmm_video", text: "🎥 预览视频缓存", title: "预览视频缓存" }, { key: "jhs_other_site", text: "🌍 第三方站点缓存", title: "第三方站点资源检测结果, 如missav,123Av等" }, { key: "jhs_screenShot", text: "🖼️ 缩略图缓存", title: "缩略图缓存" }, { key: "jhs_translate", text: "🆎 标题翻译", title: "标题翻译" }, { key: "jhs_actress_info", text: "👩 演员信息", title: "演员的年龄三围等数据信息" }, { key: "jhs_score_info", text: "⭐ Top250|热播 评分数据", title: "Top250及热播的评分数据" }]); } getName() { return "SettingPlugin"; } async initCss() { const settingObj = await storageManager.getSetting(); let containerWidth = (null == settingObj ? void 0 : settingObj.containerWidth) ?? "100", containerColumns = utils.isMobile() && window.innerWidth < 1e3 ? 1 : (null == settingObj ? void 0 : settingObj.containerColumns) ?? 5; this.applyImageMode().then(); let containerWidthCss = ` section .container{ max-width: 1000px !important; min-width: ${containerWidth}%; } .movie-list, .movie-list.v{ grid-template-columns: repeat(${containerColumns}, minmax(0, 1fr)); } `; isJavBus$1 && (containerWidthCss = ` .container-fluid .row{ max-width: 1000px !important; min-width: ${containerWidth}%; margin: auto auto; } .container { max-width: 1000px !important; min-width: 80%; margin: auto auto; } .masonry { grid-template-columns: repeat(${containerColumns}, minmax(0, 1fr)); } `); return ` `; } async handle() { await storageManager.getSetting("enableClog", YES) === YES && clog.show(); if (isJavDb$1) { let handleResize2 = function() { if ($(".navbar-search").is(":hidden")) { $(".mini-setting-box").hide(); $(".setting-box").show(); } else { $(".mini-setting-box").show(); $(".setting-box").hide(); } }; $("#navbar-menu-user .navbar-end").prepend(''); utils.loopDetector((() => $("#miniHistoryBtn").length > 0), (() => { $(".miniHistoryBtnBox").before('\n \n '); handleResize2(); })); $(window).resize(handleResize2); } if (isJavBus$1) { utils.loopDetector((() => $("#waitCheckBtn").length), (() => { $("#waitCheckBtn").parent().append('\n
\n
\n \n 设置\n \n
\n
\n
\n '); }), 1, 1e4, false); window.isDetailPage && $("h3").before('\n
\n
\n
\n \n 设置\n \n
\n
\n
\n
\n '); } $(".main-nav, .container-fluid").on("click", "#setting-btn, #mini-setting-btn", (() => { clog.lowZIndex(); this.openSettingDialog(); })); $(".main-nav, .container-fluid").on("mouseenter", ".setting-box", (async () => { $(".simple-setting").html(await this.simpleSetting()).show(); this.initSimpleSettingForm().then(); clog.lowZIndex(); })).on("mouseleave", ".setting-box", (() => { $(".simple-setting").html("").hide(); })); $(".main-nav, .container-fluid").on("mouseenter", ".mini-setting-box", (async () => { $(".mini-simple-setting").html(await this.simpleSetting()).show(); this.initSimpleSettingForm().then(); clog.lowZIndex(); })).on("mouseleave", ".mini-setting-box", (() => { $(".mini-simple-setting").html("").hide(); })); } async openSettingDialog(defaultActivePanel = "backup-panel", fun) { const buttonsHTML = this.cacheItems.map(((item) => `
${item.text}
清理 查看
`)).join(""); let videoQualityHtml = ""; qualityOptions.forEach(((option) => { option.canSelect && (videoQualityHtml += ``); })); const coverButtonPlugin = this.getBean("CoverButtonPlugin"); let settingHtml = `
💾 数据备份
⚙️ 基础配置
🚫 屏蔽配置
📋 定时任务
🌐 外部网站
🗑️ 标记删除
⌨️ 快捷键配置
🧹 清理缓存
💵 打赏作者
导入数据 导出数据 获取refresh_token
阿里云盘备份
refresh_token:

WebDav备份
服务地址:
用户名:
密码:
已鉴定标签展示位置:
已鉴定内容处理方式:
鉴定补录演员信息 ❓

列表页功能按钮
按钮-打开待鉴定:
按钮-打开已收藏:
打开待鉴定|已收藏 窗口数:
随机打开已收藏:

封面快捷按钮
${coverButtonPlugin.screenSvg}长缩略图:
${coverButtonPlugin.videoSvg}预览视频:
${coverButtonPlugin.handleSvg}鉴定按钮:
${coverButtonPlugin.siteSvg}第三方跳转:
${coverButtonPlugin.copySvg}复制按钮:

预览视频默认画质:
评论区条数:
高亮已收藏演员 ❓
分类标签|高亮演员-边框样式:

请求超时时间(毫秒):
请求失败重试次数:

启用控制台日志:
日志最大行数:
请求并发数量:
请求间隔时间(毫秒):

自动检测屏蔽黑名单演员
任务开关: ❓
任务间隔时间:
检测规则:
自动同步已收藏的演员
任务开关: ❓
任务间隔时间:
自动检测已收藏演员的最新作品
任务开关: ❓
任务间隔时间:
检测规则:
域名 - MissAv:
域名 - Jable:
域名 - Avgle:
域名 - JavTrailer:
域名 - 123Av:
域名 - JavDb:
域名 - JavBus:
域名 - SupJav:
标记删除列表
加载中...

⚠ 作品删除目前暂无法删除115下载记录,如果需要重新下载,请去115云下载记录里删除或清空记录

快捷键修改后, 刷新页面生效

🚫 屏蔽:
⭐ 收藏:
📥️ 已下载:
🔍 已观看:
❓ 对视频列表页启用快捷键:
⏩ 快进:
▲ 折叠:
💻 控制台:
显示已鉴定内容
屏蔽单番号:
屏蔽演员:
屏蔽关键词:
收藏:
已下载:
已观看:
显示所有:
启用划词屏蔽 ❓

评论区屏蔽词:

视频标题屏蔽词:

以下操作, 不会对核心数据造成影响


${buttonsHTML}

如果JHS-enhance给您带来了便捷和价值,请考虑给予一点支持,您的鼓励是我持续创作的最大动力!感谢您的慷慨支持!

SOL二维码

SOL

点击地址可复制

`; layer.open({ type: 1, title: "设置", content: settingHtml, area: utils.getResponsiveArea(["55%", "90%"]), scrollbar: false, success: (layero, index) => { $(layero).find(".layui-layer-content").css("position", "relative"); this.loadForm(); this.bindClick(); utils.setupEscClose(index); fun && fun(); }, end: () => { this.getBean("CoverButtonPlugin").enableSvgBtn(); } }); } async simpleSetting() { let settingObj = await storageManager.getSetting(); const showFilterItemHotKey = settingObj.showFilterItemHotKey, showFilterActorItemHotKey = settingObj.showFilterActorItemHotKey, showFilterKeywordItemHotKey = settingObj.showFilterKeywordItemHotKey, showFavoriteItemHotKey = settingObj.showFavoriteItemHotKey, showHasDownItemHotKey = settingObj.showHasDownItemHotKey, showHasWatchItemHotKey = settingObj.showHasWatchItemHotKey, showAllItemHotKey = settingObj.showAllItemHotKey; return `
显示已鉴定内容:
屏蔽单番号${showFilterItemHotKey ? `(${showFilterItemHotKey})` : ""}:
屏蔽演员${showFilterActorItemHotKey ? `(${showFilterActorItemHotKey})` : ""}:
屏蔽关键词${showFilterKeywordItemHotKey ? `(${showFilterKeywordItemHotKey})` : ""}:
收藏${showFavoriteItemHotKey ? `(${showFavoriteItemHotKey})` : ""}:
已下载${showHasDownItemHotKey ? `(${showHasDownItemHotKey})` : ""}:
已观看${showHasWatchItemHotKey ? `(${showHasWatchItemHotKey})` : ""}:
❓ 显示所有${showAllItemHotKey ? `(${showAllItemHotKey})` : ""}:

❓ 弹窗方式打开页面:
鉴定后立即关闭页面:

❓ 瀑布流模式:
启用标题翻译:
启用悬浮大图:
启用115视频匹配:

${isJavDb$1 ? '\n
\n \n ❓ 加载女优信息:\n \n
\n \n
\n
' : ""}
❓ 加载第三方视频资源:
❓ 加载长缩略图:
❓ 更高画质预览视频:

❓ 竖图模式:
页面列数:
页面宽度:
`; } async loadForm() { let settingObj = await storageManager.getSetting(); $("#videoQuality").val(settingObj.videoQuality); $("#reviewCount").val(settingObj.reviewCount || 20); $("#tagPosition").val(settingObj.tagPosition || "rightTop"); $("#movieShowType").val(settingObj.movieShowType || "hide"); $("#waitCheckCount").val(settingObj.waitCheckCount || 5); $("#showWaitCheckBtn").prop("checked", !settingObj.showWaitCheckBtn || settingObj.showWaitCheckBtn === YES); $("#showWaitDownBtn").prop("checked", !settingObj.showWaitDownBtn || settingObj.showWaitDownBtn === YES); $("#randomOpenWaitDown").prop("checked", !!settingObj.randomOpenWaitDown && settingObj.randomOpenWaitDown === YES); $("#checkConcurrencyCount").val(settingObj.checkConcurrencyCount || 2); $("#checkRequestSleep").val(settingObj.checkRequestSleep || 100); $("#enableCheckBlacklist").val(settingObj.enableCheckBlacklist || YES); $("#checkBlacklist_intervalTime").val(settingObj.checkBlacklist_intervalTime || 12); $("#checkBlacklist_ruleTime").val(settingObj.checkBlacklist_ruleTime || 8760); $("#enableCheckFavoriteActress").val(settingObj.enableCheckFavoriteActress || YES); $("#checkFavoriteActress_IntervalTime").val(settingObj.checkFavoriteActress_IntervalTime || 24); $("#enableCheckNewVideo").val(settingObj.enableCheckNewVideo || YES); $("#checkNewVideo_intervalTime").val(settingObj.checkNewVideo_intervalTime || 12); $("#checkNewVideo_ruleTime").val(settingObj.checkNewVideo_ruleTime || 8760); const highlightedTagNumber = settingObj.highlightedTagNumber || 1, highlightedTagColor = settingObj.highlightedTagColor || "#ce2222"; $("#highlightedTagNumber").val(settingObj.highlightedTagNumber || 1); $("#highlightedTagColor").val(settingObj.highlightedTagColor || "#ce2222"); $("#highlightedTagLabel").css("border", `${highlightedTagNumber}px solid ${highlightedTagColor}`); $("#enableClog").val(settingObj.enableClog || YES); $("#clogMsgCount").val(settingObj.clogMsgCount || 2e3); $("#refresh_token").val(settingObj.refresh_token || ""); $("#httpTimeout").val(settingObj.httpTimeout || 5e3); $("#httpRetryCount").val(settingObj.httpRetryCount || 3); $("#webDavUrl").val(settingObj.webDavUrl || ""); $("#webDavUsername").val(settingObj.webDavUsername || ""); $("#webDavPassword").val(settingObj.webDavPassword || ""); $("#enableTitleSelectFilter").prop("checked", !settingObj.enableTitleSelectFilter || settingObj.enableTitleSelectFilter === YES); $("#enableFavoriteActresses").prop("checked", !settingObj.enableFavoriteActresses || settingObj.enableFavoriteActresses === YES); $("#enableSaveActressCarInfo").prop("checked", !!settingObj.enableSaveActressCarInfo && settingObj.enableSaveActressCarInfo === YES); $("#enableScreenSvg").prop("checked", !settingObj.enableScreenSvg || settingObj.enableScreenSvg === YES); $("#enableVideoSvg").prop("checked", !settingObj.enableVideoSvg || settingObj.enableVideoSvg === YES); $("#enableHandleSvg").prop("checked", !settingObj.enableHandleSvg || settingObj.enableHandleSvg === YES); $("#enableSiteSvg").prop("checked", !settingObj.enableSiteSvg || settingObj.enableSiteSvg === YES); $("#enableCopySvg").prop("checked", !settingObj.enableCopySvg || settingObj.enableCopySvg === YES); const otherSitePlugin = this.getBean("OtherSitePlugin"), missAvUrl = await otherSitePlugin.getMissAvUrl(), jableUrl = await otherSitePlugin.getjableUrl(), avgleUrl = await otherSitePlugin.getAvgleUrl(), javTrailersUrl = await otherSitePlugin.getJavTrailersUrl(), av123Url = await otherSitePlugin.getAv123Url(), javDbUrl = await otherSitePlugin.getJavDbUrl(), javBusUrl = await otherSitePlugin.getJavBusUrl(), supJavUrl = await otherSitePlugin.getSupJavUrl(); $("#missAvUrl").val(missAvUrl); $("#jableUrl").val(jableUrl); $("#avgleUrl").val(avgleUrl); $("#javTrailersUrl").val(javTrailersUrl); $("#av123Url").val(av123Url); $("#javDbUrl").val(javDbUrl); $("#javBusUrl").val(javBusUrl); $("#supJavUrl").val(supJavUrl); let reviewKeywordList = await storageManager.getReviewFilterKeywordList(), filterKeywordList = await storageManager.getTitleFilterKeyword(); reviewKeywordList && reviewKeywordList.forEach(((reviewKeyword) => { this.addLabelTag("#reviewKeywordContainer", reviewKeyword); })); filterKeywordList && filterKeywordList.forEach(((reviewKeyword) => { this.addLabelTag("#filterKeywordContainer", reviewKeyword); })); ["#reviewKeywordContainer", "#filterKeywordContainer"].forEach(((containerId) => { $(`${containerId} .add-tag-btn`).on("click", ((event) => this.addKeyword(event, containerId))); $(`${containerId} .keyword-input`).on("keypress", ((event) => { "Enter" === event.key && this.addKeyword(event, containerId); })); })); $("#hotkey-panel [id]").map(((i, el) => el.id)).get().forEach(((containerId) => { const $element = $(`#${containerId}`), defaultValue = void 0 !== settingObj[containerId] ? settingObj[containerId] : $element.attr("data-default-hotkey") || ""; $element.val(defaultValue).on("input", ((event) => { let value = $(event.target).val(); if (/[\u4e00-\u9fa5]/.test(value) || /^Shift[a-zA-Z0-9]+$/.test(value)) { $(event.target).val(""); show.error("非法输入:不能输入中文或输入法转换错误"); } })).on("keydown", ((event) => this.handleHotkeyInput(event, $element))); })); $("#enableImageHotKey").prop("checked", !!settingObj.enableImageHotKey && settingObj.enableImageHotKey === YES); } handleHotkeyInput(event, $input) { event.preventDefault(); const hotkey = this.parseHotkey(event); "" !== hotkey ? this.isDuplicateHotkey(hotkey, $input.attr("id")) ? show.error("该快捷键已被其他功能使用!") : $input.val(hotkey) : $input.val(""); } parseHotkey(event) { if ("Backspace" === event.key || "Process" === event.key) return ""; const keys = []; event.ctrlKey && keys.push("Ctrl"); event.shiftKey && keys.push("Shift"); event.altKey && keys.push("Alt"); event.metaKey && keys.push("Cmd"); const key = { " ": "Space", Control: "Ctrl", Meta: "Cmd", ArrowUp: "Up", ArrowDown: "Down", ArrowLeft: "Left", ArrowRight: "Right" }[event.key] || (event.key.length > 1 ? event.key.replace("Arrow", "") : event.key); ["Control", "Shift", "Alt", "Meta"].includes(event.key) || keys.push(key); return keys.length > 0 ? keys.join("+") : ""; } isDuplicateHotkey(hotkey, currentInputId) { let isDuplicate = false; $("#hotkey-panel [id]").each(((i, el) => { if (el.id !== currentInputId && hotkey && hotkey === $(el).val()) { isDuplicate = true; return false; } })); return isDuplicate; } async initSimpleSettingForm() { let settingObj = await storageManager.getSetting(); $("#containerColumns").val(settingObj.containerColumns || 5); $("#showContainerColumns").text(settingObj.containerColumns || 5); $("#containerWidth").val((settingObj.containerWidth || 100) - 70); $("#showContainerWidth").text((settingObj.containerWidth || 100) + "%"); $("#dialogOpenDetail").prop("checked", !settingObj.dialogOpenDetail || settingObj.dialogOpenDetail === YES); $("#needClosePage").prop("checked", !settingObj.needClosePage || settingObj.needClosePage === YES); $("#autoPage").prop("checked", !settingObj.autoPage || settingObj.autoPage === YES); $("#translateTitle").prop("checked", !settingObj.translateTitle || settingObj.translateTitle === YES); $("#enableLoadActressInfo").prop("checked", !settingObj.enableLoadActressInfo || settingObj.enableLoadActressInfo === YES); $("#enableLoadOtherSite").prop("checked", !settingObj.enableLoadOtherSite || settingObj.enableLoadOtherSite === YES); $("#containerColumns").on("input", (async (event) => { let columns = $("#containerColumns").val(); $("#showContainerColumns").text(columns); if (isJavDb$1) { document.querySelector(".movie-list").style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`; } if (isJavBus$1) { document.querySelector(".masonry").style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`; } await storageManager.saveSettingItem("containerColumns", columns); this.applyImageMode(); })); $("#containerWidth").on("input", (async (event) => { let containerWidth = parseInt($(event.target).val()); const value = containerWidth + 70 + "%"; $("#showContainerWidth").text(value); if (isJavDb$1) { document.querySelector("section .container").style.minWidth = value; } if (isJavBus$1) { document.querySelector(".container-fluid .row").style.minWidth = value; } storageManager.saveSettingItem("containerWidth", containerWidth + 70); })); $("#dialogOpenDetail").on("change", ((event) => { let dialogOpenDetail = $("#dialogOpenDetail").is(":checked") ? YES : NO; storageManager.saveSettingItem("dialogOpenDetail", dialogOpenDetail); })); $("#showFilterItem").prop("checked", !!settingObj.showFilterItem && settingObj.showFilterItem === YES); $("#showFilterActorItem").prop("checked", !!settingObj.showFilterActorItem && settingObj.showFilterActorItem === YES); $("#showFilterKeywordItem").prop("checked", !!settingObj.showFilterKeywordItem && settingObj.showFilterKeywordItem === YES); $("#showFavoriteItem").prop("checked", !settingObj.showFavoriteItem || settingObj.showFavoriteItem === YES); $("#showHasDownItem").prop("checked", !settingObj.showHasDownItem || settingObj.showHasDownItem === YES); $("#showHasWatchItem").prop("checked", !settingObj.showHasWatchItem || settingObj.showHasWatchItem === YES); $("#showFilterItem").on("change", (async (event) => { let showFilterItem = $("#showFilterItem").is(":checked") ? YES : NO; await storageManager.saveSettingItem("showFilterItem", showFilterItem); window.refresh(); })); $("#showFilterActorItem").on("change", (async (event) => { let showFilterActorItem = $("#showFilterActorItem").is(":checked") ? YES : NO; await storageManager.saveSettingItem("showFilterActorItem", showFilterActorItem); window.refresh(); })); $("#showFilterKeywordItem").on("change", (async (event) => { let showFilterKeywordItem = $("#showFilterKeywordItem").is(":checked") ? YES : NO; await storageManager.saveSettingItem("showFilterKeywordItem", showFilterKeywordItem); window.refresh(); })); $("#showFavoriteItem").on("change", (async (event) => { let showFavoriteItem = $("#showFavoriteItem").is(":checked") ? YES : NO; await storageManager.saveSettingItem("showFavoriteItem", showFavoriteItem); window.refresh(); })); $("#showHasDownItem").on("change", (async (event) => { let showHasDownItem = $("#showHasDownItem").is(":checked") ? YES : NO; await storageManager.saveSettingItem("showHasDownItem", showHasDownItem); window.refresh(); })); $("#showHasWatchItem").on("change", (async (event) => { let showHasWatchItem = $("#showHasWatchItem").is(":checked") ? YES : NO; await storageManager.saveSettingItem("showHasWatchItem", showHasWatchItem); window.refresh(); })); const $otherCheckboxes = $("#showFilterItem, #showFilterActorItem, #showFilterKeywordItem, #showFavoriteItem, #showHasDownItem, #showHasWatchItem"), updateOtherCheckboxesState = () => { const isShowAllChecked = $("#showAllItem").is(":checked"); $otherCheckboxes.prop("disabled", isShowAllChecked); isShowAllChecked ? $otherCheckboxes.attr("data-tip", "请先关闭显示所有才可点击") : $otherCheckboxes.removeAttr("data-tip"); }; $("#showAllItem").prop("checked", !!settingObj.showAllItem && settingObj.showAllItem === YES); $("#showAllItem").on("change", (async (event) => { let showAllItem = $("#showAllItem").is(":checked") ? YES : NO; await storageManager.saveSettingItem("showAllItem", showAllItem); updateOtherCheckboxesState(); window.refresh(); })); updateOtherCheckboxesState(); $("#needClosePage").on("change", (async (event) => { await storageManager.saveSettingItem("needClosePage", $("#needClosePage").is(":checked") ? YES : NO); window.refresh(); })); $("#autoPage").on("change", (async (event) => { const autoPage = $("#autoPage").is(":checked") ? YES : NO; await storageManager.saveSettingItem("autoPage", autoPage); autoPage === YES ? ($("#sort-toggle-btn").hide(), $("#one-time-sort-btn").show()) : ($("#sort-toggle-btn").show(), $("#one-time-sort-btn").hide()); })); $("#translateTitle").on("change", (async (event) => { const translateTitle = $("#translateTitle").is(":checked") ? YES : NO; await storageManager.saveSettingItem("translateTitle", translateTitle); if (translateTitle === YES) { await this.getBean("ListPagePlugin").doFilter(); window.isDetailPage && await this.getBean("TranslatePlugin").translate(); } else { await this.getBean("ListPagePlugin").revertTranslation(); $(".translated-title").remove(); } })); $("#hoverBigImg").prop("checked", !!settingObj.hoverBigImg && settingObj.hoverBigImg === YES); $("#hoverBigImg").on("change", (async (event) => { const hoverBigImg = $("#hoverBigImg").is(":checked") ? YES : NO; await storageManager.saveSettingItem("hoverBigImg", hoverBigImg); hoverBigImg === YES ? window.imageHoverPreviewObj = new ImageHoverPreview({ selector: this.getSelector().coverImgSelector }) : window.imageHoverPreviewObj && window.imageHoverPreviewObj.destroy(); })); $("#enableLoadActressInfo").on("change", (async (event) => { const enableLoadActressInfo = $("#enableLoadActressInfo").is(":checked") ? YES : NO; await storageManager.saveSettingItem("enableLoadActressInfo", enableLoadActressInfo); enableLoadActressInfo === YES ? this.getBean("ActressInfoPlugin").loadActressInfo() : $(".actress-info").remove(); })); $("#enableLoadOtherSite").on("change", (async (event) => { const enableLoadOtherSite = $("#enableLoadOtherSite").is(":checked") ? YES : NO; await storageManager.saveSettingItem("enableLoadOtherSite", enableLoadOtherSite); enableLoadOtherSite === YES ? this.getBean("OtherSitePlugin").loadOtherSite().then() : $("#otherSiteBox").remove(); })); $("#enableLoadScreenShot").prop("checked", !settingObj.enableLoadScreenShot || settingObj.enableLoadScreenShot === YES); $("#enableLoadScreenShot").on("change", (async (event) => { const enableLoadScreenShot = $("#enableLoadScreenShot").is(":checked") ? YES : NO; await storageManager.saveSettingItem("enableLoadScreenShot", enableLoadScreenShot); enableLoadScreenShot === YES ? this.getBean("ScreenShotPlugin").loadScreenShot().then() : $(".screen-container").remove(); })); $("#enableLoadPreviewVideo").prop("checked", !settingObj.enableLoadPreviewVideo || settingObj.enableLoadPreviewVideo === YES); $("#enableLoadPreviewVideo").on("change", (async (event) => { const enableLoadPreviewVideo = $("#enableLoadPreviewVideo").is(":checked") ? YES : NO; await storageManager.saveSettingItem("enableLoadPreviewVideo", enableLoadPreviewVideo); })); $("#enable115Match").prop("checked", !!settingObj.enable115Match && settingObj.enable115Match === YES); $("#enable115Match").on("change", (async (event) => { const enable115Match = $("#enable115Match").is(":checked") ? YES : NO; await storageManager.saveSettingItem("enable115Match", enable115Match); let movieList = $(this.getSelector().itemSelector).toArray(); await this.getBean("WangPan115MatchPlugin").matchMovieList(movieList); })); $("#enableVerticalModel").prop("checked", !!settingObj.enableVerticalModel && settingObj.enableVerticalModel === YES); $("#enableVerticalModel").on("change", (async (event) => { const enableVerticalModel = $("#enableVerticalModel").is(":checked") ? YES : NO; await storageManager.saveSettingItem("enableVerticalModel", enableVerticalModel); this.applyImageMode(); })); $("#moreBtn").on("click", (() => { $(".simple-setting").html("").hide(); this.openSettingDialog("base-panel"); })); $("#helpBtn").on("click", (() => { layer.open({ type: 1, title: "", shadeClose: true, scrollbar: false, content: `

使用说明

1. 无法查看预览视频,提示分流?

JavDB限制日本IP的访问,而预览视频来自DMM,需要日本IP才能访问。

这样会导致二者无法同时使用,需要对其一进行代理转发。

将 cc3001.dmm.co.jp 及 dmm.co 分流到日本ip。

Clash Verge分流规则设置 (如果你是别的代理软件,自行搜索如何分流)

2. 如何屏蔽某一系列的番号?

方法一:设置中-添加视频标题关键词,如: VENX-

方法二:进入详情页,选中标题文字,右键可加入

进入详情页,选中标题,进行右键
3. 屏蔽某演员,如何只屏蔽单体影片?

屏蔽演员前,先筛选分类,再点屏蔽

屏蔽演员前,先筛选分类,再点屏蔽
4. 如何多浏览器同时登录115网盘?

① 访问115登录页, 选择JHS-扫码面板, 并扫码登录

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

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

`, area: utils.getResponsiveArea(["50%", "90%"]) }); })); } async applyImageMode() { $("#verticalImgStyle").remove(); if (await storageManager.getSetting("enableVerticalModel", NO) === YES) { let imgPosition = "100% 50% !important"; window.location.href.includes("/advanced_search?type=100") && (imgPosition = "50% 50% !important"); const verticalStyle = ` .cover { min-height: 350px !important; overflow: hidden !important; padding-top: 142% !important; } .cover img { object-fit: cover !important; object-position: ${imgPosition}; } /* bus的 */ .masonry .movie-box img { min-height: 500px !important; object-fit: cover !important; object-position: top right; } `; $("\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 = `
${this.actressSvg}    手动同步演员 ${this.newSvg}    手动检测最新作品 ${this.settingSvg}    配置
${this.refreshSvg}    刷新
`; layer.open({ type: 1, title: '新作品检测 ❓', content: html, scrollbar: false, 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: true }, (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: true }, (async (lock) => { lock ? await taskPlugin.checkNewVideo(true) : 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 = false; 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 `
${allNamesStr}
${data.name}
${allNamesStr}
上次检测: ${data.lastCheckTime || ""}
最后发行作品: ${data.lastPublishTime || ""}
${isUnCheck ? "停更" + checkNewVideo_ruleTime / 24 / 365 + "年以上, 下轮任务不再进行检测" : ""}
${data.remark || ""}
${this.editSvg} ${this.deleteSvg} ${this.checkSvg}
${actressTypeText}
🔔 ${(null == (_c = data.newVideoList) ? void 0 : _c.length) || 0}
`; })).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: true }, (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 = `
Avatar Preview
`; 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, index2) => `
`)).join(""), cdnSelectHtml = `

请选择头像数据源 (当前: ${CDN_SOURCES[initialIndex].name}):

${radioOptions}

切换源会清除本地缓存的数据,并在下次搜索时重新加载。

`; layer.open({ type: 1, title: "选择 CDN 源", area: ["400px", "auto"], content: cdnSelectHtml, btn: ["确定", "取消"], success: (layero2, index2) => { utils.setupEscClose(index2); }, yes: async (index2) => { 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()); window.G_FRIENDS_JSON_URL = CDN_SOURCES[newIndex].json; window.CDN_BASE_URL = CDN_SOURCES[newIndex].base; window.G_FRIENDS_DATA_CACHE = null; window.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(index2); } else layer.close(index2); } }); })(); })); 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 false; } 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) => `
...
`)).join(""), imageListHtml = `

点击图片即可选择(初始共 ${imageLinks.length} 张)

${imageItems}
`; 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); } }); } } class HistoryPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "tableObj", null); } getName() { return "HistoryPlugin"; } async initCss() { return "\n \n "; } handle() { let fc2Url = "/advanced_search?type=3&score_min=0&d=1"; $('.navbar-item:contains("FC2")').attr("href", fc2Url); $('.tabs a:contains("FC2")').attr("href", fc2Url); if (currentHref$1.includes("advanced_search?type=3")) { $("h2.section-title").contents().first().replaceWith("Fc2PPV"); $(".section .container > .box").remove(); } if (currentHref$1.includes("collection_codes?movieId")) { $("section").html(""); const urlParams = new URLSearchParams(window.location.search); let movieId = urlParams.get("movieId"), carNum2 = urlParams.get("carNum"), url = urlParams.get("url"); movieId && carNum2 && url && this.openFc2Dialog(movieId, carNum2, url); } } openFc2Dialog(movieId, carNum2, href) { let tempCarNum = carNum2.replace("FC2-", ""); if (href.includes("123av")) { this.getBean("Fc2By123AvPlugin").open123AvFc2Dialog(carNum2, href); return; } layer.open({ type: 1, title: carNum2, content: '\n
\n \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 ', area: utils.getResponsiveArea(["70%", "90%"]), skin: "movie-detail-layer", scrollbar: false, success: (layero, index) => { this.loadData(movieId, carNum2); $("#favoriteBtn").on("click", (async (event) => { const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_FAVORITE$1, publishTime }); window.refresh(); layer.closeAll(); })); $("#filterBtn").on("click", ((event) => { utils.q(event, `是否屏蔽${carNum2}?`, (async () => { const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_FILTER$1, publishTime }); window.refresh(); layer.closeAll(); window.location.href.includes("collection_codes?movieId") && utils.closePage(); })); })); $("#hasDownBtn").on("click", (async (event) => { const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_HAS_DOWN$1, publishTime }); window.refresh(); layer.closeAll(); })); $("#hasWatchBtn").on("click", (async (event) => { const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_HAS_WATCH$1, publishTime }); window.refresh(); layer.closeAll(); })); $("#subtitleSearchBtn").on("click", (() => { window.JavPackSubtitle.openSearchModal({details: {code: carNum2}}); })); window.JavPackSubtitle.preload115Matches(carNum2); $("#magnetSearchBtn").on("click", (() => { let magnetHub = this.getBean("MagnetHubPlugin").createMagnetHub(carNum2); layer.open({ type: 1, title: "磁力搜索", content: '
', area: utils.getResponsiveArea(["60%", "80%"]), scrollbar: false, success: () => { $("#magnetHubBox").append(magnetHub); } }); })); this.getBean("OtherSitePlugin").loadOtherSite(tempCarNum, carNum2).then(); utils.setupEscClose(index); }, end() { window.location.href.includes("collection_codes?movieId") && utils.closePage(); } }); } loadData(movieId, carNum2) { let tempCarNum = carNum2.replace("FC2-", ""); this.handleMovieDetail(movieId); this.handleLongImg(tempCarNum); this.handleMagnets(movieId); this.getBean("ReviewPlugin").showReview(movieId, $("#reviews-content")).then(); this.getBean("RelatedPlugin").showRelated($("#related-content"), movieId).then(); } handleMovieDetail(movieId) { javDbApi.getMovieDetail(movieId).then(((res) => { const actors = res.actors || [], imgList = res.imgList || []; let actorsHtml = ""; if (actors.length > 0) { let actress = ""; for (let i = 0; i < actors.length; i++) { let actor = actors[i]; actorsHtml += `${actor.name}`; 0 === actor.gender && (actress += actor.name + " "); } $("#data-actress").text(actress); } else actorsHtml = '暂无演员信息'; let imagesHtml = ""; imagesHtml = Array.isArray(imgList) && imgList.length > 0 ? imgList.map(((img, index) => ` `)).join("") : '
暂无剧照
'; $(".movie-info-container").html(`

${res.title || "无标题"}

番号: ${res.carNum || "未知"} 年份: ${res.releaseDate || "未知"} 评分: ${res.score || "无"} 时长: ${res.duration + " m" || "无"}
站点: fc2ppvdb fc2电子市场
主演: ${actorsHtml}
`); this.getBean("TranslatePlugin").translate(res.carNum, false).then(); })).catch(((err) => { console.error(err); $(".movie-info-container").html(`
加载失败: ${err.message}
`); })); } handleLongImg(carNum2) { utils.loopDetector((() => $(".movie-gallery .image-list").length > 0), (async () => { $(".movie-gallery .image-list").prepend('
正在加载缩略图
'); const screenShotPlugin = this.getBean("ScreenShotPlugin"), imgUrl = await screenShotPlugin.getScreenshot(carNum2); imgUrl && await screenShotPlugin.addImg("缩略图", imgUrl, carNum2); })); } handleMagnets(movieId) { javDbApi.getMagnets(movieId).then(((magnetList) => { let magnetsHtml = ""; if (magnetList.length > 0) for (let i = 0; i < magnetList.length; i++) { let magnet = magnetList[i], oddClass = ""; i % 2 == 0 && (oddClass = "odd"); magnetsHtml += `
${magnet.name}
${(magnet.size / 1024).toFixed(2)}GB, ${magnet.files_count}個文件
${magnet.hd ? '高清' : ""} ${magnet.cnsub ? '字幕' : ""}
${magnet.created_at}
`; } else magnetsHtml = '暂无磁力信息'; $("#magnets-content").html(magnetsHtml); $(".buttons button[data-clipboard-text*='magnet:']").each(((i, el) => { $(el).parent().append($("'); $tag.append(button); button.fadeIn(0); }), (function() { $(this).find(".highlight-btn").fadeOut(0, (function() { $(this).remove(); })); })); $(document).on("click", ".highlight-btn", (async function(e) { e.stopPropagation(); e.preventDefault(); const $tag = $(this).closest("a.tag"), clonedTag = $tag.clone(); clonedTag.find(".highlight-btn").remove(); const tagText = clonedTag.text().trim().replace(/\s*\(\d+\)$/, ""); let highlightedTags = await storageManager.getHighlightedTags(); if (highlightedTags.includes(tagText)) { highlightedTags = highlightedTags.filter(((item) => item !== tagText)); $tag.removeClass("highlighted"); } else { highlightedTags.push(tagText); $tag.addClass("highlighted"); } await storageManager.setHighlightedTags(highlightedTags); })); } async createFoldBtn() { const foldCategoryHotKey = await storageManager.getSetting("foldCategoryHotKey"); let $subTags = $("#tags"), checkTagStr = $("#tags dl div.tag.is-info").map((function() { return $(this).text().replaceAll("\n", "").replaceAll(" ", ""); })).get().join(" "); if (!checkTagStr) return; $(".tabs").append(`
已选分类: ${checkTagStr}
${foldCategoryHotKey ? ` (${foldCategoryHotKey})` : ""}
`); let $section = $("h2.section-title"); if ($section.length > 0) { $section.append('\n
\n \n \n \n \n
\n '); $subTags = $("section > div > div.box"); } if (!$subTags) return; let $foldCategoryBtn = $("#foldCategoryBtn"), isFolded = localStorage.getItem("jhs_foldCategory") === YES, [newText, newIcon] = isFolded ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"]; $foldCategoryBtn.find("span").text(newText).end().find("i").attr("class", newIcon); window.location.href.includes("noFold=1") || $subTags[isFolded ? "hide" : "show"](); $foldCategoryBtn.on("click", (async (event) => { event.preventDefault(); isFolded = !isFolded; localStorage.setItem("jhs_foldCategory", isFolded ? YES : NO); const [newText2, newIcon2] = isFolded ? ["展开", "icon-angle-double-down"] : ["折叠", "icon-angle-double-up"]; $foldCategoryBtn.find("span").text(newText2).end().find("i").attr("class", newIcon2); $subTags[isFolded ? "hide" : "show"](); })); } } class ActressInfoPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "apiUrl", "https://ja.wikipedia.org/wiki/"); } getName() { return "ActressInfoPlugin"; } async handle() { "yes" === await storageManager.getSetting("enableLoadActressInfo", "yes") && this.loadActressInfo(); } loadActressInfo() { this.handleDetailPage().then(); this.handleStarPage().then(); } async initCss() { return "\n \n "; } async handleDetailPage() { if ($(".actress-info").length > 0) return; let nameList = $(".female").prev().map(((i, el) => $(el).text().trim())).get(); if (!nameList.length) return; const cacheKey = "jhs_actress_info", cacheData = localStorage.getItem(cacheKey) ? JSON.parse(localStorage.getItem(cacheKey)) : {}; let result = null, infoHtml = ""; for (let i = 0; i < nameList.length; i++) { let name2 = nameList[i]; result = cacheData[name2]; if (!result) try { result = await this.searchInfo(name2); result && (cacheData[name2] = result); } catch (e) { console.error("该名称查询失败,尝试其它名称"); } let contentHtml = ""; contentHtml = result ? `
${name2}: ${result.birthday} ${result.age} ${result.height} ${result.weight} ${result.threeSizeText} ${result.braSize}
` : `
${name2}:
`; infoHtml += contentHtml; } $('strong:contains("演員")').parent().after(infoHtml); localStorage.setItem(cacheKey, JSON.stringify(cacheData)); } async handleStarPage() { if ($(".actress-info").length > 0) return; 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.length) return; const cacheKey = "jhs_actress_info", cacheData = localStorage.getItem(cacheKey) ? JSON.parse(localStorage.getItem(cacheKey)) : {}; let result = null; for (let i = 0; i < nameList.length; i++) { let name2 = nameList[i]; result = cacheData[name2]; if (result) break; try { result = await this.searchInfo(name2); } catch (e) { console.error("该名称查询失败,尝试其它名称"); } if (result) break; } result && nameList.forEach(((name2) => { cacheData[name2] = result; })); let contentHtml = '
无此相关演员信息
'; result && (contentHtml = `
出生日期: ${result.birthday} 年龄: ${result.age} 身高: ${result.height}
体重: ${result.weight} 三围: ${result.threeSizeText} 罩杯: ${result.braSize}
`); $actor.parent().append(contentHtml); localStorage.setItem(cacheKey, JSON.stringify(cacheData)); } async searchInfo(name2) { "三上悠亞" === name2 && (name2 = "三上悠亜"); let url = this.apiUrl + name2; const html = await gmHttp.get(url), parser = new DOMParser(), $dom = $(parser.parseFromString(html, "text/html")); let birthday = $dom.find('a[title="誕生日"]').parent().parent().find("td").text().trim(), age = $dom.find("th:contains('現年齢')").parent().find("td").text().trim() ? parseInt($dom.find("th:contains('現年齢')").parent().find("td").text().trim()) + "岁" : "", height = $dom.find('tr:has(a[title="身長"]) td').text().trim().split(" ")[0] + "cm", weight = $dom.find('tr:has(a[title="体重"]) td').text().trim().split("/")[1].trim(); "― kg" === weight && (weight = ""); return { birthday, age, height, weight, threeSizeText: $dom.find('a[title="スリーサイズ"]').closest("tr").find("td").text().replace("cm", "").trim(), braSize: $dom.find('th:contains("ブラサイズ")').next("td").contents().first().text().trim(), url }; } } class AliyunPanPlugin extends BasePlugin { getName() { return "AliyunPanPlugin"; } handle() { $("body").append('获取refresh_token'); $("#refresh-token-btn").on("click", ((event) => { let tokenStr = localStorage.getItem("token"); if (!tokenStr) { alert("请先登录!"); return; } let refresh_token = JSON.parse(tokenStr).refresh_token; navigator.clipboard.writeText(refresh_token).then((() => { alert("已复制到剪切板 如失败, 请手动复制: " + refresh_token); })).catch(((err) => { console.error("Failed to copy refresh token: ", err); })); })); } } class HitShowPlugin extends BasePlugin { constructor() { super(); __publicField2(this, "$contentBox", $(".section .container")); } getName() { return "HitShowPlugin"; } handle() { $('a[href*="rankings/playback"]').on("click", ((event) => { event.preventDefault(); event.stopPropagation(); window.location.href = "/advanced_search?handlePlayback=1&period=daily"; })); this.handlePlayback().then(); } hookPage() { let $h2 = $("h2.section-title"); $h2.contents().first().replaceWith("热播"); $h2.css("marginBottom", "0"); $(".empty-message").remove(); $(".section .container .box").remove(); $("#sort-toggle-btn").remove(); this.$contentBox.append('
'); this.$contentBox.append('
'); } async handlePlayback() { if (!window.location.href.includes("handlePlayback=1")) return; let period = new URLSearchParams(window.location.search).get("period"); this.toolBar(period); this.hookPage(); let $movieBox = $(".movie-list"); $movieBox.html(""); let loadObj = loading(); try { const movies = await javDbApi.playback(period); let moviesHtml = javDbApi.markDataListHtml(movies); $movieBox.html(moviesHtml); } catch (e) { clog.error("发生错误:", e); } finally { loadObj.close(); } } toolBar(period) { let conditionHtml = `
日榜 周榜 月榜
`; this.$contentBox.append(conditionHtml); } getStarRating(score) { let stars = ""; const fullStars = Math.floor(score); for (let i = 0; i < fullStars; i++) stars += ''; for (let i = 0; i < 5 - fullStars; i++) stars += ''; return stars; } loadScore(movies) { if (0 === movies.length) return; (async () => { for (const movie of movies) try { const movieId = movie.id; if (!$(`#score_${movieId}`).length) return; if ($(`#${movieId}`).is(":hidden")) continue; const cacheData = localStorage.getItem("jhs_score_info") ? JSON.parse(localStorage.getItem("jhs_score_info")) : {}, cached = cacheData[movieId]; if (cached) { this.appendScoreHtml(movieId, cached); continue; } for (; !document.hasFocus(); ) await new Promise(((r) => setTimeout(r, 500))); const res = await javDbApi.getMovieDetail(movieId); let score = res.score, watchedCount = res.watchedCount, html = ` ${this.getStarRating(score)}   ${score}分,由${watchedCount}人評價 `; this.appendScoreHtml(movieId, html); cacheData[movieId] = html; localStorage.setItem("jhs_score_info", JSON.stringify(cacheData)); await new Promise(((r) => setTimeout(r, 500))); } catch (err) { clog.error(`🚨 解析评分数据失败 | 编号: ${movie.number} `, `错误详情: ${err.message} `, err.stack ? `调用栈: ${err.stack}` : ""); } })(); } appendScoreHtml(movieId, scoreHtml) { let $scoreBox = $(`#score_${movieId}`); $scoreBox.length && "" === $scoreBox.html().trim() && $scoreBox.slideUp(0, (function() { $(this).html(scoreHtml).slideDown(500); })); } } class TOP250Plugin extends BasePlugin { constructor() { super(); __publicField2(this, "has_cnsub", ""); __publicField2(this, "$contentBox", $(".section .container")); __publicField2(this, "movies", []); } getName() { return "TOP250Plugin"; } handle() { $('.main-tabs ul li:contains("猜你喜歡")').html('Top250'); $('a[href*="rankings/top"]').on("click", ((event) => { event.preventDefault(); event.stopPropagation(); const $target = $(event.target), href = ($target.is("a") ? $target : $target.closest("a")).attr("href"); let queryString = href.includes("?") ? href.split("?")[1] : href; const urlParams = new URLSearchParams(queryString); this.checkLogin(event, urlParams); })); this.handleTop().then(); } hookPage() { $("h2.section-title").contents().first().replaceWith("Top250"); $(".empty-message").remove(); $(".section .container .box").remove(); $("#sort-toggle-btn").remove(); this.$contentBox.append('
'); this.$contentBox.append('
'); this.renderPagination(); } renderPagination() { const urlParams = new URLSearchParams(window.location.search); let currentPage = parseInt(urlParams.get("page")) || 1; this.$contentBox.append(((page) => { const isNextDisabled = page >= 5; let paginationListHTML = ""; for (let i = 1; i <= 5; i++) { paginationListHTML += `
  • ${i}
  • `; } return ` `; })(currentPage)); this.$contentBox.on("click", ".pagination-link, .pagination-previous, .pagination-next", ((event) => { event.preventDefault(); const newPage = parseInt($(event.currentTarget).data("page")); !isNaN(newPage) && newPage > 0 && ((newPage2) => { urlParams.set("page", newPage2); window.history.pushState({}, "", "?" + urlParams.toString()); window.location.reload(); })(newPage); })); } async handleTop() { if (!window.location.href.includes("handleTop=1")) return; const urlParams = new URLSearchParams(window.location.search); let type = urlParams.get("handleType") || "all", type_value = urlParams.get("type_value") || ""; this.has_cnsub = urlParams.get("has_cnsub") || ""; let page = urlParams.get("page") || 1; this.toolBar(type, type_value, page); this.hookPage(); let $movieBox = $(".movie-list"); $movieBox.html(""); let loadObj = loading(); try { const res = await javDbApi.top250(type, type_value, page, 50); let successFlag = res.success, message = res.message, action = res.action; if (1 === successFlag) { let dataList = res.data.movies; if (0 === dataList.length) { show.error("无数据"); loadObj.close(); return; } this.movies = dataList; const filter_movies = dataList.filter(((item) => "1" === this.has_cnsub ? item.has_cnsub : "0" !== this.has_cnsub || !item.has_cnsub)); let moviesHtml = javDbApi.markDataListHtml(filter_movies); $movieBox.html(moviesHtml); } else { clog.error(res); $movieBox.html(`

    ${message}

    `); show.error(message); if ("JWTVerificationError" === action) { await localStorage.removeItem("jhs_appAuthorization"); await this.checkLogin(null, new URLSearchParams(window.location.search)); } } } catch (e) { clog.error("发生错误:", e); } finally { loadObj.close(); } } toolBar(type, type_value, currentPage) { "5" === currentPage.toString() && $(".pagination-next").remove(); $(".pagination-ellipsis").closest("li").remove(); $(".pagination-list li a").each((function() { parseInt($(this).text()) > 5 && $(this).closest("li").remove(); })); let yearHtml = ""; for (let year = (/* @__PURE__ */ new Date()).getFullYear(); year >= 2008; year--) yearHtml += ` ${year} `; let conditionHtml = `
    全部 有码 无码 欧美 Fc2 含中字磁鏈 无字幕 重置
    ${yearHtml}
    `; this.$contentBox.append(conditionHtml); $("a[data-cnsub-value]").on("click", ((event) => { const cnsubValue = $(event.currentTarget).data("cnsub-value"); this.has_cnsub = cnsubValue.toString(); $("a[data-cnsub-value]").removeClass("is-info"); $(event.currentTarget).addClass("is-info"); $(".toolbar a.button").not("[data-cnsub-value]").each(((index, element) => { const $link = $(element), url = new URL($link.attr("href"), window.location.origin); url.searchParams.set("has_cnsub", cnsubValue); $link.attr("href", url.toString()); })); const filter_movies = this.movies.filter(((item) => "1" === this.has_cnsub ? item.has_cnsub : "0" !== this.has_cnsub || !item.has_cnsub)); let moviesHtml = javDbApi.markDataListHtml(filter_movies); $(".movie-list").html(moviesHtml); })); } async checkLogin(event, urlParams) { if (!localStorage.getItem("jhs_appAuthorization")) { show.error("该类别依赖移动端接口,请先完成登录"); this.openLoginDialog(); return; } let type = "all", type_value = "", t = urlParams.get("t") || ""; if (/^y\d+$/.test(t)) { type = "year"; type_value = t.substring(1); } else if ("" !== t) { type = "video_type"; type_value = t; } let url = `/advanced_search?handleTop=1&handleType=${type}&type_value=${type_value}`; event && (event.ctrlKey || event.metaKey) ? _GM_openInTab(window.location.origin + url, { insert: 0 }) : window.location.href = url; } openLoginDialog() { layer.open({ type: 1, title: "JavDB", closeBtn: 1, area: ["360px", "auto"], shadeClose: false, content: `
    `, success: (layero, index) => { $("#loginBtn").click((function() { const username = $("#username").val(), password = $("#password").val(); if (!username || !password) { show.error("请输入用户名和密码"); return; } let loadObj = loading(); javDbApi.login(username, password).then((async (res) => { let success = res.success; if (0 === success) show.error(res.message); else { if (1 !== success) { clog.error("登录失败", res); throw new Error(res.message); } { let token = res.data.token; await localStorage.setItem("jhs_appAuthorization", token); show.ok("登录成功"); layer.close(index); window.location.href = "/advanced_search?handleTop=1&period=daily"; } } })).catch(((err) => { clog.error("登录异常:", err); show.error(err.message); })).finally((() => { loadObj.close(); })); })); } }); } } class NavBarPlugin extends BasePlugin { getName() { return "NavBarPlugin"; } async initCss() { return "\n .highlight-red {\n /* 核心要求:高亮红色文本 */\n color: red !important; \n \n /* 建议:增加字体加粗,效果更明显 */\n font-weight: bold;\n \n /* 建议:增加背景色,效果更突出 */\n /* background-color: yellow; */ \n}\n "; } handle() { this.margeNav(); this.hookSearch(); this.hookOldSearch(); this.toggleOtherNavItem(); $(window).resize(this.toggleOtherNavItem); if (window.location.href.includes("/search")) { const urlParams = new URLSearchParams(window.location.search); let q = urlParams.get("q"), f = urlParams.get("f"); $("#search-keyword").val(q); f && $("#search-type").val(f); q && this.highlightKeyword(q); } } highlightKeyword(keyword) { const trimedKeyword = keyword.trim(); if (!trimedKeyword) return; const lowerCaseKeyword = trimedKeyword.toLowerCase(); $(".video-title strong, .actor-box strong").each((function() { const $strongElement = $(this); $strongElement.text().toLowerCase().includes(lowerCaseKeyword) && $strongElement.addClass("highlight-red"); })); } hookSearch() { $("#navbar-menu-hero").after('\n \n '); $("#search-keyword").on("paste", ((event) => { const items = event.originalEvent.clipboardData.items; for (let i = 0; i < items.length; i++) if (-1 !== items[i].type.indexOf("image")) { const blob = items[i].getAsFile(); $("#search-keyword").blur(); const imageRecognitionPlugin = this.getBean("ImageRecognitionPlugin"); imageRecognitionPlugin.open((() => { imageRecognitionPlugin.handleImageFile(blob); imageRecognitionPlugin.resetSearchUI(); })); return; } setTimeout((() => { $("#search-btn").click(); }), 0); })).on("keypress", ((event) => { "Enter" === event.key && setTimeout((() => { $("#search-btn").click(); }), 0); })); $("#search-btn").on("click", ((event) => { let keyword = $("#search-keyword").val(), searchCurrentType = $("#search-type option:selected").val(); "" !== keyword && (window.location.href.includes("/search") ? window.location.href = "/search?q=" + keyword + "&f=" + searchCurrentType : window.open("/search?q=" + keyword + "&f=" + searchCurrentType)); })); $("#search-img-btn").on("click", (() => { this.getBean("ImageRecognitionPlugin").open(); })); } hookOldSearch() { const searchImage = document.querySelector(".search-image"); if (!searchImage) return; const clonedImage = searchImage.cloneNode(true); searchImage.parentNode.replaceChild(clonedImage, searchImage); $("#button-search-image").attr("data-tooltip", "以图识图"); $(".search-image").on("click", ((event) => { this.getBean("ImageRecognitionPlugin").open(); })); } margeNav() { $('a[href*="/feedbacks/new"]').remove(); $('a[href*="theporndude.com"]').remove(); $('a.navbar-link[href="/makers"]').parent().after('\n \n '); } toggleOtherNavItem() { let $searchBox = $("#search-box"), $oldSearchBox = $("#search-bar-container"); if ($(window).width() < 1600 && $(window).width() > 1023) { $searchBox.hide(); $oldSearchBox.show(); } if ($(window).width() > 1600) { $searchBox.show(); $oldSearchBox.hide(); } } } class BusDetailPagePlugin extends BasePlugin { getName() { return "BusDetailPagePlugin"; } async initCss() { if (!window.isDetailPage) return ""; $("h4:contains('推薦')").hide(); } async handle() { if (window.location.href.includes("/star/")) { const $avatarBox = $(".avatar-box"); if ($avatarBox.length > 0) { let parent2 = $avatarBox.parent(); parent2.css("position", "initial"); parent2.insertBefore(parent2.parent()); } } $(".genre a").each((function() { const href2 = $(this).attr("href"); href2 && (href2.startsWith("http://") || href2.startsWith("https://") || href2.startsWith("/")) && $(this).attr("target", "_blank"); })); this.addCopyCarNumBtn(); } addCopyCarNumBtn() { let headerSpan = null; const headerSpans = document.querySelectorAll("span.header"); for (const span of headerSpans) if ("識別碼:" === span.textContent.trim()) { headerSpan = span; break; } if (headerSpan) { const targetSpan = headerSpan.nextElementSibling; if (targetSpan && "SPAN" === targetSpan.tagName) { const identifierText = targetSpan.textContent.trim(), copyButton = document.createElement("button"); copyButton.textContent = "复制"; copyButton.style.marginLeft = "10px"; copyButton.style.padding = "0 10px"; copyButton.style.cursor = "pointer"; copyButton.style.border = "1px solid #ccc"; copyButton.style.borderRadius = "5px"; copyButton.style.backgroundColor = "#f0f0f0"; copyButton.style.fontSize = "12px"; copyButton.addEventListener("click", (function(event) { event.preventDefault(); const copyAction = (text) => { this.textContent = "已复制"; setTimeout((() => { this.textContent = "复制"; }), 1500); }; navigator.clipboard && navigator.clipboard.writeText && navigator.clipboard.writeText(identifierText).then((() => copyAction())).catch(((err) => { console.error("无法通过标准API复制:", err); alert("复制失败,请手动复制: " + identifierText); })); })); targetSpan.parentNode.insertBefore(copyButton, targetSpan.nextSibling); } } } } class BusPreviewVideoPlugin extends BasePlugin { getName() { return "BusPreviewVideoPlugin"; } async initCss() { return "\n /* 弹窗/Modal 样式 */\n .bus-preview-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n background-color: rgba(0, 0, 0, 0.95); \n /* 关键修改:更新 z-index */\n z-index: 12345699; \n display: flex;\n justify-content: center;\n align-items: center;\n opacity: 0; \n visibility: hidden; \n transition: opacity 0.2s ease;\n }\n .bus-preview-modal.is-open {\n opacity: 1;\n visibility: visible;\n }\n /* 垂直排列视频和按钮,并居中 */\n .bus-preview-modal-content {\n position: relative;\n max-width: 95%; \n max-height: 95%;\n display: flex; \n flex-direction: column; \n align-items: center; \n gap: 15px; \n }\n \n /* 移除 .bus-preview-close-btn 的样式 */\n\n /* 视频播放器容器 */\n .video-player-wrapper {\n /* 关键修改:更新 width 和 max-height */\n width: 80vw; \n max-height: 85vh; \n aspect-ratio: 16 / 9; \n position: relative; \n background-color: black; \n max-width: 100%; \n }\n /* 视频元素 */\n .video-player-wrapper #preview-video {\n position: absolute; \n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: block;\n }\n\n /* 画质控制盒 (底部按钮) */\n .video-control-box {\n display: flex;\n flex-direction: row; \n justify-content: center; \n flex-wrap: wrap; \n gap: 10px;\n padding: 10px 0; \n }\n\n /* 按钮样式 (保留) */\n .video-control-btn {\n min-width:80px;\n padding: 6px 12px;\n background: rgba(255,255,255,0.2);\n color: white;\n border: 1px solid rgba(255,255,255,0.5);\n border-radius: 4px;\n cursor: pointer;\n text-align: center;\n font-size: 14px;\n transition: background-color 0.2s, border-color 0.2s;\n }\n .video-control-btn:hover {\n background: rgba(255,255,255,0.4);\n }\n .video-control-btn.active {\n background-color: #1890ff; \n color: white;\n font-weight: bold;\n border: 1px solid #096dd9;\n }\n "; } initModal() { if (0 === $("#bus-preview-modal").length) { $("body").append('\n
    \n
    \n
    \n
    \n '); const $modal = $("#bus-preview-modal"); $modal.on("click", ((e) => { "bus-preview-modal" === e.target.id && this.closeVideoModal(); })); $(document).on("keydown", ((e) => { "Escape" === e.key && $modal.hasClass("is-open") && this.closeVideoModal(); })); } } closeVideoModal() { const $previewVideo = $("#preview-video"); if ($previewVideo.length > 0) { const videoEl = $previewVideo[0]; videoEl.pause(); } $("#bus-preview-modal").removeClass("is-open"); } async handle() { if (!window.isDetailPage) return; this.initModal(); const firstImageSrc = $("#sample-waterfall .sample-box .photo-frame img:first").attr("src"), videoPreview = $(`
    ▶
    `); $("#sample-waterfall").prepend(videoPreview); "yes" === await storageManager.getSetting("enableLoadPreviewVideo", "yes") && getDmmVideo(this.getPageInfo().carNum, false).then(); let isHandlingVideo = false, $preview = $(".preview-video-container"); $preview.on("click", (async (event) => { event.preventDefault(); event.stopPropagation(); if (isHandlingVideo) show.info("正在加载中, 勿重复点击"); else { isHandlingVideo = true; try { await this.handleVideo(); } finally { isHandlingVideo = false; } } })); window.location.href.includes("autoPlay=1") && $preview.trigger("click"); } async handleVideo() { const $modal = $("#bus-preview-modal"), $modalContent = $modal.find(".bus-preview-modal-content"); let $previewVideo = $("#preview-video"); if ($previewVideo.length > 0) { $modal.addClass("is-open"); const videoEl = $previewVideo[0]; videoEl.play().catch(((e) => console.warn("尝试播放失败 (可能被浏览器阻止):", e))); return; } let carNum2 = this.getPageInfo().carNum; const dmmVideoMap = await getDmmVideo(carNum2); if (dmmVideoMap && 0 !== Object.keys(dmmVideoMap).length) { await this.createVideoPlayerAndControls(dmmVideoMap, $modalContent); $previewVideo = $("#preview-video"); if ($previewVideo.length > 0) { $modal.addClass("is-open"); $previewVideo[0].play().catch(((e) => console.warn("尝试播放失败 (可能被浏览器阻止):", e))); } else show.error("视频播放器创建失败。"); } else show.error("未找到可用的视频源。"); } async createVideoPlayerAndControls(dmmVideoMap, $container) { let defaultVideoQuality = await storageManager.getSetting("videoQuality"); defaultVideoQuality = selectDefaultQuality(Object.keys(dmmVideoMap), defaultVideoQuality); let defaultVideoUrl = dmmVideoMap[defaultVideoQuality]; $container.html(`
    `); const $videoEl = $("#preview-video"), $qualityControlsBox = $container.find(".video-control-box"); if (!$videoEl.length) return; const videoEl = $videoEl[0]; const jhs_videoMuted = localStorage.getItem("jhs_videoMuted"); videoEl.muted = !jhs_videoMuted || "yes" === jhs_videoMuted; videoEl.addEventListener("volumechange", (function() { localStorage.setItem("jhs_videoMuted", videoEl.muted ? "yes" : "no"); })); let buttonsHtml = ""; qualityOptions.forEach(((option) => { let dmmVideoUrl = dmmVideoMap[option.quality]; if (dmmVideoUrl) { const isActive = defaultVideoQuality === option.quality; buttonsHtml += ` `; } })); $qualityControlsBox.html(buttonsHtml); const $buttons = $qualityControlsBox.find(".video-control-btn"); $qualityControlsBox.off("click").on("click", ".video-control-btn", (async (e) => { try { const $button = $(e.currentTarget); if ($button.hasClass("active")) return; let videoSrc = $button.attr("data-video-src"); const currentTime = videoEl.currentTime; videoEl.src = videoSrc; videoEl.load(); if (currentTime > 0 && Number.isFinite(currentTime)) { videoEl.currentTime = currentTime; } await videoEl.play(); $buttons.removeClass("active"); $button.addClass("active"); } catch (error) { console.error("切换画质失败:", error); } })); } } const HLS_SCRIPT_URL = "https://cdn.jsdelivr.net/npm/hls.js@1.5.18/dist/hls.min.js"; let hlsLoadPromise = null; const getHlsClass = () => { const HlsClass = window.Hls || globalThis.Hls || (typeof Hls !== "undefined" ? Hls : null); return HlsClass && HlsClass.isSupported && HlsClass.isSupported() ? HlsClass : null; }; const binaryTextToArrayBuffer = (value) => { const text = String(value || ""); const bytes = new Uint8Array(text.length); for (let index = 0; index < text.length; index += 1) { bytes[index] = text.charCodeAt(index) & 255; } return bytes.buffer; }; const loadHlsLibrary = () => { const readyHls = getHlsClass(); if (readyHls) return Promise.resolve(readyHls); if (hlsLoadPromise) return hlsLoadPromise; const loadByGm = () => new Promise((resolve) => { _GM_xmlhttpRequest({ method: "GET", url: HLS_SCRIPT_URL, timeout: 15e3, onload: (r) => { if (r.status >= 200 && r.status < 300 && r.responseText) { try { Function(`${r.responseText} //# sourceURL=${HLS_SCRIPT_URL}`).call(globalThis); } catch (err) { clog.error("HLS: hls.js 执行失败", err); } } resolve(getHlsClass()); }, onerror: () => resolve(getHlsClass()), ontimeout: () => resolve(getHlsClass()) }); }); hlsLoadPromise = new Promise((resolve) => { const existing = document.querySelector('script[data-jhs-hls="1"]'); if (existing) { existing.addEventListener("load", () => resolve(getHlsClass()), {once: true}); existing.addEventListener("error", () => loadByGm().then(resolve), {once: true}); setTimeout(() => { if (!getHlsClass()) loadByGm().then(resolve); }, 4e3); return; } const hlsScript = document.createElement("script"); hlsScript.src = HLS_SCRIPT_URL; hlsScript.async = true; hlsScript.dataset.jhsHls = "1"; hlsScript.onload = () => resolve(getHlsClass()); hlsScript.onerror = () => loadByGm().then(resolve); document.head.appendChild(hlsScript); }).then((HlsClass) => { if (!HlsClass) hlsLoadPromise = null; return HlsClass; }); return hlsLoadPromise; }; const createHlsLoader = () => { class GMHlsLoader { constructor(config) { this.config = config; this.context = null; this.callbacks = null; this.loader = null; this.stats = this.createStats(); } createStats() { return { aborted: false, loaded: 0, retry: 0, total: 0, chunkCount: 0, bwEstimate: 0, trequest: 0, tfirst: 0, tload: 0, loading: {start: 0, first: 0, end: 0}, parsing: {start: 0, end: 0}, buffering: {start: 0, first: 0, end: 0} }; } destroy() { this.abort(); } abort() { if (this.stats) this.stats.aborted = true; if (this.loader) { try { this.loader.abort(); } catch (e) { } } this.loader = null; } load(context, config, callbacks) { this.context = context; this.callbacks = callbacks; const requestUrl = context.url; const wantsArrayBuffer = context.responseType === "arraybuffer" || /\.(?:ts|m4s|mp4|key)(?:[?#]|$)/i.test(requestUrl); const startedAt = performance.now(); const stats = this.stats = this.createStats(); stats.trequest = startedAt; stats.tfirst = startedAt; stats.tload = startedAt; stats.loading.start = startedAt; this.loader = _GM_xmlhttpRequest({ method: "GET", url: requestUrl, responseType: "text", overrideMimeType: wantsArrayBuffer ? "text/plain; charset=x-user-defined" : void 0, timeout: config && config.timeout ? config.timeout : 2e4, headers: { Accept: wantsArrayBuffer ? "*/*" : "application/vnd.apple.mpegurl, application/x-mpegURL, */*" }, onprogress: (event) => { stats.loaded = Number(event && event.loaded ? event.loaded : stats.loaded || 0); stats.total = Number(event && event.total ? event.total : stats.total || stats.loaded || 0); if (!stats.loading.first && stats.loaded > 0) { stats.loading.first = performance.now(); } }, onload: (r) => { const status = Number(r.status || 0); const response = { code: status, text: r.statusText || "", url: r.finalUrl || requestUrl }; stats.tfirst = stats.tfirst || performance.now(); stats.tload = performance.now(); stats.loading.first = stats.loading.first || stats.tload; stats.loading.end = stats.tload; if (status < 200 || status >= 300) { callbacks.onError && callbacks.onError(response, context, null, stats); return; } const responseText = r.responseText || r.response || ""; const data = wantsArrayBuffer ? binaryTextToArrayBuffer(responseText) : responseText; stats.loaded = data && data.byteLength ? data.byteLength : data && data.length ? data.length : stats.loaded || 0; stats.total = stats.total || stats.loaded; stats.bwEstimate = stats.loading.end > stats.loading.first ? Math.round(stats.total * 8e3 / (stats.loading.end - stats.loading.first)) : 0; callbacks.onSuccess && callbacks.onSuccess({data, url: response.url}, stats, context, response); }, onerror: () => { callbacks.onError && callbacks.onError({ code: 0, text: "network error", url: requestUrl }, context, null, stats); }, ontimeout: () => { stats.tload = performance.now(); stats.loading.end = stats.tload; callbacks.onTimeout && callbacks.onTimeout(stats, context, null); } }); } } return GMHlsLoader; }; const isM3U8Url = (url) => /\.m3u8(?:[?#].*)?$/i.test(String(url || "")); const attachHlsToVideo = (videoEl, src) => { return new Promise((resolve, reject) => { if (!videoEl || !src) { reject(new Error("video element or source missing")); return; } const HlsClass = getHlsClass(); if (!HlsClass) { videoEl.src = src; videoEl.load && videoEl.load(); resolve(false); return; } const hls = new HlsClass({ enableWorker: false, lowLatencyMode: true, loader: createHlsLoader(), autoStartLoad: true, startPosition: 0, capLevelToPlayerSize: true, testBandwidth: false, preferManagedMediaSource: false, maxBufferLength: 6, maxMaxBufferLength: 12, backBufferLength: 30, maxBufferHole: 0.5, nudgeOffset: 0.1, manifestLoadingMaxRetry: 2, levelLoadingMaxRetry: 2, fragLoadingMaxRetry: 2, manifestLoadingTimeOut: 12e3, levelLoadingTimeOut: 12e3, fragLoadingTimeOut: 12e3, abrEwmaFastLive: 3, abrEwmaSlowLive: 9 }); hls.on(HlsClass.Events.MANIFEST_PARSED, () => { hls.startLoad(0); videoEl.play().catch(() => { }); resolve(true); }); hls.on(HlsClass.Events.ERROR, (_, data) => { if (!data || !data.fatal) return; clog.error("HLS: 播放失败", data); if (data.type === HlsClass.ErrorTypes.MEDIA_ERROR) { if (videoEl.readyState >= 2) { try { hls.recoverMediaError && hls.recoverMediaError(); } catch (e) { } return; } } reject(new Error(data.details || "HLS fatal error")); }); hls.loadSource(src); hls.attachMedia(videoEl); videoEl._hls = hls; }); }; const destroyHls = (videoEl) => { if (videoEl && videoEl._hls) { try { videoEl._hls.destroy(); } catch (e) { } videoEl._hls = null; } }; const JAVXY_TOKEN = [118, 119, 112, 71, 97, 110, 28, 84, 124, 65, 76, 102, 65, 16, 77, 109, 64, 82, 85, 83, 67, 92, 125, 108, 83, 65, 124, 107, 84, 104, 71, 84, 17, 124, 118, 125, 104, 8, 125, 96, 112, 103, 29, 18, 82, 83, 87, 84].map((v) => String.fromCharCode(v ^ 37)).join(""); const JAVXY_ENDPOINTS = [ {host: String.fromCharCode(106, 97, 118, 120, 121, 46, 99, 99, 46, 99, 100), label: "Javxy"}, { host: String.fromCharCode(119, 111, 114, 107, 101, 114, 46, 106, 97, 118, 120, 121, 46, 99, 99, 46, 99, 100), label: "Javxy Worker" } ]; const JAVXY_SOURCE_LABELS = { "Tokyo-Hot": "Javxy | Tokyo-Hot", FC2: "Javxy | FC2", Direct: "Javxy | Direct", DMM: "Javxy | dmm", MGStage: "Javxy | MGStage", DUGA: "Javxy | DUGA", MYWIFE: "Javxy | MyWife", JavTrailers: "Javxy | JavTrailers", JavDB: "Javxy | Javdb", AVWikiDB: "Javxy | AVWikiDB", JAVDatabase: "Javxy | JAVDatabase", HEYZO: "Javxy | Heyzo", HeyDouga: "Javxy | HeyDouga", PACO: "Javxy | Paco", "10MU": "Javxy | 10mu", Caribbean: "Javxy | 加勒比", "1Pondo": "Javxy | 一本道" }; const JAVXY_QUALITY_OPTIONS = [ {quality: "4k", text: "4K"}, {quality: "hhb", text: "1080p"}, {quality: "1080p", text: "1080p"}, {quality: "hmb", text: "720p"}, {quality: "720p", text: "720p"}, {quality: "mhb", text: "576p"}, {quality: "540p", text: "540p"}, {quality: "mmb", text: "432p"}, {quality: "480p", text: "480p"}, {quality: "396p", text: "396p"}, {quality: "360p", text: "360p"}, {quality: "240p", text: "240p"} ]; const selectHighestQuality = (qualityMap) => { const rank = new Map(JAVXY_QUALITY_OPTIONS.map((item, index) => [item.quality, index])); return Object.keys(qualityMap || {}).filter((key) => qualityMap[key]).sort((a, b) => (rank.get(a) ?? -1) - (rank.get(b) ?? -1))[0] || null; }; const sortQualityKeys = (qualityMap) => { const rank = new Map(JAVXY_QUALITY_OPTIONS.map((item, index) => [item.quality, index])); return Object.keys(qualityMap || {}).filter((key) => qualityMap[key]).sort((a, b) => (rank.get(a) ?? -1) - (rank.get(b) ?? -1)); }; const normalizeJavxySource = (value) => { const raw = String(value || "").trim().toLowerCase(); if (raw.includes("fc2")) return "FC2"; if (raw.includes("mgstage")) return "MGStage"; if (raw.includes("heydouga")) return "Direct"; if (raw.includes("mywife")) return "MYWIFE"; if (raw.includes("duga")) return "DUGA"; if (raw.includes("javtrailers")) return "JavTrailers"; if (raw.includes("javdb")) return "JavDB"; if (raw.includes("avwikidb")) return "AVWikiDB"; if (raw.includes("javdatabase")) return "JAVDatabase"; if (raw.includes("dmm")) return "DMM"; if (raw === "direct" || raw.includes("heyzo") || raw.includes("heydouga") || raw.includes("paco") || raw.includes("10musume") || raw.includes("10mu") || raw.includes("1pondo") || raw.includes("caribbean") || raw.includes("tokyo-hot") || raw.includes("tokyohot")) return "Direct"; return String(value || "").trim(); }; const javxyQualityMapToDmmFormat = (javxyQualityMap) => { const result = {}; const qualityKeys = qualityOptions.map((o) => o.quality); for (const [key, url] of Object.entries(javxyQualityMap || {})) { if (!url) continue; const lowerKey = key.toLowerCase(); if (qualityKeys.includes(key)) { result[key] = url; } else if (qualityKeys.includes(lowerKey)) { result[lowerKey] = url; } } return result; }; const fromJavxyCcCd = async (id, rawCode = "", options = {}) => { const query = String(id || rawCode || "").trim(); if (!query) { clog.debug("Javxy 跳过:查询词为空"); return null; } for (const endpoint of JAVXY_ENDPOINTS) { const params = new URLSearchParams({client: "laosiji-new"}); if (Array.isArray(options.skip) && options.skip.length) params.set("skip", options.skip.join(",")); if (Array.isArray(options.prefer) && options.prefer.length) params.set("prefer", options.prefer.join(",")); if (Array.isArray(options.source) && options.source.length) params.set("source", options.source.join(",")); if (options.playbackFallback) params.set("purpose", "playback-fallback"); const apiUrl2 = `https://${endpoint.host}/trailers/${encodeURIComponent(query)}?${params}`; clog.debug("Javxy 请求 API", {query, apiUrl: apiUrl2, endpoint: endpoint.label}); let r; try { r = await gmHttp.get(apiUrl2, null, { "Accept": "application/json,text/plain,*/*", "X-Javxy-Token": JAVXY_TOKEN }, {timeout: 8e3}); } catch (e) { clog.debug("Javxy API 网络失败,尝试下一个节点", {endpoint: endpoint.label, error: e.message}); continue; } if (!r) { clog.debug("Javxy API 无响应,尝试下一个节点", {endpoint: endpoint.label}); continue; } const trailerUrl = String((r == null ? void 0 : r.trailer) || "").trim(); if (!trailerUrl) { clog.debug("Javxy 无 trailer 字段", {endpoint: endpoint.label, keys: Object.keys(r || {})}); return null; } const qualityMap = (r == null ? void 0 : r.qualities) && typeof r.qualities === "object" ? r.qualities : {}; const quality = (r == null ? void 0 : r.quality) && qualityMap[r.quality] ? r.quality : selectHighestQuality(qualityMap); const sourceBase = JAVXY_SOURCE_LABELS[r == null ? void 0 : r.source] || `Javxy | ${(r == null ? void 0 : r.source) || "dmm"}`; const directUrl = qualityMap[quality] || trailerUrl; clog.debug("Javxy 返回结果", { endpoint: endpoint.label, source: r == null ? void 0 : r.source, quality, qualities: Object.keys(qualityMap), url: directUrl }); const dmmQualityMap = javxyQualityMapToDmmFormat(qualityMap); return { url: directUrl, source: sourceBase, type: String((r == null ? void 0 : r.type) || "").trim() || "video", qualities: qualityMap, quality, directUrl, code: id, rawCode, javxySource: String((r == null ? void 0 : r.source) || "").trim(), requiresJP: Boolean(r == null ? void 0 : r.requiresJP), urls: Array.isArray(r == null ? void 0 : r.urls) && r.urls.length ? r.urls : sortQualityKeys(qualityMap).map((key) => qualityMap[key]), dmmQualityMap }; } return null; }; const fallbackJavxyResult = async (code, rawCode = "", failedSources = [], options = {}) => { const skip = [...new Set((failedSources || []).map((source2) => normalizeJavxySource(source2)).filter(Boolean))]; const source = [...new Set((options.source || []).map((s) => String(s || "").trim()).filter(Boolean))]; if (!skip.length && !source.length) return null; clog.debug("Javxy 播放失败回落查询", {code, skip, source}); return fromJavxyCcCd(code, rawCode, {skip, source, playbackFallback: true}); }; const getJavxyVideoUrls = async (code, failedSources = []) => { const result = await fallbackJavxyResult(code, code, failedSources, { source: ["JavTrailers", "JavDB"] }); if ((result == null ? void 0 : result.dmmQualityMap) && Object.keys(result.dmmQualityMap).length > 0) { return result.dmmQualityMap; } if (result == null ? void 0 : result.url) { const qualityMap = {}; qualityMap["720p"] = result.url; return qualityMap; } return null; }; class JavxyPreviewVideoPlugin extends BasePlugin { getName() { return "JavxyPreviewVideoPlugin"; } _attachVideoSrc(videoEl, src) { if (!videoEl || !src) return; destroyHls(videoEl); if (isM3U8Url(src)) { const HlsClass = getHlsClass(); if (HlsClass) { attachHlsToVideo(videoEl, src).catch((err) => { clog.error("视频预览 HLS 加载失败:", err); videoEl.src = src; videoEl.load && videoEl.load(); }); } else { loadHlsLibrary().then((HlsClass2) => { if (videoEl && videoEl.isConnected && HlsClass2) { attachHlsToVideo(videoEl, src).catch((err) => { clog.error("视频预览 HLS 延迟加载失败:", err); videoEl.src = src; videoEl.load && videoEl.load(); }); } else if (videoEl && videoEl.isConnected) { videoEl.src = src; videoEl.load && videoEl.load(); } }); } } else { videoEl.src = src; videoEl.load && videoEl.load(); } } _selectDefaultQuality(qualityList, intendedDefault) { if (!qualityList || qualityList.length === 0) return null; const availableSet = new Set(qualityList); if (availableSet.has(intendedDefault)) return intendedDefault; const priorityOrder = qualityOptions.map((o) => o.quality).reverse(); for (const q of priorityOrder) if (availableSet.has(q)) return q; return qualityList[0]; } async initCss() { return ` .jhs-javxy-video-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.95); z-index: 12345700; display: flex; justify-content: center; align-items: center; opacity: 0; visibility: hidden; transition: opacity 0.2s; } .jhs-javxy-video-modal.is-open { opacity: 1; visibility: visible; } .jhs-javxy-video-modal-inner { display: flex; flex-direction: column; align-items: center; gap: 12px; max-width: 90vw; max-height: 90vh; } .jhs-javxy-video-wrapper { width: 80vw; max-height: 80vh; aspect-ratio: 16/9; background: #000; position: relative; } .jhs-javxy-video-wrapper video { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .jhs-javxy-quality-bar { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; } .jhs-javxy-quality-btn { min-width: 60px; padding: 5px 10px; font-size: 13px; background: rgba(255,255,255,0.2); color: #fff; border: 1px solid rgba(255,255,255,0.5); border-radius: 4px; cursor: pointer; transition: background 0.2s; } .jhs-javxy-quality-btn:hover { background: rgba(255,255,255,0.4); } .jhs-javxy-quality-btn.active { background: #1890ff; border-color: #096dd9; font-weight: bold; } .jhs-javxy-loading { color: #fff; font-size: 16px; text-align: center; } `; } async handle() { if (!window.isDetailPage) return; const carNum2 = this.getPageInfo().carNum; if (!carNum2) return; this._addJavxyButton(carNum2); } _addJavxyButton(carNum2) { let $target = null; if (isJavDb$1) { $target = $('a[title="複製番號"]'); } else if (isJavBus$1) { const headerSpans = document.querySelectorAll("span.header"); for (const span of headerSpans) { if (span.textContent.trim() === "識別碼:") { const nextSpan = span.nextElementSibling; if (nextSpan && nextSpan.tagName === "SPAN") { const copyBtn = nextSpan.nextElementSibling; if (copyBtn && copyBtn.tagName === "BUTTON" && copyBtn.textContent.trim() === "复制") { $target = $(copyBtn); } else { $target = $(nextSpan); } } break; } } } if (!$target || !$target.length) return; const $btn = $(`视频预览`); $btn.on("click", async (e) => { e.preventDefault(); e.stopPropagation(); $btn.css("pointer-events", "none").find("span").text("..."); try { await this._openJavxyPlayer(carNum2); } finally { $btn.css("pointer-events", "").find("span").text("视频预览"); } }); $target.after($btn); } async _openJavxyPlayer(carNum2) { if ($("#jhs-javxy-video-modal").length === 0) { $("body").append(`
    `); const $modal2 = $("#jhs-javxy-video-modal"); $modal2.on("click", (e) => { if (e.target.id === "jhs-javxy-video-modal") { $modal2.removeClass("is-open"); const videoEl2 = document.getElementById("jhs-javxy-video"); if (videoEl2) { destroyHls(videoEl2); videoEl2.pause(); } } }); $(document).on("keydown", (e) => { if (e.key === "Escape" && $modal2.hasClass("is-open")) { $modal2.removeClass("is-open"); const videoEl2 = document.getElementById("jhs-javxy-video"); if (videoEl2) { destroyHls(videoEl2); videoEl2.pause(); } } }); } const $modal = $("#jhs-javxy-video-modal"); const $wrapper = $modal.find(".jhs-javxy-video-wrapper"); const $qualityBar = $modal.find(".jhs-javxy-quality-bar"); $wrapper.html('
    视频预览加载中...
    '); $qualityBar.empty(); $modal.addClass("is-open"); let videoMap = null; try { videoMap = await getJavxyVideoUrls(carNum2); } catch (err) { $wrapper.html(`
    视频预览请求失败: ${err.message}
    `); return; } if (!videoMap || Object.keys(videoMap).length === 0) { $wrapper.html('
    未找到可用的视频源
    '); return; } const qualityList = Object.keys(videoMap); const defaultQuality = this._selectDefaultQuality(qualityList, "720p"); const defaultUrl = videoMap[defaultQuality]; $wrapper.html(``); const videoEl = document.getElementById("jhs-javxy-video"); if (!videoEl) return; if (isM3U8Url(defaultUrl)) { this._attachVideoSrc(videoEl, defaultUrl); } let buttonsHtml = ""; qualityOptions.forEach((option) => { const url = videoMap[option.quality]; if (url) { buttonsHtml += ``; } }); $qualityBar.html(buttonsHtml); $qualityBar.off("click").on("click", ".jhs-javxy-quality-btn", (e) => { const $btn = $(e.currentTarget); if ($btn.hasClass("active")) return; const src = $btn.attr("data-video-src"); const currentTime = videoEl.currentTime; destroyHls(videoEl); this._attachVideoSrc(videoEl, src); videoEl.load(); if (currentTime > 0 && Number.isFinite(currentTime)) { videoEl.currentTime = currentTime; } videoEl.play().catch(() => { }); $qualityBar.find(".jhs-javxy-quality-btn").removeClass("active"); $btn.addClass("active"); }); videoEl.play().catch((e) => console.warn("视频预览播放失败:", e)); } } class ImageRecognitionPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "siteList", [{ name: "Google旧版", url: "https://www.google.com/searchbyimage?image_url={占位符}&client=firefox-b-d", ico: "https://www.google.com/favicon.ico" }, { name: "Google", url: "https://lens.google.com/uploadbyurl?url={占位符}", ico: "https://www.google.com/favicon.ico" }, { name: "Yandex", url: "https://yandex.ru/images/search?rpt=imageview&url={占位符}", ico: "https://yandex.ru/favicon.ico" }]); __publicField2(this, "isUploading", false); } getName() { return "ImageRecognitionPlugin"; } async initCss() { return "\n \n "; } open(onOpenFun) { layer.open({ type: 1, title: "以图识图", content: '\n
    \n
    \n
    \n

    拖拽图片到此处 或 点击按钮选择图片

    \n

    也可以直接 Ctrl+V 粘贴图片或 图片URL

    \n
    \n \n \n
    \n \n \n \n \n \n
    \n ', area: utils.isMobile() ? utils.getResponsiveArea() : ["40%", "80%"], success: async (layero) => { this.initEventListeners(); onOpenFun && onOpenFun(); }, end: () => { $(document).off("paste.searchImg"); } }); } initEventListeners() { const $uploadArea = $("#upload-area"), $fileInput = $("#image-file"), $selectBtn = $("#select-image-btn"), $previewArea = $("#preview-area"), $previewImage = $("#preview-image"), $actionBtns = $("#action-btns"), $searchImage = $("#handle-btn"), $cancelBtn = $("#cancel-btn"), $urlInputContainer = $("#url-input-container"), $imageUrlInput = $("#image-url"), $searchResults = $("#search-results"), $siteBtnsContainer = $("#search-img-site-btns-container"); $uploadArea.on("dragover", ((e) => { e.preventDefault(); $uploadArea.addClass("highlight"); })).on("dragleave", (() => { $uploadArea.removeClass("highlight"); })).on("drop", ((e) => { e.preventDefault(); $uploadArea.removeClass("highlight"); if (e.originalEvent.dataTransfer.files && e.originalEvent.dataTransfer.files[0]) { this.handleImageFile(e.originalEvent.dataTransfer.files[0]); this.resetSearchUI(); } })); $selectBtn.on("click", (() => { $fileInput.trigger("click"); })); $fileInput.on("change", ((e) => { if (e.target.files && e.target.files[0]) { this.handleImageFile(e.target.files[0]); this.resetSearchUI(); } })); $(document).on("paste.searchImg", (async (e) => { const items = e.originalEvent.clipboardData.items; for (let i = 0; i < items.length; i++) if (-1 !== items[i].type.indexOf("image")) { const blob = items[i].getAsFile(); this.handleImageFile(blob); this.resetSearchUI(); return; } const text = e.originalEvent.clipboardData.getData("text"); if (text && utils.isUrl(text)) { $urlInputContainer.show(); $imageUrlInput.val(text); $previewImage.attr("src", text); $previewArea.show(); this.resetSearchUI(); } })); $searchImage.on("click", (async () => { const imageSrc = $previewImage.attr("src"); if (imageSrc) { if (!this.isUploading) { this.isUploading = true; try { const imgUrl = await this.searchByImage(imageSrc); $actionBtns.hide(); $searchResults.show(); $siteBtnsContainer.empty(); const key = "jhs_selectedSites", selectedSites = JSON.parse(localStorage.getItem(key) || "{}"); this.siteList.forEach(((site) => { const siteUrl = site.url.replace("{占位符}", encodeURIComponent(imgUrl)), isChecked = false !== selectedSites[site.name]; $siteBtnsContainer.append(` ${site.name} ${site.name} `); })); $siteBtnsContainer.on("change", ".site-checkbox", (function() { const siteName = $(this).data("site-name"); selectedSites[siteName] = $(this).is(":checked"); localStorage.setItem(key, JSON.stringify(selectedSites)); })); $siteBtnsContainer.show(); } finally { this.isUploading = false; } } } else show.info("请粘贴或上传图片"); })); $cancelBtn.on("click", (() => { $previewArea.hide(); $urlInputContainer.hide(); $fileInput.val(""); $imageUrlInput.val(""); })); $imageUrlInput.on("change", (() => { if (utils.isUrl($imageUrlInput.val())) { $previewImage.attr("src", $imageUrlInput.val()); $previewArea.show(); } })); $("#openAll").on("click", (() => { $(".search-img-site-btn").each((function() { $(this).find(".site-checkbox").is(":checked") && window.open($(this).attr("href")); })); })); } resetSearchUI() { $("#action-btns").show(); $("#search-results").hide(); $("#search-img-site-btns-container").hide().empty(); } handleImageFile(file) { const previewImage = document.getElementById("preview-image"), previewArea = document.getElementById("preview-area"), urlInputContainer = document.getElementById("url-input-container"); if (!file.type.match("image.*")) { show.info("请选择图片文件"); return; } const reader = new FileReader(); reader.onload = (e) => { previewImage.src = e.target.result; previewArea.style.display = "block"; urlInputContainer.style.display = "none"; $("#handle-btn")[0].click(); }; reader.readAsDataURL(file); } async searchByImage(imageSrc) { let loadObj = loading(); try { let imageUrl = imageSrc; if (imageSrc.startsWith("data:")) { show.info("开始上传图片..."); const imgurUrl = await (async function(base64Data) { var _a2; const matches = base64Data.match(/^data:(.+);base64,(.+)$/); if (!matches || matches.length < 3) throw new Error("无效的Base64图片数据"); const mimeType = matches[1], imageData = matches[2], byteCharacters = atob(imageData), byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) byteNumbers[i] = byteCharacters.charCodeAt(i); const byteArray = new Uint8Array(byteNumbers), blob = new Blob([byteArray], { type: mimeType }), formData = new FormData(); formData.append("image", blob); const response = await fetch("https://api.imgur.com/3/image", { method: "POST", headers: { Authorization: "Client-ID d70305e7c3ac5c6" }, body: formData }), data = await response.json(); if (data.success && data.data && data.data.link) return data.data.link; throw new Error((null == (_a2 = data.data) ? void 0 : _a2.error) || "上传到Imgur失败"); })(imageSrc); if (!imgurUrl) { show.error("上传到失败"); return; } imageUrl = imgurUrl; } return imageUrl; } catch (error) { show.error(`搜索失败: ${error.message}`); console.error("搜索失败:", error); } finally { loadObj.close(); } } } class BusNavBarPlugin extends BasePlugin { getName() { return "BusNavBarPlugin"; } handle() { $("#navbar > div > div > span").append('\n \n '); $("#search-img-btn").on("click", (() => { this.getBean("ImageRecognitionPlugin").open(); })); } } class RelatedPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "floorIndex", 1); __publicField2(this, "isInit", false); } getName() { return "RelatedPlugin"; } async showRelated($eleBox, movieId) { const enableLoadRelated = await storageManager.getSetting("enableLoadRelated", NO), $magnets = $eleBox; if (movieId) { $magnets.append(`
    相关清单 ${enableLoadRelated === YES ? "折叠" : "展开"} ${enableLoadRelated === YES ? "▲" : "▼"}
    `); $("#relatedFold").on("click", ((event) => { event.preventDefault(); event.stopPropagation(); const $text = $("#relatedFold .toggle-text"), $icon = $("#relatedFold .toggle-icon"), isFolded = "展开" === $text.text(); $text.text(isFolded ? "折叠" : "展开"); $icon.text(isFolded ? "▲" : "▼"); if (isFolded) { $("#relatedContainer").show(); $("#relatedFooter").show(); if (!this.isInit) { this.fetchAndDisplayRelateds(movieId); this.isInit = true; } storageManager.saveSettingItem("enableLoadRelated", YES); } else { $("#relatedContainer").hide(); $("#relatedFooter").hide(); storageManager.saveSettingItem("enableLoadRelated", NO); } })); $magnets.append('
    '); $magnets.append('
    '); enableLoadRelated === YES && await this.fetchAndDisplayRelateds(movieId); } else show.error("未传入movieId"); } async fetchAndDisplayRelateds(movieId) { const $relatedContainer = $("#relatedContainer"), $relatedFooter = $("#relatedFooter"); $relatedContainer.append('
    获取清单中...
    '); let dataList = null; try { dataList = await javDbApi.related(movieId, 1, 20); } catch (e) { console.error("获取清单失败:", e); } finally { $("#relatedLoading").remove(); } if (dataList) if (0 !== dataList.length) { this.displayRelateds(dataList, $relatedContainer); if (20 === dataList.length) { $relatedFooter.html('\n \n \n '); let currentPage = 1, $loadMoreRelateds = $("#loadMoreRelateds"); $loadMoreRelateds.on("click", (async () => { $loadMoreRelateds.text("加载中...").prop("disabled", true); currentPage++; let moreData; try { moreData = await javDbApi.related(movieId, currentPage, 20); } catch (e) { console.error("加载更多清单失败:", e); } finally { $loadMoreRelateds.text("加载失败, 请点击重试").prop("disabled", false); } if (moreData) { this.displayRelateds(moreData, $relatedContainer); if (moreData.length < 20) { $loadMoreRelateds.remove(); $("#relatedEnd").show(); } else $loadMoreRelateds.text("加载更多清单").prop("disabled", false); } })); } else $relatedFooter.html('
    已加载全部清单
    '); } else $relatedContainer.append('
    无清单
    '); else { $relatedContainer.append('\n
    \n 获取清单失败\n 重试\n
    \n '); $("#retryFetchRelateds").on("click", (async () => { $("#retryFetchRelateds").parent().remove(); await this.fetchAndDisplayRelateds(movieId); })); } } displayRelateds(dataList, $container) { dataList.length && dataList.forEach(((item) => { let commentHtml = `
    #${this.floorIndex++} 创建时间: ${item.createTime}

    ${item.name}

    视频个数: ${item.movieCount}

    收藏次数: ${item.collectionCount} 被查看次数: ${item.viewCount}

    `; $container.append(commentHtml); })); } } class WantAndWatchedVideosPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "type", null); } getName() { return "WantAndWatchedVideosPlugin"; } async handle() { if (window.location.href.includes("/want_watch_videos")) { $("h3").append('导入至 JHS'); $("#wantWatchBtn").on("click", ((event) => { this.type = Status_FAVORITE$1; this.importWantWatchVideos(event, "是否将 想看的影片 导入到 JHS-收藏?"); })); } if (window.location.href.includes("/watched_videos")) { $("h3").append('导入至 JHS'); $("#wantWatchBtn").on("click", ((event) => { this.type = Status_HAS_DOWN$1; this.importWantWatchVideos(event, "是否将 看过的影片 导入到 JHS-已下载?"); })); } } importWantWatchVideos(event, title) { utils.q(null, `${title}
    执行此功能前请记得备份数据`, (async () => { let loadObj = loading(); try { await this.parseMovieList(); } catch (e) { console.error(e); } finally { loadObj.close(); } })); } async parseMovieList($dom) { let movieList, nextPageLink; if ($dom) { movieList = $dom.find(this.getSelector().itemSelector); nextPageLink = $dom.find(".pagination-next").attr("href"); } else { movieList = $(this.getSelector().itemSelector); nextPageLink = $(".pagination-next").attr("href"); } for (const element of movieList) { const item = $(element), href = item.find("a").attr("href"), carNum2 = item.find(".video-title strong").text().trim(), publishTime = item.find(".meta").text().trim(); if (href && carNum2) try { if (await storageManager.getCar(carNum2)) { show.info(`${carNum2} 已存在, 跳过`); continue; } await storageManager.saveCar({ carNum: carNum2, url: href, names: null, actionType: this.type, publishTime }); } catch (error) { console.error(`保存失败 [${carNum2}]:`, error); } } if (nextPageLink) { show.info("发现下一页,正在解析:", nextPageLink); await new Promise(((resolve) => setTimeout(resolve, 1e3))); $.ajax({ url: nextPageLink, method: "GET", success: (html) => { const parser = new DOMParser(), next$dom = $(parser.parseFromString(html, "text/html")); this.parseMovieList(next$dom); }, error: function(err) { console.error(err); show.error("加载下一页失败:" + err.message); } }); } else { show.ok("导入结束!"); window.refresh(); } } } class MagnetHubPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "currentEngine", null); __publicField2(this, "searchEngines", [{ name: "U9A9", id: "u9a9", url: "https://u9a9.com/?type=2&search={keyword}", targetPage: "https://u9a9.com/?type=2&search={keyword}", parseHtml: this.parseU3C3 }, { name: "U3C3", id: "u3c3", url: "https://u3c3.com/?search2=a8lr16lo&search={keyword}", targetPage: "https://u3c3.com/?search2=a8lr16lo&search={keyword}", parseHtml: this.parseU3C3 }, { name: "Sukebei", id: "Sukebei", url: "https://sukebei.nyaa.si/?f=0&c=0_0&q={keyword}", targetPage: "https://sukebei.nyaa.si/?f=0&c=0_0&q={keyword}", parseHtml: this.parseSukebei }]); } getName() { return "MagnetHubPlugin"; } async initCss() { return "\n \n "; } createMagnetHub(keyword) { keyword = keyword.replace("FC2-", ""); const $container = $('
    '), $tabs = $('
    '), key = "jhs_magnetHub_selectedEngine", savedEngineId = localStorage.getItem(key); let defaultEngineIndex = 0; const $tabBox = $('
    '); this.searchEngines.forEach(((engine, index) => { const $tab = $(`
    ${engine.name}
    `); if (savedEngineId && engine.id === savedEngineId) { $tab.addClass("active"); this.currentEngine = engine; defaultEngineIndex = index; } else if (0 === index && !savedEngineId) { $tab.addClass("active"); this.currentEngine = engine; } $tabBox.append($tab); })); $tabs.append($tabBox); $tabs.append(`原网页`); $container.append($tabs); const $resultsContainer = $('
    '); $container.append($resultsContainer); $container.on("click", ".magnet-tab", ((e) => { const engineId = $(e.target).data("engine"); this.currentEngine = this.searchEngines.find(((engine) => engine.id === engineId)); $("#targetBox").attr("href", this.currentEngine.targetPage.replace("{keyword}", encodeURIComponent(keyword))); localStorage.setItem(key, engineId); $container.find(".magnet-tab").removeClass("active"); $(e.target).addClass("active"); this.searchEngine($resultsContainer, this.currentEngine, keyword); })); this.searchEngine($resultsContainer, this.currentEngine || this.searchEngines[defaultEngineIndex], keyword); return $container; } searchEngine($container, engine, keyword) { $container.html(`
    正在从 ${engine.name} 搜索 "${keyword}"...
    `); const cacheKey = `${engine.name}_${keyword}`; sessionStorage.getItem(cacheKey); const url = engine.url.replace("{keyword}", encodeURIComponent(keyword)); engine.parseHtml && _GM_xmlhttpRequest({ method: "GET", url, onload: (response) => { try { const results = engine.parseHtml.call(this, response.responseText, keyword); results.length > 0 && sessionStorage.setItem(cacheKey, JSON.stringify(results)); this.displayResults($container, results, engine.name); } catch (e) { $container.html(`
    解析 ${engine.name} 结果失败: ${e.message}
    `); } }, onerror: (error) => { $container.html(`
    从 ${engine.name} 获取数据失败: ${error.statusText}
    `); } }); engine.parseJson && engine.parseJson.call(this, $container, engine, keyword, cacheKey); } displayResults($container, results, engineName) { $container.empty(); if (0 !== results.length) { results.forEach(((result) => { const $result = $(`
    ${result.title}
    大小: ${result.size || "未知"} 日期: ${result.date || "未知"}
    `); $container.append($result); })); $container.on("click", ".copy-btn", (function() { const $btn = $(this), magnet = $btn.data("magnet"); navigator.clipboard ? navigator.clipboard.writeText(magnet).then((() => { showCopiedFeedback($btn); })).catch(((err) => { fallbackCopy(magnet, $btn); })) : fallbackCopy(magnet, $btn); })); $container.on("click", ".down-115", (async (event) => { const magnet = $(event.currentTarget).data("magnet"); let loadObj = loading(); try { await this.getBean("WangPan115TaskPlugin").handleAddTask(magnet); } catch (e) { show.error("发生错误:" + e); console.error(e); } finally { loadObj.close(); } })); } else $container.append('
    没有找到相关结果
    '); function showCopiedFeedback($btn) { const originalText = $btn.text(); $btn.addClass("copied").text("已复制"); setTimeout((() => { $btn.removeClass("copied").text(originalText); }), 2e3); } function fallbackCopy(text, $btn) { const textarea = document.createElement("textarea"); textarea.value = text; textarea.style.position = "fixed"; document.body.appendChild(textarea); textarea.select(); try { document.execCommand("copy"); showCopiedFeedback($btn); } catch (err) { console.error("复制失败:", err); alert("复制失败,请手动复制链接"); } document.body.removeChild(textarea); } } parseBTSOW($container, engine, keyword, cacheKey) { const _this = this; _GM_xmlhttpRequest({ method: "POST", url: engine.url, headers: { "Content-Type": "application/json" }, data: `[{"search":"${keyword}"},50,1]`, onload: (response) => { try { const dataList = JSON.parse(response.responseText).data, results = []; for (let i = 0; i < dataList.length; i++) { let item = dataList[i]; results.push({ title: item.name, magnet: "magnet:?xt=urn:btih:" + item.hash, size: (item.size / 1073741824).toFixed(2) + " GB", date: utils.formatDate(new Date(1e3 * item.lastUpdateTime)) }); } results.length > 0 && sessionStorage.setItem(cacheKey, JSON.stringify(results)); _this.displayResults($container, results, engine.name); } catch (e) { $container.html(`
    解析 ${engine.name} 结果失败: ${e.message}
    `); } }, onerror: (error) => { $container.html(`
    从 ${engine.name} 获取数据失败: ${error.statusText}
    `); } }); } parseU3C3(html, keyword) { const $dom = utils.htmlTo$dom(html), results = []; $dom.find(".torrent-list tbody tr").each(((i, el) => { const $el = $(el); if ($el.text().includes("置顶")) return; const title = $el.find("td:nth-child(2) a").attr("title") || $el.find("td:nth-child(2) a").text().trim(); if (!title.toLowerCase().includes(keyword.toLowerCase())) return; const magnet = $el.find("td:nth-child(3) a[href^='magnet:']").attr("href"), size = $el.find("td:nth-child(4)").text().trim(), date = $el.find("td:nth-child(5)").text().trim(); magnet && results.push({ title, magnet, size, date }); })); return results; } parseSukebei(html, keyword) { const $dom = utils.htmlTo$dom(html), results = []; $dom.find(".torrent-list tbody tr").each(((i, el) => { const $el = $(el); if ($el.text().includes("置顶")) return; const title = $el.find("td:nth-child(2) a").attr("title") || $el.find("td:nth-child(2) a").text().trim(); if (!title.toLowerCase().includes(keyword.toLowerCase())) return; const magnet = $el.find("td:nth-child(3) a[href^='magnet:']").attr("href"), size = $el.find("td:nth-child(4)").text().trim(), date = $el.find("td:nth-child(5)").text().trim(); magnet && results.push({ title, magnet, size, date }); })); return results; } } const Thumbnail = { sources: ["javfree", "projectjav", "javstore"], cacheKey(code) { return `thumb_cache_v3_${code}`; }, lookupCode(code) { const text = String(code || "").trim(); const fc2 = text.match(/^(?:FC2[-_\s]?(?:PPV[-_\s]?)?)?(\d{6,9})$/i); return fc2 ? fc2[1] : text; }, sourceOrder() { const savedOrder = ["javfree", "projectjav", "javstore"]; const seen = /* @__PURE__ */ new Set(); return [...savedOrder, ...this.sources].filter((src) => { if (seen.has(src) || typeof this[src] !== "function") return false; seen.add(src); return true; }); }, async fetchFromSource(source, code) { try { return await this[source](this.lookupCode(code)); } catch (e) { clog.debug(`Thumbnail[${source}] 异常:`, e.message); return null; } }, normalizeForCompare(text) { return String(text || "").toLowerCase().replace(/[^a-z0-9]/g, ""); }, isCodeMatched(text, code) { const normalizedText = this.normalizeForCompare(text); const normalizedCode = this.normalizeForCompare(code); return !!normalizedCode && normalizedText.includes(normalizedCode); }, isDetailMatched(doc, url, code) { var _a, _b; const title = ((_a = doc == null ? void 0 : doc.querySelector("title")) == null ? void 0 : _a.textContent) || ""; const headings = [...(doc == null ? void 0 : doc.querySelectorAll("h1,h2,h3,.entry-title,.movie-title,.post-title")) || []].map((el) => el.textContent || "").join(" "); const bodyText = (((_b = doc == null ? void 0 : doc.body) == null ? void 0 : _b.textContent) || "").slice(0, 5e3); return this.isCodeMatched([url, title, headings, bodyText].join(" "), code); }, normalizePreviewUrl(url, baseUrl = "") { if (!url) return ""; const absolute = /^https?:\/\//i.test(url) ? url : baseUrl ? new URL(url, baseUrl).href : url; return absolute.replace(/^http:/, "https:"); }, isJavfreePreviewImage(url, code) { const cleanUrl = String(url || "").split("?")[0]; const lookupCode = this.lookupCode(code); const isFc2Numeric = /^\d{6,9}$/.test(lookupCode); const fc2ShotPattern = isFc2Numeric ? new RegExp(`${lookupCode}_\\d+\\.(?:jpe?g|png|webp)$`, "i") : null; return this.isCodeMatched(cleanUrl, code) && (/-(?:1080p|demosaic)\.(?:jpe?g|png|webp)$/i.test(cleanUrl) || isFc2Numeric && fc2ShotPattern.test(cleanUrl)); }, selectJavfreePreviewUrl(doc, detailUrl, code) { const urls = [...doc.querySelectorAll("p > img[src]")].map((img) => this.normalizePreviewUrl(img.getAttribute("src") || img.src || "", detailUrl)).filter((url) => this.isJavfreePreviewImage(url, code)); return urls.find((url) => /-1080p\./i.test(url)) || urls.find((url) => /-demosaic\./i.test(url)) || urls.find((url) => /_1\.(?:jpe?g|png|webp)$/i.test(url)) || ""; }, async javfree(code) { var _a; code = this.lookupCode(code); const cacheKey = this.cacheKey(code); const cached = sessionStorage.getItem(cacheKey); if (cached) return cached; try { const html = await gmHttp.get(`https://javfree.me/search/${code}`); const doc = new DOMParser().parseFromString(html, "text/html"); const link = (_a = [...doc.querySelectorAll(".entry-title>a")].find((a) => this.isCodeMatched([a.href, a.textContent].join(" "), code))) == null ? void 0 : _a.href; if (!link) return null; const dHtml = await gmHttp.get(link); const dDoc = new DOMParser().parseFromString(dHtml, "text/html"); if (!this.isDetailMatched(dDoc, link, code)) return null; const url = this.selectJavfreePreviewUrl(dDoc, link, code); if (url) { sessionStorage.setItem(cacheKey, url); return url; } return null; } catch { return null; } }, async javstore(code) { code = this.lookupCode(code); try { const normalizedCode = code.replace(/^fc2-?/i, "").replace(/-/g, "").toLowerCase(); const searchUrl = `https://javstore.net/search?q=${encodeURIComponent(code)}`; const searchHtml = await gmHttp.get(searchUrl); const searchDoc = new DOMParser().parseFromString(searchHtml, "text/html"); const candidateLinks = searchDoc.querySelectorAll('a[href*="/"]'); const detailUrls = []; for (const link of candidateLinks) { const href = link.getAttribute("href"); if (!href) continue; if (href.startsWith("http") && !href.includes("javstore.net")) continue; const urlObj = new URL(href, searchUrl); if (!/javstore\.net$/i.test(urlObj.hostname)) continue; if (/^\/search(?:[/?#]|$)/i.test(urlObj.pathname)) continue; const fullUrl = urlObj.href; const pathLastPart = decodeURIComponent(urlObj.pathname.split("/").pop() || ""); const normalizedPath = pathLastPart.toLowerCase().replace(/-/g, ""); const looksLikeDetail = /\.html$/i.test(urlObj.pathname) || /^\/\d+[-/]/.test(urlObj.pathname); if (looksLikeDetail && normalizedPath.includes(normalizedCode) && !detailUrls.includes(fullUrl)) { detailUrls.push(fullUrl); } } if (detailUrls.length === 0) return null; for (const detailUrl of detailUrls) { const imgUrl = await this._extractImgFromDetail(detailUrl, code); if (imgUrl) return imgUrl; } return null; } catch (e) { clog.debug("javstore 获取失败", e); return null; } }, async _extractImgFromDetail(detailUrl, code) { try { const detailHtml = await gmHttp.get(detailUrl); const detailDoc = new DOMParser().parseFromString(detailHtml, "text/html"); if (!this.isDetailMatched(detailDoc, detailUrl, code)) return null; for (const link of detailDoc.querySelectorAll("a")) { if (link.textContent.includes("CLICK HERE")) { const imgUrl = link.href || link.getAttribute("href") || ""; if (imgUrl) return this.normalizePreviewUrl(imgUrl, detailUrl); } } const img = detailDoc.querySelector('img[src*="_s.jpg"]'); if (img) { let src = img.getAttribute("src") || ""; if (!src.startsWith("http")) src = new URL(src, detailUrl).href; return this.normalizePreviewUrl(src.replace(/_s\.jpg$/, "_l.jpg"), detailUrl); } return null; } catch (e) { clog.debug("javstore: 详情页请求失败", detailUrl, e.message); return null; } }, async projectjav(code) { var _a; code = this.lookupCode(code); try { const request = (url) => gmHttp.get(url, {}, { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8" }); const searchUrl = `https://projectjav.com/?searchTerm=${encodeURIComponent(code)}`; const searchHtml = await request(searchUrl); const searchDoc = new DOMParser().parseFromString(searchHtml, "text/html"); let detailUrl = ""; const allMovieLinks = [...searchDoc.querySelectorAll('a[href*="/movie/"]')]; const firstLink = ((_a = allMovieLinks[0]) == null ? void 0 : _a.getAttribute("href")) || ""; if (firstLink) { detailUrl = firstLink.startsWith("http") ? firstLink : `https://projectjav.com${firstLink}`; } if (!detailUrl) return null; const detailHtml = await request(detailUrl); const detailDoc = new DOMParser().parseFromString(detailHtml, "text/html"); const screenshotLink = [...detailDoc.querySelectorAll('.col-md-12.thumbnail a[data-featherlight="image"], .thumbnail a[data-featherlight="image"]')].find((a) => { var _a2; return this.isCodeMatched([a.outerHTML, (_a2 = a.closest(".thumbnail")) == null ? void 0 : _a2.outerHTML, detailUrl].join(" "), code); }); if (screenshotLink) { const thumbImg = screenshotLink.querySelector("img"); const href = screenshotLink.getAttribute("href") || ""; if (href) return this.normalizePreviewUrl(href, detailUrl); if (thumbImg) { const src = (thumbImg.getAttribute("src") || "").replace(/\?.*$/, ""); if (src) return this.normalizePreviewUrl(src, detailUrl); } } return null; } catch (e) { clog.debug("[projectjav] 异常:", e.message); return null; } }, async get(code) { const cacheKey = this.cacheKey(code); const cached = sessionStorage.getItem(cacheKey); if (cached) return {url: cached, source: null}; let url = null, source = null; for (const src of this.sourceOrder()) { url = await this.fetchFromSource(src, code); if (url) { source = src; break; } } if (url) sessionStorage.setItem(cacheKey, url); return {url, source}; } }; let previewOverlay = null; function showPreviewOverlay$1(imgUrl, code, source = null) { if (previewOverlay) { previewOverlay.close(); } let currentBlobUrl = null; const originalHtmlOverflow = document.documentElement.style.overflow; const originalBodyOverflow = document.body.style.overflow; document.documentElement.style.overflow = "hidden"; document.body.style.overflow = "hidden"; const container = document.createElement("div"); container.className = "jhs-preview-overlay"; const img = document.createElement("img"); img.src = imgUrl; img.className = "jhs-preview-img"; img.onclick = (e) => { e.stopPropagation(); img.classList.toggle("zoomed"); }; img.onerror = () => { img.src = imgUrl; }; const toolbar = document.createElement("div"); toolbar.className = "jhs-preview-toolbar"; const createButton = (text, className, onClick) => { const btn = document.createElement("button"); btn.className = `jhs-preview-btn ${className}`; btn.textContent = text; btn.onclick = onClick; return btn; }; const setActiveSource = (activeSource) => { toolbar.querySelectorAll(".jhs-preview-btn").forEach((b) => b.classList.remove("active")); const activeBtn = toolbar.querySelector(`.jhs-preview-btn-${activeSource}`); if (activeBtn) activeBtn.classList.add("active"); }; const changeSource = async (src, srcName) => { const loadingOverlay = document.createElement("div"); loadingOverlay.className = "jhs-loading-overlay"; loadingOverlay.innerHTML = '
    正在加载预览图...
    '; document.body.appendChild(loadingOverlay); try { const newUrl = src === "javfree" ? await Thumbnail.javfree(code) : src === "projectjav" ? await Thumbnail.projectjav(code) : await Thumbnail.javstore(code); if (newUrl) { if (currentBlobUrl) { URL.revokeObjectURL(currentBlobUrl); currentBlobUrl = null; } img.src = newUrl; setActiveSource(src); } else { showToast(`${srcName} 未找到预览图`, "error"); } } catch (err) { showToast(`${srcName} 加载失败: ${err.message}`, "error"); } finally { loadingOverlay.remove(); } }; let toastTimer = null; const showToast = (msg, type = "info") => { const existing = container.querySelector(".jhs-preview-toast"); if (existing) existing.remove(); if (toastTimer) clearTimeout(toastTimer); const toast = document.createElement("div"); toast.className = `jhs-preview-toast jhs-preview-toast-${type}`; toast.textContent = msg; container.appendChild(toast); requestAnimationFrame(() => toast.classList.add("show")); toastTimer = setTimeout(() => { toast.classList.remove("show"); setTimeout(() => toast.remove(), 300); }, 3e3); }; const javfreeBtn = createButton("javfree", "jhs-preview-btn-javfree javfree", (e) => { e.stopPropagation(); changeSource("javfree", "javfree"); }); const projectjavBtn = createButton("projectjav", "jhs-preview-btn-projectjav projectjav", (e) => { e.stopPropagation(); changeSource("projectjav", "projectjav"); }); const javstoreBtn = createButton("javstore", "jhs-preview-btn-javstore javstore", (e) => { e.stopPropagation(); changeSource("javstore", "javstore"); }); const newWindowBtn = createButton("新窗口打开", "jhs-preview-btn-action action", (e) => { e.stopPropagation(); window.open(img.src); }); const downloadBtn = createButton("下载", "jhs-preview-btn-action action", (e) => { e.stopPropagation(); const a = document.createElement("a"); a.href = img.src; a.download = `${code}.jpg`; a.click(); }); toolbar.appendChild(javfreeBtn); toolbar.appendChild(projectjavBtn); toolbar.appendChild(javstoreBtn); toolbar.appendChild(newWindowBtn); toolbar.appendChild(downloadBtn); if (source === "javfree") setActiveSource("javfree"); else if (source === "projectjav") setActiveSource("projectjav"); else if (source === "javstore") setActiveSource("javstore"); else setActiveSource("javfree"); container.appendChild(img); const closeOverlay = () => { if (container.parentNode) { container.remove(); toolbar.remove(); document.documentElement.style.overflow = originalHtmlOverflow; document.body.style.overflow = originalBodyOverflow; if (currentBlobUrl) { URL.revokeObjectURL(currentBlobUrl); currentBlobUrl = null; } } }; container.onclick = closeOverlay; const escHandler = (e) => { if (e.key === "Escape") { closeOverlay(); document.removeEventListener("keydown", escHandler); } }; document.addEventListener("keydown", escHandler); document.body.appendChild(container); document.body.appendChild(toolbar); previewOverlay = {container, toolbar, close: closeOverlay}; } class ScreenShotPlugin extends BasePlugin { getName() { return "ScreenShotPlugin"; } async handle() { this.loadScreenShot().then(); } async loadScreenShot() { if (!window.isDetailPage) return; if ("yes" !== await storageManager.getSetting("enableLoadScreenShot", "yes")) return; let carNum2 = this.getPageInfo().carNum; isJavDb$1 && $(".preview-images .tile-item").first().before( '
    正在加载缩略图
    ' ); isJavBus$1 && $("#sample-waterfall .sample-box:first").after( '
    正在加载缩略图
    ' ); try { const result = await Thumbnail.get(carNum2); if (result.url) { this.addImg("缩略图", result.url, carNum2, result.source); clog.log("加载缩略图:", result.url, result.source); } else { this.showErrorFallback(carNum2, new Error("所有来源均未找到预览图")); } } catch (e) { this.showErrorFallback(carNum2, e); } } async getScreenshot(carNum2) { const result = await Thumbnail.get(carNum2); if (result.url) return result.url; throw new Error("未找到预览图"); } async getJavStoreScreenShot(carNum2) { return await Thumbnail.javstore(carNum2); } async getJavFreeScreenShot(carNum2) { return await Thumbnail.javfree(carNum2); } addImg(title, imgUrl, code, source) { if (!imgUrl) return; isJavDb$1 && $(".screen-container").html( `${title}` ); isJavBus$1 && $(".screen-container").html( `
    ${title}
    ` ); $(".screen-container").on("click", (event) => { event.stopPropagation(); event.preventDefault(); showPreviewOverlay$1(imgUrl, code || title, source); }); } showErrorFallback(carNum2, error) { var _a, _b; console.error("获取缩略图失败:", ((_b = (_a = error == null ? void 0 : error.message) == null ? void 0 : _a.substring) == null ? void 0 : _b.call(_a, 0, 100)) || error); let differentCss = isJavBus$1 ? "margin-top:30px" : "margin-top:50px"; $(".screen-container").html( `
    获取缩略图失败

    点击重试 或 前往确认` ).off("click", ".retry-link").off("click", ".check-link").on("click", ".retry-link", async (e) => { e.stopPropagation(); e.preventDefault(); $(".screen-container").html( `
    正在重新加载...
    ` ); try { const result = await Thumbnail.get(carNum2); if (result.url) { this.addImg("缩略图", result.url, carNum2, result.source); } else { this.showErrorFallback(carNum2, new Error("所有来源均未找到预览图")); } } catch (err) { this.showErrorFallback(carNum2, err); } }).on("click", ".check-link", (e) => { e.stopPropagation(); e.preventDefault(); window.open(`https://javstore.net/search?q=${encodeURIComponent(carNum2)}`, "_blank"); }); } } window.showPreviewOverlay = showPreviewOverlay$1; const getDownPathList = async () => { const res = await gmHttp.get("https://webapi.115.com/offine/downpath"); return "object" == typeof res ? res.data : null; }; const searchFiles = async (search_value, offset = 0, limit = 30) => { const url = `https://webapi.115.com/files/search?search_value=${encodeURIComponent(search_value)}&offset=${offset}&limit=${limit}`; return await gmHttp.get(url); }; class WangPan115TaskPlugin extends BasePlugin { getName() { return "WangPan115TaskPlugin"; } async handle() { $(".buttons button[data-clipboard-text*='magnet:']").each(((i, el) => { $(el).parent().append($(" `; 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 _WangPan115MatchPlugin2 extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "loginStatus", _WangPan115MatchPlugin2.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); })); $(document).on("click", ".jhs-detail-delete-btn", (async (event) => { event.preventDefault(); event.stopPropagation(); const $btn = $(event.currentTarget); const matchData = $btn.attr("data-match"); if (!matchData) return; const matchList = JSON.parse(matchData); const carNum2 = this.getPageInfo().carNum; this.showDeleteDialog(carNum2, matchList, (async (newMatchList) => { $("#115-match-table").remove(); await this.matchDetailPage(newMatchList); })); })); 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 (!window.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 === _WangPan115MatchPlugin2.LoginStatus.LOGGED_OUT) $tbody.append(` 未登录 `); else if (matchList.length > 0) { const rowsHtml = matchList.map(((match) => ` ${match.name} ${this.formatSize(match.size)} ${match.createTime} 播放 `)).join(""); $tbody.append(rowsHtml); $detail.css("position", "relative"); $detail.append(`🗑️ 删除`); } else $tbody.append(` 未匹配 `); } catch (error) { $tbody.append(` 加载失败,请重试 `); console.error("加载文件列表时发生错误:", error); } if (isJavDb$1) 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 = $(`
    ${matches.map(((match) => ` `)).join("")}
    名称 大小 时间 播放
    ${match.name} ${this.formatSize(match.size)} ${match.createTime} 播放
    `); $("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) { if (matchList.length > 0) { $box2.find(".jhs-match-btn").replaceWith(`匹配${matchList.length}个`); const $deleteBtn = $box2.find(".delete115Svg"); const $markBtn = $box2.find(".markDeleteSvg"); if ($deleteBtn.length > 0 && matchList.some((m) => m.dirId != null)) { $deleteBtn.attr("data-match", JSON.stringify(matchList)); $deleteBtn.show(); } if ($markBtn.length > 0) { $markBtn.show(); this.updateMarkBtnStyle($markBtn, carNum2); } } else { $box2.find(".jhs-match-btn").replaceWith(`未匹配`); const $deleteBtn = $box2.find(".delete115Svg"); const $markBtn = $box2.find(".markDeleteSvg"); if ($deleteBtn.length > 0) { $deleteBtn.removeAttr("data-match"); $deleteBtn.hide(); } if ($markBtn.length > 0) { $markBtn.hide(); } } } async updateMarkBtnStyle($markBtn, carNum2) { const markedList = await storageManager.forage.getItem("markedDeleteList") || []; const isMarked = markedList.some((item) => item.carNum === carNum2); isMarked ? $markBtn.addClass("marked") : $markBtn.removeClass("marked"); $markBtn.attr("title", isMarked ? "取消标记删除" : "标记删除"); } async batchDeleteMarked(selectedItems, onProgress) { let deletedCount = 0; let failedCount = 0; const randomDelay = () => new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 2e3)); for (let i = 0; i < selectedItems.length; i++) { const item = selectedItems[i]; try { if (i > 0) { await randomDelay(); } const matchList = await this.searchFiles(item.carNum); if (matchList.length === 0) { failedCount++; onProgress && onProgress(i + 1, selectedItems.length, item, "未匹配到115文件"); continue; } const upperCarNum = item.carNum.toUpperCase(); const safeMatchList = matchList.filter((m) => m.name.toUpperCase().includes(upperCarNum)); if (safeMatchList.length === 0) { failedCount++; onProgress && onProgress(i + 1, selectedItems.length, item, "无安全可删除目录"); continue; } let itemDeleted = 0; const groupedByDir = {}; for (const m of safeMatchList) { if (!groupedByDir[m.dirId]) groupedByDir[m.dirId] = []; groupedByDir[m.dirId].push(m); } const entries = Object.entries(groupedByDir); for (let g = 0; g < entries.length; g++) { if (g > 0) { await randomDelay(); } const [dirId, items] = entries[g]; if (items.length === 1) { const result = await this._deleteOne(dirId, false, i, selectedItems, item, onProgress); if (result) { itemDeleted++; deletedCount++; } } else { for (let f = 0; f < items.length; f++) { if (f > 0) await randomDelay(); const fileDeleted = await this._deleteOne(items[f].folderId, true, i, selectedItems, item, onProgress); if (fileDeleted) { itemDeleted++; deletedCount++; } } } } onProgress && onProgress(i + 1, selectedItems.length, item, itemDeleted > 0 ? `已删除${itemDeleted}个` : "删除失败"); } catch (error) { failedCount++; onProgress && onProgress(i + 1, selectedItems.length, item, error.message || "出错"); console.error(`批量删除出错 [${item.carNum}]:`, error); } } return {deletedCount, failedCount}; } isRateLimited(response) { if (!response) return false; if (response.state === false) { const msg = (response.error || response.msg || "").toLowerCase(); if (msg.includes("频繁") || msg.includes("操作太频繁") || msg.includes("稍后再试") || msg.includes("rate") || msg.includes("limit") || msg.includes("too many")) { return true; } } if (response.status === 403 || response.status === 429) { return true; } return false; } async _deleteOne(deleteId, isFile, batchIndex, selectedItems, item, onProgress) { let retryCount = 0; const maxRetries = 3; const label = isFile ? "文件" : "目录"; while (retryCount <= maxRetries) { try { const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", { "fid[0]": deleteId, ignore_warn: "1" }); if (result && result.state) { return 1; } else { const errMsg = result && result.error || "未知错误"; if (retryCount < maxRetries && this.isRateLimited(result)) { retryCount++; const backoff = (retryCount + 1) * 2e3 + Math.random() * 1e3; onProgress && onProgress(batchIndex + 1, selectedItems.length, item, `风控触发,${backoff / 1e3}秒后重试(${retryCount}/${maxRetries})`); await new Promise((resolve) => setTimeout(resolve, backoff)); continue; } console.error(`删除${label}失败: ${deleteId}`, errMsg); return 0; } } catch (e) { if (retryCount < maxRetries && this.isRateLimited(e)) { retryCount++; const backoff = (retryCount + 1) * 2e3 + Math.random() * 1e3; onProgress && onProgress(batchIndex + 1, selectedItems.length, item, `风控触发,${backoff / 1e3}秒后重试(${retryCount}/${maxRetries})`); await new Promise((resolve) => setTimeout(resolve, backoff)); continue; } console.error(`删除${label}失败: ${deleteId}`, e); return 0; } } return 0; } showDeleteDialog(carNum2, matchList, afterDelete) { const upperCarNum = carNum2.toUpperCase(); const safeMatchList = matchList.filter((m) => m.name.toUpperCase().includes(upperCarNum)); const unsafeCount = matchList.length - safeMatchList.length; if (safeMatchList.length === 0) { show.error(`作品 "${carNum2}" 匹配到的 ${matchList.length} 个项目均不含番号,无法安全删除。请手动在115中删除。`); return; } const 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]; }; let html = '
    '; html += `
    作品 ${carNum2} 匹配到 ${matchList.length} 个文件夹(可删除 ${safeMatchList.length} 个):
    `; if (unsafeCount > 0) { html += `
    ⚠ 已自动排除 ${unsafeCount} 个目录名不含番号的项目,防止误删
    `; } html += '
    '; for (let i = 0; i < safeMatchList.length; i++) { const m = safeMatchList[i]; html += `
    `; } html += "
    "; const layerIndex = layer.open({ type: 1, title: "删除115作品文件夹", content: html, area: ["520px", "auto"], btn: ["确定删除", "取消"], shadeClose: false, yes: async (index) => { var _a, _b; const $layer = $(`#layui-layer${index}`); const checked = $layer.find(".jhs-delete-folder-cb:checked"); if (0 === checked.length) { show.error("请至少选择一个文件夹"); return; } const selected = checked.map((function () { return parseInt($(this).data("index")); })).get(); layer.close(index); let loadObj = loading(); let deletedCount = 0; try { const selectedItems = selected.map((i) => safeMatchList[i]); const totalByDir = {}; for (const m of safeMatchList) { totalByDir[m.dirId] = (totalByDir[m.dirId] || 0) + 1; } const groupedByDir = {}; for (const item of selectedItems) { if (!groupedByDir[item.dirId]) groupedByDir[item.dirId] = []; groupedByDir[item.dirId].push(item); } for (const [dirId, items] of Object.entries(groupedByDir)) { if (items.length === totalByDir[dirId]) { const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", { "fid[0]": dirId, ignore_warn: "1" }); result && result.state ? deletedCount++ : console.error(`删除目录失败: ${dirId}`, result); } else { for (const item of items) { const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", { "fid[0]": item.folderId, ignore_warn: "1" }); result && result.state ? deletedCount++ : console.error(`删除文件失败: ${item.folderId}`, result); } } } loadObj.close(); show.ok(`作品 "${carNum2}" 已删除 ${deletedCount} 个文件夹`); const newMatchList = await this.searchFiles(carNum2); afterDelete && afterDelete(newMatchList); const remainingCids = new Set(newMatchList.map((m) => m.dirId)); JavPackSubtitle.cached115Matches = JavPackSubtitle.cached115Matches.filter((m) => remainingCids.has(m.cid)); JavPackSubtitle.current115Cids = JavPackSubtitle.current115Cids.filter((cid) => remainingCids.has(cid)); try { (_b = (_a = this.getBean("WangPan115TaskPlugin")).deleteOfflineTasksByKeyword) == null ? void 0 : _b.call(_a, carNum2); } catch (e) { console.warn("删除离线任务暂不可用:", e.message); } } catch (error) { loadObj.close(); console.error("删除115文件失败:", error); show.error(`删除失败: ${error.message || "网络错误"}`); } } }); $(`#layui-layer${layerIndex}`).on("change", ".jhs-delete-select-all", (function () { const checked = $(this).prop("checked"); $(`#layui-layer${layerIndex} .jhs-delete-folder-cb`).prop("checked", checked); })); } 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, dirId: data.cid, 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 === _WangPan115MatchPlugin2.LoginStatus.UNCHECKED) try { const testResult = await searchFiles("test"); this.loginStatus = (null == (_a2 = testResult.error) ? void 0 : _a2.includes("登录")) ? _WangPan115MatchPlugin2.LoginStatus.LOGGED_OUT : _WangPan115MatchPlugin2.LoginStatus.LOGGED_IN; } catch { this.loginStatus = _WangPan115MatchPlugin2.LoginStatus.LOGGED_OUT; } } async processMovieElements(movieListElement) { const promises = Array.from(movieListElement).filter(((ele) => !utils.isHidden(ele))).filter(((ele) => !(isJavBus$1 && $(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 !== _WangPan115MatchPlugin2.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 === _WangPan115MatchPlugin2.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}个`); const $deleteBtn = $box2.find(".delete115Svg"); const $markBtn = $box2.find(".markDeleteSvg"); if ($deleteBtn.length > 0 && matchList.some((m) => m.dirId != null)) { $deleteBtn.attr("data-match", JSON.stringify(matchList)); $deleteBtn.show(); } if ($markBtn.length > 0) { $markBtn.show(); this.updateMarkBtnStyle($markBtn, carNum2); } } 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]}`; } }; __publicField2(_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 (!window.isDetailPage) return; if (await storageManager.getSetting("enableFavoriteActresses", YES) !== YES) return; const favoriteActressesInfoList = await storageManager.getFavoriteActressList(); if (!favoriteActressesInfoList || 0 === favoriteActressesInfoList.length) return; const favoriteStarIdSet = /* @__PURE__ */ 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 = false; 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 }; 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, 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 ListPageButtonPlugin extends BasePlugin { getName() { return "ListPageButtonPlugin"; } async handle() { if (!window.isListPage) return; await this.createMenuBtn(); this.bindEvent(); await storageManager.getSetting("autoPage") === YES ? $("#sort-toggle-btn").hide() : this.sortItems().then(); this.initOneTimeSortBtn(); } async createMenuBtn() { const showWaitCheckBtn = await storageManager.getSetting("showWaitCheckBtn", YES), showWaitDownBtn = await storageManager.getSetting("showWaitDownBtn", YES); if (isJavDb$1) { const isStarPage = currentHref$1.includes("/actors/"); let $el = $(".main-tabs, .tabs"), addBlacklistBtnText = "加入黑名单", addBlacklistBtnColor = "#d22020", otherCss = "", blacklistItem = null; if (isStarPage) { $el = $(".toolbar, .section-addition").filter(":last"); const blacklist = await storageManager.getBlacklist(), actressPageInfo = this.getActressPageInfo(); if (blacklist.find(((item) => item.starId === actressPageInfo.starId))) { addBlacklistBtnText = "已加入黑名单"; addBlacklistBtnColor = "#885d5d"; } } else currentHref$1.includes("/tags") && utils.loopDetector((() => $("#jhs-check-tag").text().trim()), (async () => { const $addBlacklistBtn = $("#addBlacklistBtn"); $addBlacklistBtn.attr("data-tip", "将当前分类标签加入到黑名单, 后续有作品更新也会纳入屏蔽中"); const checkTag = $("#jhs-check-tag").text().trim(); if (!checkTag) return; const tagStarId = "no-" + checkTag, blacklist = await storageManager.getBlacklist(); blacklistItem = blacklist.find(((item) => item.starId === tagStarId)); if (blacklistItem) { $addBlacklistBtn.css("backgroundColor", "#885d5d"); $("#addBlacklistBtn span").text("已加入黑名单"); } })); const isFc2Page2 = currentHref$1.includes("advanced_search"); isFc2Page2 ? $el = $("h2.section-title") : otherCss = "flex-grow:1;"; const jhs_sortMethod = localStorage.getItem("jhs_sortMethod"), sortText = "当前排序方式: " + ("rateCount" === jhs_sortMethod ? "评价人数" : "date" === jhs_sortMethod ? "时间" : "默认"); $el.append(`
    打开待鉴定 打开已收藏 ${isStarPage ? ` ${addBlacklistBtnText} 一键屏蔽所有作品 ` : ""} ${currentHref$1.includes("/tags") ? ` ${addBlacklistBtnText} ` : ""}
    一次性排序: 默认 新作品检测 (0) 演员黑名单 ${isSearchPage || isFc2Page2 ? "" : ` ${sortText} `}
    `); } if (isJavBus$1) { const isStarPage = currentHref$1.includes("/star/"); let addBlacklistBtnText = "加入黑名单", addBlacklistBtnColor = "#d22020"; if (isStarPage) { const blacklist = await storageManager.getBlacklist(), actressPageInfo = this.getActressPageInfo(); if (blacklist.find(((item) => item.starId === actressPageInfo.starId))) { addBlacklistBtnText = "已加入黑名单"; addBlacklistBtnColor = "#885d5d"; } } $(".masonry").parent().prepend(`
    打开待鉴定 打开已收藏 ${isStarPage ? ` ${addBlacklistBtnText} 一键屏蔽所有作品 ` : '演员黑名单'}
    `); } const newVideoPlugin = window.pluginManager.getBean("NewVideoPlugin"); newVideoPlugin && newVideoPlugin.showNewVideoCount().then(); } bindEvent() { $("#waitCheckBtn").on("click", ((event) => { this.openWaitCheck(event).then(); })); $("#waitDownBtn").on("click", ((event) => { this.openFavorite(event).then(); })); $("#newVideoBtn").on("click", ((event) => { this.getBean("NewVideoPlugin").openDialog(); })); $("#blacklistBtn").on("click", ((event) => { this.getBean("BlacklistPlugin").openBlacklistDialog(); })); $("#sort-toggle-btn").on("click", ((event) => { const currentMethod = localStorage.getItem("jhs_sortMethod") || "default"; const sortCycle = ["default", "rateCount", "totalScore", "date"]; let nextIndex = sortCycle.indexOf(currentMethod) + 1; if (nextIndex >= sortCycle.length || nextIndex < 0) nextIndex = 0; const newMethod = sortCycle[nextIndex]; const methodText = { default: "默认", rateCount: "评价人数", totalScore: "总分", date: "时间" }[newMethod]; $(event.target).text(`当前排序方式: ${methodText}`); localStorage.setItem("jhs_sortMethod", newMethod); this.sortItems().then(); })); $("#one-time-sort-btn").on("click", ((event) => { const currentMethod = localStorage.getItem("jhs_oneTimeSortMethod") || "default"; const sortCycle = ["default", "rateCount", "totalScore", "date"]; let nextIndex = sortCycle.indexOf(currentMethod) + 1; if (nextIndex >= sortCycle.length || nextIndex < 0) nextIndex = 0; const newMethod = sortCycle[nextIndex]; const methodText = { default: "默认", rateCount: "评价人数", totalScore: "总分", date: "时间" }[newMethod]; $("#one-time-sort-btn").text(`一次性排序: ${methodText}`); localStorage.setItem("jhs_oneTimeSortMethod", newMethod); this.oneTimeSortItems().then(); })); const blacklistPlugin = this.getBean("BlacklistPlugin"); $("#addBlacklistBtn").on("click", (async (event) => { await blacklistPlugin.addBlacklist(event); })); $("#filterAllVideo").on("click", (async (event) => { let tempEvent = { clientX: event.clientX, clientY: event.clientY + 80 }, $actor = isJavDb$1 ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10"); if (0 === $actor.length) { show.error("获取演员名称失败"); return; } let actorName = $actor.text().trim().split(",")[0]; utils.q(tempEvent, "一键屏蔽已选分类的视频列表至鉴定记录中?", (async () => { this.loadObj = loading(); try { await blacklistPlugin.filterAllVideo(actorName); window.refresh(); } catch (e) { console.error(e); } finally { this.loadObj.close(); } })); })); } async sortItems() { if (currentHref$1.includes("handle") || currentHref$1.includes("advanced_search")) return; const autoPage = await storageManager.getSetting("autoPage"); if (isSearchPage || autoPage === YES) return; const method = localStorage.getItem("jhs_sortMethod"); if (!method) return; $(".movie-list .item").each((function(index) { $(this).attr("data-original-index") || $(this).attr("data-original-index", index); })); const $container = $(".movie-list"), $items = $(".item", $container); if ("default" === method) { $items.sort((function(a, b) { return $(a).data("original-index") - $(b).data("original-index"); })).appendTo($container); } else { const items = $items.get(); items.sort((function(a, b) { if ("rateCount" === method) { const getScore = (el) => { const match = $(el).find(".score .value").text().match(/由(\d+)人/); return match ? parseFloat(match[1]) : 0; }; return getScore(b) - getScore(a); } else if ("totalScore" === method) { const getTotalScore = (el) => { const text = $(el).find(".score .value").text(); const scoreMatch = text.match(/([\d.]+)分/); const countMatch = text.match(/由(\d+)人/); const score = scoreMatch ? parseFloat(scoreMatch[1]) : 0; const count = countMatch ? parseFloat(countMatch[1]) : 0; return score * count; }; return getTotalScore(b) - getTotalScore(a); } else { const getDate = (el) => { const dateStr = $(el).find(".meta").text().trim(); return new Date(dateStr); }; return getDate(b) - getDate(a); } })); $container.empty().append(items); } } async initOneTimeSortBtn() { const autoPage = await storageManager.getSetting("autoPage"); if (autoPage !== YES) { $("#one-time-sort-btn").hide(); return; } const oneTimeMethod = localStorage.getItem("jhs_oneTimeSortMethod") || "default"; const methodText = { default: "默认", rateCount: "评价人数", totalScore: "总分", date: "时间" }[oneTimeMethod]; $("#one-time-sort-btn").text(`一次性排序: ${methodText}`).show(); } async oneTimeSortItems() { if (currentHref$1.includes("handle") || currentHref$1.includes("advanced_search")) return; if (isSearchPage) return; const method = localStorage.getItem("jhs_oneTimeSortMethod"); if (!method) return; $(".movie-list .item").each((function (index) { $(this).attr("data-original-index") || $(this).attr("data-original-index", index); })); const $container = $(".movie-list"), $items = $(".item", $container); if ("default" === method) { $items.sort((function (a, b) { return $(a).data("original-index") - $(b).data("original-index"); })).appendTo($container); } else { const items = $items.get(); items.sort((function (a, b) { if ("rateCount" === method) { const getScore = (el) => { const match = $(el).find(".score .value").text().match(/由(\d+)人/); return match ? parseFloat(match[1]) : 0; }; return getScore(b) - getScore(a); } else if ("totalScore" === method) { const getTotalScore = (el) => { const text = $(el).find(".score .value").text(); const scoreMatch = text.match(/([\d.]+)分/); const countMatch = text.match(/由(\d+)人/); const score = scoreMatch ? parseFloat(scoreMatch[1]) : 0; const count = countMatch ? parseFloat(countMatch[1]) : 0; return score * count; }; return getTotalScore(b) - getTotalScore(a); } else { const getDate = (el) => { const dateStr = $(el).find(".meta").text().trim(); return new Date(dateStr); }; return getDate(b) - getDate(a); } })); $container.empty().append(items); } } async openWaitCheck(event) { const maxCount = await storageManager.getSetting("waitCheckCount", 5); let count = 0; $(`${this.getSelector().itemSelector}:visible`).each(((i, el) => { if (count >= maxCount) return false; const $box2 = $(el); if ($box2.find(".status-tag").length > 0) return; const { carNum: carNum2, aHref } = this.getBoxCarInfo($box2); if (carNum2.includes("FC2-")) { const movieId = this.parseMovieId(aHref); this.getBean("Fc2Plugin").openFc2Page(movieId, carNum2, aHref); } else { let url = aHref + (aHref.includes("?") ? "&autoPlay=1" : "?autoPlay=1"); window.open(url); } count++; })); 0 === count && show.info("没有需鉴定的视频"); } async openFavorite() { let favoriteList, openCount = await storageManager.getSetting("waitCheckCount", 5), randomOpenWaitDown = await storageManager.getSetting("randomOpenWaitDown", NO), dataList = await storageManager.getCarList(); favoriteList = randomOpenWaitDown === YES ? dataList.filter(((item) => item.status === Status_FAVORITE$1)).sort((() => Math.random() - 0.5)) : dataList.filter(((item) => item.status === Status_FAVORITE$1)).sort(((a, b) => b.createDate - a.createDate)); for (let i = 0; i < openCount; i++) { if (i >= favoriteList.length) return; let data = favoriteList[i], carNum2 = data.carNum, url = data.url; if (carNum2.includes("FC2-")) { const movieId = this.parseMovieId(url); await this.getBean("Fc2Plugin").openFc2Page(movieId, carNum2, url); } else window.open(url); clog.debug("打开已收藏", carNum2, url); } } } const translateText = async (text, sourceLang = "ja", targetLang = "zh-CN") => { if (!text) throw new Error("翻译文本不能为空"); const url = "https://translate-pa.googleapis.com/v1/translate?" + new URLSearchParams({ "params.client": "gtx", dataTypes: "TRANSLATION", key: "AIzaSyDLEeFI5OtFBwYBIoK_jj5m32rZK5CkCXA", "query.sourceLanguage": sourceLang, "query.targetLanguage": targetLang, "query.text": text }), res = await fetch(url); if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); return (await res.json()).translation; }, TAG_STYLES = { IS_FILTERED: { text: "🚫 已屏蔽", color: "#de3333", reasonType: "单番号屏蔽", isCounted: true, countKey: "currentPageFilterCount" }, IS_FAVORITE: { text: "⭐ 已收藏", color: "#25b1dc", reasonType: "", isCounted: true, countKey: "currentPageFavoriteCount" }, IS_HAS_DOWN: { text: "📥️ 已下载", color: "#7bc73b", reasonType: "", isCounted: true, countKey: "currentPageHasDownCount" }, IS_HAS_WATCH: { text: "🔍 已观看", color: "#d7a80c", reasonType: "", isCounted: true, countKey: "currentPageHasWatchCount" }, IS_KEYWORD_FILTER: { text: "❌ 关键词屏蔽", color: "#de3333", reasonType: "", isCounted: true, countKey: "currentPageKeywordFilterCount" }, IS_ACTOR_FILTER: { text: "♂️ 男演员屏蔽", color: "#b22222", reasonType: "", isCounted: true, countKey: "currentPageActorFilterCount" }, IS_ACTRESS_FILTER: { text: "♀️ 女演员屏蔽", color: "#cd5c5c", reasonType: "", isCounted: true, countKey: "currentPageActorFilterCount" }, IS_WAIT_CHECK: { text: "", color: "", reasonType: "", isCounted: true, countKey: "currentPageWaitCheckCount" } }; class TranslatePlugin extends BasePlugin { getName() { return "TranslatePlugin"; } async initCss() { return "\n \n "; } handle() { window.isDetailPage && this.translate(); } async translate(carNum2, showCarNum = true) { if (await storageManager.getSetting("translateTitle", YES) !== YES) return; isJavBus$1 && (showCarNum = false); 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 OtherSitePlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "okBackgroundColor", "#7bc73b"); __publicField2(this, "errorBackgroundColor", "#de3333"); __publicField2(this, "warnBackgroundColor", "#d7a80c"); __publicField2(this, "domainErrorBackgroundColor", "#d7780c"); __publicField2(this, "asyncQueue", new AsyncQueue()); __publicField2(this, "siteConfigs", [{ id: "javTrailersBtn", getBaseUrl: async () => await this.getJavTrailersUrl(), itemSelector: ".videos-list .video-link", searchPath: (baseUrl, carNum2) => `${baseUrl}/search/${carNum2}`, getDetailPageHref: ($box2) => $box2.attr("href"), findCarNumOrTitle: ($box2) => $box2.find("p.card-text").text() }, { id: "123AvBtn", getBaseUrl: async () => await this.getAv123Url() + "/ja", itemSelector: ".box-item", searchPath: (baseUrl, carNum2) => `${baseUrl}/search?keyword=${carNum2}`, getDetailPageHref: ($box2) => $box2.find(".detail a").attr("href"), findCarNumOrTitle: ($box2) => $box2.find("img").attr("title") }, { id: "jableBtn", getBaseUrl: async () => await this.getjableUrl(), itemSelector: "#list_videos_videos_list_search_result .detail .title a", searchPath: (baseUrl, carNum2) => `${baseUrl}/search/${carNum2}/`, getDetailPageHref: ($box2) => $box2.attr("href"), findCarNumOrTitle: ($box2) => $box2.text() }, { id: "avgleBtn", getBaseUrl: async () => await this.getAvgleUrl(), itemSelector: ".text-secondary", searchPath: (baseUrl, carNum2) => `${baseUrl}/vod/search.html?wd=${carNum2}`, getDetailPageHref: ($box2) => $box2.attr("href"), findCarNumOrTitle: ($box2) => $box2.text() }, { id: "missAvBtn", getBaseUrl: async () => await this.getMissAvUrl(), itemSelector: ".text-secondary", searchPath: (baseUrl, carNum2) => `${baseUrl}/search/${carNum2}`, getDetailPageHref: ($box2) => $box2.attr("href"), findCarNumOrTitle: ($box2) => $box2.text() }, { id: "supJavBtn", getBaseUrl: async () => await this.getSupJavUrl(), itemSelector: ".posts post", searchPath: (baseUrl, carNum2) => `${baseUrl}/?s=${carNum2}`, getDetailPageHref: ($box2, baseUrl, carNum2) => $box2.attr("href"), findCarNumOrTitle: ($box2) => $box2.attr("title") }, { id: "javDbBtn", getBaseUrl: async () => await this.getJavDbUrl(), itemSelector: ".movie-list .item", searchPath: (baseUrl, carNum2) => `${baseUrl}/search?q=${carNum2}`, getDetailPageHref: ($box2) => $box2.find("a").attr("href"), findCarNumOrTitle: ($box2) => $box2.find(".video-title").text(), condition: (sourceCarNum) => isJavBus$1 }, { id: "javBusBtn", getBaseUrl: async () => await this.getJavBusUrl(), itemSelector: ".container h3", searchPath: (baseUrl, carNum2) => `${baseUrl}/${carNum2}`, getDetailPageHref: ($box2, baseUrl, carNum2) => `${baseUrl}/${carNum2}`, findCarNumOrTitle: ($box2) => $box2.text(), condition: (sourceCarNum) => isJavDb$1 && sourceCarNum && !sourceCarNum.includes("FC2") }, { id: "fanzaBtn", noHandle: true, initUrl: (carNum2) => `https://www.dmm.co.jp/search/=/searchstr=${carNum2}`, condition: (sourceCarNum) => sourceCarNum && !sourceCarNum.includes("FC2") }]); __publicField2(this, "settingCache", null); __publicField2(this, "lastFetchTime", 0); __publicField2(this, "CACHE_DURATION", 1e4); } getName() { return "OtherSitePlugin"; } async initCss() { return "\n \n "; } async handle() { window.isDetailPage && this.loadOtherSite().then(); } async loadOtherSite(carNum2, sourceCarNum) { if ("yes" !== await storageManager.getSetting("enableLoadOtherSite", "yes")) return; carNum2 || (carNum2 = this.getPageInfo().carNum); const enabledSites = this.getEnabledSites(), html = `
    ${this.siteConfigs.map(((config) => { config.sourceCarNum = sourceCarNum; if (config.condition && false === config.condition(config.sourceCarNum)) return ""; return `${config.id.replace("Btn", "")}`; })).join("")} 设置
    `; $(".movie-panel-info").append(html); $(".container .info").append(html); $("#javTrailersBtn").on("click", (async (event) => { event.preventDefault(); let settingObj = await storageManager.getSetting(); const filterHotKey = settingObj.filterHotKey, favoriteHotKey = settingObj.favoriteHotKey, speedVideoHotKey = settingObj.speedVideoHotKey; let href = $("#javTrailersBtn").attr("href"), url = href + `?handle=1&filterHotKey=${filterHotKey}&favoriteHotKey=${favoriteHotKey}&speedVideoHotKey=${speedVideoHotKey}`; event && (event.ctrlKey || event.metaKey) && (url = href); utils.openPage(url, carNum2, false, event); })); await Promise.all(this.siteConfigs.map((async (config) => { config.condition && false === config.condition(config.sourceCarNum) || await this.handleSite(carNum2, config); }))); this.renderSettingsArea(); this.setupEventListeners(); } async handleSite(carNum2, config) { const $btn = $(`#${config.id}`); if (config.initUrl) { $btn.attr("href", config.initUrl(carNum2)); $btn.css("backgroundColor", this.warnBackgroundColor); } if (config.noHandle && true === config.noHandle) { const dmmCacheKey = "jhs_other_site_dmm", dmmCachedResult = (localStorage.getItem(dmmCacheKey) ? JSON.parse(localStorage.getItem(dmmCacheKey)) : {})[carNum2]; if (dmmCachedResult) { if ("single" === dmmCachedResult.type) { $btn.attr("href", dmmCachedResult.url); $btn.css("backgroundColor", this.okBackgroundColor); } else if ("multiple" === dmmCachedResult.type) { $btn.attr("href", dmmCachedResult.url); $btn.append('多结果'); $btn.css("backgroundColor", this.okBackgroundColor); } } } else try { if ($btn.attr("href")) return; if (utils.isHidden($btn)) return; const cacheKey = "jhs_other_site", cacheData = localStorage.getItem(cacheKey) ? JSON.parse(localStorage.getItem(cacheKey)) : {}, siteKey = carNum2 + "_" + config.id.replace("Btn", ""), cachedResult = cacheData[siteKey]; if (cachedResult) { if ("single" === cachedResult.type) { $btn.attr("href", cachedResult.url); $btn.css("backgroundColor", this.okBackgroundColor); } else if ("multiple" === cachedResult.type) { $btn.attr("href", cachedResult.url); $btn.append('多结果'); $btn.css("backgroundColor", this.okBackgroundColor); } return; } const baseUrl = await config.getBaseUrl(), searchUrl = config.searchPath(baseUrl, carNum2); $btn.attr("href", searchUrl); const html = await gmHttp.get(searchUrl, null, config.headers, true), $dom = utils.htmlTo$dom(html), resultUrlList = []; $dom.find(config.itemSelector).each(((index, element) => { const $box2 = $(element); if (!config.findCarNumOrTitle($box2).toLowerCase().includes(carNum2.toLowerCase())) return; let href = config.getDetailPageHref($box2, baseUrl, carNum2); if (!href) throw new Error("解析href失败"); href.includes("http") || (href = baseUrl + (href.startsWith("/") ? href : "/" + href)); resultUrlList.push(href); })); let tagHtml = "", insertCacheData = null; if (1 === resultUrlList.length) { let resultUrl = resultUrlList[0]; $btn.attr("href", resultUrl); $btn.css("backgroundColor", this.okBackgroundColor); insertCacheData = { type: "single", url: resultUrl }; } else if (resultUrlList.length > 1) { $btn.attr("href", searchUrl); tagHtml += '多结果'; $btn.css("backgroundColor", this.okBackgroundColor); insertCacheData = { type: "multiple", url: searchUrl }; } else { $btn.attr("href", searchUrl); $btn.attr("title", "未查询到, 点击前往搜索页"); $btn.css("backgroundColor", this.errorBackgroundColor); } insertCacheData && this.asyncQueue.addTask((() => { const newCacheData = localStorage.getItem(cacheKey) ? JSON.parse(localStorage.getItem(cacheKey)) : {}; newCacheData[siteKey] = insertCacheData; localStorage.setItem(cacheKey, JSON.stringify(newCacheData)); })); tagHtml && $btn.append(tagHtml); } catch (e) { const errorString = String(e), siteName = config.id.replace("Btn", ""); if (errorString.includes("Just a moment")) { $btn.attr("title", "请求失败:Cloudflare 安全检查。"); $btn.css("backgroundColor", this.warnBackgroundColor); clog.warn(`检测第三方资源失败, ${siteName} 需Cloudflare安全检查`); } else if (errorString.includes("重定向")) { $btn.attr("title", "域名失效"); $btn.css("backgroundColor", this.domainErrorBackgroundColor); clog.warn(`检测第三方资源失败, ${siteName} 域名被重定向`); } else if (errorString.includes("404 Page Not Found")) { $btn.attr("title", "未查询到, 点击前往搜索页"); $btn.css("backgroundColor", this.errorBackgroundColor); } else { console.error(e); $btn.attr("title", "请求失败。"); $btn.css("backgroundColor", this.errorBackgroundColor); clog.warn(`检测第三方资源失败, ${siteName}`); } } } async getSettingCache() { const now = Date.now(); if (!this.settingCache || now - this.lastFetchTime > this.CACHE_DURATION) { this.settingCache = await storageManager.getSetting(); this.lastFetchTime = now; } return this.settingCache; } async getMissAvUrl() { return (await this.getSettingCache()).missAvUrl || "https://missav.live"; } async getjableUrl() { return (await this.getSettingCache()).jableUrl || "https://jable.tv"; } async getAvgleUrl() { return (await this.getSettingCache()).avgleUrl || "https://jav.rs"; } async getJavTrailersUrl() { return (await this.getSettingCache()).javTrailersUrl || "https://javtrailers.com"; } async getAv123Url() { return (await this.getSettingCache()).av123Url || "https://123av.com"; } async getJavDbUrl() { return (await this.getSettingCache()).javDbUrl || "https://javdb.com"; } async getJavBusUrl() { return (await this.getSettingCache()).javBusUrl || "https://www.javbus.com"; } async getSupJavUrl() { return (await this.getSettingCache()).supJavUrl || "https://supjav.com"; } getEnabledSites() { const enabledSites = localStorage.getItem("jhs_enabled_sites"); return enabledSites ? JSON.parse(enabledSites) : this.siteConfigs.map(((c) => c.id)); } saveEnabledSites(sites) { localStorage.setItem("jhs_enabled_sites", JSON.stringify(sites)); } renderSettingsArea() { const enabledSites = this.getEnabledSites(), checkboxesDiv = document.getElementById("siteCheckboxes"); checkboxesDiv && (checkboxesDiv.innerHTML = this.siteConfigs.map(((config) => { const isEnabled = enabledSites.includes(config.id); return `
    `; })).join("")); } setupEventListeners() { const settingsArea = document.getElementById("settingsArea"); document.addEventListener("click", ((event) => { if ("settingSiteBtn" === event.target.id || event.target.closest("#settingSiteBtn")) { const isHidden = "none" === settingsArea.style.display || "" === settingsArea.style.display; settingsArea.style.display = isHidden ? "block" : "none"; } })); settingsArea.addEventListener("change", ((event) => { if ("checkbox" === event.target.type) { const siteId = event.target.getAttribute("data-site-id"); if (event.target.checked) { $(`#${siteId}`).show(); const carNum2 = this.getPageInfo().carNum, config = this.siteConfigs.find(((item) => item.id === siteId)); this.handleSite(carNum2, config).then(); } else $(`#${siteId}`).hide(); const enabledSites = Array.from(settingsArea.querySelectorAll('input[type="checkbox"]:checked')).map(((checkbox) => checkbox.getAttribute("data-site-id"))); this.saveEnabledSites(enabledSites); } })); } } class ReviewPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "floorIndex", 1); __publicField2(this, "isInit", false); } getName() { return "ReviewPlugin"; } async handle() { $(document).on("click", ".down-115", (async (event) => { const magnet = $(event.currentTarget).data("magnet"); let loadObj = loading(); try { await this.getBean("WangPan115TaskPlugin").handleAddTask(magnet); } catch (e) { show.error("发生错误:" + e); console.error(e); } finally { loadObj.close(); } })); if (window.isDetailPage) { if (isJavDb$1) { const movieId = this.parseMovieId(window.location.href); await this.showReview(movieId); await this.getBean("RelatedPlugin").showRelated($("#magnets-content"), movieId); } if (isJavBus$1) { let carNum2 = this.getPageInfo().carNum; const movies = await javDbApi.searchMovie(carNum2); let movieId = null; for (let i = 0; i < movies.length; i++) { let item = movies[i]; if (item.number.toLowerCase() === carNum2.toLowerCase()) { movieId = item.id; break; } } if (!movieId) return; this.showReview(movieId, $("#sample-waterfall")).then(); } } } async showReview(movieId, $eleBox) { const enableLoadReview = await storageManager.getSetting("enableLoadReview", YES), $magnets = $eleBox || $("#magnets-content"); $magnets.append(`
    ❓ 评论区 ${enableLoadReview === YES ? "折叠" : "展开"} ${enableLoadReview === YES ? "▲" : "▼"}
    `); $("#reviewsFold").on("click", ((event) => { event.preventDefault(); event.stopPropagation(); const $text = $("#reviewsFold .toggle-text"), $icon = $("#reviewsFold .toggle-icon"), isFolded = "展开" === $text.text(); $text.text(isFolded ? "折叠" : "展开"); $icon.text(isFolded ? "▲" : "▼"); if (isFolded) { $("#reviewsContainer").show(); $("#reviewsFooter").show(); if (!this.isInit) { this.fetchAndDisplayReviews(movieId); this.isInit = true; } storageManager.saveSettingItem("enableLoadReview", YES); } else { $("#reviewsContainer").hide(); $("#reviewsFooter").hide(); storageManager.saveSettingItem("enableLoadReview", NO); } })); $magnets.append('
    '); $magnets.append('
    '); enableLoadReview === YES && await this.fetchAndDisplayReviews(movieId); } async fetchAndDisplayReviews(movieId) { const $reviewsContainer = $("#reviewsContainer"), $reviewsFooter = $("#reviewsFooter"); $reviewsContainer.append('
    获取评论中...
    '); const reviewCount = await storageManager.getSetting("reviewCount", 20); let dataList = null; try { dataList = await javDbApi.getReviews(movieId, 1, reviewCount); } catch (e) { e.toString().includes("簽名已過期") && show.error("生成签名失败, 请检查系统时间及时区是否正确!"); clog.error("获取评论失败:", e); console.error("获取评论失败:", e); } finally { $("#reviewsLoading").remove(); } if (!dataList) { $reviewsContainer.append('\n
    \n 获取评论失败\n 重试\n
    \n '); $("#retryFetchReviews").on("click", (async () => { $("#retryFetchReviews").parent().remove(); await this.fetchAndDisplayReviews(movieId); })); return; } if (0 === dataList.length) { $reviewsContainer.append('
    无评论
    '); return; } const reviewKeywordList = await storageManager.getReviewFilterKeywordList(); this.displayReviews(dataList, $reviewsContainer, reviewKeywordList); if (dataList.length === reviewCount) { $reviewsFooter.html('\n \n \n '); let currentPage = 1, $loadMoreReviews = $("#loadMoreReviews"); $loadMoreReviews.on("click", (async () => { $loadMoreReviews.text("加载中...").prop("disabled", true); currentPage++; let moreData; try { moreData = await javDbApi.getReviews(movieId, currentPage, reviewCount); } catch (e) { console.error("加载更多评论失败:", e); } finally { $loadMoreReviews.text("加载失败, 请点击重试").prop("disabled", false); } if (moreData) { this.displayReviews(moreData, $reviewsContainer, reviewKeywordList); if (moreData.length < reviewCount) { $loadMoreReviews.remove(); $("#reviewsEnd").show(); } else $loadMoreReviews.text("加载更多评论").prop("disabled", false); } })); } else $reviewsFooter.html('
    已加载全部评论
    '); } displayReviews(dataList, $container, reviewKeywordList) { if (dataList.length) { dataList.forEach(((item) => { if (reviewKeywordList.some(((keyword) => item.content.includes(keyword)))) return; const starsHtml = Array(item.score).fill('').join(""), content = item.content.replace(/ed2k:\/\/\|file\|[^|]+\|\d+\|[a-fA-F0-9]{32}\|\/|magnet:\?[^\s"'<>`\u4e00-\u9fa5,。?!()【】]+|https?:\/\/[^\s"'<>`\u4e00-\u9fa5,。?!()【】]+/g, ((match) => match.startsWith("ed2k://") ? ` ${match} ` : match.startsWith("magnet:") ? ` ${match} ` : match.startsWith("http://") || match.startsWith("https://") ? ` ${match} ` : match)), commentHtml = `
    #${this.floorIndex++}楼 ${item.username}    ${starsHtml} ${utils.formatDate(item.created_at)}    点赞:${item.likes_count}

    ${content}

    `; $container.append(commentHtml); })); this.rightClickFilter(); } } async rightClickFilter() { await storageManager.getSetting("enableTitleSelectFilter", YES) === YES && utils.rightClick(document.body, ".review-content", (async (event) => { const selectedText = window.getSelection().toString(); if (selectedText) { event.preventDefault(); await utils.q(event, `是否将 '${selectedText}' 加入评论区关键词?`, (async () => { await storageManager.saveReviewFilterKeyword(selectedText); show.ok("操作成功, 刷新页面后生效"); })); } })); } } class FilterTitleKeywordPlugin extends BasePlugin { getName() { return "FilterTitleKeywordPlugin"; } async handle() { if (!window.isDetailPage && !window.isFc2Page) return; if (await storageManager.getSetting("enableTitleSelectFilter", YES) !== YES) return; let targetSelector; isJavDb$1 ? targetSelector = ".title strong, .current-title" : isJavBus$1 && (targetSelector = "h3"); utils.rightClick(document.body, targetSelector, ((event) => { const selectedText = window.getSelection().toString(); if (selectedText) { event.preventDefault(); let tempEvent = { clientX: event.clientX, clientY: event.clientY + 80 }; utils.q(tempEvent, `是否屏蔽标题关键词 ${selectedText}?`, (async () => { await storageManager.saveTitleFilterKeyword(selectedText); window.refresh(); utils.closePage(); })); } })); } } class CoverButtonPlugin extends BasePlugin { getName() { return "CoverButtonPlugin"; } async initCss() { return ` `; } handle() { if (window.isListPage) { this.addSvgBtn(); this.bindClick().then(); } } async addSvgBtn() { $(this.getSelector().itemSelector).toArray().forEach(((ele) => { let $box2 = $(ele); if (!($box2.find(".tool-box").length > 0)) { isJavDb$1 && $box2.find(".tags").append(`
    ${this.screenSvg} ${this.videoSvg}
    ${this.handleSvg}
    ${this.siteSvg}
    ${this.copySvg}
    `); if (isJavBus$1) { if ($box2.find(".avatar-box").length > 0) return; $box2.find(".photo-info").append(`
    ${this.screenSvg} ${this.videoSvg}
    ${this.handleSvg}
    ${this.siteSvg}
    ${this.copySvg}
    `); } } })); this.enableSvgBtn(); } async enableSvgBtn() { const settingObj = await storageManager.getSetting(), { enableScreenSvg = YES, enableVideoSvg = YES, enableHandleSvg = YES, enableSiteSvg = YES, enableCopySvg = YES } = settingObj; [{ selector: ".screenSvg", enabled: enableScreenSvg }, { selector: ".videoSvg", enabled: enableVideoSvg }, { selector: ".handleSvg", enabled: enableHandleSvg }, { selector: ".siteSvg", enabled: enableSiteSvg }, { selector: ".copySvg", enabled: enableCopySvg }].forEach((({ selector, enabled }) => { $(selector).toggle(enabled === YES); })); } async bindClick() { this.getSelector(); const listPagePlugin = this.getBean("ListPagePlugin"); $(document).on("click", ".more-tools-container", ((event) => { event.preventDefault(); var $currentTools = $(event.target).closest(".more-tools-container").find(".more-tools"); $(".more-tools").not($currentTools).stop(true, true).removeClass("elastic-in").addClass("elastic-out").hide(); $currentTools.is(":visible") ? $currentTools.stop(true, true).removeClass("elastic-in").addClass("elastic-out").hide() : $currentTools.stop(true, true).removeClass("elastic-out").addClass("elastic-in").show(); })); $(document).on("click", (function(event) { $(event.target).closest(".more-tools-container").length || $(".more-tools").stop(true, true).removeClass("elastic-in").addClass("elastic-out").hide(); })); $(document).on("click", ".videoSvg", ((event) => { event.preventDefault(); $('.videoSvg[title!="播放视频"]').each(((index, element) => { const $otherSvgElement = $(element); let $otherBox = $otherSvgElement.closest(".item"), $otherImg = $otherBox.find("img"), { carNum: carNum2 } = this.getBoxCarInfo($otherBox); this.showImg($otherSvgElement, $otherImg, carNum2); $otherSvgElement.html(this.videoSvg).attr("title", "播放视频"); })); const $currentBox = $(event.target).closest(".item"), $svgElement = $currentBox.find(".videoSvg"); if ("播放视频" === $svgElement.attr("title")) { $svgElement.html(this.recoveryVideoSvg).attr("title", "切回封面"); const { carNum: carNum2 } = this.getBoxCarInfo($currentBox); let $img = $currentBox.find("img"); if (!$img.length) { show.error("没有找到图片"); return; } this.showVideo($svgElement, $img, carNum2).then(); } })); $(document).on("click", ".screenSvg", (async (event) => { event.preventDefault(); let loadObj = loading(); try { const $box2 = $(event.currentTarget).closest(".item"); let { carNum: carNum2 } = this.getBoxCarInfo($box2); carNum2 = carNum2.replace("FC2-", ""); const imgUrl = await this.getBean("ScreenShotPlugin").getScreenshot(carNum2); loadObj.close(); showPreviewOverlay(imgUrl, carNum2); } catch (error) { console.error("图片预览出错:", error); show.error("图片预览出错:" + error); } finally { loadObj.close(); } })); $(document).on("click", ".filterBtn, .favoriteBtn, .hasDownBtn, .hasWatchBtn", ((event) => { event.preventDefault(); event.stopPropagation(); const $btn = $(event.target).closest(".menu-btn"), $box2 = $btn.closest(".item"), { carNum: carNum2, url, publishTime } = this.getBoxCarInfo($box2), handleAction = async (status) => { let actress = await listPagePlugin.parseActressName(url); await storageManager.saveCar({ carNum: carNum2, url, names: actress, actionType: status, publishTime }); window.refresh(); show.ok("操作成功"); }; $btn.hasClass("filterBtn") ? utils.q(event, `是否屏蔽${carNum2}?`, (() => handleAction(Status_FILTER$1))) : $btn.hasClass("favoriteBtn") ? handleAction(Status_FAVORITE$1).then() : $btn.hasClass("hasDownBtn") ? handleAction(Status_HAS_DOWN$1).then() : $btn.hasClass("hasWatchBtn") && handleAction(Status_HAS_WATCH$1).then(); $(".more-tools").stop(true, true).removeClass("elastic-in").addClass("elastic-out").hide(); })); const otherSitePlugin = this.getBean("OtherSitePlugin"), missAvUrl = await otherSitePlugin.getMissAvUrl(), jableUrl = await otherSitePlugin.getjableUrl(), avgleUrl = await otherSitePlugin.getAvgleUrl(), av123Url = await otherSitePlugin.getAv123Url(); $(document).on("click", ".site-jable, .site-avgle, .site-miss-av, .site-123-av", ((event) => { event.preventDefault(); event.stopPropagation(); const $currentTarget = $(event.currentTarget), $box2 = $currentTarget.closest(".item"), { carNum: carNum2 } = this.getBoxCarInfo($box2); let url = null; $currentTarget.hasClass("site-jable") ? url = `${jableUrl}/search/${carNum2}/` : $currentTarget.hasClass("site-avgle") ? url = `${avgleUrl}/vod/search.html?wd=${carNum2}` : $currentTarget.hasClass("site-miss-av") ? url = `${missAvUrl}/search/${carNum2}` : $currentTarget.hasClass("site-123-av") && (url = `${av123Url}/ja/search?keyword=${carNum2}`); event && (event.ctrlKey || event.metaKey) ? _GM_openInTab(url, { insert: 0 }) : window.open(url); })); $(document).on("click", ".titleSvg, .carNumSvg, .downSvg", ((event) => { event.preventDefault(); event.stopPropagation(); const $box2 = $(event.currentTarget).closest(".item"), { carNum: carNum2, title } = this.getBoxCarInfo($box2), $img = $box2.find(isJavBus$1 ? ".photo-frame img" : ".cover img"); $(event.currentTarget).hasClass("titleSvg") ? utils.copyToClipboard("标题", title) : $(event.currentTarget).hasClass("carNumSvg") ? utils.copyToClipboard("番号", carNum2) : $(event.currentTarget).hasClass("downSvg") && fetch($img.attr("src")).then(((response) => response.blob())).then(((blob) => { utils.download(blob, carNum2 + " " + title + ".jpg"); })); })); $(document).on("click", ".markDeleteSvg", (async (event) => { event.preventDefault(); event.stopPropagation(); const $btn = $(event.currentTarget); const $box2 = $btn.closest(".item"); const {carNum: carNum2, title, publishTime, url} = this.getBoxCarInfo($box2); const score = $box2.find(".score").text().trim() || "无评分"; let markedList = await storageManager.forage.getItem("markedDeleteList") || []; const existingIndex = markedList.findIndex((item) => item.carNum === carNum2); if (existingIndex > -1) { markedList.splice(existingIndex, 1); $btn.removeClass("marked"); $btn.attr("title", "标记删除"); show.ok(`已取消标记: ${title}`); } else { markedList.push({ carNum: carNum2, title, publishTime, score, url, markedTime: (/* @__PURE__ */ new Date()).toISOString() }); $btn.addClass("marked"); $btn.attr("title", "取消标记删除"); show.ok(`已标记删除: ${title}`); } await storageManager.forage.setItem("markedDeleteList", markedList); })); $(document).on("click", ".delete115Svg", (async (event) => { event.preventDefault(); event.stopPropagation(); const $btn = $(event.currentTarget); const matchData = $btn.attr("data-match"); if (!matchData) { show.error("未找到115匹配数据"); return; } const matchList = JSON.parse(matchData); const $box2 = $btn.closest(".item"); const { carNum: carNum2 } = this.getBoxCarInfo($box2); this.getBean("WangPan115MatchPlugin").showDeleteDialog(carNum2, matchList, (async (newMatchList) => { this.getBean("WangPan115MatchPlugin").updateMatchStatus($box2, carNum2, newMatchList); })); })); } showImg($svgElement, $img, carNum2) { $svgElement.html(this.videoSvg).attr("title", "播放视频"); let $video = $(`#${`${carNum2}_preview_video`}`); if ($video.length > 0) { $video[0].pause(); $video.parent().hide(); } $img.show(); $img.removeClass("loading"); $img.next(".loading-spinner").remove(); } async showVideo($svgElement, $img, carNum2) { const id = `${carNum2}_preview_video`; let $video = $(`#${id}`); if ($video.length > 0) { $video.parent().show(); $video[0].play(); $img.hide(); return; } $img.addClass("loading"); $img.after('
    '); const poster = $img.attr("src"), dmmVideoMap = await getDmmVideo(carNum2); if (!dmmVideoMap) { show.error("未解析到视频"); this.showImg($svgElement, $img, carNum2); return; } let defaultVideoQuality = await storageManager.getSetting("videoQuality"); defaultVideoQuality = selectDefaultQuality(Object.keys(dmmVideoMap), defaultVideoQuality); let videoUrl = dmmVideoMap[defaultVideoQuality], videoHtml = `
    `; isJavBus$1 && (videoHtml = `
    `); $img.parent().append(videoHtml); $img.hide(); $img.removeClass("loading"); $img.next(".loading-spinner").remove(); $video = $(`#${id}`); let videoElement = $video[0]; videoElement.load(); videoElement.muted = false; videoElement.play(); $video.trigger("focus"); } } class Fc2By123AvPlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "$contentBox", $(".section .container")); __publicField2(this, "urlParams", new URLSearchParams(window.location.search)); __publicField2(this, "sortVal", this.urlParams.get("sort") || "release_date"); __publicField2(this, "currentPage", this.urlParams.get("page") ? parseInt(this.urlParams.get("page")) : 1); __publicField2(this, "maxPage", null); __publicField2(this, "keyword", this.urlParams.get("keyword") || null); } getName() { return "Fc2By123AvPlugin"; } async getBaseUrl() { const otherSitePlugin = this.getBean("OtherSitePlugin"); return await otherSitePlugin.getAv123Url() + "/ja"; } handle() { $("#navbar-menu-hero > div > div:nth-child(1) > div > a:nth-child(4)").after('123Av-Fc2'); $('.tabs li:contains("FC2")').after('
  • 123Av-Fc2
  • '); if (currentHref$1.includes("/advanced_search?type=100")) { this.hookPage(); this.handleQuery().then(); } } hookPage() { let $h2 = $("h2.section-title"); $h2.contents().first().replaceWith("123Av"); $h2.css("marginBottom", "0"); $h2.append('\n
    \n \n 搜索\n 重置\n
    \n '); $("#search-123av-keyword").val(this.keyword); $("#search-123av-btn").on("click", (async () => { let keyword = $("#search-123av-keyword").val().trim(); if (keyword) { this.keyword = keyword; utils.setHrefParam("keyword", keyword); await this.handleQuery(); } })); $("#clear-123av-btn").on("click", (async () => { $("#search-123av-keyword").val(""); this.keyword = ""; utils.setHrefParam("keyword", ""); $(".page-box").show(); $(".tool-box").show(); await this.handleQuery(); })); $(".empty-message").remove(); $("#foldCategoryBtn").remove(); $(".section .container .box").remove(); $("#sort-toggle-btn").remove(); this.$contentBox.append('
    '); this.$contentBox.append('
    '); this.$contentBox.append('
    '); $(".tool-box").append('\n
    \n
    \n 发布日期\n 最近更新\n 热门\n 今天最多观看\n 本周最多观看\n 本月最多观看\n 最多观看\n 最受欢迎\n
    \n
    \n '); $(`#conditionBox a[data-sort="${this.sortVal}"]`).addClass("is-info"); utils.setHrefParam("sort", this.sortVal); utils.setHrefParam("page", this.currentPage); $("#conditionBox").on("click", "a.button", ((e) => { let $target = $(e.target); this.sortVal = $target.data("sort"); utils.setHrefParam("sort", this.sortVal); $target.siblings().removeClass("is-info"); $target.addClass("is-info"); this.handleQuery(); })); $(".page-box").append('\n \n '); $(document).on("click", ".pagination-link", ((e) => { e.preventDefault(); this.currentPage = parseInt($(e.target).data("page")); utils.setHrefParam("page", this.currentPage); this.renderPagination(); this.handleQuery(); })); $(".pagination-previous").on("click", ((e) => { e.preventDefault(); if (this.currentPage > 1) { this.currentPage--; utils.setHrefParam("page", this.currentPage); this.renderPagination(); this.handleQuery(); } })); $(".pagination-next").on("click", ((e) => { e.preventDefault(); if (this.currentPage < this.maxPage) { this.currentPage++; utils.setHrefParam("page", this.currentPage); this.renderPagination(); this.handleQuery(); } })); } renderPagination() { const $paginationList = $(".pagination-list"); $paginationList.empty(); let startPage = Math.max(1, this.currentPage - 2), endPage = Math.min(this.maxPage, this.currentPage + 2); this.currentPage <= 3 ? endPage = Math.min(6, this.maxPage) : this.currentPage >= this.maxPage - 2 && (startPage = Math.max(this.maxPage - 5, 1)); if (startPage > 1) { $paginationList.append('
  • 1
  • '); startPage > 2 && $paginationList.append('
  • …
  • '); } for (let i = startPage; i <= endPage; i++) { const activeClass = i === this.currentPage ? " is-current" : ""; $paginationList.append(`
  • ${i}
  • `); } if (endPage < this.maxPage) { endPage < this.maxPage - 1 && $paginationList.append('
  • …
  • '); $paginationList.append(`
  • ${this.maxPage}
  • `); } } async handleQuery() { let loadObj = loading(); try { let pagesToFetch = []; pagesToFetch = 1 === this.currentPage ? [1, 2] : [2 * this.currentPage - 1, 2 * this.currentPage]; if (this.keyword) { pagesToFetch = [1]; $(".page-box").hide(); $(".tool-box").hide(); } const baseUrl = await this.getBaseUrl(), fetchPromises = pagesToFetch.map(((page) => { let url = `${baseUrl}/tags/fc2?sort=${this.sortVal}&page=${page}`; this.keyword && (url = `${baseUrl}/search?keyword=${this.keyword}`); return gmHttp.get(url); })), htmlResults = await Promise.all(fetchPromises); let dataList = []; for (const html of htmlResults) { let $dom = $(html); $dom.find(".box-item").each(((index, element) => { const $item = $(element), imgSrc = $item.find("img").attr("data-src"); let carNum2 = $item.find("img").attr("title"); const detailLink = $item.find(".detail a"), link = detailLink.attr("href"), href = baseUrl + (link.startsWith("/") ? link : "/" + link), title = detailLink.text().trim().replace(carNum2 + " - ", ""); carNum2 = carNum2.replace("FC2-PPV", "FC2"); dataList.push({ imgSrc, carNum: carNum2, href, title }); })); if (!this.maxPage) { let rawMaxPage, lastPageItem = $dom.find(".page-item:not(.disabled)").last(); if (lastPageItem.find("a.page-link").length) { let href = lastPageItem.find("a.page-link").attr("href"); rawMaxPage = parseInt(href.split("page=")[1]); } else rawMaxPage = parseInt(lastPageItem.find("span.page-link").text()); this.maxPage = Math.ceil(rawMaxPage / 2); this.renderPagination(); } } if (0 === dataList.length) { console.log(dataList); show.error("无结果"); let errorUrl = `${baseUrl}/dm4/tags/fc2?sort=${this.sortVal}`; this.keyword && (errorUrl = `${baseUrl}/search?keyword=${this.keyword}`); console.error("获取数据失败!", errorUrl); } let movieHtml = this.markDataListHtml(dataList); $(".movie-list").html(movieHtml); await utils.smoothScrollToTop(); } catch (e) { console.error(e); } finally { loadObj.close(); } } async open123AvFc2Dialog(carNum2, href) { let otherHtml = ""; await storageManager.getSetting("enableLoadOtherSite", YES) === YES && (otherHtml = '
    第三方站点:
    '); let pageHtml = `
    加载中...
    ${otherHtml}
    🚫 屏蔽 ⭐ 收藏 📥️ 已下载 🔍 已观看 字幕搜索
    `; layer.open({ type: 1, title: carNum2, content: pageHtml, area: utils.getDefaultArea(), skin: "movie-detail-layer", scrollbar: false, success: (layero, index) => { utils.setupEscClose(index); this.loadData(carNum2, href); let keyword = carNum2.replace("FC2-", ""); $("#magnets-content").append(this.getBean("MagnetHubPlugin").createMagnetHub(keyword)); $("#favoriteBtn").on("click", (async (event) => { const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_FAVORITE$1, publishTime }); window.refresh(); layer.closeAll(); })); $("#filterBtn").on("click", ((event) => { utils.q(event, `是否屏蔽${carNum2}?`, (async () => { const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_FILTER$1, publishTime }); window.refresh(); layer.closeAll(); window.location.href.includes("collection_codes?movieId") && utils.closePage(); })); })); $("#hasDownBtn").on("click", (async (event) => { const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_HAS_DOWN$1, publishTime }); window.refresh(); layer.closeAll(); })); $("#hasWatchBtn").on("click", (async (event) => { const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text(); await storageManager.saveCar({ carNum: carNum2, url: href, names: actress, actionType: Status_HAS_WATCH$1, publishTime }); window.refresh(); layer.closeAll(); })); $("#subtitleSearchBtn").on("click", (() => { window.JavPackSubtitle.openSearchModal({details: {code: carNum2}}); })); window.JavPackSubtitle.preload115Matches(carNum2); let tempCarNum = carNum2.replace("FC2-", ""); this.getBean("OtherSitePlugin").loadOtherSite(tempCarNum, carNum2).then(); } }); } async loadData(carNum2, href) { let loadObj = loading(); try { const { id, publishDate, title, moviePoster } = await this.get123AvVideoInfo(href); $(".movie-info-container").html(`

    ${title || "无标题"}

    番号: ${carNum2 || "未知"} 年份: ${publishDate || "未知"} 站点: fc2ppvdb fc2电子市场
    主演:
    販売者:
    `); this.getImgList(carNum2).then(); this.getActressInfo(carNum2).then(); this.getBean("TranslatePlugin").translate(carNum2, false).then(); } catch (e) { console.error(e); } finally { loadObj.close(); } } handleLongImg(carNum2) { utils.loopDetector((() => $(".movie-gallery .image-list").length > 0), (async () => { $(".movie-gallery .image-list").prepend('
    正在加载缩略图
    '); const imgUrl = await this.getBean("ScreenShotPlugin").getScreenshot(carNum2); if (imgUrl) { $(".screen-container").html(``); $(".screen-container").on("click", ((event) => { event.stopPropagation(); event.preventDefault(); showPreviewOverlay(imgUrl, carNum2); })); } })); } async get123AvVideoInfo(href) { const html = await gmHttp.get(href), match = html.match(/v-scope="Movie\({id:\s*(\d+),/), id = match ? match[1] : null, $dom = utils.htmlTo$dom(html); return { id, publishDate: $dom.find('span:contains("リリース日:")').next("span").text(), title: $dom.find("h1").text().trim(), moviePoster: $dom.find("#player").attr("data-poster") }; } async getActressInfo(fc2Num) { let url = `https://fc2ppvdb.com/articles/${fc2Num.replace("FC2-", "")}`; const html = await gmHttp.get(url), $dom = $(html), actressNodeList = $dom.find("div").filter((function() { return 0 === $(this).text().trim().indexOf("女優:"); })); if (0 === actressNodeList.length || actressNodeList.length > 1) { show.error("解析女优信息失败"); return; } const $actress = $(actressNodeList[0]).find("a"); let actorsHtml = "主演: "; if ($actress.length > 0) { let actress = ""; $actress.each(((index, ele) => { let $actor = $(ele), name2 = $actor.text(), actressHref = $actor.attr("href"); actorsHtml += `${name2}`; actress += name2 + " "; })); $("#data-actress").text(actress); } else actorsHtml += "暂无演员信息"; $(".actor-list").html(actorsHtml); const sellerNodeList = $dom.find("div").filter((function() { return 0 === $(this).text().trim().indexOf("販売者:"); })); if (sellerNodeList.length > 0) { const $sellerA = $(sellerNodeList[0]).find("a"); if ($sellerA.length > 0) { const $seller = $($sellerA[0]); let name2 = $seller.text(), sellerHref = $seller.attr("href"); $(".movie-seller").html(`販売者: ${name2}`); } } } async getImgList(fc2Num) { let tempCarNum = fc2Num.replace("FC2-", ""), url = `https://adult.contents.fc2.com/article/${fc2Num.replace("FC2-", "")}/`; const html = await gmHttp.get(url, null, { referer: url }); let imgList = $(html).find(".items_article_SampleImagesArea img").map((function() { return $(this).attr("src"); })).get(), imagesHtml = ""; Array.isArray(imgList) && imgList.length > 0 ? imagesHtml = imgList.map(((img, index) => ` `)).join("") : $(".movie-gallery").html("

    剧照: 暂无剧照

    "); $(".image-list").html(imagesHtml); this.handleLongImg(tempCarNum); } async getMovie(id, moviePoster) { let url = `${await this.getBaseUrl()}/ajax/v/${id}/videos`, loadObj = loading(); try { let movieList = (await gmHttp.get(url)).result.watch; if (movieList.length > 0) { movieList.forEach(((movieItem) => { movieItem.url = movieItem.url + "?poster=" + moviePoster; })); return movieList; } return null; } catch (e) { console.error(e); } finally { loadObj.close(); } } markDataListHtml(movies) { let moviesHtml = ""; movies.forEach(((movie) => { moviesHtml += `
    ${movie.carNum} ${movie.title}
    `; })); return moviesHtml; } } class DetailPageButtonPlugin extends BasePlugin { getName() { return "DetailPageButtonPlugin"; } constructor() { super(); this.answerCount = 1; } async handle() { let settingObj = await storageManager.getSetting(); this.filterHotKey = settingObj.filterHotKey; this.favoriteHotKey = settingObj.favoriteHotKey; this.hasDownHotKey = settingObj.hasDownHotKey; this.hasWatchHotKey = settingObj.hasWatchHotKey; this.speedVideoHotKey = settingObj.speedVideoHotKey; this.bindHotkey().then(); this.hideVideoControls(); window.isDetailPage && this.createMenuBtn(); } async createMenuBtn() { const pageInfo = this.getPageInfo(), carNum2 = pageInfo.carNum, buttonsHtml = '\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 '; isJavDb$1 && $(".tabs").after(buttonsHtml); isJavBus$1 && $("#mag-submit-show").before(buttonsHtml); $("#favoriteBtn").on("click", (() => this.favoriteOne())); $("#filterBtn").on("click", ((event) => this.filterOne(event))); $("#hasDownBtn").on("click", (async () => this.hasDownOne())); $("#hasWatchBtn").on("click", (async () => this.hasWatchOne())); $("#magnetSearchBtn").on("click", (() => { let magnetHub = this.getBean("MagnetHubPlugin").createMagnetHub(pageInfo.carNum); layer.open({ type: 1, title: "磁力搜索 " + pageInfo.carNum, content: '
    ', area: utils.getResponsiveArea(["60%", "80%"]), scrollbar: false, success: () => { $("#magnetHubBox").append(magnetHub); } }); })); const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin"), enableMagnetsFilter = await storageManager.getSetting("enableMagnetsFilter", NO); if (enableMagnetsFilter === YES) { const savedSettings = await storageManager.getSetting("magnetSortAndFilter", "{}"); let settings; try { settings = JSON.parse(savedSettings); } catch (e) { settings = {}; } const sortEnabled = settings.sortEnabled || {}; const defaultFullSortOrder = isJavBus$1 ? ["date", "size"] : ["date", "size", "files"]; const fullSortOrder = settings.sortOrder && settings.sortOrder.length === defaultFullSortOrder.length ? settings.sortOrder : defaultFullSortOrder; const effectiveSortOrder = fullSortOrder.filter((key) => sortEnabled[key]); const applySettings = { filterHD: settings.filterHD || false, filter4K: settings.filter4K || false, filterSubtitle: settings.filterSubtitle || false, filterUncensored: settings.filterUncensored || false, sortOrder: effectiveSortOrder }; const rowReadyCondition = isJavBus$1 ? (() => $("#magnet-table tr").length > 1) : (() => $("#magnets-content .item[data-rank]").length > 0); utils.loopDetector(rowReadyCondition, () => { highlightMagnetPlugin.applySortAndFilter(applySettings); }); $("#magnets-span").text("取消磁力过滤"); } else { $("#magnets-span").text("排序与过滤"); } $("#enable-magnets-filter").on("click", ((event) => { const $span = $("#magnets-span"); if ("取消磁力过滤" === $span.text()) { highlightMagnetPlugin.showAll(); $span.text("排序与过滤"); storageManager.saveSettingItem("enableMagnetsFilter", NO); } else { this.openMagnetSortFilterPanel(highlightMagnetPlugin); } })); $("#subtitleSearchBtn").on("click", (() => { window.JavPackSubtitle.openSearchModal({details: {code: carNum2}}); })); window.JavPackSubtitle.preload115Matches(carNum2); this.showStatus(carNum2).then(); } async openMagnetSortFilterPanel(highlightMagnetPlugin) { const existingPanel = document.getElementById("magnet-sort-filter-panel"); if (existingPanel) { existingPanel.remove(); return; } const savedSettings = await storageManager.getSetting("magnetSortAndFilter", "{}"); let settings; try { settings = JSON.parse(savedSettings); } catch (e) { settings = {}; } const sortEnabled = settings.sortEnabled || (isJavBus$1 ? {date: false, size: false} : { date: false, size: false, files: false }); const defaultSortOrder = isJavBus$1 ? ["date", "size"] : ["date", "size", "files"]; const sortOrder = settings.sortOrder && settings.sortOrder.length === defaultSortOrder.length ? settings.sortOrder : defaultSortOrder; const filterHD = settings.filterHD || false; const filter4K = settings.filter4K || false; const filterSubtitle = settings.filterSubtitle || false; const filterUncensored = settings.filterUncensored || false; const SORT_LABELS = isJavBus$1 ? {date: "发布日期", size: "文件大小"} : { date: "发布日期", size: "文件大小", files: "文件数量" }; const SORT_HINTS = isJavBus$1 ? {date: "从新到旧", size: "从大到小"} : { date: "从新到旧", size: "从大到小", files: "从少到多" }; const FILTER_ITEMS = [ {key: "HD", label: "高清", checked: filterHD}, {key: "4K", label: "4K", checked: filter4K}, {key: "Subtitle", label: "字幕", checked: filterSubtitle}, {key: "Uncensored", label: "无码", checked: filterUncensored} ]; const currentSortOrder = [...sortOrder]; const currentSortEnabled = {...sortEnabled}; const currentFilters = { HD: filterHD, "4K": filter4K, Subtitle: filterSubtitle, Uncensored: filterUncensored }; const buildSortItemsHTML = () => { return currentSortOrder.map((key) => { const checked = currentSortEnabled[key] ? "checked" : ""; return `
    ⋮⋮
    `; }).join(""); }; const buildFilterItemsHTML = () => { return FILTER_ITEMS.map((item) => { const checked = currentFilters[item.key] ? "checked" : ""; return ``; }).join(""); }; const panelHTML = `
    磁力排序与过滤 ×
    排序优先级(拖动调整顺序,勾选启用)
    ${buildSortItemsHTML()}
    过滤条件(可多选,并集关系)
    ${buildFilterItemsHTML()}
    `; const wrapper = document.createElement("div"); wrapper.innerHTML = panelHTML; document.body.appendChild(wrapper); const panel = document.getElementById("magnet-sort-filter-panel"); const overlay = document.getElementById("msf-overlay"); const closePanel = () => { panel.remove(); overlay.remove(); }; overlay.addEventListener("click", closePanel); document.getElementById("msf-close-btn").addEventListener("click", closePanel); document.getElementById("msf-cancel-btn").addEventListener("click", closePanel); const rebuildSortList = () => { const sortList = document.getElementById("msf-sort-list"); sortList.innerHTML = buildSortItemsHTML(); bindSortEvents(); }; const bindSortEvents = () => { const sortItems = document.querySelectorAll("#msf-sort-list .msf-sort-item"); sortItems.forEach((item) => { item.addEventListener("dragstart", (e) => { e.dataTransfer.effectAllowed = "move"; e.dataTransfer.setData("text/plain", item.dataset.sortKey); item.classList.add("msf-dragging"); }); item.addEventListener("dragend", () => { item.classList.remove("msf-dragging"); document.querySelectorAll("#msf-sort-list .msf-sort-item").forEach((el) => el.classList.remove("msf-drag-over")); }); item.addEventListener("dragover", (e) => { e.preventDefault(); e.dataTransfer.dropEffect = "move"; item.classList.add("msf-drag-over"); }); item.addEventListener("dragleave", () => { item.classList.remove("msf-drag-over"); }); item.addEventListener("drop", (e) => { e.preventDefault(); item.classList.remove("msf-drag-over"); const fromKey = e.dataTransfer.getData("text/plain"); const toKey = item.dataset.sortKey; if (fromKey === toKey) return; const fromIdx = currentSortOrder.indexOf(fromKey); const toIdx = currentSortOrder.indexOf(toKey); currentSortOrder.splice(fromIdx, 1); currentSortOrder.splice(toIdx, 0, fromKey); rebuildSortList(); }); const checkbox = item.querySelector(".msf-sort-checkbox"); checkbox.addEventListener("change", () => { currentSortEnabled[item.dataset.sortKey] = checkbox.checked; }); }); }; bindSortEvents(); document.querySelectorAll(".msf-filter-checkbox").forEach((cb) => { cb.addEventListener("change", () => { currentFilters[cb.dataset.filterKey] = cb.checked; }); }); document.getElementById("msf-confirm-btn").addEventListener("click", async () => { try { const enabledSortOrder = currentSortOrder.filter((key) => currentSortEnabled[key]); const newSettings = { sortOrder: currentSortOrder, sortEnabled: currentSortEnabled, filterHD: currentFilters.HD, filter4K: currentFilters["4K"], filterSubtitle: currentFilters.Subtitle, filterUncensored: currentFilters.Uncensored }; await storageManager.saveSettingItem("magnetSortAndFilter", JSON.stringify(newSettings)); const hasAnyFilter = currentFilters.HD || currentFilters["4K"] || currentFilters.Subtitle || currentFilters.Uncensored; const hasAnySort = enabledSortOrder.length > 0; if (hasAnyFilter || hasAnySort) { await highlightMagnetPlugin.applySortAndFilter({ filterHD: currentFilters.HD, filter4K: currentFilters["4K"], filterSubtitle: currentFilters.Subtitle, filterUncensored: currentFilters.Uncensored, sortOrder: enabledSortOrder }); $("#magnets-span").text("取消磁力过滤"); await storageManager.saveSettingItem("enableMagnetsFilter", YES); } else { highlightMagnetPlugin.showAll(); $("#magnets-span").text("排序与过滤"); await storageManager.saveSettingItem("enableMagnetsFilter", NO); } } catch (e) { console.error("[磁力排序] 应用失败:", e); } closePanel(); }); } async showStatus(carNum2) { const $filterBtn = $("#filterBtn span"), $favoriteBtn = $("#favoriteBtn span"), $hasDownBtn = $("#hasDownBtn span"), $hasWatchBtn = $("#hasWatchBtn span"), hotKeyDisplay = (hotKey) => hotKey ? `(${hotKey})` : ""; $filterBtn.text(`🚫 屏蔽 ${hotKeyDisplay(this.filterHotKey)}`); $favoriteBtn.text(`⭐ 收藏 ${hotKeyDisplay(this.favoriteHotKey)}`); $hasDownBtn.text(`📥️ 已下载 ${hotKeyDisplay(this.hasDownHotKey)}`); $hasWatchBtn.text(`🔍 已观看 ${hotKeyDisplay(this.hasWatchHotKey)}`); const car = await storageManager.getCar(carNum2); if (car) switch (car.status) { case Status_FILTER$1: $filterBtn.text(`🚫 已屏蔽 ${hotKeyDisplay(this.filterHotKey)}`); break; case Status_FAVORITE$1: $favoriteBtn.text(`⭐ 已收藏 ${hotKeyDisplay(this.favoriteHotKey)}`); break; case Status_HAS_DOWN$1: $hasDownBtn.text(`📥️ 已标记下载 ${hotKeyDisplay(this.hasDownHotKey)}`); break; case Status_HAS_WATCH$1: $hasWatchBtn.text(`🔍 已标记观看 ${hotKeyDisplay(this.hasWatchHotKey)}`); } } async favoriteOne() { let pageInfo = this.getPageInfo(); await storageManager.saveCar({ carNum: pageInfo.carNum, url: pageInfo.url, names: pageInfo.actress, actionType: Status_FAVORITE$1, publishTime: pageInfo.publishTime }); this.showStatus(pageInfo.carNum).then(); window.refresh(); utils.closePage(); } async hasDownOne() { let pageInfo = this.getPageInfo(); await storageManager.saveCar({ carNum: pageInfo.carNum, url: pageInfo.url, names: pageInfo.actress, actionType: Status_HAS_DOWN$1, publishTime: pageInfo.publishTime }); this.showStatus(pageInfo.carNum).then(); window.refresh(); utils.closePage(); } async hasWatchOne() { let pageInfo = this.getPageInfo(); await storageManager.saveCar({ carNum: pageInfo.carNum, url: pageInfo.url, names: pageInfo.actress, actionType: Status_HAS_WATCH$1, publishTime: pageInfo.publishTime }); this.showStatus(pageInfo.carNum).then(); window.refresh(); utils.closePage(); } /* searchXunLeiSubtitle(carNum2) { let loadObj = loading(); gmHttp.get(`https://api-shoulei-ssl.xunlei.com/oracle/subtitle?gcid=&cid=&name=${carNum2}`).then((res => { let dataList = res.data; dataList && 0 !== dataList.length ? layer.open({ type: 1, title: "迅雷字幕", content: '\n
    \n
    \n
    \n ', scrollbar: !1, area: utils.getResponsiveArea([ "60%", "70%" ]), anim: -1, success: (layero, index) => { new Tabulator("#xunlei-table-container", { layout: "fitColumns", placeholder: "暂无数据", virtualDom: !0, data: dataList, responsiveLayout: "collapse", responsiveLayoutCollapse: !0, columnDefaults: { headerHozAlign: "center", hozAlign: "center" }, columns: [ { title: "文件名", field: "name", headerSort: !1, responsive: 0 }, { title: "类型", field: "ext", headerSort: !1, responsive: 0 }, { title: "操作", responsive: 0, headerSort: !1, formatter: (cell, formatterParams, onRendered) => { const item = cell.getData(); onRendered((() => { const previewButton = cell.getElement().querySelector(".a-primary"), downButton = cell.getElement().querySelector(".a-success"); previewButton && previewButton.addEventListener("click", (async e => { let url = item.url, name2 = carNum2 + "." + item.ext; this.previewSubtitle(url, name2); })); downButton && downButton.addEventListener("click", (async e => { let url = item.url, name2 = carNum2 + "." + item.ext, content = await gmHttp.get(url); utils.download(content, name2); })); })); return '\n 预览\n 下载\n '; } } ], locale: "zh-cn", langs: { "zh-cn": { pagination: { first: "首页", first_title: "首页", last: "尾页", last_title: "尾页", prev: "上一页", prev_title: "上一页", next: "下一页", next_title: "下一页", all: "所有", page_size: "每页行数" } } } }); utils.setupEscClose(index); } }) : show.error("迅雷中找不到相关字幕!"); })).catch((e => { console.error(e); show.error(e); })).finally((() => { loadObj.close(); })); } */ async filterOne(event, noAlert) { event && event.preventDefault(); let pageInfo = this.getPageInfo(); if (noAlert) { await storageManager.saveCar({ carNum: pageInfo.carNum, url: pageInfo.url, names: pageInfo.actress, actionType: Status_FILTER$1, publishTime: pageInfo.publishTime }); this.showStatus(pageInfo.carNum).then(); window.refresh(); utils.closePage(); layer.closeAll(); this.answerCount = 1; } else utils.q(event, `是否屏蔽${pageInfo.carNum}?`, (async () => { await storageManager.saveCar({ carNum: pageInfo.carNum, url: pageInfo.url, names: pageInfo.actress, actionType: Status_FILTER$1, publishTime: pageInfo.publishTime }); this.showStatus(pageInfo.carNum).then(); window.refresh(); utils.closePage(); }), (() => { this.answerCount = 1; })); } speedVideo() { if ($("#preview-video").is(":visible")) { const videoEl = document.getElementById("preview-video"); if (videoEl) { videoEl.muted = false; videoEl.controls = false; if (videoEl.currentTime + 5 < videoEl.duration) videoEl.currentTime += 5; else { show.info("预览视频结束, 已回到开头"); videoEl.currentTime = 1; } } return; } const iframe = $('iframe[id^="layui-layer-iframe"]'); if (iframe.length > 0) { iframe[0].contentWindow.postMessage("speedVideo", "*"); return; } let $videoPlayBtn = $(".preview-video-container"); if ($videoPlayBtn.length > 0) { $videoPlayBtn[0].click(); const videoEl = document.getElementById("preview-video"); if (videoEl) { videoEl.currentTime += 5; videoEl.muted = false; } } else $("#javTrailersBtn").click(); } hideVideoControls() { $(document).on("mouseenter", "#preview-video", (function() { $(this).prop("controls", true); })); } async bindHotkey() { const handlers = {}; this.filterHotKey && (handlers[this.filterHotKey] = () => { this.answerCount >= 2 ? this.filterOne(null, true) : this.filterOne(null); this.answerCount++; }); this.favoriteHotKey && (handlers[this.favoriteHotKey] = () => this.favoriteOne(null)); this.hasDownHotKey && (handlers[this.hasDownHotKey] = () => this.hasDownOne()); this.hasWatchHotKey && (handlers[this.hasWatchHotKey] = () => this.hasWatchOne()); this.speedVideoHotKey && (handlers[this.speedVideoHotKey] = () => this.speedVideo()); const registerHotkey = (key, handler) => { HotkeyManager.registerHotkey(key, ((event) => { const activeElement = document.activeElement; "INPUT" === activeElement.tagName || "TEXTAREA" === activeElement.tagName || activeElement.isContentEditable || (window.isDetailPage ? handler() : ((message) => { const childIframe = $(".layui-layer-content iframe"); if (0 === childIframe.length) return false; childIframe[0].contentWindow.postMessage(message, "*"); })(key)); })); }; window.isDetailPage && window.addEventListener("message", ((event) => { handlers[event.data] && handlers[event.data](); })); Object.entries(handlers).forEach((([key, handler]) => { registerHotkey(key, handler); })); } async previewSubtitle(url, name2) { if (!url) { console.error("未提供文件URL"); return; } const fileExt = url.split(".").pop().toLowerCase(); if ("ass" === fileExt || "srt" === fileExt) try { let resText = await gmHttp.get(url), title = "字幕预览"; "ass" === fileExt ? title = "ASS字幕预览 - " + name2 : "srt" === fileExt && (title = "SRT字幕预览 - " + name2); const lines = resText.split("\n"); let numberedContent = ""; const maxLineNumberLength = String(lines.length).length; lines.forEach(((line, index) => { const paddedLineNumber = String(index + 1).padStart(maxLineNumberLength, " "); numberedContent += `${paddedLineNumber}. ${line} `; })); const finalContent = numberedContent; layer.open({ type: 1, title, area: ["80%", "80%"], scrollbar: false, content: `
    ${finalContent}
    `, btn: ["下载", "关闭"], btn1: function(index, layero, that) { utils.download(resText, name2); return false; } }); } catch (error) { show.error(`预览失败: ${error.message}`); console.error("预览字幕文件出错:", error); } else show.error("仅支持预览ASS和SRT字幕文件"); } } class BlacklistPlugin extends BasePlugin { getName() { return "BlacklistPlugin"; } async addBlacklist(event) { let tempEvent = { clientX: event.clientX, clientY: event.clientY + 80 }; const hasAddBlacklist = $("#addBlacklistBtn span").text().includes("已加入"); let dataInfo, title; if (currentHref$1.includes("/tags")) { const urlObj = new URL(currentHref$1); urlObj.searchParams.delete("page"); const checkTag = $("#jhs-check-tag").text().trim(); dataInfo = { starId: "no-" + checkTag, name: "虚拟演员-" + checkTag, allName: ["虚拟演员"], role: "虚拟演员", movieType: "虚拟演员", blacklistUrl: urlObj.toString() }; title = `是否将分类 ${checkTag} 加入到黑名单中?`; hasAddBlacklist && (title = `分类 ${checkTag} 已在黑名单中, 是否从当前页开始追加屏蔽?`); } else { dataInfo = this.getActressPageInfo(); title = `是否将该演员 ${dataInfo.name} 加入到黑名单中?`; hasAddBlacklist && (title = `演员 ${dataInfo.name} 已在黑名单中, 是否从当前页开始追加屏蔽?`); } const { starId, name: name2, allName, role, movieType, blacklistUrl } = dataInfo; currentHref$1.includes("page") && !currentHref$1.includes("page=1") && (title += "
    注意: 当前页面非第一页, 屏蔽数据将从此页面开始"); if (isJavBus$1) { const afterStarParts = currentHref$1.split("/star/")[1].split("/"); if (afterStarParts.length > 1) { parseInt(afterStarParts[1]) > 1 && (title += "
    注意: 当前页面非第一页, 屏蔽数据将从此页面开始"); } } utils.q(tempEvent, title, (async () => { const taskPlugin = this.getBean("TaskPlugin"); navigator.locks.request(taskPlugin.singleTaskKey, { ifAvailable: true }, (async (lock) => { if (lock) { this.loadObj = loading(); try { await storageManager.addBlacklistItem({ starId, name: name2, allName, role, movieType, url: blacklistUrl }); await this.filterActorVideo(name2, starId); const toast = show.ok(`屏蔽结束,是否跳转到最后一页: ${this.lastPageLink || "无"}`, { duration: -1, close: true, onClick: () => { toast.closeShow(); this.lastPageLink && (window.location.href = this.lastPageLink); } }); } catch (e) { clog.error(e); const toast = show.error("发生错误, 是否填转到解析失败的那一页? (点击并跳转)", { duration: -1, close: true, onClick: () => { toast.closeShow(); window.location.href = this.nextPageLink; } }); } finally { this.loadObj.close(); } } else show.error("当前有定时任务在后台执行中, 无法发起此操作"); })).catch(((error) => { console.error("锁任务出现错误:", error); clog.error("锁任务出现错误:", error); })); })); } async resetBtnTip() { const taskPlugin = this.getBean("TaskPlugin"), lastCheckBlacklistTimeStr = localStorage.getItem(taskPlugin.lastCheckBlacklistTimeKey) || "无", checkBlacklist_intervalTime = await storageManager.getSetting("checkBlacklist_intervalTime", 12); this.checkBlacklist_ruleTime = await storageManager.getSetting("checkBlacklist_ruleTime", 8760); $("#checkBlacklistBtn").attr("data-tip", `上次检测时间: ${lastCheckBlacklistTimeStr}; 检测间隔时间: ${checkBlacklist_intervalTime}小时`); } async openBlacklistDialog() { const taskPlugin = this.getBean("TaskPlugin"), settingObj = await storageManager.getSetting(); let html = `
    ${this.blacklistSvg}  手动检测黑名单 ${this.removeSvg}    清理数据 ${this.settingSvg}    配置
    重置
    `; layer.open({ type: 1, title: "演员黑名单", content: html, scrollbar: false, area: utils.getResponsiveArea(["80%", "90%"]), anim: -1, success: async (layero) => { await this.loadTableData(); $(".layui-layer-content").on("click", "#cleanQueryBtn", (async (event) => { $("#searchValue").val(""); $("#dataType").val(""); $("#statusType").val(""); await this.reloadTable(); })).on("focusout keydown", "#searchValue", (async (event) => { if ("focusout" === event.type || "Enter" === event.key) { "Enter" === event.key && event.preventDefault(); if ("keydown" === event.type && "Enter" !== event.key) return; $("#dataType").val(""); await this.reloadTable(); } })).on("change", "#dataType", (async () => { $("#searchValue").val(""); await this.reloadTable(); })).on("change", "#statusType", (async () => { await this.reloadTable(); })).on("change", "#urlType", (async () => { await this.reloadTable(); })).on("click", "#toSetting", (() => { this.getBean("SettingPlugin").openSettingDialog("task-panel", (() => { $("#setting-blacklist").css({ border: "1px solid #f40" }); })); })).on("click", "#clearKeywordBlacklist", (async () => { await this.clearKeywordBlacklist(); })).on("click", ".open-url", ((event) => { event.preventDefault(); const $el = $(event.currentTarget), url = $el.attr("data-url"), name2 = $el.attr("data-name"); utils.openPage(url, name2, true, event); })).on("click", "#checkBlacklistBtn", ((event) => { utils.q({ clientX: event.clientX, clientY: event.clientY + 20 }, "是否手动检测黑名单?", (() => { navigator.locks.request(taskPlugin.singleTaskKey, { ifAvailable: true }, (async (lock) => { if (lock) { await taskPlugin.loadConfig(); await taskPlugin.checkBlacklist(true); } else show.error("当前有定时任务在后台执行中, 无法发起手动任务"); })).catch(((error) => { console.error("锁任务出现错误:", error); clog.error("锁任务出现错误:", error); })); })); })); }, end: () => { if (this.tableObj) { this.tableObj.destroy(); this.tableObj = null; } window.refresh(); } }); } async reloadTable() { if (!this.tableObj) return; const tableData = await this.getTableData(); this.tableObj.setData(tableData); } async getTableData() { const taskPlugin = this.getBean("TaskPlugin"), blacklist = await storageManager.getBlacklist(), blacklistCarList = await storageManager.getBlacklistCarList(), searchValue = $("#searchValue").val(), statusType = $("#statusType").val(), $dataType = $("#dataType"), dataType = $dataType.val(), urlType = $("#urlType").val(), blacklistCount = blacklist.length; let actorCount = 0, actressCount = 0, noStarCount = 0; const filteredBlacklist = blacklist.map(((item) => { "actor" === item.role ? actorCount++ : "actress" === item.role ? actressCount++ : "虚拟演员" === item.role && noStarCount++; let isUnCheck = false; item.lastPublishTime && (isUnCheck = !taskPlugin.isUnnecessaryCheck(item.lastPublishTime, this.checkBlacklist_ruleTime)); return { ...item, isUnCheck }; })).filter(((item) => !(searchValue && !item.name.includes(searchValue)) && (("normal" !== statusType || !item.isUnCheck) && (!("stop" === statusType && !item.isUnCheck) && (dataType ? item.role === dataType : !("hasT" === urlType && !item.url.includes("t=") && !item.url.includes("/tags")) && ("noT" !== urlType || !item.url.includes("t=") && !item.url.includes("/tags"))))))); $dataType.html(` `); $dataType.val(dataType); const carListMap = /* @__PURE__ */ new Map(); for (const car of blacklistCarList) { const starId = car.starId; carListMap.has(starId) || carListMap.set(starId, []); carListMap.get(starId).push(car); } const finalResult = filteredBlacklist.map(((item) => { const starId = item.starId, carList = carListMap.get(starId) || []; return { ...item, carList, count: carList.length }; })); this.currentCarCount = finalResult.reduce(((accumulator, currentObject) => accumulator + (currentObject.count || 0)), 0); return finalResult; } async loadTableData() { this.checkBlacklist_ruleTime = await storageManager.getSetting("checkBlacklist_ruleTime") || 8760; const tableData = await this.getTableData(); this.tableObj = new Tabulator("#table-container", { layout: "fitColumns", placeholder: "暂无数据", virtualDom: true, data: tableData, pagination: true, paginationMode: "local", paginationSize: 20, paginationSizeSelector: [20, 50, 100, 1e3, 99999], paginationCounter: (pageSize, currentRow, currentPage, totalRows, totalPages) => `演员: ${totalRows}    番号总数: ${this.currentCarCount} `, responsiveLayout: "collapse", responsiveLayoutCollapse: true, columnDefaults: { headerHozAlign: "center", hozAlign: "center" }, index: "starId", columns: [{ title: "演员", field: "name", sorter: "string", minWidth: 100, responsive: 0, headerSort: false, formatter: (cell, formatterParams, onRendered) => { const item = cell.getData(); return `${item.name}`; } }, { title: "性别角色", field: "role", sorter: "string", width: 120, responsive: 5, formatter: (cell, formatterParams, onRendered) => { const role = cell.getData().role; let content = role; "actor" === role ? content = "男演员" : "actress" === role && (content = "女演员"); return content; } }, { title: "影视类别", field: "movieType", sorter: "string", width: 120, responsive: 5, formatter: (cell, formatterParams, onRendered) => { const movieType = cell.getData().movieType; let content = movieType; "censored" === movieType ? content = "有码" : "uncensored" === movieType && (content = "无码"); return content; } }, { title: "屏蔽类型", field: "url", sorter: "string", minWidth: 120, responsive: 4, visible: isJavDb$1, formatter: (cell, formatterParams, onRendered) => { const item = cell.getData(); if ("虚拟演员" === item.role) { const nameSplitList = item.name.split("-"); return `${nameSplitList.length > 0 ? nameSplitList[1] : item.name}`; } const tParam = utils.getUrlParam(item.url, "t"); if (!tParam) return "所有分类"; { const tParamList = tParam.toString().split(","); let selectTypeStr = ""; tParamList.forEach(((i) => { let value = categoryMap[i]; selectTypeStr += value ? value + " " : "未知类别:" + i + " "; })); selectTypeStr = selectTypeStr.trim(); if (selectTypeStr) return `${selectTypeStr}`; } } }, { title: "番号数量", field: "count", sorter: "number", width: 170, responsive: 1 }, { title: "创建时间", field: "createTime", sorter: "string", width: 170, responsive: 5 }, { title: "最后发行时间", field: "lastPublishTime", sorter: "string", width: 170, responsive: 1 }, { title: "状态", field: "isUnCheck", sorter: "string", width: 120, responsive: 1, formatter: (cell, formatterParams, onRendered) => { let dataTip = "", content = "正常检测"; if (cell.getData().isUnCheck) { dataTip = `停更${this.checkBlacklist_ruleTime / 24 / 365}年以上, 下轮任务不再进行检测`; content = "停止检测"; } return `${content}`; } }, { title: "操作", sorter: "string", cssClass: "action-cell-dropdown", minWidth: 150, responsive: 0, headerSort: false, formatter: (cell, formatterParams, onRendered) => { const item = cell.getData(); onRendered((() => { var _a2, _b; null == (_a2 = cell.getElement().querySelector(".delete-btn")) || _a2.addEventListener("click", ((e) => { const name2 = item.name, starId = item.starId; name2 ? starId ? utils.q(e, `是否移除对 ${name2} 的屏蔽?`, (async () => { await storageManager.removeBlacklistCarList(starId); await storageManager.deleteBlacklistItem(starId); show.info("操作成功"); this.reloadTable().then(); })) : show.error("获取starId失败") : show.error("获取名称失败"); })); null == (_b = cell.getElement().querySelector(".keyword-btn")) || _b.addEventListener("click", ((e) => { const prefixMap = item.carList.reduce(((dataMap, carItem) => { const prefix = carItem.carNum.split("-")[0] + "-"; dataMap[prefix] = (dataMap[prefix] || 0) + 1; return dataMap; }), {}), sortedPrefixList = Object.entries(prefixMap).map((([prefix, count]) => ({ prefix, count }))).sort(((a, b) => b.count - a.count)); console.log(sortedPrefixList); })); })); return '\n \n ✂️ 删除 \n '; } }], initialSort: [{ column: "createTime", dir: "desc" }], locale: "zh-cn", langs: { "zh-cn": { pagination: { first: "首页", first_title: "首页", last: "尾页", last_title: "尾页", prev: "上一页", prev_title: "上一页", next: "下一页", next_title: "下一页", all: "所有", page_size: "每页行数" } } } }); } getCurrentStarUrl() { let urlWithoutSortType = window.location.href.replace(/([&?])sort_type=[^&]+(&|$)/, "$1"); urlWithoutSortType = urlWithoutSortType.replace(/[&?]$/, ""); urlWithoutSortType = urlWithoutSortType.replace(/\?&/, "?"); let urlWithoutPageNumber = urlWithoutSortType; urlWithoutPageNumber = urlWithoutPageNumber.replace(/([&?])page=\d+(&|$)/, "$1"); urlWithoutPageNumber = urlWithoutPageNumber.replace(/[&?]$/, ""); urlWithoutPageNumber = urlWithoutPageNumber.replace(/\?&/, "?"); urlWithoutPageNumber = urlWithoutPageNumber.replace(/\/(\d+)(?:\/(\d+))?(\?|$)/, ((match, id, page, suffix) => void 0 !== page ? `/${id}${suffix}` : match)); return urlWithoutPageNumber; } parseUrlId(url) { if (!url) throw new Error("url未传入"); return new URL(url).pathname.split("/").filter(((segment) => "" !== segment.trim())).pop(); } async filterAllVideo(actorName, $dom) { let movieList, nextPageLink; if ($dom) { isJavBus$1 && $dom.find(".avatar-box").length > 0 && $dom.find(".avatar-box").parent().remove(); movieList = $dom.find(this.getSelector().requestDomItemSelector); nextPageLink = $dom.find(this.getSelector().nextPageSelector).attr("href"); } else { movieList = $(this.getSelector().itemSelector); nextPageLink = $(this.getSelector().nextPageSelector).attr("href"); } if (nextPageLink && 0 === movieList.length) { show.error("解析列表失败"); throw new Error("解析列表失败"); } for (const element of movieList) { const $item = $(element), { carNum: carNum2, url, publishTime } = this.getBoxCarInfo($item); if (url && carNum2) try { if (await storageManager.getCar(carNum2)) continue; await storageManager.saveCar({ carNum: carNum2, url, names: actorName, actionType: Status_FILTER$1, publishTime }); clog.log("屏蔽演员番号", actorName, carNum2); } catch (error) { console.error(`保存失败 [${carNum2}]:`, error); } } if (nextPageLink) { show.info("请不要关闭窗口, 正在解析下一页:" + nextPageLink); await new Promise(((resolve) => setTimeout(resolve, 500))); const html = await gmHttp.get(nextPageLink), parser = new DOMParser(), next$dom = $(parser.parseFromString(html, "text/html")); await this.filterAllVideo(actorName, next$dom); } else { show.ok("执行结束!"); window.refresh(); } } async filterActorVideo(actorName, starId, $dom) { let { nextPageLink } = await this.parseAndSaveFilterInfo($dom, actorName, starId); this.nextPageLink = nextPageLink; if (nextPageLink) { this.lastPageLink = nextPageLink; show.info("请不要关闭窗口, 正在解析下一页:" + nextPageLink); let next$dom; const pageNum = utils.getUrlParam(nextPageLink, "page") || 0, beyond60Plugin = this.getBean("Beyond60Plugin"); if (isJavDb$1 && beyond60Plugin && pageNum > 60) { let { html, nextUrl, hasMore } = await beyond60Plugin.handleBeyond60(nextPageLink), mergeHtml = `
    ${html}
    ${nextUrl ? `` : ""} `; next$dom = utils.htmlTo$dom(mergeHtml); } else { clog.log("正在请求下一页内容:", nextPageLink); const html = await gmHttp.get(nextPageLink); next$dom = utils.htmlTo$dom(html); } await this.filterActorVideo(actorName, starId, next$dom); } else { show.ok("执行结束!"); window.refresh(); } } async parseAndSaveFilterInfo($dom, actorName, starId) { let movieList, nextPageLink; if ($dom) { let tempIsJavBus = false, selectorType = "javdb"; if ($dom.text().includes("javbus")) { tempIsJavBus = true; selectorType = "javbus"; } tempIsJavBus && $dom.find(".avatar-box").length > 0 && $dom.find(".avatar-box").parent().remove(); movieList = $dom.find(this.getSelector(selectorType).requestDomItemSelector); nextPageLink = $dom.find(this.getSelector(selectorType).nextPageSelector).attr("href"); } else { movieList = $(this.getSelector().itemSelector); nextPageLink = $(this.getSelector().nextPageSelector).attr("href"); } if (nextPageLink && 0 === movieList.length) return { nextPageLink: null, lastPublishTime: null }; const filterKeywordList = await storageManager.getTitleFilterKeyword(); let carDataList = [], lastPublishTime = null; for (const element of movieList) { const $item = $(element), { carNum: carNum2, url, publishTime, title } = this.getBoxCarInfo($item); lastPublishTime || (lastPublishTime = publishTime); filterKeywordList.find(((kw) => title.includes(kw) || carNum2.startsWith(kw))) || url && carNum2 && carDataList.push({ carNum: carNum2, url, names: actorName, actionType: Status_FILTER$1, starId, publishTime }); } await storageManager.batchSaveBlacklistCarList(carDataList); return { nextPageLink, lastPublishTime }; } async clearKeywordBlacklist() { let filterKeywordList = await storageManager.getTitleFilterKeyword(); const processedKeywordsSet = /* @__PURE__ */ new Set(); filterKeywordList.forEach(((item) => { /^[a-z]{2,}-/i.test(item) && processedKeywordsSet.add(item); })); if (0 === processedKeywordsSet.size) { show.info("没有需要清理的关键词数据"); return; } const carList = await storageManager.getCarList(), blacklistCarList = await storageManager.getBlacklistCarList(), carNumSet = new Set(carList.map(((car) => car.carNum))), keywords = Array.from(processedKeywordsSet), waitRemoveCarNumList = [], removableCarData = []; for (const car of blacklistCarList) { const carNum2 = car.carNum; let matched = false; for (const keyword of keywords) if (carNum2.startsWith(keyword)) { removableCarData.push({ carNum: carNum2, names: car.names, matchedKeyword: keyword }); waitRemoveCarNumList.push(carNum2); matched = true; break; } if (!matched && carNumSet.has(carNum2)) { removableCarData.push({ carNum: carNum2, names: car.names, matchedKeyword: "已在鉴定记录中" }); waitRemoveCarNumList.push(carNum2); } } const carNumCount = waitRemoveCarNumList.length; 0 !== carNumCount ? layer.open({ type: 1, title: `确认清理黑名单数据 (共 ${carNumCount} 个番号)`, area: ["80%", "70%"], anim: -1, content: `

    以下匹配到 ${carNumCount} 个黑名单番号存在于屏蔽关键词中, 是否移除?

    `, btn: ["确定清理", "取消"], success: function(layero, index) { new Tabulator("#wait-remove-table", { data: removableCarData, virtualDom: true, layout: "fitColumns", pagination: true, paginationMode: "local", paginationSize: 50, paginationSizeSelector: [50, 100, 1e3, 99999], columns: [{ title: "演员", field: "names" }, { title: "番号", field: "carNum" }, { title: "匹配关键词", field: "matchedKeyword" }], locale: "zh-cn", initialSort: [{ column: "names", dir: "asc" }], langs: { "zh-cn": { pagination: { first: "首页", first_title: "首页", last: "尾页", last_title: "尾页", prev: "上一页", prev_title: "上一页", next: "下一页", next_title: "下一页", all: "所有", page_size: "每页行数" } } } }); }, yes: async (index) => { const removedCount = await storageManager.batchRemoveBlacklistCars(waitRemoveCarNumList); if (0 === removedCount) show.error("移除失败"); else { show.ok(`🎉 清理完成!已移除${removedCount}个相关黑名单番号数据`); await this.reloadTable(); layer.close(index); } }, btn2: function(index) { layer.close(index); } }) : show.info("没有需要清理的黑名单番号数据"); } } class ListPagePlugin extends BasePlugin { constructor() { super(...arguments); __publicField2(this, "currentPageFilterCount", 0); __publicField2(this, "currentPageFavoriteCount", 0); __publicField2(this, "currentPageHasDownCount", 0); __publicField2(this, "currentPageHasWatchCount", 0); __publicField2(this, "currentPageKeywordFilterCount", 0); __publicField2(this, "currentPageActorFilterCount", 0); __publicField2(this, "currentPageWaitCheckCount", 0); __publicField2(this, "currentPageTotalCount", 0); __publicField2(this, "cache", localStorage.getItem("jhs_translate") ? JSON.parse(localStorage.getItem("jhs_translate")) : {}); __publicField2(this, "writeQueue", Promise.resolve()); } getName() { return "ListPagePlugin"; } async handle() { this.cleanRepeatId(); this.replaceHdImg(); this.addJumpPageControl(); this.fixBusTitleBox(); await this.doFilter(); this.bindClick().then(); this.bindListPageHotKey().then(); this.rememberTagExpand(); $(this.getSelector().itemSelector + " a").attr("target", "_blank"); this.checkDom(); } rememberTagExpand() { if (!window.location.href.includes("actors")) return; const $expandButton = $(".tag-expand"); if (0 === $expandButton.length) return; const isExpanded = "true" === localStorage.getItem("jhs_tag_expand"), $actorTagsContent = $(".actor-tags .content"); isExpanded && $actorTagsContent.hasClass("collapse") && $expandButton[0].click(); $expandButton.on("click", (function() { const newState = !$actorTagsContent.hasClass("collapse"); localStorage.setItem("jhs_tag_expand", newState.toString()); })); } checkDom() { if (!window.isListPage) return; const selector = this.getSelector(), targetNode = document.querySelector(selector.boxSelector); if (!targetNode) { console.error("没有找到容器节点!"); return; } const observer = new MutationObserver((async (mutations) => { observer.disconnect(); try { this.replaceHdImg(); this.addJumpPageControl(); this.fixBusTitleBox(); this.getBean("CoverButtonPlugin").addSvgBtn().then(); await this.doFilter(); await this.getBean("ListPageButtonPlugin").sortItems(); $(this.getSelector().itemSelector + " a").attr("target", "_blank"); this.getBean("AutoPagePlugin").checkLoad(); } finally { observer.observe(targetNode, config); } })), config = { childList: true, subtree: false }; observer.observe(targetNode, config); } fixBusTitleBox() { if (!isJavBus$1) return; $(this.getSelector().itemSelector).toArray().forEach(((ele) => { var _a2; let $box2 = $(ele); if ($box2.find(".avatar-box").length > 0) return; const title = (null == (_a2 = $box2.find("img").attr("title")) ? void 0 : _a2.trim()) || ""; $box2.find(".photo-info span:first").contents().first().wrap(`${title}`); $box2.find("br").remove(); })); } cleanRepeatId() { if (!isJavBus$1) return; $("#waterfall_h").removeAttr("id").attr("id", "no-page"); const $waterfalls = $('[id="waterfall"]'); 0 !== $waterfalls.length && $waterfalls.each((function() { const $current = $(this); if (!$current.hasClass("masonry")) { $current.children().insertAfter($current); $current.remove(); } })); } async doFilter() { if (!window.isListPage) return; let movieList = $(this.getSelector().itemSelector).toArray(); if (movieList.length) { await this.filterMovieList(movieList); await this.getBean("WangPan115MatchPlugin").matchMovieList(movieList); isJavBus$1 && await this.getBean("BusImgPlugin").logImageHeightsByRow(); } } async filterMovieList(movieList) { utils.time("累计耗费时间"); utils.time("读取数据耗时"); const [carList, filterKeywordList, blacklist, blacklistCarList, settingObj] = await Promise.all([storageManager.getCarList(), storageManager.getTitleFilterKeyword(), storageManager.getBlacklist(), storageManager.getBlacklistCarList(), storageManager.getSetting()]), t1 = utils.time("读取数据耗时"), carNumSets = carList.reduce(((dataMap, item) => { const statusKey = item.status; dataMap.hasOwnProperty(statusKey) && dataMap[statusKey].add(item.carNum); return dataMap; }), { [Status_FILTER$1]: /* @__PURE__ */ new Set(), [Status_FAVORITE$1]: /* @__PURE__ */ new Set(), [Status_HAS_DOWN$1]: /* @__PURE__ */ new Set(), [Status_HAS_WATCH$1]: /* @__PURE__ */ new Set() }); utils.time("组装数据耗时"); const starIdToRoleMap = new Map(blacklist.map(((item) => [item.starId, item.role]))), { actorCarNumToNameMap, actressCarNumToNameMap } = blacklistCarList.reduce(((dataMap, carItem) => { const role = starIdToRoleMap.get(carItem.starId); if (!role) { clog.error("黑名单数据源丢失演员信息", carItem); return dataMap; } const targetMap = "actor" === role ? dataMap.actorCarNumToNameMap : dataMap.actressCarNumToNameMap; targetMap.has(carItem.carNum) || targetMap.set(carItem.carNum, carItem.names); return dataMap; }), { actorCarNumToNameMap: /* @__PURE__ */ new Map(), actressCarNumToNameMap: /* @__PURE__ */ new Map() }), t2 = utils.time("组装数据耗时"), settings = { showFilterItem: (null == settingObj ? void 0 : settingObj.showFilterItem) ?? NO, showFilterActorItem: (null == settingObj ? void 0 : settingObj.showFilterActorItem) ?? NO, showFilterKeywordItem: (null == settingObj ? void 0 : settingObj.showFilterKeywordItem) ?? NO, showFavoriteItem: (null == settingObj ? void 0 : settingObj.showFavoriteItem) ?? YES, showHasDownItem: (null == settingObj ? void 0 : settingObj.showHasDownItem) ?? YES, showHasWatchItem: (null == settingObj ? void 0 : settingObj.showHasWatchItem) ?? YES, showAllItem: (null == settingObj ? void 0 : settingObj.showAllItem) ?? NO, tagPosition: (null == settingObj ? void 0 : settingObj.tagPosition) || "rightTop", movieShowType: (null == settingObj ? void 0 : settingObj.movieShowType) || "hide" }; this.currentPageFilterCount = 0; this.currentPageFavoriteCount = 0; this.currentPageHasDownCount = 0; this.currentPageHasWatchCount = 0; this.currentPageKeywordFilterCount = 0; this.currentPageActorFilterCount = 0; this.currentPageWaitCheckCount = 0; this.currentPageTotalCount = 0; utils.time("处理页面耗时"); await Promise.all(movieList.map((async (ele) => { let $box2 = $(ele); if (isJavBus$1 && $box2.find(".avatar-box").length > 0) return; const { carNum: carNum2, title } = this.getBoxCarInfo($box2), { filter, favorite, hasDown, hasWatch } = carNumSets, isFavorite = favorite.has(carNum2), isHasDown = hasDown.has(carNum2), isHasWatch = hasWatch.has(carNum2), isFiltered = filter.has(carNum2), isFilterActorMale = actorCarNumToNameMap.has(carNum2), isFilterActorFemale = actressCarNumToNameMap.has(carNum2), isFilterActor = isFilterActorMale || isFilterActorFemale, foundKeyword = filterKeywordList.find(((kw) => title.includes(kw) || carNum2.startsWith(kw))), isFilterKeyword = !!foundKeyword; if (!isSearchPage) { let shouldHide = settings.showFavoriteItem === NO && isFavorite || settings.showHasDownItem === NO && isHasDown || settings.showHasWatchItem === NO && isHasWatch || settings.showFilterItem === NO && isFiltered && !(isFavorite || isHasDown || isHasWatch) || settings.showFilterActorItem === NO && isFilterActor || settings.showFilterKeywordItem === NO && isFilterKeyword; if ($box2.attr("data-movieShowType") !== settings.movieShowType) { $box2.css("border", ""); $box2.children().css("visibility", ""); $box2.removeAttr("data-hide"); $box2.show(); } const isCurrentlyHidden = $box2.attr("data-hide") === YES; settings.showAllItem === YES && (shouldHide = false); if (shouldHide !== isCurrentlyHidden) { shouldHide ? $box2.attr("data-hide", YES) : $box2.removeAttr("data-hide"); if ("hide" === settings.movieShowType) shouldHide ? $box2.hide() : $box2.show(); else { if ("visibility" !== settings.movieShowType) throw new Error("movieShowType值有误:" + settings.movieShowType); { const $content = $box2.children(), borderStyle = shouldHide ? "1px solid rgb(192 176 176)" : "none", visibilityValue = shouldHide ? "hidden" : "visible"; $box2.css("border", borderStyle); $content.css("visibility", visibilityValue); } } $box2.attr("data-movieShowType") !== settings.movieShowType && $box2.attr("data-movieShowType", settings.movieShowType); } } let tag = TAG_STYLES.IS_WAIT_CHECK, filterReason = null; if (isFiltered) tag = TAG_STYLES.IS_FILTERED; else if (isFavorite) tag = TAG_STYLES.IS_FAVORITE; else if (isHasDown) tag = TAG_STYLES.IS_HAS_DOWN; else if (isHasWatch) tag = TAG_STYLES.IS_HAS_WATCH; else if (isFilterKeyword) { tag = TAG_STYLES.IS_KEYWORD_FILTER; filterReason = foundKeyword || "未知"; } else if (isFilterActorMale) { tag = TAG_STYLES.IS_ACTOR_FILTER; filterReason = actorCarNumToNameMap.get(carNum2) || ""; } else if (isFilterActorFemale) { tag = TAG_STYLES.IS_ACTRESS_FILTER; filterReason = actressCarNumToNameMap.get(carNum2) || ""; } filterReason || (filterReason = tag.reasonType); tag.isCounted && this[tag.countKey]++; this.currentPageTotalCount++; $box2.find(".status-tag").remove(); const tagPositionCss = "rightTop" === settings.tagPosition ? "right: 0; top:5px;" : "left: 0; top:5px;"; if (tag.text) { const tagHtml = isJavDb$1 ? ` ${tag.text} ` : ` ${tag.text} `; isJavDb$1 && $box2.find(".tags").append(tagHtml); if (isJavBus$1) { const $itemTag = $box2.find(".item-tag"); $itemTag.length ? $itemTag.append(tagHtml) : $box2.find(".photo-info > span > div").append(tagHtml); } } await this.translate($box2); }))); const t3 = utils.time("处理页面耗时"), t4 = utils.time("累计耗费时间"); $("#waitDownBtn span").text(`打开已收藏 (${carNumSets.favorite.size})`); clog.log(`
    ${t1} ${t2}
    ${t3} ${t4}
    项目 数量 项目 数量
    屏蔽单番号 ${this.currentPageFilterCount} 收藏 ${this.currentPageFavoriteCount}
    屏蔽演员 ${this.currentPageActorFilterCount} 已下载 ${this.currentPageHasDownCount}
    屏蔽关键词 ${this.currentPageKeywordFilterCount} 已观看 ${this.currentPageHasWatchCount}
    待鉴定 ${this.currentPageWaitCheckCount}
    总数 ${this.currentPageTotalCount}
    `); } async bindClick() { let selector = this.getSelector(); $(selector.boxSelector).on("click", ".item img", (async (event) => { event.preventDefault(); event.stopPropagation(); if ($(event.target).closest("div.meta-buttons").length) return; const $box2 = $(event.target).closest(".item"), { carNum: carNum2, aHref } = this.getBoxCarInfo($box2); let dialogOpenDetail = await storageManager.getSetting("dialogOpenDetail", YES); if (carNum2.includes("FC2-")) { let movieId = this.parseMovieId(aHref); this.getBean("Fc2Plugin").openFc2Dialog(movieId, carNum2, aHref); } else if (dialogOpenDetail === YES) { utils.openPage(aHref, carNum2, true, event); this.$currentImage = null; } else window.open(aHref); })); $(selector.boxSelector).on("click", ".item video", (async (event) => { const video = event.currentTarget; video.paused ? video.play().catch(((e) => console.error("播放失败:", e))) : video.pause(); event.preventDefault(); event.stopPropagation(); })); $(selector.boxSelector).on("click", ".item .video-title", (async (event) => { if ($(event.target).closest('[class^="jhs-match-"]').length) return; const $box2 = $(event.currentTarget).closest(".item"), { carNum: carNum2, aHref } = this.getBoxCarInfo($box2); if (carNum2.includes("FC2-")) { event.preventDefault(); let movieId = this.parseMovieId(aHref); this.getBean("Fc2Plugin").openFc2Dialog(movieId, carNum2, aHref); } })); $(selector.boxSelector).on("contextmenu", ".item img, .item video", (async (event) => { event.preventDefault(); const $box2 = $(event.target).closest(".item"), { carNum: carNum2, url, publishTime } = this.getBoxCarInfo($box2); let $actor = isJavDb$1 ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10"), actorName = ""; $actor.length && (actorName = $actor.text().trim().split(",")[0].replace("(無碼)", "")); utils.q(event, `是否屏蔽番号 ${carNum2}?`, (async () => { setTimeout((async () => { actorName || (actorName = await this.parseActressName(url)); await storageManager.saveCar({ carNum: carNum2, url, names: actorName, actionType: Status_FILTER$1, publishTime }); window.refresh(); show.ok("操作成功"); })); })); })); } async parseActressName(url) { let actorName = null; if (await storageManager.getSetting("enableSaveActressCarInfo", NO) === YES) { clog.debug("鉴定补录演员信息-已启用, 开始解析详情页"); clog.debug("开始解析演员详情页", url); const html = await gmHttp.get(url), $dom = utils.htmlTo$dom(html); isJavDb$1 ? actorName = $dom.find(".female").prev().map(((i, el) => $(el).text())).get().join(" ") : isJavBus$1 && (actorName = $dom.find('span[onmouseover*="star_"] a').map(((i, el) => $(el).text())).get().join(" ")); clog.debug("解析到名称:", actorName); } return actorName; } async bindListPageHotKey() { this.$currentImage = null; $(document).on("mouseenter", this.getSelector().coverImgSelector, ((e) => { this.$currentImage = $(e.currentTarget); })).on("mouseleave", this.getSelector().coverImgSelector, (() => { this.$currentImage = null; })); let settingObj = await storageManager.getSetting(); this.filterHotKey = settingObj.filterHotKey; this.favoriteHotKey = settingObj.favoriteHotKey; this.hasDownHotKey = settingObj.hasDownHotKey; this.hasWatchHotKey = settingObj.hasWatchHotKey; this.enableImageHotKey = settingObj.enableImageHotKey || NO; this.clogHotKey = settingObj.clogHotKey; this.foldCategoryHotKey = settingObj.foldCategoryHotKey; this.showFilterItemHotKey = settingObj.showFilterItemHotKey; this.showFilterActorItemHotKey = settingObj.showFilterActorItemHotKey; this.showFilterKeywordItemHotKey = settingObj.showFilterKeywordItemHotKey; this.showFavoriteItemHotKey = settingObj.showFavoriteItemHotKey; this.showHasDownItemHotKey = settingObj.showHasDownItemHotKey; this.showHasWatchItemHotKey = settingObj.showHasWatchItemHotKey; this.showAllItemHotKey = settingObj.showAllItemHotKey; const HOTKEY_REGISTRY = { showFilterItemHotKey: "showFilterItem", showFilterActorItemHotKey: "showFilterActorItem", showFilterKeywordItemHotKey: "showFilterKeywordItem", showFavoriteItemHotKey: "showFavoriteItem", showHasDownItemHotKey: "showHasDownItem", showHasWatchItemHotKey: "showHasWatchItem", showAllItemHotKey: "showAllItem" }, registerToggleHotkey = (hotkeyName, settingKey) => { const hotkeyString = this[hotkeyName]; hotkeyString && HotkeyManager.registerHotkey(hotkeyString, (async (event) => { const newVal = await storageManager.getSetting(settingKey) === YES ? NO : YES; await storageManager.saveSettingItem(settingKey, newVal); window.refresh(); })); }; for (const hotkeyName in HOTKEY_REGISTRY) if (HOTKEY_REGISTRY.hasOwnProperty(hotkeyName)) { registerToggleHotkey(hotkeyName, HOTKEY_REGISTRY[hotkeyName]); } if (this.enableImageHotKey === NO) return; const handleAction = async (boxInfo, status) => { setTimeout((async () => { let actorName = await this.parseActressName(boxInfo.url); await storageManager.saveCar({ carNum: boxInfo.carNum, url: boxInfo.url, names: actorName, actionType: status, publishTime: boxInfo.publishTime }); window.refresh(); show.ok("操作成功"); })); }, handlers = {}; this.filterHotKey && (handlers[this.filterHotKey] = (boxInfo) => { handleAction(boxInfo, Status_FILTER$1); }); this.favoriteHotKey && (handlers[this.favoriteHotKey] = (boxInfo) => { handleAction(boxInfo, Status_FAVORITE$1); }); this.hasDownHotKey && (handlers[this.hasDownHotKey] = (boxInfo) => { handleAction(boxInfo, Status_HAS_DOWN$1); }); this.hasWatchHotKey && (handlers[this.hasWatchHotKey] = (boxInfo) => { handleAction(boxInfo, Status_HAS_WATCH$1); }); this.clogHotKey && HotkeyManager.registerHotkey(this.clogHotKey, ((event) => { clog.toggleExpandCollapsed(); })); this.foldCategoryHotKey && HotkeyManager.registerHotkey(this.foldCategoryHotKey, ((event) => { const $btn = $("#foldCategoryBtn"); $btn.length && $btn[0].click(); })); const registerImageHotkey = (key, handler) => { HotkeyManager.registerHotkey(key, ((event) => { const activeElement = document.activeElement; if (!("INPUT" === activeElement.tagName || "TEXTAREA" === activeElement.tagName || activeElement.isContentEditable) && this.$currentImage) { const $box2 = this.$currentImage.closest(".item"), boxInfo = this.getBoxCarInfo($box2); handler(boxInfo); } })); }; Object.entries(handlers).forEach((([key, handler]) => { registerImageHotkey(key, handler); })); } replaceHdImg(coverImgNodeList) { coverImgNodeList && "string" == typeof coverImgNodeList.jquery && (coverImgNodeList = coverImgNodeList.toArray()); coverImgNodeList || (coverImgNodeList = document.querySelectorAll(this.getSelector().coverImgSelector)); isJavDb$1 && coverImgNodeList.forEach(((img) => { img.src = img.src.replace("thumbs", "covers"); img.title = ""; })); if (isJavBus$1) { const THUMB_PATH_REGEX = /\/(imgs|pics)\/(thumb|thumbs)\//, IMG_EXT_REGEX = /(\.jpg|\.jpeg|\.png)$/i, replaceWithHd = (img) => { if (img.src && THUMB_PATH_REGEX.test(img.src) && "true" !== img.dataset.hdReplaced) { img.src = img.src.replace(THUMB_PATH_REGEX, "/$1/cover/").replace(IMG_EXT_REGEX, "_b$1"); img.dataset.hdReplaced = "true"; img.dataset.title = img.title; img.title = ""; } }, DMM_THUMB_REGEX = /ps(\.jpg|\.jpeg|\.png)$/i, replaceWithHdForDMM = (img) => { if (img.src && DMM_THUMB_REGEX.test(img.src) && "true" !== img.dataset.hdReplaced) { img.src = img.src.replace(DMM_THUMB_REGEX, "pl$1"); img.dataset.hdReplaced = "true"; img.dataset.title = img.title; img.title = ""; } }; coverImgNodeList.forEach(((img) => { replaceWithHd(img); replaceWithHdForDMM(img); })); } storageManager.getSetting("hoverBigImg", NO).then(((hoverBigImg) => { hoverBigImg === YES && (window.imageHoverPreviewObj ? window.imageHoverPreviewObj.bindEvents() : window.imageHoverPreviewObj = new ImageHoverPreview({ selector: this.getSelector().coverImgSelector })); })); } async translate($box2) { if (await storageManager.getSetting("translateTitle", YES) !== YES) return; let content, carNum2, $title = $box2.find(".video-title"); if (isJavDb$1) { content = $title.contents().filter(((i, node) => 3 === node.nodeType && "" !== node.textContent.trim())).text().trim(); carNum2 = $box2.find(".video-title strong").text().trim(); } else { content = $box2.find("img").attr("data-title").trim(); carNum2 = $box2.find("a").attr("href").split("/").filter(Boolean).pop().trim(); } if (this.cache[carNum2]) { let _this = this; $title.attr("data-original-title", content); $title.contents().each((function() { 3 === this.nodeType && "" !== this.textContent.trim() && (this.textContent = " " + _this.cache[carNum2] + " "); })); $title.attr("title", _this.cache[carNum2]); } else translateText(content).then(((result) => { $title.attr("data-original-title", content); if (isJavDb$1) { $title.contents().each((function() { 3 !== this.nodeType || "" === this.textContent.trim() || this.textContent.includes(carNum2) || (this.textContent = " " + result + " "); })); $title.attr("title", result); } else $title.text(result); this.writeQueue = this.writeQueue.then((() => { this.cache[carNum2] = result; localStorage.setItem("jhs_translate", JSON.stringify(this.cache)); })); })).catch(((error) => { console.error("翻译失败:", error); })); } async revertTranslation() { $(this.getSelector().itemSelector).toArray().forEach(((ele) => { let $box2 = $(ele); const originalContent = $box2.find(".video-title").attr("data-original-title") || $box2.find(".box").attr("title") || $box2.find(".video-title").attr("title") || $box2.find("img").attr("data-title"); let carNum2; isJavDb$1 && (carNum2 = $box2.find(".video-title strong").text().trim()); const $title = $box2.find(".video-title"); $title.contents().each((function() { 3 !== this.nodeType || "" === this.textContent.trim() || this.textContent.includes(carNum2) || (this.textContent = " " + originalContent + " "); })); $title.removeAttr("title"); })); } addJumpPageControl() { if ($("#gemini-jump-page-control").length > 0) return; if (0 === $(".pagination-link.is-current").length) return; const currentPageNum = utils.getUrlParam(currentHref$1, "page") || 1, $input = $("", { type: "number", id: "jumpPageInput", placeholder: "页码", min: "1", style: "width: 60px; margin-left: 10px; padding: 10px; border: 1px solid #ccc; font-size: 14px;", value: currentPageNum + 1 }), $button = $("