From 3c582535c03821d725668575ba3e9d78f2521bca Mon Sep 17 00:00:00 2001 From: "lzyme.dev@gmail.com" Date: Sat, 29 Aug 2026 16:54:48 +0800 Subject: [PATCH] add missing file --- .gitignore | 4 + dist/jhs-enhance.user.js | 13168 ++++++++++++++++++++++++++ src/api/javdb.js | 129 + src/core/base-plugin.js | 198 + src/core/constants.js | 500 + src/core/gmHttp.js | 154 + src/core/plugin-manager.js | 65 + src/core/storage.js | 474 + src/core/utils.js | 423 + src/main.js | 940 ++ src/plugins/actress-info.js | 95 + src/plugins/aliyun-pan.js | 27 + src/plugins/auto-page.js | 446 + src/plugins/blacklist.js | 585 ++ src/plugins/bus-detail-page.js | 66 + src/plugins/bus-img.js | 56 + src/plugins/bus-nav-bar.js | 17 + src/plugins/bus-preview-video.js | 110 + src/plugins/cover-button.js | 185 + src/plugins/detail-page-button.js | 336 + src/plugins/detail-page.js | 39 + src/plugins/favorite-actresses.js | 104 + src/plugins/fc2-by123av.js | 354 + src/plugins/fc2.js | 190 + src/plugins/filter-title-keyword.js | 375 + src/plugins/fold-category.js | 85 + src/plugins/highlight-magnet.js | 56 + src/plugins/history.js | 479 + src/plugins/hit-show.js | 93 + src/plugins/image-recognition.js | 192 + src/plugins/jav-trailers.js | 88 + src/plugins/list-page-button.js | 283 + src/plugins/list-page.js | 475 + src/plugins/magnet-hub.js | 212 + src/plugins/nav-bar.js | 92 + src/plugins/new-video.js | 363 + src/plugins/other-site.js | 275 + src/plugins/preview-video.js | 221 + src/plugins/related.js | 96 + src/plugins/review.js | 153 + src/plugins/screenshot.js | 153 + src/plugins/setting.js | 867 ++ src/plugins/subtitle-cat.js | 23 + src/plugins/task.js | 434 + src/plugins/top250.js | 186 + src/plugins/translate.js | 41 + src/plugins/wangpan-115-task.js | 151 + src/plugins/wangpan-115.js | 418 + src/plugins/want-watched.js | 90 + 49 files changed, 24566 insertions(+) create mode 100644 .gitignore create mode 100644 dist/jhs-enhance.user.js create mode 100644 src/api/javdb.js create mode 100644 src/core/base-plugin.js create mode 100644 src/core/constants.js create mode 100644 src/core/gmHttp.js create mode 100644 src/core/plugin-manager.js create mode 100644 src/core/storage.js create mode 100644 src/core/utils.js create mode 100644 src/main.js create mode 100644 src/plugins/actress-info.js create mode 100644 src/plugins/aliyun-pan.js create mode 100644 src/plugins/auto-page.js create mode 100644 src/plugins/blacklist.js create mode 100644 src/plugins/bus-detail-page.js create mode 100644 src/plugins/bus-img.js create mode 100644 src/plugins/bus-nav-bar.js create mode 100644 src/plugins/bus-preview-video.js create mode 100644 src/plugins/cover-button.js create mode 100644 src/plugins/detail-page-button.js create mode 100644 src/plugins/detail-page.js create mode 100644 src/plugins/favorite-actresses.js create mode 100644 src/plugins/fc2-by123av.js create mode 100644 src/plugins/fc2.js create mode 100644 src/plugins/filter-title-keyword.js create mode 100644 src/plugins/fold-category.js create mode 100644 src/plugins/highlight-magnet.js create mode 100644 src/plugins/history.js create mode 100644 src/plugins/hit-show.js create mode 100644 src/plugins/image-recognition.js create mode 100644 src/plugins/jav-trailers.js create mode 100644 src/plugins/list-page-button.js create mode 100644 src/plugins/list-page.js create mode 100644 src/plugins/magnet-hub.js create mode 100644 src/plugins/nav-bar.js create mode 100644 src/plugins/new-video.js create mode 100644 src/plugins/other-site.js create mode 100644 src/plugins/preview-video.js create mode 100644 src/plugins/related.js create mode 100644 src/plugins/review.js create mode 100644 src/plugins/screenshot.js create mode 100644 src/plugins/setting.js create mode 100644 src/plugins/subtitle-cat.js create mode 100644 src/plugins/task.js create mode 100644 src/plugins/top250.js create mode 100644 src/plugins/translate.js create mode 100644 src/plugins/wangpan-115-task.js create mode 100644 src/plugins/wangpan-115.js create mode 100644 src/plugins/want-watched.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..711106b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.idea/ +*.log \ No newline at end of file diff --git a/dist/jhs-enhance.user.js b/dist/jhs-enhance.user.js new file mode 100644 index 0000000..b81f5bc --- /dev/null +++ b/dist/jhs-enhance.user.js @@ -0,0 +1,13168 @@ +// ==UserScript== +// @name JAV-JHS +// @namespace JAV-JHS +// @version 3.3.6-fix +// @author xie bro +// @description Jav-鉴黄师 收藏、屏蔽、标记已下载; 屏蔽标签、屏蔽演员、同步收藏演员、新作品检测; 免VIP查看热播、Top250排行榜、Fc2ppv、可查看所有评论信息、相关清单; 支持云盘备份; 以图识图; 字幕搜索; JavDb|JavBus +// @license MIT +// @icon https://www.google.com/s2/favicons?sz=64&domain=javdb.com +// @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://raw.githubusercontent.com/Tampermonkey/utils/refs/heads/main/requires/gh_2215_make_GM_xhr_more_parallel_again.js +// @require https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js +// @require https://cdn.jsdelivr.net/npm/tabulator-tables@6.3.1/dist/js/tabulator.min.js +// @require https://cdn.jsdelivr.net/npm/layui-layer@1.0.9/dist/layer.min.js +// @require https://cdn.jsdelivr.net/npm/blueimp-md5@2.19.0/js/md5.min.js +// @require https://cdn.jsdelivr.net/npm/toastify-js@1.12.0/src/toastify.min.js +// @require https://cdn.jsdelivr.net/npm/localforage@1.10.0/dist/localforage.min.js +// @require https://cdn.jsdelivr.net/npm/viewerjs@1.11.1/dist/viewer.min.js +// @require https://cdn.jsdelivr.net/npm/qrcodejs@1.0.0/qrcode.min.js +// @connect xunlei.com +// @connect geilijiasu.com +// @connect aliyundrive.com +// @connect aliyundrive.net +// @connect ja.wikipedia.org +// @connect beta.magnet.pics +// @connect jdforrepam.com +// @connect cc3001.dmm.co.jp +// @connect cc3001.dmm.com +// @connect www.dmm.co.jp +// @connect www.dmm.com +// @connect api.dmm.com +// @connect special.dmm.co.jp +// @connect adult.contents.fc2.com +// @connect fc2ppvdb.com +// @connect 123av.com +// @connect u3c3.com +// @connect u9a9.com +// @connect btsow.lol +// @connect sukebei.nyaa.si +// @connect javstore.net +// @connect javbest.net +// @connect missav.live +// @connect jable.tv +// @connect www.av.gl +// @connect jav.rs +// @connect javtrailers.com +// @connect javdb.com +// @connect javbus.com +// @connect supjav.com +// @connect 115.com +// @connect jianguoyun.com +// @connect 127.0.0.1 +// @connect * +// @grant GM_openInTab +// @grant GM_xmlhttpRequest +// @grant unsafeWindow +// @grant window.close +// @run-at document-idle +// ==/UserScript== + +(function ($$1, localforage$1, Toastify$1, md5$1, tabulatorTables, Viewer$1, QRCode$1) { + 'use strict'; + + var __defProp = Object.defineProperty, __typeError = (msg) => { + throw TypeError(msg); + }, __publicField = (obj, key, value) => ((obj2, key2, value2) => key2 in obj2 ? __defProp(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 = ` + +`; + function insertStyle(css) { + if (css) if (css.includes("\n" + ); + insertStyle(mainCss); + window.$ = window.jQuery = $$1; + window.localforage = localforage$1; + window.Toastify = Toastify$1; + window.md5 = md5$1; + window.Tabulator = tabulatorTables.TabulatorFull; + 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; + 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; + __publicField(this, "car_list_key", "car_list"); + __publicField(this, "filter_keyword_title_key", "filter_keyword_title"); + __publicField(this, "filter_keyword_review_key", "filter_keyword_review"); + __publicField(this, "setting_key", "setting"); + __publicField(this, "blacklist_key", "blacklist"); + __publicField(this, "blacklist_car_list_key", "blacklist_car_list"); + __publicField(this, "favorite_actresses_key", "favorite_actresses"); + __publicField(this, "highlighted_tags_key", "highlighted_tags"); + __publicField(this, "forage", localforage.createInstance({ + driver: localforage.INDEXEDDB, + name: "JAV-JHS", + version: 1, + storeName: "appData" + })); + __publicField(this, "cacheCarList", null); + __publicField(this, "cacheBlacklistCarList", null); + __publicField(this, "cacheFavoriteActressList", null); + __publicField(this, "cacheSettingObj", null); + if (_StorageManager.instance) throw new Error("StorageManager已被实例化过了!"); + _StorageManager.instance = this; + } + async getCarList() { + if (this.cacheCarList) return utils.copyObj(this.cacheCarList); + this.cacheCarList = await this.forage.getItem(this.car_list_key) || []; + return utils.copyObj(this.cacheCarList); + } + async getCar(carNum2) { + return (await this.getCarList()).find(((item) => item.carNum === carNum2)); + } + _handleSingleCar(carParam, carList) { + let { carNum: carNum2, url, 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() { + __publicField(this, "intervalContainer", {}); + __publicField(this, "mimeTypes", { + txt: "text/plain", + html: "text/html", + css: "text/css", + csv: "text/csv", + json: "application/json", + xml: "application/xml", + jpg: "image/jpeg", + jpeg: "image/jpeg", + png: "image/png", + gif: "image/gif", + webp: "image/webp", + svg: "image/svg+xml", + pdf: "application/pdf", + doc: "application/msword", + docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + xls: "application/vnd.ms-excel", + xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ppt: "application/vnd.ms-powerpoint", + pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation", + zip: "application/zip", + rar: "application/x-rar-compressed", + "7z": "application/x-7z-compressed", + mp3: "audio/mpeg", + wav: "audio/wav", + mp4: "video/mp4", + webm: "video/webm", + ogg: "audio/ogg" + }); + __publicField(this, "timers", /* @__PURE__ */ new Map()); + __publicField(this, "insertStyle", ((css) => { + if (css) { + -1 === css.indexOf(""); + $("head").append(css); + } + })); + __publicField(this, "layerIndexStack", []); + Utils.instance || (Utils.instance = this); + return Utils.instance; + } + importResource(url) { + let tag; + if (url.indexOf("css") >= 0) { + tag = document.createElement("link"); + tag.setAttribute("rel", "stylesheet"); + tag.href = url; + } else { + tag = document.createElement("script"); + tag.setAttribute("type", "text/javascript"); + tag.src = url; + } + document.documentElement.appendChild(tag); + } + openPage(url, title, shadeClose, event) { + if (!url) throw new Error("未传入url"); + shadeClose = shadeClose ?? 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() { + __publicField(this, "pluginManager", null); + __publicField(this, "settingSvg", ''); + __publicField(this, "editSvg", ''); + __publicField(this, "deleteSvg", ''); + __publicField(this, "checkSvg", ''); + __publicField(this, "actressSvg", ''); + __publicField(this, "newSvg", ''); + __publicField(this, "refreshSvg", ''); + __publicField(this, "blacklistSvg", ''); + __publicField(this, "removeSvg", ''); + __publicField(this, "copySvg", ''); + __publicField(this, "titleSvg", ''); + __publicField(this, "carNumSvg", ''); + __publicField(this, "downSvg", ''); + __publicField(this, "handleSvg", ''); + __publicField(this, "siteSvg", ''); + __publicField(this, "videoSvg", ''); + __publicField(this, "screenSvg", ''); + __publicField(this, "recoveryVideoSvg", ''); + } + getName() { + throw new Error(`${this.constructor.name} 未显示getName()`); + } + getBean(name2) { + return this.pluginManager.getBean(name2); + } + async initCss() { + return ""; + } + async handle() { + } + getPageInfo() { + let carNum2, url, actress, actors, publishTime, currentHref2 = window.location.href; + if (isJavDb) { + carNum2 = $('a[title="複製番號"]').attr("data-clipboard-text"); + url = currentHref2.split("?")[0].split("#")[0]; + actress = $(".female").prev().map(((i, el) => $(el).text())).get().join(" "); + actors = $(".male").prev().map(((i, el) => $(el).text())).get().join(" "); + publishTime = $('strong:contains("日期:")').parent(".panel-block").find(".value").text().trim(); + } + if (isJavBus) { + url = currentHref2.split("?")[0]; + carNum2 = url.split("/").filter(Boolean).pop().replace(/_\d{4}-\d{2}-\d{2}$/, ""); + actress = $('span[onmouseover*="star_"] a').map(((i, el) => $(el).text())).get().join(" "); + actors = ""; + publishTime = $('span.header:contains("發行日期:")').parent("p").text().trim().replace("發行日期:", "").trim(); + } + return { + carNum: carNum2, + url, + 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; + } + } + class AutoPagePlugin extends BasePlugin { + constructor() { + super(...arguments); + __publicField(this, "preloadDistance", 500); + __publicField(this, "currentPage", this.getInitialPageNumber()); + __publicField(this, "pageItems", []); + } + getName() { + return "AutoPagePlugin"; + } + async initCss() { + return "\n \n "; + } + async handle() { + this.waterfall().then(); + } + getInitialPageNumber() { + if (isJavBus$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); + __publicField(this, "folderName", "JHS-数据备份"); + __publicField(this, "cacheItems", [{ + key: "jhs_dmm_video", + text: "🎥 预览视频缓存", + title: "预览视频缓存" + }, { + key: "jhs_other_site", + text: "🌍 第三方站点缓存", + title: "第三方站点资源检测结果, 如missav,123Av等" + }, { + key: "jhs_screenShot", + text: "🖼️ 缩略图缓存", + title: "缩略图缓存" + }, { + key: "jhs_translate", + text: "🆎 标题翻译", + title: "标题翻译" + }, { + key: "jhs_actress_info", + text: "👩 演员信息", + title: "演员的年龄三围等数据信息" + }, { + key: "jhs_score_info", + text: "⭐ Top250|热播 评分数据", + title: "Top250及热播的评分数据" + }]); + } + getName() { + return "SettingPlugin"; + } + async initCss() { + const settingObj = await storageManager.getSetting(); + let containerWidth = (null == settingObj ? void 0 : settingObj.containerWidth) ?? "100", containerColumns = utils.isMobile() && window.innerWidth < 1e3 ? 1 : (null == settingObj ? void 0 : settingObj.containerColumns) ?? 5; + this.applyImageMode().then(); + let containerWidthCss = ` + 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: +
+ +
+
+ +
+ +
+ WebDav备份 + +
+
+ 服务地址: +
+ +
+
+
+ 用户名: +
+ +
+
+
+ 密码: +
+ +
+
+
+ + + +
+
+ 已鉴定标签展示位置: +
+ +
+
+ +
+ 已鉴定内容处理方式: +
+ +
+
+ +
+ + 鉴定补录演员信息 ❓ + +
+ +
+
+ +
+ +
+ + 列表页功能按钮 + +
+ +
+ 按钮-打开待鉴定: +
+ +
+
+ +
+ 按钮-打开已收藏: +
+ +
+
+ +
+ 打开待鉴定|已收藏 窗口数: +
+ +
+
+ +
+ 随机打开已收藏: +
+ +
+
+ + +
+ +
+ + 封面快捷按钮 + +
+ +
+ + ${coverButtonPlugin.screenSvg}长缩略图: + +
+ +
+
+ +
+ + ${coverButtonPlugin.videoSvg}预览视频: + +
+ +
+
+ +
+ + ${coverButtonPlugin.handleSvg}鉴定按钮: + +
+ +
+
+ +
+ + ${coverButtonPlugin.siteSvg}第三方跳转: + +
+ +
+
+ +
+ + ${coverButtonPlugin.copySvg}复制按钮: + +
+ +
+
+ +
+ +
+ 预览视频默认画质: +
+ +
+
+ +
+ 评论区条数: +
+ +
+
+ +
+ + 高亮已收藏演员 ❓ + +
+ +
+
+ +
+ + 分类标签|高亮演员-边框样式: + +
+ + +
+
+ +
+ +
+ 请求超时时间(毫秒): +
+ +
+
+ +
+ 请求失败重试次数: +
+ +
+
+ +
+ +
+ + 启用控制台日志: + +
+ +
+
+ +
+ 日志最大行数: +
+ +
+
+
+ + +
+ +
+ 请求并发数量: +
+ +
+
+
+ 请求间隔时间(毫秒): +
+ +
+
+ +
+ +
+ 自动检测屏蔽黑名单演员 +
+ + 任务开关: ❓ + +
+ +
+
+
+ 任务间隔时间: +
+ +
+
+
+ 检测规则: +
+ +
+
+
+ +
+ 自动同步已收藏的演员 +
+ + 任务开关: ❓ + +
+ +
+
+
+ 任务间隔时间: +
+ +
+
+
+ +
+ 自动检测已收藏演员的最新作品 +
+ + 任务开关: ❓ + +
+ +
+
+
+ 任务间隔时间: +
+ +
+
+
+ 检测规则: +
+ +
+
+
+
+ + +
+
+ 域名 - MissAv: +
+ +
+
+
+ 域名 - Jable: +
+ +
+
+
+ 域名 - Avgle: +
+ +
+
+
+ 域名 - JavTrailer: +
+ +
+
+
+ 域名 - 123Av: +
+ +
+
+
+ 域名 - JavDb: +
+ +
+
+
+ 域名 - JavBus: +
+ +
+
+
+ 域名 - SupJav: +
+ +
+
+
+ + +
+

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

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

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

+
+
+ ${buttonsHTML} +
+ +
+
+

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

+
+
+
+ TRC20-USDT二维码 +

TRC20-USDT

+ +

点击地址可复制

+
+
+
+
+
+ +
+ + +
+
+
+ `; + 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() : $("#sort-toggle-btn").show(); + })); + $("#translateTitle").on("change", (async (event) => { + const translateTitle = $("#translateTitle").is(":checked") ? YES : NO; + await storageManager.saveSettingItem("translateTitle", translateTitle); + if (translateTitle === YES) { + await this.getBean("ListPagePlugin").doFilter(); + 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); + __publicField(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 字幕 (SubTitleCat)\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(); + })); + $("#search-subtitle-btn").on("click", ((event) => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum2}`, carNum2, false, event))); + $("#xunLeiSubtitleBtn").on("click", (() => this.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(carNum2))); + $("#magnetSearchBtn").on("click", (() => { + let magnetHub = this.getBean("MagnetHubPlugin").createMagnetHub(carNum2); + layer.open({ + type: 1, + title: "磁力搜索", + content: '
', + area: utils.getResponsiveArea(["60%", "80%"]), + scrollbar: 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); + })); + } + 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.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); + __publicField(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(); + __publicField(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(); + __publicField(this, "has_cnsub", ""); + __publicField(this, "$contentBox", $(".section .container")); + __publicField(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 = ` +
    + + +
    + ${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"); + $previewVideo.length > 0 && $previewVideo[0].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"); + $previewVideo[0].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"), $previewSource = $videoEl.find("source"), $qualityControlsBox = $container.find(".video-control-box"); + if (!$videoEl.length || !$previewSource.length) return; + const videoEl = $videoEl[0], 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"); + $previewSource.attr("src", videoSrc); + const currentTime = videoEl.currentTime; + videoEl.load(); + videoEl.currentTime = currentTime; + await videoEl.play(); + $buttons.removeClass("active"); + $button.addClass("active"); + } catch (error) { + console.error("切换画质失败:", error); + } + })); + } + } + class ImageRecognitionPlugin extends BasePlugin { + constructor() { + super(...arguments); + __publicField(this, "siteList", [{ + name: "Google旧版", + url: "https://www.google.com/searchbyimage?image_url={占位符}&client=firefox-b-d", + ico: "https://www.google.com/favicon.ico" + }, { + name: "Google", + url: "https://lens.google.com/uploadbyurl?url={占位符}", + ico: "https://www.google.com/favicon.ico" + }, { + name: "Yandex", + url: "https://yandex.ru/images/search?rpt=imageview&url={占位符}", + ico: "https://yandex.ru/favicon.ico" + }]); + __publicField(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); + __publicField(this, "floorIndex", 1); + __publicField(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); + __publicField(this, "type", null); + } + getName() { + return "WantAndWatchedVideosPlugin"; + } + async handle() { + if (window.location.href.includes("/want_watch_videos")) { + $("h3").append('导入至 JHS'); + $("#wantWatchBtn").on("click", ((event) => { + this.type = Status_FAVORITE$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); + __publicField(this, "currentEngine", null); + __publicField(this, "searchEngines", [{ + name: "U9A9", + id: "u9a9", + url: "https://u9a9.com/?type=2&search={keyword}", + targetPage: "https://u9a9.com/?type=2&search={keyword}", + parseHtml: this.parseU3C3 + }, { + name: "U3C3", + id: "u3c3", + url: "https://u3c3.com/?search2=a8lr16lo&search={keyword}", + targetPage: "https://u3c3.com/?search2=a8lr16lo&search={keyword}", + parseHtml: this.parseU3C3 + }, { + name: "Sukebei", + id: "Sukebei", + url: "https://sukebei.nyaa.si/?f=0&c=0_0&q={keyword}", + targetPage: "https://sukebei.nyaa.si/?f=0&c=0_0&q={keyword}", + parseHtml: this.parseSukebei + }]); + } + getName() { + return "MagnetHubPlugin"; + } + async initCss() { + return "\n \n "; + } + createMagnetHub(keyword) { + keyword = keyword.replace("FC2-", ""); + const $container = $('
    '), $tabs = $('
    '), key = "jhs_magnetHub_selectedEngine", savedEngineId = localStorage.getItem(key); + let defaultEngineIndex = 0; + const $tabBox = $('
    '); + this.searchEngines.forEach(((engine, index) => { + const $tab = $(`
    ${engine.name}
    `); + if (savedEngineId && engine.id === savedEngineId) { + $tab.addClass("active"); + this.currentEngine = engine; + defaultEngineIndex = index; + } else if (0 === index && !savedEngineId) { + $tab.addClass("active"); + this.currentEngine = engine; + } + $tabBox.append($tab); + })); + $tabs.append($tabBox); + $tabs.append(`原网页`); + $container.append($tabs); + const $resultsContainer = $('
    '); + $container.append($resultsContainer); + $container.on("click", ".magnet-tab", ((e) => { + const engineId = $(e.target).data("engine"); + this.currentEngine = this.searchEngines.find(((engine) => engine.id === engineId)); + $("#targetBox").attr("href", this.currentEngine.targetPage.replace("{keyword}", encodeURIComponent(keyword))); + localStorage.setItem(key, engineId); + $container.find(".magnet-tab").removeClass("active"); + $(e.target).addClass("active"); + this.searchEngine($resultsContainer, this.currentEngine, keyword); + })); + this.searchEngine($resultsContainer, this.currentEngine || this.searchEngines[defaultEngineIndex], keyword); + return $container; + } + searchEngine($container, engine, keyword) { + $container.html(`
    正在从 ${engine.name} 搜索 "${keyword}"...
    `); + const cacheKey = `${engine.name}_${keyword}`; + sessionStorage.getItem(cacheKey); + const url = engine.url.replace("{keyword}", encodeURIComponent(keyword)); + engine.parseHtml && _GM_xmlhttpRequest({ + method: "GET", + url, + 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.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 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); + __publicField(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); + })); + 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); + } 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) { + matchList.length > 0 ? $box2.find(".jhs-match-btn").replaceWith(`匹配${matchList.length}个`) : $box2.find(".jhs-match-btn").replaceWith(`未匹配`); + } + async handleLoginRedirect() { + window.open("https://115.com"); + } + async searchFiles(carNum2) { + var _a2; + let searchKeyword = carNum2.toLowerCase().replace("fc2-", ""); + return (null == (_a2 = (await searchFiles(searchKeyword)).data) ? void 0 : _a2.map(((data) => ({ + folderId: data.fid, + videoId: data.pc, + name: data.n, + createTime: utils.formatDate(new Date(1e3 * data.te)), + size: data.s, + isVideo: [".mp4", ".avi", ".mov", ".mkv", ".flv", ".wmv"].some(((ext) => { + var _a3; + return null == (_a3 = data.n) ? void 0 : _a3.toLowerCase().endsWith(ext); + })) + }))).filter(((x) => x.folderId && x.isVideo && x.name.toLowerCase().includes(searchKeyword)))) || []; + } + showMatchError($box2, carNum2, error) { + $box2.find(".jhs-match-btn").replaceWith(`匹配失败`); + show.error(`${carNum2} 匹配失败: ${error.message || "网络错误"}`); + } + async checkLoginStatus() { + var _a2; + if (this.loginStatus === _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}个`); + } else $box2.find(".video-title").prepend(`未匹配`); + } + formatSize(bytes) { + if (!bytes) return "-"; + const units = ["B", "KB", "MB", "GB", "TB"]; + let size = parseFloat(bytes), unit = 0; + for (; size >= 1024 && unit < units.length - 1; ) { + size /= 1024; + unit++; + } + return `${size.toFixed(2)} ${units[unit]}`; + } + }; + __publicField(_WangPan115MatchPlugin, "LoginStatus", { + UNCHECKED: -1, + LOGGED_OUT: 0, + LOGGED_IN: 1 + }); + let WangPan115MatchPlugin = _WangPan115MatchPlugin; + class FavoriteActressesPlugin extends BasePlugin { + getName() { + return "FavoriteActressesPlugin"; + } + async handle() { + this.bindEvent(); + await this.highlightActress(); + this.replaceActressAvatar(); + } + async highlightActress() { + if (!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(); + } + 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(); + })); + 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 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); + __publicField(this, "okBackgroundColor", "#7bc73b"); + __publicField(this, "errorBackgroundColor", "#de3333"); + __publicField(this, "warnBackgroundColor", "#d7a80c"); + __publicField(this, "domainErrorBackgroundColor", "#d7780c"); + __publicField(this, "asyncQueue", new AsyncQueue()); + __publicField(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") + }]); + __publicField(this, "settingCache", null); + __publicField(this, "lastFetchTime", 0); + __publicField(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); + __publicField(this, "floorIndex", 1); + __publicField(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}
    + +
    + ${this.carNumSvg} + ${this.titleSvg} + ${this.downSvg} +
    +
    +
    + `); + 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.carNumSvg} + ${this.titleSvg} + ${this.downSvg} +
    +
    +
    + `); + } + } + })); + 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(); + showImageViewer(imgUrl); + } catch (error) { + console.error("图片预览出错:", error); + show.error("图片预览出错:" + error); + } finally { + loadObj.close(); + } + })); + $(document).on("click", ".filterBtn, .favoriteBtn, .hasDownBtn, .hasWatchBtn", ((event) => { + event.preventDefault(); + event.stopPropagation(); + const $btn = $(event.target).closest(".menu-btn"), $box2 = $btn.closest(".item"), { carNum: carNum2, url, 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"); + })); + })); + } + 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); + __publicField(this, "$contentBox", $(".section .container")); + __publicField(this, "urlParams", new URLSearchParams(window.location.search)); + __publicField(this, "sortVal", this.urlParams.get("sort") || "release_date"); + __publicField(this, "currentPage", this.urlParams.get("page") ? parseInt(this.urlParams.get("page")) : 1); + __publicField(this, "maxPage", null); + __publicField(this, "keyword", this.urlParams.get("keyword") || null); + } + getName() { + return "Fc2By123AvPlugin"; + } + async getBaseUrl() { + const otherSitePlugin = this.getBean("OtherSitePlugin"); + return await otherSitePlugin.getAv123Url() + "/ja"; + } + handle() { + $("#navbar-menu-hero > div > div:nth-child(1) > div > a:nth-child(4)").after('123Av-Fc2'); + $('.tabs li:contains("FC2")').after('
  • 123Av-Fc2
  • '); + if (currentHref$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 '); + $(`#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} + +
    + 🚫 屏蔽 + ⭐ 收藏 + 📥️ 已下载 + 🔍 已观看 + + + 字幕 (SubTitleCat) + + + 字幕 (迅雷) + +
    +
    + +
    +
    +
    + + + +
    + `; + 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(); + })); + $("#search-subtitle-btn").on("click", ((event) => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum2}`, carNum2, false, event))); + $("#xunLeiSubtitleBtn").on("click", (() => this.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(carNum2))); + let tempCarNum = carNum2.replace("FC2-", ""); + this.getBean("OtherSitePlugin").loadOtherSite(tempCarNum, carNum2).then(); + } + }); + } + async loadData(carNum2, href) { + let loadObj = loading(); + try { + const { id, 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(); + showImageViewer(event.currentTarget); + })); + } + })); + } + async get123AvVideoInfo(href) { + const html = await gmHttp.get(href), match = html.match(/v-scope="Movie\({id:\s*(\d+),/), id = match ? match[1] : null, $dom = utils.htmlTo$dom(html); + return { + id, + 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 字幕 (SubTitleCat)\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", YES); + $("#magnets-span").text(enableMagnetsFilter === YES ? "关闭磁力过滤" : "开启磁力过滤"); + enableMagnetsFilter === YES && highlightMagnetPlugin.doFilterMagnet(); + $("#enable-magnets-filter").on("click", ((event) => { + let $span = $("#magnets-span"); + if ("关闭磁力过滤" === $span.text()) { + highlightMagnetPlugin.showAll(); + $span.text("开启磁力过滤"); + storageManager.saveSettingItem("enableMagnetsFilter", NO); + } else { + highlightMagnetPlugin.doFilterMagnet(); + $span.text("关闭磁力过滤"); + storageManager.saveSettingItem("enableMagnetsFilter", YES); + } + })); + $("#search-subtitle-btn").on("click", ((event) => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum2}`, carNum2, false, event))); + $("#xunLeiSubtitleBtn").on("click", (() => this.searchXunLeiSubtitle(carNum2))); + this.showStatus(carNum2).then(); + } + 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: false, + area: utils.getResponsiveArea(["60%", "70%"]), + anim: -1, + success: (layero, index) => { + new Tabulator("#xunlei-table-container", { + layout: "fitColumns", + placeholder: "暂无数据", + virtualDom: true, + data: dataList, + responsiveLayout: "collapse", + responsiveLayoutCollapse: true, + columnDefaults: { + headerHozAlign: "center", + hozAlign: "center" + }, + columns: [{ + title: "文件名", + field: "name", + headerSort: false, + responsive: 0 + }, { + title: "类型", + field: "ext", + headerSort: false, + responsive: 0 + }, { + title: "操作", + responsive: 0, + headerSort: false, + 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); + __publicField(this, "currentPageFilterCount", 0); + __publicField(this, "currentPageFavoriteCount", 0); + __publicField(this, "currentPageHasDownCount", 0); + __publicField(this, "currentPageHasWatchCount", 0); + __publicField(this, "currentPageKeywordFilterCount", 0); + __publicField(this, "currentPageActorFilterCount", 0); + __publicField(this, "currentPageWaitCheckCount", 0); + __publicField(this, "currentPageTotalCount", 0); + __publicField(this, "cache", localStorage.getItem("jhs_translate") ? JSON.parse(localStorage.getItem("jhs_translate")) : {}); + __publicField(this, "writeQueue", Promise.resolve()); + } + getName() { + return "ListPagePlugin"; + } + async handle() { + this.cleanRepeatId(); + this.replaceHdImg(); + this.addJumpPageControl(); + this.fixBusTitleBox(); + await this.doFilter(); + this.bindClick().then(); + this.bindListPageHotKey().then(); + this.rememberTagExpand(); + $(this.getSelector().itemSelector + " a").attr("target", "_blank"); + this.checkDom(); + } + rememberTagExpand() { + if (!window.location.href.includes("actors")) return; + const $expandButton = $(".tag-expand"); + if (0 === $expandButton.length) return; + const isExpanded = "true" === localStorage.getItem("jhs_tag_expand"), $actorTagsContent = $(".actor-tags .content"); + isExpanded && $actorTagsContent.hasClass("collapse") && $expandButton[0].click(); + $expandButton.on("click", (function() { + const newState = !$actorTagsContent.hasClass("collapse"); + localStorage.setItem("jhs_tag_expand", newState.toString()); + })); + } + checkDom() { + if (!window.isListPage) return; + const selector = this.getSelector(), targetNode = document.querySelector(selector.boxSelector); + if (!targetNode) { + console.error("没有找到容器节点!"); + return; + } + const observer = new MutationObserver((async (mutations) => { + observer.disconnect(); + try { + this.replaceHdImg(); + this.addJumpPageControl(); + this.fixBusTitleBox(); + await this.doFilter(); + await this.getBean("ListPageButtonPlugin").sortItems(); + this.getBean("CoverButtonPlugin").addSvgBtn().then(); + $(this.getSelector().itemSelector + " a").attr("target", "_blank"); + this.getBean("AutoPagePlugin").checkLoad(); + } finally { + observer.observe(targetNode, config); + } + })), config = { + childList: 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.contents().each((function() { + 3 === this.nodeType && "" !== this.textContent.trim() && (this.textContent = " " + _this.cache[carNum2] + " "); + })); + $title.attr("title", _this.cache[carNum2]); + } else translateText(content).then(((result) => { + if (isJavDb$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(".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 = $("\n '); + $("#search-img-btn").on("click", (() => { + this.getBean("ImageRecognitionPlugin").open(); + })); + } +} + + +export { BusNavBarPlugin }; diff --git a/src/plugins/bus-preview-video.js b/src/plugins/bus-preview-video.js new file mode 100644 index 0000000..4259606 --- /dev/null +++ b/src/plugins/bus-preview-video.js @@ -0,0 +1,110 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, qualityOptions } from '../core/constants.js'; + +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"); + $previewVideo.length > 0 && $previewVideo[0].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 = $(`\n \n
    \n \n
    \n ▶\n
    \n
    \n
    `); + $("#sample-waterfall").prepend(videoPreview); + "yes" === await storageManager.getSetting("enableLoadPreviewVideo", "yes") && getDmmVideo(this.getPageInfo().carNum, !1).then(); + let isHandlingVideo = !1, $preview = $(".preview-video-container"); + $preview.on("click", (async event => { + event.preventDefault(); + event.stopPropagation(); + if (isHandlingVideo) show.info("正在加载中, 勿重复点击"); else { + isHandlingVideo = !0; + try { + await this.handleVideo(); + } finally { + isHandlingVideo = !1; + } + } + })); + 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"); + $previewVideo[0].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(`\n
    \n \n
    \n
    \n
    \n `); + const $videoEl = $("#preview-video"), $previewSource = $videoEl.find("source"), $qualityControlsBox = $container.find(".video-control-box"); + if (!$videoEl.length || !$previewSource.length) return; + const videoEl = $videoEl[0], 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 += `\n \n `; + } + })); + $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"); + $previewSource.attr("src", videoSrc); + const currentTime = videoEl.currentTime; + videoEl.load(); + videoEl.currentTime = currentTime; + await videoEl.play(); + $buttons.removeClass("active"); + $button.addClass("active"); + } catch (error) { + console.error("切换画质失败:", error); + } + })); + } +} + + +export { BusPreviewVideoPlugin }; \ No newline at end of file diff --git a/src/plugins/cover-button.js b/src/plugins/cover-button.js new file mode 100644 index 0000000..a2302e8 --- /dev/null +++ b/src/plugins/cover-button.js @@ -0,0 +1,185 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, isJavBus, isJavDb, Status_FILTER, Status_FAVORITE, Status_HAS_DOWN, Status_HAS_WATCH, YES } from '../core/constants.js'; +import { GM_openInTab } from 'vite-plugin-monkey/dist/client'; + +class CoverButtonPlugin extends BasePlugin { + getName() { + return "CoverButtonPlugin"; + } + async initCss() { + return `\n \n `; + } + handle() { + if (window.isListPage) { + this.addSvgBtn(); + this.bindClick().then(); + } + } + async addSvgBtn() { + $(this.getSelector().itemSelector).toArray().forEach((ele => { + let $box2 = $(ele); + if (!($box2.find(".tool-box").length > 0)) { + isJavDb && $box2.find(".tags").append(`\n
    \n ${this.screenSvg}\n \n ${this.videoSvg}\n \n
    \n
    ${this.handleSvg}
    \n \n \n
    \n \n
    \n
    ${this.siteSvg}
    \n \n \n
    \n \n
    \n
    ${this.copySvg}
    \n \n
    \n ${this.carNumSvg}\n ${this.titleSvg}\n ${this.downSvg}\n
    \n
    \n
    \n `); + if (isJavBus) { + if ($box2.find(".avatar-box").length > 0) return; + $box2.find(".photo-info").append(`\n
    \n ${this.screenSvg}\n\n ${this.videoSvg}\n \n
    \n
    ${this.handleSvg}
    \n \n \n
    \n \n
    \n
    ${this.siteSvg}
    \n \n \n
    \n \n
    \n
    ${this.copySvg}
    \n \n
    \n ${this.carNumSvg}\n ${this.titleSvg}\n ${this.downSvg}\n
    \n
    \n
    \n `); + } + } + })); + this.enableSvgBtn(); + } + async enableSvgBtn() { + const settingObj = await storageManager.getSetting(), {enableScreenSvg: enableScreenSvg = YES, enableVideoSvg: enableVideoSvg = YES, enableHandleSvg: enableHandleSvg = YES, enableSiteSvg: enableSiteSvg = YES, enableCopySvg: enableCopySvg = YES} = settingObj; + [ { + selector: ".screenSvg", + enabled: enableScreenSvg + }, { + selector: ".videoSvg", + enabled: enableVideoSvg + }, { + selector: ".handleSvg", + enabled: enableHandleSvg + }, { + selector: ".siteSvg", + enabled: enableSiteSvg + }, { + selector: ".copySvg", + enabled: enableCopySvg + } ].forEach((({selector: selector, enabled: enabled}) => { + $(selector).toggle(enabled === YES); + })); + } + async bindClick() { + this.getSelector(); + const listPagePlugin = this.getBean("ListPagePlugin"); + $(document).on("click", ".more-tools-container", (event => { + event.preventDefault(); + var $currentTools = $(event.target).closest(".more-tools-container").find(".more-tools"); + $(".more-tools").not($currentTools).stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide(); + $currentTools.is(":visible") ? $currentTools.stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide() : $currentTools.stop(!0, !0).removeClass("elastic-out").addClass("elastic-in").show(); + })); + $(document).on("click", (function(event) { + $(event.target).closest(".more-tools-container").length || $(".more-tools").stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide(); + })); + $(document).on("click", ".videoSvg", (event => { + event.preventDefault(); + $('.videoSvg[title!="播放视频"]').each(((index, element) => { + const $otherSvgElement = $(element); + let $otherBox = $otherSvgElement.closest(".item"), $otherImg = $otherBox.find("img"), {carNum: carNum2} = this.getBoxCarInfo($otherBox); + this.showImg($otherSvgElement, $otherImg, carNum2); + $otherSvgElement.html(this.videoSvg).attr("title", "播放视频"); + })); + const $currentBox = $(event.target).closest(".item"), $svgElement = $currentBox.find(".videoSvg"); + if ("播放视频" === $svgElement.attr("title")) { + $svgElement.html(this.recoveryVideoSvg).attr("title", "切回封面"); + const {carNum: carNum2} = this.getBoxCarInfo($currentBox); + let $img = $currentBox.find("img"); + if (!$img.length) { + show.error("没有找到图片"); + return; + } + this.showVideo($svgElement, $img, carNum2).then(); + } + })); + $(document).on("click", ".screenSvg", (async event => { + event.preventDefault(); + let loadObj = loading(); + try { + const $box2 = $(event.currentTarget).closest(".item"); + let {carNum: carNum2} = this.getBoxCarInfo($box2); + carNum2 = carNum2.replace("FC2-", ""); + const imgUrl = await this.getBean("ScreenShotPlugin").getScreenshot(carNum2); + loadObj.close(); + showImageViewer(imgUrl); + } catch (error) { + console.error("图片预览出错:", error); + show.error("图片预览出错:" + error); + } finally { + loadObj.close(); + } + })); + $(document).on("click", ".filterBtn, .favoriteBtn, .hasDownBtn, .hasWatchBtn", (event => { + event.preventDefault(); + event.stopPropagation(); + const $btn = $(event.target).closest(".menu-btn"), $box2 = $btn.closest(".item"), {carNum: carNum2, url: url, publishTime: publishTime} = this.getBoxCarInfo($box2), handleAction = async status => { + let actress = await listPagePlugin.parseActressName(url); + await storageManager.saveCar({ + carNum: carNum2, + url: url, + names: actress, + actionType: status, + publishTime: publishTime + }); + window.refresh(); + show.ok("操作成功"); + }; + $btn.hasClass("filterBtn") ? utils.q(event, `是否屏蔽${carNum2}?`, (() => handleAction(Status_FILTER))) : $btn.hasClass("favoriteBtn") ? handleAction(Status_FAVORITE).then() : $btn.hasClass("hasDownBtn") ? handleAction(Status_HAS_DOWN).then() : $btn.hasClass("hasWatchBtn") && handleAction(Status_HAS_WATCH).then(); + $(".more-tools").stop(!0, !0).removeClass("elastic-in").addClass("elastic-out").hide(); + })); + const otherSitePlugin = this.getBean("OtherSitePlugin"), missAvUrl = await otherSitePlugin.getMissAvUrl(), jableUrl = await otherSitePlugin.getjableUrl(), avgleUrl = await otherSitePlugin.getAvgleUrl(), av123Url = await otherSitePlugin.getAv123Url(); + $(document).on("click", ".site-jable, .site-avgle, .site-miss-av, .site-123-av", (event => { + event.preventDefault(); + event.stopPropagation(); + const $currentTarget = $(event.currentTarget), $box2 = $currentTarget.closest(".item"), {carNum: carNum2} = this.getBoxCarInfo($box2); + let url = null; + $currentTarget.hasClass("site-jable") ? url = `${jableUrl}/search/${carNum2}/` : $currentTarget.hasClass("site-avgle") ? url = `${avgleUrl}/vod/search.html?wd=${carNum2}` : $currentTarget.hasClass("site-miss-av") ? url = `${missAvUrl}/search/${carNum2}` : $currentTarget.hasClass("site-123-av") && (url = `${av123Url}/ja/search?keyword=${carNum2}`); + event && (event.ctrlKey || event.metaKey) ? GM_openInTab(url, { + insert: 0 + }) : window.open(url); + })); + $(document).on("click", ".titleSvg, .carNumSvg, .downSvg", (event => { + event.preventDefault(); + event.stopPropagation(); + const $box2 = $(event.currentTarget).closest(".item"), {carNum: carNum2, title: title} = this.getBoxCarInfo($box2), $img = $box2.find(isJavBus ? ".photo-frame img" : ".cover img"); + $(event.currentTarget).hasClass("titleSvg") ? utils.copyToClipboard("标题", title) : $(event.currentTarget).hasClass("carNumSvg") ? utils.copyToClipboard("番号", carNum2) : $(event.currentTarget).hasClass("downSvg") && fetch($img.attr("src")).then((response => response.blob())).then((blob => { + utils.download(blob, carNum2 + " " + title + ".jpg"); + })); + })); + } + showImg($svgElement, $img, carNum2) { + $svgElement.html(this.videoSvg).attr("title", "播放视频"); + let $video = $(`#${`${carNum2}_preview_video`}`); + if ($video.length > 0) { + $video[0].pause(); + $video.parent().hide(); + } + $img.show(); + $img.removeClass("loading"); + $img.next(".loading-spinner").remove(); + } + async showVideo($svgElement, $img, carNum2) { + const id = `${carNum2}_preview_video`; + let $video = $(`#${id}`); + if ($video.length > 0) { + $video.parent().show(); + $video[0].play(); + $img.hide(); + return; + } + $img.addClass("loading"); + $img.after('
    '); + const poster = $img.attr("src"), dmmVideoMap = await getDmmVideo(carNum2); + if (!dmmVideoMap) { + show.error("未解析到视频"); + this.showImg($svgElement, $img, carNum2); + return; + } + let defaultVideoQuality = await storageManager.getSetting("videoQuality"); + defaultVideoQuality = selectDefaultQuality(Object.keys(dmmVideoMap), defaultVideoQuality); + let videoUrl = dmmVideoMap[defaultVideoQuality], videoHtml = `\n
    \n \n
    \n `; + isJavBus && (videoHtml = `\n
    \n \n
    \n `); + $img.parent().append(videoHtml); + $img.hide(); + $img.removeClass("loading"); + $img.next(".loading-spinner").remove(); + $video = $(`#${id}`); + let videoElement = $video[0]; + videoElement.load(); + videoElement.muted = !1; + videoElement.play(); + $video.trigger("focus"); + } +} + + +export { CoverButtonPlugin }; \ No newline at end of file diff --git a/src/plugins/detail-page-button.js b/src/plugins/detail-page-button.js new file mode 100644 index 0000000..89c3033 --- /dev/null +++ b/src/plugins/detail-page-button.js @@ -0,0 +1,336 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, isJavBus, isJavDb, Status_FILTER, Status_FAVORITE, Status_HAS_DOWN, Status_HAS_WATCH, NO, YES } from '../core/constants.js'; +import { HotkeyManager } from './preview-video.js'; + +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 字幕 (SubTitleCat)\n \n
    \n
    \n '; + isJavDb && $(".tabs").after(buttonsHtml); + isJavBus && $("#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: !1, + success: () => { + $("#magnetHubBox").append(magnetHub); + } + }); + })); + const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin"), enableMagnetsFilter = await storageManager.getSetting("enableMagnetsFilter", YES); + $("#magnets-span").text(enableMagnetsFilter === YES ? "关闭磁力过滤" : "开启磁力过滤"); + enableMagnetsFilter === YES && highlightMagnetPlugin.doFilterMagnet(); + $("#enable-magnets-filter").on("click", (event => { + let $span = $("#magnets-span"); + if ("关闭磁力过滤" === $span.text()) { + highlightMagnetPlugin.showAll(); + $span.text("开启磁力过滤"); + storageManager.saveSettingItem("enableMagnetsFilter", NO); + } else { + highlightMagnetPlugin.doFilterMagnet(); + $span.text("关闭磁力过滤"); + storageManager.saveSettingItem("enableMagnetsFilter", YES); + } + })); + $("#search-subtitle-btn").on("click", (event => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum2}`, carNum2, !1, event))); + $("#xunLeiSubtitleBtn").on("click", (() => this.searchXunLeiSubtitle(carNum2))); + this.showStatus(carNum2).then(); + } + 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: + $filterBtn.text(`🚫 已屏蔽 ${hotKeyDisplay(this.filterHotKey)}`); + break; + + case Status_FAVORITE: + $favoriteBtn.text(`⭐ 已收藏 ${hotKeyDisplay(this.favoriteHotKey)}`); + break; + + case Status_HAS_DOWN: + $hasDownBtn.text(`📥️ 已标记下载 ${hotKeyDisplay(this.hasDownHotKey)}`); + break; + + case Status_HAS_WATCH: + $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, + 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, + 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, + 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, + 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, + 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 = !1; + videoEl.controls = !1; + 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 = !1; + } + } else $("#javTrailersBtn").click(); + } + hideVideoControls() { + $(document).on("mouseenter", "#preview-video", (function() { + $(this).prop("controls", !0); + })); + } + async bindHotkey() { + const handlers = {}; + this.filterHotKey && (handlers[this.filterHotKey] = () => { + this.answerCount >= 2 ? this.filterOne(null, !0) : 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 !1; + 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}\n`; + })); + const finalContent = numberedContent; + layer.open({ + type: 1, + title: title, + area: [ "80%", "80%" ], + scrollbar: !1, + content: `
    ${finalContent}
    `, + btn: [ "下载", "关闭" ], + btn1: function(index, layero, that) { + utils.download(resText, name2); + return !1; + } + }); + } catch (error) { + show.error(`预览失败: ${error.message}`); + console.error("预览字幕文件出错:", error); + } else show.error("仅支持预览ASS和SRT字幕文件"); + } +} + + +export { DetailPageButtonPlugin }; diff --git a/src/plugins/detail-page.js b/src/plugins/detail-page.js new file mode 100644 index 0000000..b03bca8 --- /dev/null +++ b/src/plugins/detail-page.js @@ -0,0 +1,39 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField } from '../core/constants.js'; + +const _unsafeWindow = (() => typeof unsafeWindow !== 'undefined' ? unsafeWindow : window)(); + +class DetailPagePlugin extends BasePlugin { + getName() { + return "DetailPagePlugin"; + } + constructor() { + super(); + } + handle() { + if (window.isDetailPage) { + $(".video-meta-panel a").each((function() { + const href = $(this).attr("href"); + href && (href.startsWith("http://") || href.startsWith("https://") || href.startsWith("/")) && $(this).attr("target", "_blank"); + })); + this.handleFancyBox(); + } + } + handleFancyBox() { + document.addEventListener("click", (function(e) { + if (e.target.closest(".fancybox-button--thumbs")) { + const isVisible = !$(".fancybox-thumbs").is(":hidden"); + localStorage.setItem("jhs_fancyboxThumbs", isVisible.toString()); + _unsafeWindow.$.fancybox.defaults.thumbs.autoStart = isVisible; + } + })); + if (void 0 !== _unsafeWindow.$.fancybox) { + const savedState = localStorage.getItem("jhs_fancyboxThumbs"); + _unsafeWindow.$.fancybox.defaults.thumbs.autoStart = "true" === savedState; + } + } +} + + + +export { DetailPagePlugin }; \ No newline at end of file diff --git a/src/plugins/favorite-actresses.js b/src/plugins/favorite-actresses.js new file mode 100644 index 0000000..99f389b --- /dev/null +++ b/src/plugins/favorite-actresses.js @@ -0,0 +1,104 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, YES } from '../core/constants.js'; + +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 = new Set; + favoriteActressesInfoList.forEach((actress => { + actress.starId && favoriteStarIdSet.add(String(actress.starId).trim()); + })); + 0 !== favoriteStarIdSet.size && $(".female").prev().each(((index, element) => { + const $element = $(element), href = $element.attr("href"); + let elementStarId = null; + if (href) { + const parts = (href.endsWith("/") ? href.slice(0, -1) : href).split("/"), lastPart = parts[parts.length - 1]; + lastPart && (elementStarId = lastPart.trim()); + } + let isFavorite = !1; + elementStarId && (isFavorite = favoriteStarIdSet.has(elementStarId)); + if (isFavorite) { + $element.addClass("highlighted"); + $element.attr("title", "高亮已收藏演员, 可在设置-基础配置中关闭"); + } + })); + } + async removeActorFromStorage(actorId) { + await storageManager.removeFavoriteActress(actorId) && clog.log("移除演员成功"); + } + bindEvent() { + const listIdRegex = /\/actors\/(\w+)\/(collect|uncollect)/; + $(document).on("confirm:complete", 'a[href*="/actors/"][href*="/uncollect"]', (async event => { + const [userConfirmed] = event.detail; + if (!userConfirmed) return; + const match = $(event.currentTarget).attr("href").match(listIdRegex), actorId = match ? match[1] : null; + actorId && await this.removeActorFromStorage(actorId); + })); + $("#button-collect-actor").click((async event => { + const match = $("#button-collect-actor").attr("href").match(listIdRegex), actorId = match ? match[1] : null; + let nameList = [], $actor = $(".actor-section-name"); + $actor.length && $actor.text().trim().split(",").forEach((name2 => { + nameList.push(name2.trim()); + })); + let $sectionMeta = $(".section-meta:not(:contains('影片'))"); + $sectionMeta.length && $sectionMeta.text().trim().split(",").forEach((name2 => { + nameList.push(name2.trim()); + })); + if (!nameList) { + clog.error("获取演员名称失败"); + return; + } + const actorName = nameList[0]; + if (!actorId) { + clog.error("无法获取演员ID进行收藏操作。"); + return; + } + const avatar = ($(".avatar").first().css("background-image") || "").replace(/^url\(["']?|["']?\)$/g, ""), actressInfo = { + starId: actorId, + name: actorName, + allName: nameList, + avatar: avatar + }; + 1 === await storageManager.addFavoriteActressList([ actressInfo ]) ? clog.log(`收藏演员成功: ${actorName} (ID: ${actorId})`) : clog.log(`收藏演员失败: ${actorName} (ID: ${actorId})`); + })); + $("#button-uncollect-actor").click((async event => { + const match = $("#button-uncollect-actor").attr("href").match(listIdRegex), actorId = match ? match[1] : null; + actorId ? await this.removeActorFromStorage(actorId) : clog.error("无法获取演员ID进行取消收藏操作。"); + })); + } + async replaceActressAvatar() { + const actressId = this.getActressId(); + if (!actressId) return; + const actress = (await storageManager.getFavoriteActressList()).find((item => item.starId === actressId)); + if (actress && actress.avatar) { + const newAvatarUrl = `url('${actress.avatar}')`; + let $avatarElement = $(".avatar").first(); + if (0 === $avatarElement.length) { + const newAvatarHtml = '
    '; + $(".section-columns").prepend(newAvatarHtml); + $avatarElement = $(".avatar").first(); + } + if (0 === $avatarElement.length) return; + if ($avatarElement.css("background-image").trim().toLowerCase() !== newAvatarUrl.trim().toLowerCase()) { + $avatarElement.css("background-image", newAvatarUrl); + $avatarElement.css("background-size", "cover"); + $avatarElement.css("background-position", "top center"); + $avatarElement.css("background-repeat", "no-repeat"); + } + } + } +} + + +export { FavoriteActressesPlugin }; \ No newline at end of file diff --git a/src/plugins/fc2-by123av.js b/src/plugins/fc2-by123av.js new file mode 100644 index 0000000..7ab925f --- /dev/null +++ b/src/plugins/fc2-by123av.js @@ -0,0 +1,354 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, currentHref, Status_FILTER, Status_FAVORITE, Status_HAS_DOWN, Status_HAS_WATCH, YES } from '../core/constants.js'; + +class Fc2By123AvPlugin extends BasePlugin { + constructor() { + super(...arguments); + __publicField(this, "$contentBox", $(".section .container")); + __publicField(this, "urlParams", new URLSearchParams(window.location.search)); + __publicField(this, "sortVal", this.urlParams.get("sort") || "release_date"); + __publicField(this, "currentPage", this.urlParams.get("page") ? parseInt(this.urlParams.get("page")) : 1); + __publicField(this, "maxPage", null); + __publicField(this, "keyword", this.urlParams.get("keyword") || null); + } + getName() { + return "Fc2By123AvPlugin"; + } + async getBaseUrl() { + const otherSitePlugin = this.getBean("OtherSitePlugin"); + return await otherSitePlugin.getAv123Url() + "/ja"; + } + handle() { + $("#navbar-menu-hero > div > div:nth-child(1) > div > a:nth-child(4)").after('123Av-Fc2'); + $('.tabs li:contains("FC2")').after('
  • 123Av-Fc2
  • '); + if (currentHref.includes("/advanced_search?type=100")) { + this.hookPage(); + this.handleQuery().then(); + } + } + hookPage() { + let $h2 = $("h2.section-title"); + $h2.contents().first().replaceWith("123Av"); + $h2.css("marginBottom", "0"); + $h2.append('\n
    \n \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 '); + $(`#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: imgSrc, + carNum: carNum2, + href: href, + title: title + }); + })); + if (!this.maxPage) { + let rawMaxPage, lastPageItem = $dom.find(".page-item:not(.disabled)").last(); + if (lastPageItem.find("a.page-link").length) { + let href = lastPageItem.find("a.page-link").attr("href"); + rawMaxPage = parseInt(href.split("page=")[1]); + } else rawMaxPage = parseInt(lastPageItem.find("span.page-link").text()); + this.maxPage = Math.ceil(rawMaxPage / 2); + this.renderPagination(); + } + } + if (0 === dataList.length) { + console.log(dataList); + show.error("无结果"); + let errorUrl = `${baseUrl}/dm4/tags/fc2?sort=${this.sortVal}`; + this.keyword && (errorUrl = `${baseUrl}/search?keyword=${this.keyword}`); + console.error("获取数据失败!", errorUrl); + } + let movieHtml = this.markDataListHtml(dataList); + $(".movie-list").html(movieHtml); + await utils.smoothScrollToTop(); + } catch (e) { + console.error(e); + } finally { + loadObj.close(); + } + } + async open123AvFc2Dialog(carNum2, href) { + let otherHtml = ""; + await storageManager.getSetting("enableLoadOtherSite", YES) === YES && (otherHtml = '
    第三方站点:
    '); + let pageHtml = `\n
    \n \x3c!--
    \n \n
    \n
    --\x3e\n
    \n
    加载中...
    \n
    \n \n ${otherHtml}\n \n
    \n 🚫 屏蔽\n ⭐ 收藏\n 📥️ 已下载\n 🔍 已观看\n \n \n 字幕 (SubTitleCat)\n \n \n 字幕 (迅雷)\n \n
    \n
    \n \n
    \n
    \n
    \n \n \n \x3c!--
    --\x3e\n
    \n `; + layer.open({ + type: 1, + title: carNum2, + content: pageHtml, + area: utils.getDefaultArea(), + skin: "movie-detail-layer", + scrollbar: !1, + success: (layero, index) => { + utils.setupEscClose(index); + this.loadData(carNum2, href); + let keyword = carNum2.replace("FC2-", ""); + $("#magnets-content").append(this.getBean("MagnetHubPlugin").createMagnetHub(keyword)); + $("#favoriteBtn").on("click", (async event => { + const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text(); + await storageManager.saveCar({ + carNum: carNum2, + url: href, + names: actress, + actionType: Status_FAVORITE, + publishTime: publishTime + }); + window.refresh(); + layer.closeAll(); + })); + $("#filterBtn").on("click", (event => { + utils.q(event, `是否屏蔽${carNum2}?`, (async () => { + const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text(); + await storageManager.saveCar({ + carNum: carNum2, + url: href, + names: actress, + actionType: Status_FILTER, + publishTime: publishTime + }); + window.refresh(); + layer.closeAll(); + window.location.href.includes("collection_codes?movieId") && utils.closePage(); + })); + })); + $("#hasDownBtn").on("click", (async event => { + const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text(); + await storageManager.saveCar({ + carNum: carNum2, + url: href, + names: actress, + actionType: Status_HAS_DOWN, + publishTime: publishTime + }); + window.refresh(); + layer.closeAll(); + })); + $("#hasWatchBtn").on("click", (async event => { + const actress = $("#data-actress").text(), publishTime = $("#data-publishTime").text(); + await storageManager.saveCar({ + carNum: carNum2, + url: href, + names: actress, + actionType: Status_HAS_WATCH, + publishTime: publishTime + }); + window.refresh(); + layer.closeAll(); + })); + $("#search-subtitle-btn").on("click", (event => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum2}`, carNum2, !1, event))); + $("#xunLeiSubtitleBtn").on("click", (() => this.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(carNum2))); + let tempCarNum = carNum2.replace("FC2-", ""); + this.getBean("OtherSitePlugin").loadOtherSite(tempCarNum, carNum2).then(); + } + }); + } + async loadData(carNum2, href) { + let loadObj = loading(); + try { + const {id: id, publishDate: publishDate, title: title, moviePoster: moviePoster} = await this.get123AvVideoInfo(href); + $(".movie-info-container").html(`\n

    ${title || "无标题"}

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

    剧照: 暂无剧照

    "); + $(".image-list").html(imagesHtml); + this.handleLongImg(tempCarNum); + } + async getMovie(id, moviePoster) { + let url = `${await this.getBaseUrl()}/ajax/v/${id}/videos`, loadObj = loading(); + try { + let movieList = (await gmHttp.get(url)).result.watch; + if (movieList.length > 0) { + movieList.forEach((movieItem => { + movieItem.url = movieItem.url + "?poster=" + moviePoster; + })); + return movieList; + } + return null; + } catch (e) { + console.error(e); + } finally { + loadObj.close(); + } + } + markDataListHtml(movies) { + let moviesHtml = ""; + movies.forEach((movie => { + moviesHtml += `\n
    \n \n
    \n \n
    \n
    ${movie.carNum} ${movie.title}
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n `; + })); + return moviesHtml; + } +} + + +export { Fc2By123AvPlugin }; diff --git a/src/plugins/fc2.js b/src/plugins/fc2.js new file mode 100644 index 0000000..e585e3a --- /dev/null +++ b/src/plugins/fc2.js @@ -0,0 +1,190 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, currentHref, Status_FILTER, Status_FAVORITE, Status_HAS_DOWN, Status_HAS_WATCH } from '../core/constants.js'; +import { javDbApi } from '../api/javdb.js'; + +class Fc2Plugin extends BasePlugin { + getName() { + return "Fc2Plugin"; + } + async initCss() { + return "\n \n "; + } + handle() { + let fc2Url = "/advanced_search?type=3&score_min=0&d=1"; + $('.navbar-item:contains("FC2")').attr("href", fc2Url); + $('.tabs a:contains("FC2")').attr("href", fc2Url); + if (currentHref.includes("advanced_search?type=3")) { + $("h2.section-title").contents().first().replaceWith("Fc2PPV"); + $(".section .container > .box").remove(); + } + if (currentHref.includes("collection_codes?movieId")) { + $("section").html(""); + const urlParams = new URLSearchParams(window.location.search); + let movieId = urlParams.get("movieId"), carNum2 = urlParams.get("carNum"), url = urlParams.get("url"); + movieId && carNum2 && url && this.openFc2Dialog(movieId, carNum2, url); + } + } + openFc2Dialog(movieId, carNum2, href) { + let tempCarNum = carNum2.replace("FC2-", ""); + if (href.includes("123av")) { + this.getBean("Fc2By123AvPlugin").open123AvFc2Dialog(carNum2, href); + return; + } + layer.open({ + type: 1, + title: carNum2, + content: '\n
    \n \x3c!--
    \n \n
    --\x3e\n \x3c!--
    --\x3e\n
    \n
    加载中...
    \n
    \n \n
    第三方资源:
    \n \n
    \n 🚫 屏蔽\n ⭐ 收藏\n 📥️ 已下载\n 🔍 已观看\n \n \n 字幕 (SubTitleCat)\n \n \n 字幕 (迅雷)\n \n \n 磁力搜索\n \n
    \n
    \n \n
    \n
    \n
    \n \n \n \x3c!--
    --\x3e\n
    \n ', + area: utils.getResponsiveArea([ "70%", "90%" ]), + skin: "movie-detail-layer", + scrollbar: !1, + success: (layero, index) => { + this.loadData(movieId, carNum2); + $("#favoriteBtn").on("click", (async event => { + const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text(); + await storageManager.saveCar({ + carNum: carNum2, + url: href, + names: actress, + actionType: Status_FAVORITE, + publishTime: publishTime + }); + window.refresh(); + layer.closeAll(); + })); + $("#filterBtn").on("click", (event => { + utils.q(event, `是否屏蔽${carNum2}?`, (async () => { + const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text(); + await storageManager.saveCar({ + carNum: carNum2, + url: href, + names: actress, + actionType: Status_FILTER, + publishTime: publishTime + }); + window.refresh(); + layer.closeAll(); + window.location.href.includes("collection_codes?movieId") && utils.closePage(); + })); + })); + $("#hasDownBtn").on("click", (async event => { + const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text(); + await storageManager.saveCar({ + carNum: carNum2, + url: href, + names: actress, + actionType: Status_HAS_DOWN, + publishTime: publishTime + }); + window.refresh(); + layer.closeAll(); + })); + $("#hasWatchBtn").on("click", (async event => { + const actress = $("#data-actress").text(), publishTime = $("#data-releaseDate").text(); + await storageManager.saveCar({ + carNum: carNum2, + url: href, + names: actress, + actionType: Status_HAS_WATCH, + publishTime: publishTime + }); + window.refresh(); + layer.closeAll(); + })); + $("#search-subtitle-btn").on("click", (event => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum2}`, carNum2, !1, event))); + $("#xunLeiSubtitleBtn").on("click", (() => this.getBean("DetailPageButtonPlugin").searchXunLeiSubtitle(carNum2))); + $("#magnetSearchBtn").on("click", (() => { + let magnetHub = this.getBean("MagnetHubPlugin").createMagnetHub(carNum2); + layer.open({ + type: 1, + title: "磁力搜索", + content: '
    ', + area: utils.getResponsiveArea([ "60%", "80%" ]), + scrollbar: !1, + success: () => { + $("#magnetHubBox").append(magnetHub); + } + }); + })); + this.getBean("OtherSitePlugin").loadOtherSite(tempCarNum, carNum2).then(); + utils.setupEscClose(index); + }, + end() { + window.location.href.includes("collection_codes?movieId") && utils.closePage(); + } + }); + } + loadData(movieId, carNum2) { + let tempCarNum = carNum2.replace("FC2-", ""); + this.handleMovieDetail(movieId); + this.handleLongImg(tempCarNum); + this.handleMagnets(movieId); + this.getBean("ReviewPlugin").showReview(movieId, $("#reviews-content")).then(); + this.getBean("RelatedPlugin").showRelated($("#related-content"), movieId).then(); + } + handleMovieDetail(movieId) { + javDbApi.getMovieDetail(movieId).then((res => { + const actors = res.actors || [], imgList = res.imgList || []; + let actorsHtml = ""; + if (actors.length > 0) { + let actress = ""; + for (let i = 0; i < actors.length; i++) { + let actor = actors[i]; + actorsHtml += `${actor.name}`; + 0 === actor.gender && (actress += actor.name + " "); + } + $("#data-actress").text(actress); + } else actorsHtml = '暂无演员信息'; + let imagesHtml = ""; + imagesHtml = Array.isArray(imgList) && imgList.length > 0 ? imgList.map(((img, index) => `\n \n \n \n `)).join("") : '
    暂无剧照
    '; + $(".movie-info-container").html(`\n

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

    \n
    \n 番号: ${res.carNum || "未知"}\n 年份: ${res.releaseDate || "未知"}\n 评分: ${res.score || "无"}\n 时长: ${res.duration + " m" || "无"}\n
    \n
    \n \n 站点: \n fc2ppvdb\n fc2电子市场\n \n
    \n
    \n
    主演: ${actorsHtml}
    \n
    \n \n \n `); + this.getBean("TranslatePlugin").translate(res.carNum, !1).then(); + })).catch((err => { + console.error(err); + $(".movie-info-container").html(`\n
    加载失败: ${err.message}
    \n `); + })); + } + handleLongImg(carNum2) { + utils.loopDetector((() => $(".movie-gallery .image-list").length > 0), (async () => { + $(".movie-gallery .image-list").prepend('
    正在加载缩略图
    '); + const screenShotPlugin = this.getBean("ScreenShotPlugin"), imgUrl = await screenShotPlugin.getScreenshot(carNum2); + imgUrl && await screenShotPlugin.addImg("缩略图", imgUrl); + })); + } + handleMagnets(movieId) { + javDbApi.getMagnets(movieId).then((magnetList => { + let magnetsHtml = ""; + if (magnetList.length > 0) for (let i = 0; i < magnetList.length; i++) { + let magnet = magnetList[i], oddClass = ""; + i % 2 == 0 && (oddClass = "odd"); + magnetsHtml += `\n
    \n \n
    \n \n
    \n
    ${magnet.created_at}
    \n
    \n `; + } else magnetsHtml = '暂无磁力信息'; + $("#magnets-content").html(magnetsHtml); + $(".buttons button[data-clipboard-text*='magnet:']").each(((i, el) => { + $(el).parent().append($("'); + $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(`\n
    \n
    已选分类: ${checkTagStr}
    \n \n \n ${foldCategoryHotKey ? ` (${foldCategoryHotKey})` : ""}\n \n \n\n
    \n `); + 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"](); + })); + } +} + + +export { FoldCategoryPlugin }; diff --git a/src/plugins/highlight-magnet.js b/src/plugins/highlight-magnet.js new file mode 100644 index 0000000..a532ef6 --- /dev/null +++ b/src/plugins/highlight-magnet.js @@ -0,0 +1,56 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, isJavBus, isJavDb } from '../core/constants.js'; + +class HighlightMagnetPlugin extends BasePlugin { + getName() { + return "HighlightMagnetPlugin"; + } + doFilterMagnet() { + this.handleDb(); + this.handleBus(); + } + handleDb() { + if (!isJavDb) return; + let magnetNameList = $("#magnets-content .name"); + if (0 === magnetNameList.length) return; + const HIGH_QUALITY_KEYWORDS = [ "4k", "-c", "-u", "-uc" ]; + let hasHighQuality = !1; + magnetNameList.each(((_, el) => { + const $el = $(el), text = $el.text().toLowerCase(), isHighQuality = HIGH_QUALITY_KEYWORDS.some((keyword => text.includes(keyword))); + $el.parent().parent().parent().addClass("magnet-row"); + text.includes("4k") && $el.css("color", "#f40"); + if (isHighQuality) { + hasHighQuality = !0; + $el.parent().parent().parent().addClass("high-quality"); + } + })); + hasHighQuality ? $("#magnets-content .magnet-row").not(".high-quality").hide() : $("#enable-magnets-filter").addClass("do-hide"); + } + handleBus() { + isJavBus && window.isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => { + const rows = $("#magnet-table tr"), QUALITY_KEYWORDS = [ "4k", "-c", "-u", "-uc" ]; + let hasHighQuality = !1; + rows.each(((_, row) => { + const $row = $(row), $firstTd = $row.find("td:first-child"), $firstLink = $firstTd.find("a:first-child"), $secondLink = $firstTd.find("a:nth-child(2)"), linkText = $firstLink.text().toLowerCase(); + linkText.includes("4k") && $firstLink.css("color", "#f40"); + if (QUALITY_KEYWORDS.some((keyword => linkText.includes(keyword))) || $secondLink.length && $secondLink.text().includes("字幕")) { + hasHighQuality = !0; + $row.addClass("high-quality"); + } + })); + hasHighQuality ? rows.each(((_, row) => { + const $row = $(row); + $row.hasClass("high-quality") || $row.hide(); + })) : $("#enable-magnets-filter").addClass("do-hide"); + })); + } + showAll() { + if (isJavDb) { + $("#magnets-content .item").toArray().forEach((el => $(el).show())); + } + isJavBus && $("#magnet-table tr").toArray().forEach((el => $(el).show())); + } +} + + +export { HighlightMagnetPlugin }; \ No newline at end of file diff --git a/src/plugins/history.js b/src/plugins/history.js new file mode 100644 index 0000000..e2b81b8 --- /dev/null +++ b/src/plugins/history.js @@ -0,0 +1,479 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, isJavBus, isJavDb, Status_FILTER, Status_FAVORITE, Status_HAS_DOWN, Status_HAS_WATCH } from '../core/constants.js'; + +class HistoryPlugin extends BasePlugin { + constructor() { + super(...arguments); + __publicField(this, "tableObj", null); + } + getName() { + return "HistoryPlugin"; + } + 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 = !0; + try { + const imgUrl = await this.searchByImage(imageSrc); + $actionBtns.hide(); + $searchResults.show(); + $siteBtnsContainer.empty(); + const key = "jhs_selectedSites", selectedSites = JSON.parse(localStorage.getItem(key) || "{}"); + this.siteList.forEach((site => { + const siteUrl = site.url.replace("{占位符}", encodeURIComponent(imgUrl)), isChecked = !1 !== selectedSites[site.name]; + $siteBtnsContainer.append(`\n \n \n ${site.name}\n ${site.name}\n \n `); + })); + $siteBtnsContainer.on("change", ".site-checkbox", (function() { + const siteName = $(this).data("site-name"); + selectedSites[siteName] = $(this).is(":checked"); + localStorage.setItem(key, JSON.stringify(selectedSites)); + })); + $siteBtnsContainer.show(); + } finally { + this.isUploading = !1; + } + } + } else show.info("请粘贴或上传图片"); + })); + $cancelBtn.on("click", (() => { + $previewArea.hide(); + $urlInputContainer.hide(); + $fileInput.val(""); + $imageUrlInput.val(""); + })); + $imageUrlInput.on("change", (() => { + if (utils.isUrl($imageUrlInput.val())) { + $previewImage.attr("src", $imageUrlInput.val()); + $previewArea.show(); + } + })); + $("#openAll").on("click", (() => { + $(".search-img-site-btn").each((function() { + $(this).find(".site-checkbox").is(":checked") && window.open($(this).attr("href")); + })); + })); + } + resetSearchUI() { + $("#action-btns").show(); + $("#search-results").hide(); + $("#search-img-site-btns-container").hide().empty(); + } + handleImageFile(file) { + const previewImage = document.getElementById("preview-image"), previewArea = document.getElementById("preview-area"), urlInputContainer = document.getElementById("url-input-container"); + if (!file.type.match("image.*")) { + show.info("请选择图片文件"); + return; + } + const reader = new FileReader; + reader.onload = e => { + previewImage.src = e.target.result; + previewArea.style.display = "block"; + urlInputContainer.style.display = "none"; + $("#handle-btn")[0].click(); + }; + reader.readAsDataURL(file); + } + async searchByImage(imageSrc) { + let loadObj = loading(); + try { + let imageUrl = imageSrc; + if (imageSrc.startsWith("data:")) { + show.info("开始上传图片..."); + const imgurUrl = await async function(base64Data) { + var _a2; + const matches = base64Data.match(/^data:(.+);base64,(.+)$/); + if (!matches || matches.length < 3) throw new Error("无效的Base64图片数据"); + const mimeType = matches[1], imageData = matches[2], byteCharacters = atob(imageData), byteNumbers = new Array(byteCharacters.length); + for (let i = 0; i < byteCharacters.length; i++) byteNumbers[i] = byteCharacters.charCodeAt(i); + const byteArray = new Uint8Array(byteNumbers), blob = new Blob([ byteArray ], { + type: mimeType + }), formData = new FormData; + formData.append("image", blob); + const response = await fetch("https://api.imgur.com/3/image", { + method: "POST", + headers: { + Authorization: "Client-ID d70305e7c3ac5c6" + }, + body: formData + }), data = await response.json(); + if (data.success && data.data && data.data.link) return data.data.link; + throw new Error((null == (_a2 = data.data) ? void 0 : _a2.error) || "上传到Imgur失败"); + }(imageSrc); + if (!imgurUrl) { + show.error("上传到失败"); + return; + } + imageUrl = imgurUrl; + } + return imageUrl; + } catch (error) { + show.error(`搜索失败: ${error.message}`); + console.error("搜索失败:", error); + } finally { + loadObj.close(); + } + } +} + + +export { ImageRecognitionPlugin }; diff --git a/src/plugins/jav-trailers.js b/src/plugins/jav-trailers.js new file mode 100644 index 0000000..91c6b70 --- /dev/null +++ b/src/plugins/jav-trailers.js @@ -0,0 +1,88 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField } from '../core/constants.js'; +import { HotkeyManager } from './preview-video.js'; + +class JavTrailersPlugin extends BasePlugin { + getName() { + return "JavTrailersPlugin"; + } + constructor() { + super(); + this.hasBand = !1; + } + handle() { + let href = window.location.href; + if (!href.includes("handle=1")) return; + if ($("h1:contains('Page not found')").length) { + console.log("番号无法匹配, 跳搜索"); + let keyword = href.split("?")[0].split("video/")[1].toLowerCase().replace("00", "-"); + window.location.href = "/search/" + encodeURIComponent(keyword) + window.location.search; + return; + } + let findList = $(".videos-list .video-link").toArray(); + if (findList.length) { + const keyword = href.split("?")[0].split("search/")[1].toLowerCase(), matchedLink = findList.find((el => $(el).find(".vid-title").text().toLowerCase().includes(keyword))); + if (matchedLink) { + window.location.href = $(matchedLink).attr("href") + window.location.search; + return; + } + } + this.handlePlayJavTrailers(); + $("#videoPlayerContainer").on("click", (() => { + this.handlePlayJavTrailers(); + })); + window.addEventListener("message", (event => { + let videoEl = document.getElementById("vjs_video_3_html5_api"); + videoEl && (videoEl.currentTime += 5); + })); + const urlParams = new URLSearchParams(window.location.search), filterHotKey = urlParams.get("filterHotKey"), favoriteHotKey = urlParams.get("favoriteHotKey"), speedVideoHotKey = urlParams.get("speedVideoHotKey"); + filterHotKey && HotkeyManager.registerHotkey(filterHotKey, (() => window.parent.postMessage(filterHotKey, "*"))); + favoriteHotKey && HotkeyManager.registerHotkey(favoriteHotKey, (() => window.parent.postMessage(favoriteHotKey, "*"))); + speedVideoHotKey && HotkeyManager.registerHotkey(speedVideoHotKey, (() => { + const videoEl = document.getElementById("vjs_video_3_html5_api"); + videoEl && (videoEl.currentTime += 5); + })); + } + handlePlayJavTrailers() { + if (!this.hasBand) { + utils.loopDetector((() => 0 !== $("#vjs_video_3_html5_api").length), (() => { + setTimeout((() => { + this.hasBand = !0; + let videoEl = document.getElementById("vjs_video_3_html5_api"); + console.log(videoEl); + videoEl.play(); + videoEl.currentTime = 5; + videoEl.addEventListener("timeupdate", (function() { + videoEl.currentTime >= 14 && videoEl.currentTime < 16 && (videoEl.currentTime += 2); + })); + $("#vjs_video_3_html5_api").css({ + position: "fixed", + width: "100vw", + height: "100vh", + objectFit: "cover", + zIndex: "999999999" + }); + $(".vjs-control-bar").css({ + position: "fixed", + bottom: "20px", + zIndex: "999999999" + }); + }), 100); + })); + utils.loopDetector((() => $("#vjs_video_3 canvas").length > 0), (() => { + 0 !== $("#vjs_video_3 canvas").length && $("#vjs_video_3 canvas").css({ + position: "fixed", + width: "100vw", + height: "100vh", + objectFit: "cover", + top: "0", + right: "0", + zIndex: "999999998" + }); + })); + } + } +} + + +export { JavTrailersPlugin }; diff --git a/src/plugins/list-page-button.js b/src/plugins/list-page-button.js new file mode 100644 index 0000000..af57d69 --- /dev/null +++ b/src/plugins/list-page-button.js @@ -0,0 +1,283 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, isJavBus, isJavDb, isSearchPage, currentHref, Status_FAVORITE, NO, YES } from '../core/constants.js'; + +class ListPageButtonPlugin extends BasePlugin { + getName() { + return "ListPageButtonPlugin"; + } + async handle() { + if (!window.isListPage) return; + await this.createMenuBtn(); + this.bindEvent(); + await storageManager.getSetting("autoPage") === YES ? $("#sort-toggle-btn").hide() : this.sortItems().then(); + } + async createMenuBtn() { + const showWaitCheckBtn = await storageManager.getSetting("showWaitCheckBtn", YES), showWaitDownBtn = await storageManager.getSetting("showWaitDownBtn", YES); + if (isJavDb) { + const isStarPage = currentHref.includes("/actors/"); + let $el = $(".main-tabs, .tabs"), addBlacklistBtnText = "加入黑名单", addBlacklistBtnColor = "#d22020", otherCss = "", blacklistItem = null; + if (isStarPage) { + $el = $(".toolbar, .section-addition").filter(":last"); + const blacklist = await storageManager.getBlacklist(), actressPageInfo = this.getActressPageInfo(); + if (blacklist.find((item => item.starId === actressPageInfo.starId))) { + addBlacklistBtnText = "已加入黑名单"; + addBlacklistBtnColor = "#885d5d"; + } + } else currentHref.includes("/tags") && utils.loopDetector((() => $("#jhs-check-tag").text().trim()), (async () => { + const $addBlacklistBtn = $("#addBlacklistBtn"); + $addBlacklistBtn.attr("data-tip", "将当前分类标签加入到黑名单, 后续有作品更新也会纳入屏蔽中"); + const checkTag = $("#jhs-check-tag").text().trim(); + if (!checkTag) return; + const tagStarId = "no-" + checkTag, blacklist = await storageManager.getBlacklist(); + blacklistItem = blacklist.find((item => item.starId === tagStarId)); + if (blacklistItem) { + $addBlacklistBtn.css("backgroundColor", "#885d5d"); + $("#addBlacklistBtn span").text("已加入黑名单"); + } + })); + const isFc2Page2 = currentHref.includes("advanced_search"); + isFc2Page2 ? $el = $("h2.section-title") : otherCss = "flex-grow:1;"; + const jhs_sortMethod = localStorage.getItem("jhs_sortMethod"), sortText = "当前排序方式: " + ("rateCount" === jhs_sortMethod ? "评价人数" : "date" === jhs_sortMethod ? "时间" : "默认"); + $el.append(`\n
    \n 打开待鉴定\n 打开已收藏\n ${isStarPage ? `\n ${addBlacklistBtnText}\n 一键屏蔽所有作品\n ` : ""}\n ${currentHref.includes("/tags") ? `\n ${addBlacklistBtnText}\n ` : ""}\n
    \n
    \n 新作品检测 (0)\n 演员黑名单\n ${isSearchPage || isFc2Page2 ? "" : ` ${sortText} `}\n
    \n `); + } + if (isJavBus) { + const isStarPage = currentHref.includes("/star/"); + let addBlacklistBtnText = "加入黑名单", addBlacklistBtnColor = "#d22020"; + if (isStarPage) { + const blacklist = await storageManager.getBlacklist(), actressPageInfo = this.getActressPageInfo(); + if (blacklist.find((item => item.starId === actressPageInfo.starId))) { + addBlacklistBtnText = "已加入黑名单"; + addBlacklistBtnColor = "#885d5d"; + } + } + $(".masonry").parent().prepend(`\n
    \n 打开待鉴定\n 打开已收藏\n \n ${isStarPage ? ` \n ${addBlacklistBtnText}\n 一键屏蔽所有作品\n ` : '演员黑名单'}\n
    \n `); + } + const newVideoPlugin = window.pluginManager.getBean("NewVideoPlugin"); + newVideoPlugin && newVideoPlugin.showNewVideoCount().then(); + } + bindEvent() { + $("#waitCheckBtn").on("click", (event => { + this.openWaitCheck(event).then(); + })); + $("#waitDownBtn").on("click", (event => { + this.openFavorite(event).then(); + })); + $("#newVideoBtn").on("click", (event => { + this.getBean("NewVideoPlugin").openDialog(); + })); + $("#blacklistBtn").on("click", (event => { + this.getBean("BlacklistPlugin").openBlacklistDialog(); + })); + $("#sort-toggle-btn").on("click", (event => { + const currentMethod = localStorage.getItem("jhs_sortMethod") || "default"; + + // 1. 定义排序状态的循环顺序 + const sortCycle = ["default", "rateCount", "totalScore", "date"]; + let nextIndex = sortCycle.indexOf(currentMethod) + 1; + if (nextIndex >= sortCycle.length || nextIndex < 0) nextIndex = 0; + + const newMethod = sortCycle[nextIndex]; + + // 2. 映射对应的文本 + const methodText = { + default: "默认", + rateCount: "评价人数", + totalScore: "总分", + date: "时间" + }[newMethod]; + + $(event.target).text(`当前排序方式: ${methodText}`); + localStorage.setItem("jhs_sortMethod", newMethod); + this.sortItems().then(); + })); + const blacklistPlugin = this.getBean("BlacklistPlugin"); + $("#addBlacklistBtn").on("click", (async event => { + await blacklistPlugin.addBlacklist(event); + })); + $("#filterAllVideo").on("click", (async event => { + let tempEvent = { + clientX: event.clientX, + clientY: event.clientY + 80 + }, $actor = isJavDb ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10"); + if (0 === $actor.length) { + show.error("获取演员名称失败"); + return; + } + let actorName = $actor.text().trim().split(",")[0]; + utils.q(tempEvent, "一键屏蔽已选分类的视频列表至鉴定记录中?", (async () => { + this.loadObj = loading(); + try { + await blacklistPlugin.filterAllVideo(actorName); + window.refresh(); + } catch (e) { + console.error(e); + } finally { + this.loadObj.close(); + } + })); + })); + } + async sortItems() { + if (currentHref.includes("handle") || currentHref.includes("advanced_search")) return; + const autoPage = await storageManager.getSetting("autoPage"); + if (isSearchPage || autoPage === YES) return; + + const method = localStorage.getItem("jhs_sortMethod"); + if (!method) return; + + $(".movie-list .item").each((function(index) { + $(this).attr("data-original-index") || $(this).attr("data-original-index", index); + })); + + const $container = $(".movie-list"), $items = $(".item", $container); + + if ("default" === method) { + $items.sort((function(a, b) { + return $(a).data("original-index") - $(b).data("original-index"); + })).appendTo($container); + } else { + const items = $items.get(); + items.sort((function(a, b) { + + // 按评价人数排序 + if ("rateCount" === method) { + const getScore = el => { + const match = $(el).find(".score .value").text().match(/由(\d+)人/); + return match ? parseFloat(match[1]) : 0; + }; + return getScore(b) - getScore(a); + } + // 新增:按总分排序 (平均分 * 评价人数) + else if ("totalScore" === method) { + const getTotalScore = el => { + const text = $(el).find(".score .value").text(); + // 分别提取分数和人数,增加容错率 + const scoreMatch = text.match(/([\d.]+)分/); + const countMatch = text.match(/由(\d+)人/); + + const score = scoreMatch ? parseFloat(scoreMatch[1]) : 0; + const count = countMatch ? parseFloat(countMatch[1]) : 0; + return score * count; + }; + return getTotalScore(b) - getTotalScore(a); + } + // 按时间排序 + else { + const getDate = el => { + const dateStr = $(el).find(".meta").text().trim(); + return new Date(dateStr); + }; + return getDate(b) - getDate(a); + } + + })); + $container.empty().append(items); + } + } + async openWaitCheck(event) { + const maxCount = await storageManager.getSetting("waitCheckCount", 5); + let count = 0; + $(`${this.getSelector().itemSelector}:visible`).each(((i, el) => { + if (count >= maxCount) return !1; + const $box2 = $(el); + if ($box2.find(".status-tag").length > 0) return; + const {carNum: carNum2, aHref: aHref} = this.getBoxCarInfo($box2); + if (carNum2.includes("FC2-")) { + const movieId = this.parseMovieId(aHref); + this.getBean("Fc2Plugin").openFc2Page(movieId, carNum2, aHref); + } else { + let url = aHref + (aHref.includes("?") ? "&autoPlay=1" : "?autoPlay=1"); + window.open(url); + } + count++; + })); + 0 === count && show.info("没有需鉴定的视频"); + } + async openFavorite() { + let favoriteList, openCount = await storageManager.getSetting("waitCheckCount", 5), randomOpenWaitDown = await storageManager.getSetting("randomOpenWaitDown", NO), dataList = await storageManager.getCarList(); + favoriteList = randomOpenWaitDown === YES ? dataList.filter((item => item.status === Status_FAVORITE)).sort((() => Math.random() - .5)) : dataList.filter((item => item.status === Status_FAVORITE)).sort(((a, b) => b.createDate - a.createDate)); + for (let i = 0; i < openCount; i++) { + if (i >= favoriteList.length) return; + let data = favoriteList[i], carNum2 = data.carNum, url = data.url; + if (carNum2.includes("FC2-")) { + const movieId = this.parseMovieId(url); + await this.getBean("Fc2Plugin").openFc2Page(movieId, carNum2, url); + } else window.open(url); + clog.debug("打开已收藏", carNum2, url); + } + } +} + +export const translateText = async (text, sourceLang = "ja", targetLang = "zh-CN") => { + if (!text) throw new Error("翻译文本不能为空"); + const url = "https://translate-pa.googleapis.com/v1/translate?" + new URLSearchParams({ + "params.client": "gtx", + dataTypes: "TRANSLATION", + key: "AIzaSyDLEeFI5OtFBwYBIoK_jj5m32rZK5CkCXA", + "query.sourceLanguage": sourceLang, + "query.targetLanguage": targetLang, + "query.text": text + }), res = await fetch(url); + if (!res.ok) throw new Error(`${res.status} ${res.statusText}`); + return (await res.json()).translation; +}, TAG_STYLES = { + IS_FILTERED: { + text: "🚫 已屏蔽", + color: "#de3333", + reasonType: "单番号屏蔽", + isCounted: !0, + countKey: "currentPageFilterCount" + }, + IS_FAVORITE: { + text: "⭐ 已收藏", + color: "#25b1dc", + reasonType: "", + isCounted: !0, + countKey: "currentPageFavoriteCount" + }, + IS_HAS_DOWN: { + text: "📥️ 已下载", + color: "#7bc73b", + reasonType: "", + isCounted: !0, + countKey: "currentPageHasDownCount" + }, + IS_HAS_WATCH: { + text: "🔍 已观看", + color: "#d7a80c", + reasonType: "", + isCounted: !0, + countKey: "currentPageHasWatchCount" + }, + IS_KEYWORD_FILTER: { + text: "❌ 关键词屏蔽", + color: "#de3333", + reasonType: "", + isCounted: !0, + countKey: "currentPageKeywordFilterCount" + }, + IS_ACTOR_FILTER: { + text: "♂️ 男演员屏蔽", + color: "#b22222", + reasonType: "", + isCounted: !0, + countKey: "currentPageActorFilterCount" + }, + IS_ACTRESS_FILTER: { + text: "♀️ 女演员屏蔽", + color: "#cd5c5c", + reasonType: "", + isCounted: !0, + countKey: "currentPageActorFilterCount" + }, + IS_WAIT_CHECK: { + text: "", + color: "", + reasonType: "", + isCounted: !0, + countKey: "currentPageWaitCheckCount" + } +}; + + +export { ListPageButtonPlugin }; \ No newline at end of file diff --git a/src/plugins/list-page.js b/src/plugins/list-page.js new file mode 100644 index 0000000..994f00e --- /dev/null +++ b/src/plugins/list-page.js @@ -0,0 +1,475 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, isJavBus, isJavDb, isSearchPage, currentHref, Status_FILTER, Status_FAVORITE, Status_HAS_DOWN, Status_HAS_WATCH, NO, YES } from '../core/constants.js'; +import { HotkeyManager } from './preview-video.js'; +import { translateText, TAG_STYLES } from './list-page-button.js'; + +class ListPagePlugin extends BasePlugin { + constructor() { + super(...arguments); + __publicField(this, "currentPageFilterCount", 0); + __publicField(this, "currentPageFavoriteCount", 0); + __publicField(this, "currentPageHasDownCount", 0); + __publicField(this, "currentPageHasWatchCount", 0); + __publicField(this, "currentPageKeywordFilterCount", 0); + __publicField(this, "currentPageActorFilterCount", 0); + __publicField(this, "currentPageWaitCheckCount", 0); + __publicField(this, "currentPageTotalCount", 0); + __publicField(this, "cache", localStorage.getItem("jhs_translate") ? JSON.parse(localStorage.getItem("jhs_translate")) : {}); + __publicField(this, "writeQueue", Promise.resolve()); + } + getName() { + return "ListPagePlugin"; + } + async handle() { + this.cleanRepeatId(); + this.replaceHdImg(); + this.addJumpPageControl(); + this.fixBusTitleBox(); + await this.doFilter(); + this.bindClick().then(); + this.bindListPageHotKey().then(); + this.rememberTagExpand(); + $(this.getSelector().itemSelector + " a").attr("target", "_blank"); + this.checkDom(); + } + rememberTagExpand() { + if (!window.location.href.includes("actors")) return; + const $expandButton = $(".tag-expand"); + if (0 === $expandButton.length) return; + const isExpanded = "true" === localStorage.getItem("jhs_tag_expand"), $actorTagsContent = $(".actor-tags .content"); + isExpanded && $actorTagsContent.hasClass("collapse") && $expandButton[0].click(); + $expandButton.on("click", (function() { + const newState = !$actorTagsContent.hasClass("collapse"); + localStorage.setItem("jhs_tag_expand", newState.toString()); + })); + } + checkDom() { + if (!window.isListPage) return; + const selector = this.getSelector(), targetNode = document.querySelector(selector.boxSelector); + if (!targetNode) { + console.error("没有找到容器节点!"); + return; + } + const observer = new MutationObserver((async mutations => { + observer.disconnect(); + try { + this.replaceHdImg(); + this.addJumpPageControl(); + this.fixBusTitleBox(); + await this.doFilter(); + await this.getBean("ListPageButtonPlugin").sortItems(); + this.getBean("CoverButtonPlugin").addSvgBtn().then(); + $(this.getSelector().itemSelector + " a").attr("target", "_blank"); + this.getBean("AutoPagePlugin").checkLoad(); + } finally { + observer.observe(targetNode, config); + } + })), config = { + childList: !0, + subtree: !1 + }; + observer.observe(targetNode, config); + } + fixBusTitleBox() { + if (!isJavBus) return; + $(this.getSelector().itemSelector).toArray().forEach((ele => { + var _a2; + let $box2 = $(ele); + if ($box2.find(".avatar-box").length > 0) return; + const title = (null == (_a2 = $box2.find("img").attr("title")) ? void 0 : _a2.trim()) || ""; + $box2.find(".photo-info span:first").contents().first().wrap(`${title}`); + $box2.find("br").remove(); + })); + } + cleanRepeatId() { + if (!isJavBus) return; + $("#waterfall_h").removeAttr("id").attr("id", "no-page"); + const $waterfalls = $('[id="waterfall"]'); + 0 !== $waterfalls.length && $waterfalls.each((function() { + const $current = $(this); + if (!$current.hasClass("masonry")) { + $current.children().insertAfter($current); + $current.remove(); + } + })); + } + async doFilter() { + if (!window.isListPage) return; + let movieList = $(this.getSelector().itemSelector).toArray(); + if (movieList.length) { + await this.filterMovieList(movieList); + await this.getBean("WangPan115MatchPlugin").matchMovieList(movieList); + isJavBus && await this.getBean("BusImgPlugin").logImageHeightsByRow(); + } + } + async filterMovieList(movieList) { + utils.time("累计耗费时间"); + utils.time("读取数据耗时"); + const [carList, filterKeywordList, blacklist, blacklistCarList, settingObj] = await Promise.all([ storageManager.getCarList(), storageManager.getTitleFilterKeyword(), storageManager.getBlacklist(), storageManager.getBlacklistCarList(), storageManager.getSetting() ]), t1 = utils.time("读取数据耗时"), carNumSets = carList.reduce(((dataMap, item) => { + const statusKey = item.status; + dataMap.hasOwnProperty(statusKey) && dataMap[statusKey].add(item.carNum); + return dataMap; + }), { + [Status_FILTER]: new Set, + [Status_FAVORITE]: new Set, + [Status_HAS_DOWN]: new Set, + [Status_HAS_WATCH]: new Set + }); + utils.time("组装数据耗时"); + const starIdToRoleMap = new Map(blacklist.map((item => [ item.starId, item.role ]))), {actorCarNumToNameMap: actorCarNumToNameMap, actressCarNumToNameMap: actressCarNumToNameMap} = blacklistCarList.reduce(((dataMap, carItem) => { + const role = starIdToRoleMap.get(carItem.starId); + if (!role) { + clog.error("黑名单数据源丢失演员信息", carItem); + return dataMap; + } + const targetMap = "actor" === role ? dataMap.actorCarNumToNameMap : dataMap.actressCarNumToNameMap; + targetMap.has(carItem.carNum) || targetMap.set(carItem.carNum, carItem.names); + return dataMap; + }), { + actorCarNumToNameMap: new Map, + actressCarNumToNameMap: new Map + }), t2 = utils.time("组装数据耗时"), settings = { + showFilterItem: (null == settingObj ? void 0 : settingObj.showFilterItem) ?? NO, + showFilterActorItem: (null == settingObj ? void 0 : settingObj.showFilterActorItem) ?? NO, + showFilterKeywordItem: (null == settingObj ? void 0 : settingObj.showFilterKeywordItem) ?? NO, + showFavoriteItem: (null == settingObj ? void 0 : settingObj.showFavoriteItem) ?? YES, + showHasDownItem: (null == settingObj ? void 0 : settingObj.showHasDownItem) ?? YES, + showHasWatchItem: (null == settingObj ? void 0 : settingObj.showHasWatchItem) ?? YES, + showAllItem: (null == settingObj ? void 0 : settingObj.showAllItem) ?? NO, + tagPosition: (null == settingObj ? void 0 : settingObj.tagPosition) || "rightTop", + movieShowType: (null == settingObj ? void 0 : settingObj.movieShowType) || "hide" + }; + this.currentPageFilterCount = 0; + this.currentPageFavoriteCount = 0; + this.currentPageHasDownCount = 0; + this.currentPageHasWatchCount = 0; + this.currentPageKeywordFilterCount = 0; + this.currentPageActorFilterCount = 0; + this.currentPageWaitCheckCount = 0; + this.currentPageTotalCount = 0; + utils.time("处理页面耗时"); + await Promise.all(movieList.map((async ele => { + let $box2 = $(ele); + if (isJavBus && $box2.find(".avatar-box").length > 0) return; + const {carNum: carNum2, title: title} = this.getBoxCarInfo($box2), {filter: filter, favorite: favorite, hasDown: hasDown, hasWatch: hasWatch} = carNumSets, isFavorite = favorite.has(carNum2), isHasDown = hasDown.has(carNum2), isHasWatch = hasWatch.has(carNum2), isFiltered = filter.has(carNum2), isFilterActorMale = actorCarNumToNameMap.has(carNum2), isFilterActorFemale = actressCarNumToNameMap.has(carNum2), isFilterActor = isFilterActorMale || isFilterActorFemale, foundKeyword = filterKeywordList.find((kw => title.includes(kw) || carNum2.startsWith(kw))), isFilterKeyword = !!foundKeyword; + if (!isSearchPage) { + let shouldHide = settings.showFavoriteItem === NO && isFavorite || settings.showHasDownItem === NO && isHasDown || settings.showHasWatchItem === NO && isHasWatch || settings.showFilterItem === NO && isFiltered && !(isFavorite || isHasDown || isHasWatch) || settings.showFilterActorItem === NO && isFilterActor || settings.showFilterKeywordItem === NO && isFilterKeyword; + if ($box2.attr("data-movieShowType") !== settings.movieShowType) { + $box2.css("border", ""); + $box2.children().css("visibility", ""); + $box2.removeAttr("data-hide"); + $box2.show(); + } + const isCurrentlyHidden = $box2.attr("data-hide") === YES; + settings.showAllItem === YES && (shouldHide = !1); + if (shouldHide !== isCurrentlyHidden) { + shouldHide ? $box2.attr("data-hide", YES) : $box2.removeAttr("data-hide"); + if ("hide" === settings.movieShowType) shouldHide ? $box2.hide() : $box2.show(); else { + if ("visibility" !== settings.movieShowType) throw new Error("movieShowType值有误:" + settings.movieShowType); + { + const $content = $box2.children(), borderStyle = shouldHide ? "1px solid rgb(192 176 176)" : "none", visibilityValue = shouldHide ? "hidden" : "visible"; + $box2.css("border", borderStyle); + $content.css("visibility", visibilityValue); + } + } + $box2.attr("data-movieShowType") !== settings.movieShowType && $box2.attr("data-movieShowType", settings.movieShowType); + } + } + let tag = TAG_STYLES.IS_WAIT_CHECK, filterReason = null; + if (isFiltered) tag = TAG_STYLES.IS_FILTERED; else if (isFavorite) tag = TAG_STYLES.IS_FAVORITE; else if (isHasDown) tag = TAG_STYLES.IS_HAS_DOWN; else if (isHasWatch) tag = TAG_STYLES.IS_HAS_WATCH; else if (isFilterKeyword) { + tag = TAG_STYLES.IS_KEYWORD_FILTER; + filterReason = foundKeyword || "未知"; + } else if (isFilterActorMale) { + tag = TAG_STYLES.IS_ACTOR_FILTER; + filterReason = actorCarNumToNameMap.get(carNum2) || ""; + } else if (isFilterActorFemale) { + tag = TAG_STYLES.IS_ACTRESS_FILTER; + filterReason = actressCarNumToNameMap.get(carNum2) || ""; + } + filterReason || (filterReason = tag.reasonType); + tag.isCounted && this[tag.countKey]++; + this.currentPageTotalCount++; + $box2.find(".status-tag").remove(); + const tagPositionCss = "rightTop" === settings.tagPosition ? "right: 0; top:5px;" : "left: 0; top:5px;"; + if (tag.text) { + const tagHtml = isJavDb ? `\n ${tag.text}\n ` : `\n ${tag.text}\n `; + isJavDb && $box2.find(".tags").append(tagHtml); + if (isJavBus) { + const $itemTag = $box2.find(".item-tag"); + $itemTag.length ? $itemTag.append(tagHtml) : $box2.find(".photo-info > span > div").append(tagHtml); + } + } + await this.translate($box2); + }))); + const t3 = utils.time("处理页面耗时"), t4 = utils.time("累计耗费时间"); + $("#waitDownBtn span").text(`打开已收藏 (${carNumSets.favorite.size})`); + clog.log(`\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
    ${t1}${t2}
    ${t3}${t4}
    项目数量项目数量
    屏蔽单番号${this.currentPageFilterCount}收藏${this.currentPageFavoriteCount}
    屏蔽演员${this.currentPageActorFilterCount}已下载${this.currentPageHasDownCount}
    屏蔽关键词${this.currentPageKeywordFilterCount}已观看${this.currentPageHasWatchCount}
    待鉴定${this.currentPageWaitCheckCount}
    总数${this.currentPageTotalCount}
    \n `); + } + async bindClick() { + let selector = this.getSelector(); + $(selector.boxSelector).on("click", ".item img", (async event => { + event.preventDefault(); + event.stopPropagation(); + if ($(event.target).closest("div.meta-buttons").length) return; + const $box2 = $(event.target).closest(".item"), {carNum: carNum2, aHref: aHref} = this.getBoxCarInfo($box2); + let dialogOpenDetail = await storageManager.getSetting("dialogOpenDetail", YES); + if (carNum2.includes("FC2-")) { + let movieId = this.parseMovieId(aHref); + this.getBean("Fc2Plugin").openFc2Dialog(movieId, carNum2, aHref); + } else if (dialogOpenDetail === YES) { + utils.openPage(aHref, carNum2, !0, event); + this.$currentImage = null; + } else window.open(aHref); + })); + $(selector.boxSelector).on("click", ".item video", (async event => { + const video = event.currentTarget; + video.paused ? video.play().catch((e => console.error("播放失败:", e))) : video.pause(); + event.preventDefault(); + event.stopPropagation(); + })); + $(selector.boxSelector).on("click", ".item .video-title", (async event => { + if ($(event.target).closest('[class^="jhs-match-"]').length) return; + const $box2 = $(event.currentTarget).closest(".item"), {carNum: carNum2, aHref: aHref} = this.getBoxCarInfo($box2); + if (carNum2.includes("FC2-")) { + event.preventDefault(); + let movieId = this.parseMovieId(aHref); + this.getBean("Fc2Plugin").openFc2Dialog(movieId, carNum2, aHref); + } + })); + $(selector.boxSelector).on("contextmenu", ".item img, .item video", (async event => { + event.preventDefault(); + const $box2 = $(event.target).closest(".item"), {carNum: carNum2, url: url, publishTime: publishTime} = this.getBoxCarInfo($box2); + let $actor = isJavDb ? $(".actor-section-name") : $(".avatar-box .photo-info .pb10"), actorName = ""; + $actor.length && (actorName = $actor.text().trim().split(",")[0].replace("(無碼)", "")); + utils.q(event, `是否屏蔽番号 ${carNum2}?`, (async () => { + setTimeout((async () => { + actorName || (actorName = await this.parseActressName(url)); + await storageManager.saveCar({ + carNum: carNum2, + url: url, + names: actorName, + actionType: Status_FILTER, + publishTime: publishTime + }); + window.refresh(); + show.ok("操作成功"); + })); + })); + })); + } + async parseActressName(url) { + let actorName = null; + if (await storageManager.getSetting("enableSaveActressCarInfo", NO) === YES) { + clog.debug("鉴定补录演员信息-已启用, 开始解析详情页"); + clog.debug("开始解析演员详情页", url); + const html = await gmHttp.get(url), $dom = utils.htmlTo$dom(html); + isJavDb ? actorName = $dom.find(".female").prev().map(((i, el) => $(el).text())).get().join(" ") : isJavBus && (actorName = $dom.find('span[onmouseover*="star_"] a').map(((i, el) => $(el).text())).get().join(" ")); + clog.debug("解析到名称:", actorName); + } + return actorName; + } + async bindListPageHotKey() { + this.$currentImage = null; + $(document).on("mouseenter", this.getSelector().coverImgSelector, (e => { + this.$currentImage = $(e.currentTarget); + })).on("mouseleave", this.getSelector().coverImgSelector, (() => { + this.$currentImage = null; + })); + let settingObj = await storageManager.getSetting(); + this.filterHotKey = settingObj.filterHotKey; + this.favoriteHotKey = settingObj.favoriteHotKey; + this.hasDownHotKey = settingObj.hasDownHotKey; + this.hasWatchHotKey = settingObj.hasWatchHotKey; + this.enableImageHotKey = settingObj.enableImageHotKey || NO; + this.clogHotKey = settingObj.clogHotKey; + this.foldCategoryHotKey = settingObj.foldCategoryHotKey; + this.showFilterItemHotKey = settingObj.showFilterItemHotKey; + this.showFilterActorItemHotKey = settingObj.showFilterActorItemHotKey; + this.showFilterKeywordItemHotKey = settingObj.showFilterKeywordItemHotKey; + this.showFavoriteItemHotKey = settingObj.showFavoriteItemHotKey; + this.showHasDownItemHotKey = settingObj.showHasDownItemHotKey; + this.showHasWatchItemHotKey = settingObj.showHasWatchItemHotKey; + this.showAllItemHotKey = settingObj.showAllItemHotKey; + const HOTKEY_REGISTRY = { + showFilterItemHotKey: "showFilterItem", + showFilterActorItemHotKey: "showFilterActorItem", + showFilterKeywordItemHotKey: "showFilterKeywordItem", + showFavoriteItemHotKey: "showFavoriteItem", + showHasDownItemHotKey: "showHasDownItem", + showHasWatchItemHotKey: "showHasWatchItem", + showAllItemHotKey: "showAllItem" + }, registerToggleHotkey = (hotkeyName, settingKey) => { + const hotkeyString = this[hotkeyName]; + hotkeyString && HotkeyManager.registerHotkey(hotkeyString, (async event => { + const newVal = await storageManager.getSetting(settingKey) === YES ? NO : YES; + await storageManager.saveSettingItem(settingKey, newVal); + window.refresh(); + })); + }; + for (const hotkeyName in HOTKEY_REGISTRY) if (HOTKEY_REGISTRY.hasOwnProperty(hotkeyName)) { + registerToggleHotkey(hotkeyName, HOTKEY_REGISTRY[hotkeyName]); + } + if (this.enableImageHotKey === NO) return; + const handleAction = async (boxInfo, status) => { + setTimeout((async () => { + let actorName = await this.parseActressName(boxInfo.url); + await storageManager.saveCar({ + carNum: boxInfo.carNum, + url: boxInfo.url, + names: actorName, + actionType: status, + publishTime: boxInfo.publishTime + }); + window.refresh(); + show.ok("操作成功"); + })); + }, handlers = {}; + this.filterHotKey && (handlers[this.filterHotKey] = boxInfo => { + handleAction(boxInfo, Status_FILTER); + }); + this.favoriteHotKey && (handlers[this.favoriteHotKey] = boxInfo => { + handleAction(boxInfo, Status_FAVORITE); + }); + this.hasDownHotKey && (handlers[this.hasDownHotKey] = boxInfo => { + handleAction(boxInfo, Status_HAS_DOWN); + }); + this.hasWatchHotKey && (handlers[this.hasWatchHotKey] = boxInfo => { + handleAction(boxInfo, Status_HAS_WATCH); + }); + this.clogHotKey && HotkeyManager.registerHotkey(this.clogHotKey, (event => { + clog.toggleExpandCollapsed(); + })); + this.foldCategoryHotKey && HotkeyManager.registerHotkey(this.foldCategoryHotKey, (event => { + const $btn = $("#foldCategoryBtn"); + $btn.length && $btn[0].click(); + })); + const registerImageHotkey = (key, handler) => { + HotkeyManager.registerHotkey(key, (event => { + const activeElement = document.activeElement; + if (!("INPUT" === activeElement.tagName || "TEXTAREA" === activeElement.tagName || activeElement.isContentEditable) && this.$currentImage) { + const $box2 = this.$currentImage.closest(".item"), boxInfo = this.getBoxCarInfo($box2); + handler(boxInfo); + } + })); + }; + Object.entries(handlers).forEach((([key, handler]) => { + registerImageHotkey(key, handler); + })); + } + replaceHdImg(coverImgNodeList) { + coverImgNodeList && "string" == typeof coverImgNodeList.jquery && (coverImgNodeList = coverImgNodeList.toArray()); + coverImgNodeList || (coverImgNodeList = document.querySelectorAll(this.getSelector().coverImgSelector)); + isJavDb && coverImgNodeList.forEach((img => { + img.src = img.src.replace("thumbs", "covers"); + img.title = ""; + })); + if (isJavBus) { + const THUMB_PATH_REGEX = /\/(imgs|pics)\/(thumb|thumbs)\//, IMG_EXT_REGEX = /(\.jpg|\.jpeg|\.png)$/i, replaceWithHd = img => { + if (img.src && THUMB_PATH_REGEX.test(img.src) && "true" !== img.dataset.hdReplaced) { + img.src = img.src.replace(THUMB_PATH_REGEX, "/$1/cover/").replace(IMG_EXT_REGEX, "_b$1"); + img.dataset.hdReplaced = "true"; + img.dataset.title = img.title; + img.title = ""; + } + }, DMM_THUMB_REGEX = /ps(\.jpg|\.jpeg|\.png)$/i, replaceWithHdForDMM = img => { + if (img.src && DMM_THUMB_REGEX.test(img.src) && "true" !== img.dataset.hdReplaced) { + img.src = img.src.replace(DMM_THUMB_REGEX, "pl$1"); + img.dataset.hdReplaced = "true"; + img.dataset.title = img.title; + img.title = ""; + } + }; + coverImgNodeList.forEach((img => { + replaceWithHd(img); + replaceWithHdForDMM(img); + })); + } + storageManager.getSetting("hoverBigImg", NO).then((hoverBigImg => { + hoverBigImg === YES && (window.imageHoverPreviewObj ? window.imageHoverPreviewObj.bindEvents() : window.imageHoverPreviewObj = new ImageHoverPreview({ + selector: this.getSelector().coverImgSelector + })); + })); + } + async translate($box2) { + if (await storageManager.getSetting("translateTitle", YES) !== YES) return; + let content, carNum2, $title = $box2.find(".video-title"); + if (isJavDb) { + content = $title.contents().filter(((i, node) => 3 === node.nodeType && "" !== node.textContent.trim())).text().trim(); + carNum2 = $box2.find(".video-title strong").text().trim(); + } else { + content = $box2.find("img").attr("data-title").trim(); + carNum2 = $box2.find("a").attr("href").split("/").filter(Boolean).pop().trim(); + } + if (this.cache[carNum2]) { + let _this = this; + $title.contents().each((function() { + 3 === this.nodeType && "" !== this.textContent.trim() && (this.textContent = " " + _this.cache[carNum2] + " "); + })); + $title.attr("title", _this.cache[carNum2]); + } else translateText(content).then((result => { + if (isJavDb) { + $title.contents().each((function() { + 3 !== this.nodeType || "" === this.textContent.trim() || this.textContent.includes(carNum2) || (this.textContent = " " + result + " "); + })); + $title.attr("title", result); + } else $title.text(result); + this.writeQueue = this.writeQueue.then((() => { + this.cache[carNum2] = result; + localStorage.setItem("jhs_translate", JSON.stringify(this.cache)); + })); + })).catch((error => { + console.error("翻译失败:", error); + })); + } + async revertTranslation() { + $(this.getSelector().itemSelector).toArray().forEach((ele => { + let $box2 = $(ele); + const originalContent = $box2.find(".box").attr("title") || $box2.find(".video-title").attr("title") || $box2.find("img").attr("data-title"); + let carNum2; + isJavDb && (carNum2 = $box2.find(".video-title strong").text().trim()); + const $title = $box2.find(".video-title"); + $title.contents().each((function() { + 3 !== this.nodeType || "" === this.textContent.trim() || this.textContent.includes(carNum2) || (this.textContent = " " + originalContent + " "); + })); + $title.removeAttr("title"); + })); + } + addJumpPageControl() { + if ($("#gemini-jump-page-control").length > 0) return; + if (0 === $(".pagination-link.is-current").length) return; + const currentPageNum = utils.getUrlParam(currentHref, "page") || 1, $input = $("", { + type: "number", + id: "jumpPageInput", + placeholder: "页码", + min: "1", + style: "width: 60px; margin-left: 10px; padding: 10px; border: 1px solid #ccc; font-size: 14px;", + value: currentPageNum + 1 + }), $button = $("\n \n \n \n `); + $container.append($result); + })); + $container.on("click", ".copy-btn", (function() { + const $btn = $(this), magnet = $btn.data("magnet"); + navigator.clipboard ? navigator.clipboard.writeText(magnet).then((() => { + showCopiedFeedback($btn); + })).catch((err => { + fallbackCopy(magnet, $btn); + })) : fallbackCopy(magnet, $btn); + })); + $container.on("click", ".down-115", (async event => { + const magnet = $(event.currentTarget).data("magnet"); + let loadObj = loading(); + try { + await this.getBean("WangPan115TaskPlugin").handleAddTask(magnet); + } catch (e) { + show.error("发生错误:" + e); + console.error(e); + } finally { + loadObj.close(); + } + })); + } else $container.append('
    没有找到相关结果
    '); + function showCopiedFeedback($btn) { + const originalText = $btn.text(); + $btn.addClass("copied").text("已复制"); + setTimeout((() => { + $btn.removeClass("copied").text(originalText); + }), 2e3); + } + function fallbackCopy(text, $btn) { + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.style.position = "fixed"; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand("copy"); + showCopiedFeedback($btn); + } catch (err) { + console.error("复制失败:", err); + alert("复制失败,请手动复制链接"); + } + document.body.removeChild(textarea); + } + } + parseBTSOW($container, engine, keyword, cacheKey) { + const _this = this; + GM_xmlhttpRequest({ + method: "POST", + url: engine.url, + headers: { + "Content-Type": "application/json" + }, + data: `[{"search":"${keyword}"},50,1]`, + onload: response => { + try { + const dataList = JSON.parse(response.responseText).data, results = []; + for (let i = 0; i < dataList.length; i++) { + let item = dataList[i]; + results.push({ + title: item.name, + magnet: "magnet:?xt=urn:btih:" + item.hash, + size: (item.size / 1073741824).toFixed(2) + " GB", + date: utils.formatDate(new Date(1e3 * item.lastUpdateTime)) + }); + } + results.length > 0 && sessionStorage.setItem(cacheKey, JSON.stringify(results)); + _this.displayResults($container, results, engine.name); + } catch (e) { + $container.html(`
    解析 ${engine.name} 结果失败: ${e.message}
    `); + } + }, + onerror: error => { + $container.html(`
    从 ${engine.name} 获取数据失败: ${error.statusText}
    `); + } + }); + } + parseU3C3(html, keyword) { + const $dom = utils.htmlTo$dom(html), results = []; + $dom.find(".torrent-list tbody tr").each(((i, el) => { + const $el = $(el); + if ($el.text().includes("置顶")) return; + const title = $el.find("td:nth-child(2) a").attr("title") || $el.find("td:nth-child(2) a").text().trim(); + if (!title.toLowerCase().includes(keyword.toLowerCase())) return; + const magnet = $el.find("td:nth-child(3) a[href^='magnet:']").attr("href"), size = $el.find("td:nth-child(4)").text().trim(), date = $el.find("td:nth-child(5)").text().trim(); + magnet && results.push({ + title: title, + magnet: magnet, + size: size, + date: date + }); + })); + return results; + } + parseSukebei(html, keyword) { + const $dom = utils.htmlTo$dom(html), results = []; + $dom.find(".torrent-list tbody tr").each(((i, el) => { + const $el = $(el); + if ($el.text().includes("置顶")) return; + const title = $el.find("td:nth-child(2) a").attr("title") || $el.find("td:nth-child(2) a").text().trim(); + if (!title.toLowerCase().includes(keyword.toLowerCase())) return; + const magnet = $el.find("td:nth-child(3) a[href^='magnet:']").attr("href"), size = $el.find("td:nth-child(4)").text().trim(), date = $el.find("td:nth-child(5)").text().trim(); + magnet && results.push({ + title: title, + magnet: magnet, + size: size, + date: date + }); + })); + return results; + } +} + + +export { MagnetHubPlugin }; \ No newline at end of file diff --git a/src/plugins/nav-bar.js b/src/plugins/nav-bar.js new file mode 100644 index 0000000..ed6b319 --- /dev/null +++ b/src/plugins/nav-bar.js @@ -0,0 +1,92 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField } from '../core/constants.js'; + +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(!0); + 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(); + } + } +} + +export { NavBarPlugin }; \ No newline at end of file diff --git a/src/plugins/new-video.js b/src/plugins/new-video.js new file mode 100644 index 0000000..eeca556 --- /dev/null +++ b/src/plugins/new-video.js @@ -0,0 +1,363 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField } from '../core/constants.js'; +import { CDN_SOURCES, STORE_NAME, dbHelper } from './task.js'; + +class NewVideoPlugin extends BasePlugin { + constructor() { + super(...arguments); + __publicField(this, "currentPage", 1); + __publicField(this, "pageSize", 30); + } + getName() { + return "NewVideoPlugin"; + } + async initCss() { + return "\n \n "; + } + async showNewVideoCount() { + const totalNewVideoCount = (await storageManager.getFavoriteActressList()).reduce(((accumulator, actress) => { + var _a2; + return accumulator + ((null == (_a2 = actress.newVideoList) ? void 0 : _a2.length) ?? 0); + }), 0); + $("#newVideoCount").text(`${totalNewVideoCount}`); + } + async resetBtnTip() { + const taskPlugin = this.getBean("TaskPlugin"), settingObj = await storageManager.getSetting(), lastCheckTimeStr = localStorage.getItem(taskPlugin.lastCheckFavoriteActressTimeKey) || "无", checkFavoriteActress_IntervalTime = settingObj.checkFavoriteActress_IntervalTime, lastCheckNewVideoTimeStr = localStorage.getItem(taskPlugin.lastCheckNewVideoTimeKey) || "无", checkNewVideo_intervalTime = settingObj.checkNewVideo_intervalTime; + $("#checkFavoriteActress").attr("data-tip", `上次自动同步时间: ${lastCheckTimeStr}; 检测间隔时间: ${checkFavoriteActress_IntervalTime}小时`); + $("#checkNewVideo").attr("data-tip", `上次检测时间: ${lastCheckNewVideoTimeStr}; 检测间隔时间: ${checkNewVideo_intervalTime}小时`); + } + async openDialog() { + const taskPlugin = this.getBean("TaskPlugin"), settingObj = await storageManager.getSetting(), lastCheckTimeStr = localStorage.getItem(taskPlugin.lastCheckFavoriteActressTimeKey) || "无", checkFavoriteActress_IntervalTime = settingObj.checkFavoriteActress_IntervalTime, lastCheckNewVideoTimeStr = localStorage.getItem(taskPlugin.lastCheckNewVideoTimeKey) || "无", checkNewVideo_intervalTime = settingObj.checkNewVideo_intervalTime; + let html = `\n
    \n
    \n
    \n ${this.actressSvg}    手动同步演员\n ${this.newSvg}    手动检测最新作品\n ${this.settingSvg}    配置\n \n
    \n
    \n \n \n ${this.refreshSvg}    刷新\n
    \n\n
    \n
    \n
    \n
    \n `; + layer.open({ + type: 1, + title: '新作品检测 ❓', + content: html, + scrollbar: !1, + area: utils.getResponsiveArea([ "80%", "90%" ]), + anim: -1, + success: async (layero, index) => { + this.loadData(); + this.bindClick(); + utils.setupEscClose(index); + } + }); + } + bindClick() { + const taskPlugin = this.getBean("TaskPlugin"); + $("#reLoad").on("click", (event => { + this.loadData(); + $("#checkNewVideoMsg").text(""); + })); + $("#toSetting").on("click", (event => { + this.getBean("SettingPlugin").openSettingDialog("task-panel", (() => { + $("#setting-checkFavoriteActress").css({ + border: "1px solid #f40" + }); + $("#setting-checkNewVideo").css({ + border: "1px solid #f40" + }); + })); + })); + $("#checkFavoriteActress").on("click", (event => { + utils.q({ + clientX: event.clientX, + clientY: event.clientY + 20 + }, "是否手动同步演员?", (() => { + navigator.locks.request(taskPlugin.singleTaskKey, { + ifAvailable: !0 + }, (async lock => { + if (!lock) { + show.error("当前有定时任务在后台执行中, 无法发起手动任务"); + return; + } + if ($('a[href*="/users/profile"]').length > 0) { + await taskPlugin.checkFavoriteActress(); + this.loadData(); + } else show.error("未登录JavDb, 同步失败"); + })).catch((error => { + console.error("锁任务出现错误:", error); + clog.error("锁任务出现错误:", error); + })); + })); + })); + $("#checkNewVideo").on("click", (event => { + utils.q({ + clientX: event.clientX, + clientY: event.clientY + 20 + }, "是否手动检测最新作品?", (() => { + navigator.locks.request(taskPlugin.singleTaskKey, { + ifAvailable: !0 + }, (async lock => { + lock ? await taskPlugin.checkNewVideo(!0) : show.error("当前有定时任务在后台执行中, 无法发起手动任务"); + })).catch((error => { + console.error("锁任务出现错误:", error); + clog.error("锁任务出现错误:", error); + })); + })); + })); + $("#paramActressType").on("change", (event => { + this.loadData(); + })); + } + loadData() { + this.currentPage = 1; + this.renderActressCards().then(); + } + async renderActressCards() { + const $actressCardContainer = $("#actress-card-container"); + if (!$actressCardContainer.length) return; + let favoriteActressesList = await storageManager.getFavoriteActressList(); + const paramActressType = $("#paramActressType").val(); + "all" !== paramActressType && (favoriteActressesList = favoriteActressesList.filter((actress => actress.actressType === paramActressType))); + const fullActressList = utils.genericSort(favoriteActressesList, [ { + key: item => { + var _a2; + return (null == (_a2 = item.newVideoList) ? void 0 : _a2.length) ?? 0; + }, + order: "desc" + }, { + key: "lastPublishTime", + order: "desc" + } ]), totalCount = fullActressList.length, totalPages = Math.ceil(totalCount / this.pageSize), startIndex = (this.currentPage - 1) * this.pageSize, endIndex = startIndex + this.pageSize, pagedData = fullActressList.slice(startIndex, endIndex), javDbUrl = await this.getBean("OtherSitePlugin").getJavDbUrl(), taskPlugin = this.getBean("TaskPlugin"), checkNewVideo_ruleTime = await storageManager.getSetting("checkNewVideo_ruleTime") || 8760, cardsHtml = pagedData.map((data => { + var _a2, _b, _c; + const allNamesStr = Array.isArray(data.allName) ? data.allName.join(",") : ""; + Array.isArray(data.newVideoList) && data.newVideoList.join(","); + const detailLink = `${javDbUrl}/actors/${data.starId}?t=d`; + let isUnCheck = !1; + data.lastPublishTime && (isUnCheck = !taskPlugin.isUnnecessaryCheck(data.lastPublishTime, checkNewVideo_ruleTime)); + let actressTypeText = "未知", actressTypeBgc = "#9E9E9E"; + if ("uncensored" === data.actressType) { + actressTypeText = "无码"; + actressTypeBgc = "#4CAF50"; + } else if ("censored" === data.actressType) { + actressTypeText = "有码"; + actressTypeBgc = "#FF9800"; + } + let cardUnnecessaryBtnCss = ""; + isUnCheck && (cardUnnecessaryBtnCss = "background: linear-gradient(145deg, #e0e0e0 0%, #cabdbd 100%);box-shadow: none"); + return `\n
    \n \n ${allNamesStr}\n \n\n
    \n \n
    ${data.name}
    \n
    \n
    ${allNamesStr}
    \n
    \n \n
    \n 上次检测: ${data.lastCheckTime || ""}\n
    \n
    \n 最后发行作品: ${data.lastPublishTime || ""}\n
    \n\n
    \n ${isUnCheck ? "停更" + checkNewVideo_ruleTime / 24 / 365 + "年以上, 下轮任务不再进行检测" : ""}\n
    \n \n
    \n ${data.remark || ""}\n
    \n \n
    \n ${this.editSvg}\n ${this.deleteSvg}\n ${this.checkSvg}\n
    \n \n
    ${actressTypeText}
    \n
    \n 🔔 ${(null == (_c = data.newVideoList) ? void 0 : _c.length) || 0} \n
    \n
    \n `; + })).join(""); + $actressCardContainer.html(cardsHtml); + $(".btn-delete-actress").off("click").on("click", (e => { + e.preventDefault(); + const starId = $(e.currentTarget).attr("data-starId"), actress = fullActressList.find((item => item.starId === starId)); + utils.q(e, `是否取消收藏 ${actress.name}?`, (async () => { + let deleteActressUrl = `${await this.getBean("OtherSitePlugin").getJavDbUrl()}/actors/${starId}/uncollect`; + const csrfToken = document.querySelector("meta[name=csrf-token]").content, res = await gmHttp.post(deleteActressUrl, null, { + "x-csrf-token": csrfToken + }); + if (res.includes("removeClass")) { + await storageManager.removeFavoriteActress(starId); + this.loadData(); + } else { + show.error("移除失败"); + clog.error("移除失败,返回值:", res); + } + })); + })); + $(".btn-edit-actress").off("click").on("click", (e => { + e.preventDefault(); + const starId = $(e.currentTarget).attr("data-starId"), actress = fullActressList.find((item => item.starId === starId)); + actress ? this.editActress(actress) : show.error(`未找到 starId 为 ${starId} 的女优记录。`); + })); + $(".btn-check-actress").off("click").on("click", (e => { + e.preventDefault(); + navigator.locks.request(taskPlugin.singleTaskKey, { + ifAvailable: !0 + }, (async lock => { + if (!lock) { + show.error("当前有定时任务在后台执行中, 无法发起手动任务"); + return; + } + const starId = $(e.currentTarget).attr("data-starId"), actress = fullActressList.find((item => item.starId === starId)); + await taskPlugin.checkOneNewVideo(actress); + })).catch((error => { + console.error("锁任务出现错误:", error); + clog.error("锁任务出现错误:", error); + })); + })); + this.renderPagination(totalCount, totalPages); + show.ok("加载完成"); + } + async editActress(actress) { + const initialName = actress.name, initialAvatar = actress.avatar, initialRemark = actress.remark || "", initialAllName = Array.isArray(actress.allName) ? actress.allName.join(",") : "", initialNewVideoList = Array.isArray(actress.newVideoList) ? actress.newVideoList.join(",") : "", starId = actress.starId, textareaStyle = "width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; min-height: 60px; overflow-y: hidden;", initActressType = actress.actressType || "", editFormHtml = `\n
    \n
    \n Avatar Preview\n
    \n \n \n
    \n \n \n
    \n
    \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n
    \n \n \n
    \n
    \n `; + layer.open({ + type: 1, + title: `编辑女优: ${initialName} (${starId})`, + area: [ "500px", "750px" ], + content: editFormHtml, + btn: [ "保存", "取消" ], + success: (layero, index) => { + const autoResizeTextarea = $textarea => { + $textarea.css("height", "auto"); + $textarea.css("height", $textarea[0].scrollHeight + 15 + "px"); + }; + $("#edit-actress-avatar").on("input", (function() { + const newUrl = $(this).val(); + $("#edit-avatar-preview").attr("src", newUrl); + })); + const $allNameTextarea = $("#edit-actress-allname"); + $allNameTextarea.on("input", (function() { + autoResizeTextarea($(this)); + })); + autoResizeTextarea($allNameTextarea); + const $videoListTextarea = $("#edit-actress-newvideolist"); + $videoListTextarea.on("input", (function() { + autoResizeTextarea($(this)); + })); + autoResizeTextarea($videoListTextarea); + $("#search-avatar-btn").on("click", (async () => { + await this.searchAvatar(); + })); + $("#select-cdn-btn").on("click", (async () => { + await async function() { + const initialIndex = currentCdnIndex, radioOptions = CDN_SOURCES.map(((source, index) => `\n
    \n \n \n
    \n `)).join(""), cdnSelectHtml = `\n
    \n

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

    \n ${radioOptions}\n

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

    \n
    \n `; + layer.open({ + type: 1, + title: "选择 CDN 源", + area: [ "400px", "auto" ], + content: cdnSelectHtml, + btn: [ "确定", "取消" ], + success: (layero, index) => { + utils.setupEscClose(index); + }, + yes: async index => { + const newIndexStr = $('input[name="cdn-source"]:checked').val(), newIndex = parseInt(newIndexStr, 10); + if (newIndex !== currentCdnIndex) { + currentCdnIndex = newIndex; + localStorage.setItem("jhs_img_cdn_index", newIndex.toString()); + 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(index); + } else layer.close(index); + } + }); + }(); + })); + utils.setupEscClose(index); + }, + yes: async index => { + const newAvatar = $("#edit-actress-avatar").val().trim(), newName = $("#edit-actress-name").val().trim(), newAllNameStr = $("#edit-actress-allname").val().trim(), newVideoListStr = $("#edit-actress-newvideolist").val().trim(), newRemark = $("#edit-remark").val().trim(), newActressType = $("#actressType").val(); + if (!newName) { + show.error("主名称不能为空"); + return !1; + } + const newAllName = newAllNameStr.split(/[\uff0c,]/).map((n => n.trim())).filter((n => n.length > 0)), newVideoList = newVideoListStr.split(/[\uff0c,]/).map((n => n.trim())).filter((n => n.length > 0)); + actress.avatar = newAvatar; + actress.name = newName; + actress.allName = newAllName; + actress.newVideoList = newVideoList; + actress.actressType = newActressType; + actress.remark = newRemark; + if (await storageManager.updateFavoriteActress(actress)) show.error("修改失败"); else { + show.ok(`女优 ${newName} 信息已更新`); + await this.renderActressCards(); + layer.close(index); + } + } + }); + } + renderPagination(totalCount, totalPages) { + const currentPage = this.currentPage; + let paginationHtml = ""; + const $actressPagination = $("#actress-pagination"); + if (0 === totalPages) { + paginationHtml = '共 0 条记录'; + $actressPagination.html(paginationHtml); + return; + } + currentPage > 1 && totalPages > 5 && (paginationHtml += ''); + currentPage > 1 && (paginationHtml += ``); + let startPage = Math.max(1, currentPage - Math.floor(2.5)), endPage = Math.min(totalPages, startPage + 5 - 1); + endPage - startPage < 4 && (startPage = Math.max(1, endPage - 5 + 1)); + for (let i = startPage; i <= endPage; i++) { + paginationHtml += ``; + } + currentPage < totalPages && (paginationHtml += ``); + currentPage < totalPages && totalPages > 5 && (paginationHtml += ``); + paginationHtml += `共 ${totalCount} 条记录 (第 ${currentPage}/${totalPages} 页)`; + $actressPagination.html(paginationHtml); + $(".pagination-btn").off("click").on("click", (e => { + if ($(e.currentTarget).is("[disabled]")) return; + const newPage = parseInt($(e.currentTarget).data("page")); + if (newPage >= 1 && newPage <= totalPages && newPage !== this.currentPage) { + this.currentPage = newPage; + this.renderActressCards(); + } + })); + } + async searchAvatar() { + const $mainNameInput = $("#edit-actress-name"), $aliasInput = $("#edit-actress-allname"), currentName = $mainNameInput.val().trim(), searchNames = $aliasInput.val().trim().split(/[\uff0c,]/).map((n => n.trim())).filter((n => n.length > 0)); + currentName && searchNames.unshift(currentName); + if (0 === searchNames.length) { + show.error("请先填写女优主名称或别名进行搜索。"); + return; + } + const loadObj = loading("正在搜索头像..."); + let imageLinks = []; + try { + imageLinks = await searchActorAvatars(searchNames); + } catch (e) { + show.error(`头像数据加载或搜索失败: ${e.message || e}`); + return; + } finally { + loadObj.close(); + } + if (0 === imageLinks.length) { + show.error(`未找到与 '${searchNames.join("、")}' 相关的头像。请检查名称。`); + return; + } + const imageItems = imageLinks.map(((url, index) => `\n
    \n \n
    ...
    \n
    \n `)).join(""), imageListHtml = `\n \n \n
    \n

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

    \n
    \n
    \n ${imageItems}\n
    \n
    \n
    \n `; + let errorCount = 0; + layer.open({ + type: 1, + title: `选择女优头像 (${imageLinks.length} 张)`, + area: utils.getResponsiveArea([ "900px", "85%" ]), + content: imageListHtml, + btn: [ "关闭" ], + success: (selectionLayero, selectionIndex) => { + const $container = $(selectionLayero), $images = $container.find(".gfriends-selectable-img"), $prompt = $container.find("#gfriends-prompt"); + $images.each((function() { + const $img = $(this), wrapperId = $img.data("wrapper-id"), $wrapper = $container.find(`#${wrapperId}`), $sizeTag = $container.find(`.gfriends-size-tag[data-size-for="${wrapperId}"]`); + $img.on("load", (function() { + const width = this.naturalWidth, height = this.naturalHeight; + $sizeTag.text(`${width} x ${height}`); + })); + $img.on("error", (function() { + $wrapper.remove(); + errorCount++; + const validCount = imageLinks.length - errorCount; + $prompt.text(`点击图片即可选择(已移除 ${errorCount} 张错误图片,剩余 ${validCount} 张)`); + if (0 === validCount) { + show.error("所有搜索到的头像链接均已失效,无法选择。"); + layer.close(selectionIndex); + } + })); + this.complete && (this.naturalWidth > 0 ? $img.trigger("load") : $img.trigger("error")); + })); + $images.on("click", (function() { + const $clickedImg = $(this), selectedUrl = $clickedImg.data("url"); + $("#edit-actress-avatar").val(selectedUrl); + $("#edit-avatar-preview").attr("src", selectedUrl); + $images.removeClass("is-selected"); + $clickedImg.addClass("is-selected"); + setTimeout((() => { + layer.close(selectionIndex); + }), 150); + })); + utils.setupEscClose(selectionIndex); + } + }); + } +} + +export { NewVideoPlugin }; \ No newline at end of file diff --git a/src/plugins/other-site.js b/src/plugins/other-site.js new file mode 100644 index 0000000..90b196a --- /dev/null +++ b/src/plugins/other-site.js @@ -0,0 +1,275 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, isJavBus, isJavDb } from '../core/constants.js'; +import { AsyncQueue } from '../core/utils.js'; + +class OtherSitePlugin extends BasePlugin { + constructor() { + super(...arguments); + __publicField(this, "okBackgroundColor", "#7bc73b"); + __publicField(this, "errorBackgroundColor", "#de3333"); + __publicField(this, "warnBackgroundColor", "#d7a80c"); + __publicField(this, "domainErrorBackgroundColor", "#d7780c"); + __publicField(this, "asyncQueue", new AsyncQueue); + __publicField(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 + }, { + 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 && sourceCarNum && !sourceCarNum.includes("FC2") + }, { + id: "fanzaBtn", + noHandle: !0, + initUrl: carNum2 => `https://www.dmm.co.jp/search/=/searchstr=${carNum2}`, + condition: sourceCarNum => sourceCarNum && !sourceCarNum.includes("FC2") + } ]); + __publicField(this, "settingCache", null); + __publicField(this, "lastFetchTime", 0); + __publicField(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 = `\n
    \n
    \n ${this.siteConfigs.map((config => { + config.sourceCarNum = sourceCarNum; + if (config.condition && !1 === config.condition(config.sourceCarNum)) return ""; + return `${config.id.replace("Btn", "")}`; + })).join("")}\n 设置\n
    \n
    \n \n \n `; + $(".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, !1, event); + })); + await Promise.all(this.siteConfigs.map((async config => { + config.condition && !1 === 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 && !0 === 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, !0), $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 `\n
    \n \n \n
    \n `; + })).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); + } + })); + } +} + + +export { OtherSitePlugin }; \ No newline at end of file diff --git a/src/plugins/preview-video.js b/src/plugins/preview-video.js new file mode 100644 index 0000000..2e50c01 --- /dev/null +++ b/src/plugins/preview-video.js @@ -0,0 +1,221 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, currentHref, NO, YES, qualityOptions } from '../core/constants.js'; + +class PreviewVideoPlugin extends BasePlugin { + getName() { + return "PreviewVideoPlugin"; + } + async initCss() { + return "\n .video-control-btn {\n min-width:120px;\n padding: 7px 12px;\n font-size: 12px;\n background: rgba(0,0,0,0.7);\n color: white;\n border: none;\n border-radius: 4px;\n cursor: pointer;\n }\n .video-control-btn.active {\n background-color: #1890ff;\n color: white;\n font-weight: bold;\n border: 2px solid #096dd9;\n }\n "; + } + async handle() { + if (!window.isDetailPage) return; + let settingObj = await storageManager.getSetting(); + this.filterHotKey = settingObj.filterHotKey; + this.favoriteHotKey = settingObj.favoriteHotKey; + this.speedVideoHotKey = settingObj.speedVideoHotKey; + let $preview = $(".preview-video-container"); + $preview.on("click", (event => { + utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => { + this.handleVideo().then(); + })); + })); + await storageManager.getSetting("enableLoadPreviewVideo", YES) !== YES || currentHref.includes("autoPlay=1") || this.initDmm().then(); + let href = window.location.href; + (href.includes("gallery-1") || href.includes("gallery-2")) && utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (() => { + $(".fancybox-content #preview-video").length > 0 && this.handleVideo().then(); + })); + href.includes("autoPlay=1") && $preview.length > 0 && $preview[0].click(); + } + async initDmm() { + try { + const dmmVideoMap = await getDmmVideo(this.getPageInfo().carNum, !1); + if (!dmmVideoMap) return; + let settingDefaultVideoQuality = await storageManager.getSetting("videoQuality"); + clog.debug("解析其它画质预览视频", "设置-期望画质", settingDefaultVideoQuality); + const defaultVideoUrl = dmmVideoMap[selectDefaultQuality(Object.keys(dmmVideoMap), settingDefaultVideoQuality)]; + clog.log("切换其它画质预览视频: ", defaultVideoUrl); + const $previewVideoEL = $("#preview-video"), videoEl = $previewVideoEL.length ? $previewVideoEL[0] : null, isVideoElementHidden = !videoEl || utils.isHidden($previewVideoEL); + if ($previewVideoEL.length) { + if (videoEl) { + const currentTime = videoEl.currentTime; + $previewVideoEL.attr("src", defaultVideoUrl); + if (!isVideoElementHidden) { + clog.debug("播放器已手动打开, 变更进度条"); + videoEl.currentTime = currentTime; + videoEl.play(); + } + } + } else { + clog.debug("JavDB没有视频播放元素, 开始创建..."); + const videoCoverSrc = $(".column-video-cover img").attr("src"); + $(".preview-images").prepend(`\n \n 預告片\n \n \n `); + $(".preview-video-container").on("click", (event => { + utils.loopDetector((() => $(".fancybox-content #preview-video").length > 0), (async () => { + await this.handleVideo(); + })); + })); + } + } catch (error) { + clog.error("预加载dmm失败:", error); + } + } + async handleVideo() { + if (await storageManager.getSetting("enableLoadPreviewVideo", YES) === NO) return; + const $videoEl = $("#preview-video"); + if (!$videoEl.length) return; + const $videoContainer = $videoEl.parent(); + $videoContainer.css("position", "relative"); + const videoEl = $videoEl[0], jhs_videoMuted = localStorage.getItem("jhs_videoMuted"); + jhs_videoMuted && (videoEl.muted = "yes" === jhs_videoMuted); + videoEl.addEventListener("volumechange", (function() { + localStorage.setItem("jhs_videoMuted", videoEl.muted ? "yes" : "no"); + })); + videoEl.play(); + let carNum2 = this.getPageInfo().carNum; + const dmmVideoMap = await getDmmVideo(carNum2); + let $bottomToolbar = $("
    ").attr("id", "video-bottom-toolbar").css({ + display: "flex", + gap: "5px", + "align-items": "center", + "flex-wrap": "wrap" + }), $qualityButtonGroup = $("
    ").css({ + display: "flex", + gap: "5px", + "align-items": "center" + }), defaultVideoQuality = null; + if (dmmVideoMap) { + let storedQuality = await storageManager.getSetting("videoQuality"); + defaultVideoQuality = selectDefaultQuality(Object.keys(dmmVideoMap), storedQuality); + let defaultVideoUrl = dmmVideoMap[defaultVideoQuality]; + if ($videoEl.attr("src") !== defaultVideoUrl) { + $videoEl.attr("src", defaultVideoUrl); + videoEl.load(); + videoEl.play(); + } + qualityOptions.forEach((option => { + let dmmVideoUrl = dmmVideoMap[option.quality]; + if (dmmVideoUrl) { + const isActive = defaultVideoQuality === option.quality; + let qualityButton = $(`\n \n `); + $qualityButtonGroup.append(qualityButton); + } + })); + } + $bottomToolbar.append($qualityButtonGroup); + let $functionButtonGroup = $("
    ").css({ + display: "flex", + gap: "5px", + "align-items": "center", + "margin-left": "auto" + }), filterButton = $(``); + $functionButtonGroup.append(filterButton); + let favoriteButton = $(``); + $functionButtonGroup.append(favoriteButton); + let speedButton = $(``); + $functionButtonGroup.append(speedButton); + $bottomToolbar.append($functionButtonGroup); + $videoContainer.append($bottomToolbar); + $bottomToolbar.on("click", ".video-control-btn", (async e => { + const $button = $(e.currentTarget), videoSrc = $button.data("video-src"); + if (!$button.hasClass("active")) try { + const currentTime = videoEl.currentTime; + $videoEl.attr("src", videoSrc); + videoEl.load(); + videoEl.currentTime = currentTime; + await videoEl.play(); + $bottomToolbar.find(".video-control-btn").removeClass("active").css({ + "background-color": "#fff", + color: "black" + }); + $button.addClass("active").css({ + "background-color": "#007bff", + color: "white" + }); + } catch (error) { + console.error("切换画质失败:", error); + } + })); + $("#speed-btn").on("click", (() => { + this.getBean("DetailPageButtonPlugin").speedVideo(); + })); + utils.rightClick(document.body, "#speed-btn", (event => { + this.getBean("DetailPageButtonPlugin").filterOne(event); + })); + $("#video-filterBtn").on("click", (event => { + this.getBean("DetailPageButtonPlugin").filterOne(event); + })); + $("#video-favoriteBtn").on("click", (event => { + this.getBean("DetailPageButtonPlugin").favoriteOne(event); + })); + } +} + +const _HotkeyManager = class _HotkeyManager { + constructor() { + if (new.target === _HotkeyManager) throw new Error("HotkeyManager cannot be instantiated."); + } + static registerHotkey(hotkeyString, callback, keyupCallback = null) { + if (Array.isArray(hotkeyString)) { + let id_list = []; + hotkeyString.forEach((hotkey => { + if (!this.isHotkeyFormat(hotkey)) throw new Error("快捷键格式错误"); + let id = this.recordHotkey(hotkey, callback, keyupCallback); + id_list.push(id); + })); + return id_list; + } + if (!this.isHotkeyFormat(hotkeyString)) throw new Error("快捷键格式错误"); + return this.recordHotkey(hotkeyString, callback, keyupCallback); + } + static recordHotkey(hotkeyString, callback, keyupCallback) { + let id = Math.random().toString(36).substr(2); + this.registerHotKeyMap.set(id, { + hotkeyString: hotkeyString, + callback: callback, + keyupCallback: keyupCallback + }); + return id; + } + static unregisterHotkey(id) { + this.registerHotKeyMap.has(id) && this.registerHotKeyMap.delete(id); + } + static isHotkeyFormat(hotkeyString) { + return hotkeyString.toLowerCase().split("+").map((k => k.trim())).every((k => [ "ctrl", "shift", "alt" ].includes(k) || 1 === k.length)); + } + static judgeHotkey(hotkeyString, event) { + const keyList = hotkeyString.toLowerCase().split("+").map((k => k.trim())), ctrl = keyList.includes("ctrl"), shift = keyList.includes("shift"), alt = keyList.includes("alt"), key = keyList.find((k => "ctrl" !== k && "shift" !== k && "alt" !== k)); + return (this.isMac ? event.metaKey : event.ctrlKey) === ctrl && event.shiftKey === shift && event.altKey === alt && event.key.toLowerCase() === key; + } +}; + +__publicField(_HotkeyManager, "isMac", 0 === navigator.platform.indexOf("Mac")); + +__publicField(_HotkeyManager, "registerHotKeyMap", new Map); + +__publicField(_HotkeyManager, "handleKeydown", (event => { + for (const [id, data] of _HotkeyManager.registerHotKeyMap) { + let hotkeyString = data.hotkeyString, callback = data.callback; + _HotkeyManager.judgeHotkey(hotkeyString, event) && callback(event); + } +})); + +__publicField(_HotkeyManager, "handleKeyup", (event => { + for (const [id, data] of _HotkeyManager.registerHotKeyMap) { + let hotkeyString = data.hotkeyString, keyupCallback = data.keyupCallback; + keyupCallback && (_HotkeyManager.judgeHotkey(hotkeyString, event) && keyupCallback(event)); + } +})); + +let HotkeyManager = _HotkeyManager; + +document.addEventListener("keydown", (event => { + HotkeyManager.handleKeydown(event); +})); + +document.addEventListener("keyup", (event => { + HotkeyManager.handleKeyup(event); +})); + + +export { PreviewVideoPlugin, HotkeyManager }; \ No newline at end of file diff --git a/src/plugins/related.js b/src/plugins/related.js new file mode 100644 index 0000000..149fda8 --- /dev/null +++ b/src/plugins/related.js @@ -0,0 +1,96 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, NO, YES } from '../core/constants.js'; +import { javDbApi } from '../api/javdb.js'; + +class RelatedPlugin extends BasePlugin { + constructor() { + super(...arguments); + __publicField(this, "floorIndex", 1); + __publicField(this, "isInit", !1); + } + getName() { + return "RelatedPlugin"; + } + async showRelated($eleBox, movieId) { + const enableLoadRelated = await storageManager.getSetting("enableLoadRelated", NO), $magnets = $eleBox; + if (movieId) { + $magnets.append(`\n
    \n \n 相关清单\n \n ${enableLoadRelated === YES ? "折叠" : "展开"}\n ${enableLoadRelated === YES ? "▲" : "▼"}\n \n \n
    \n `); + $("#relatedFold").on("click", (event => { + event.preventDefault(); + event.stopPropagation(); + const $text = $("#relatedFold .toggle-text"), $icon = $("#relatedFold .toggle-icon"), isFolded = "展开" === $text.text(); + $text.text(isFolded ? "折叠" : "展开"); + $icon.text(isFolded ? "▲" : "▼"); + if (isFolded) { + $("#relatedContainer").show(); + $("#relatedFooter").show(); + if (!this.isInit) { + this.fetchAndDisplayRelateds(movieId); + this.isInit = !0; + } + storageManager.saveSettingItem("enableLoadRelated", YES); + } else { + $("#relatedContainer").hide(); + $("#relatedFooter").hide(); + storageManager.saveSettingItem("enableLoadRelated", NO); + } + })); + $magnets.append('
    '); + $magnets.append('
    '); + enableLoadRelated === YES && await this.fetchAndDisplayRelateds(movieId); + } else show.error("未传入movieId"); + } + async fetchAndDisplayRelateds(movieId) { + const $relatedContainer = $("#relatedContainer"), $relatedFooter = $("#relatedFooter"); + $relatedContainer.append('
    获取清单中...
    '); + let dataList = null; + try { + dataList = await javDbApi.related(movieId, 1, 20); + } catch (e) { + console.error("获取清单失败:", e); + } finally { + $("#relatedLoading").remove(); + } + if (dataList) if (0 !== dataList.length) { + this.displayRelateds(dataList, $relatedContainer); + if (20 === dataList.length) { + $relatedFooter.html('\n \n \n '); + let currentPage = 1, $loadMoreRelateds = $("#loadMoreRelateds"); + $loadMoreRelateds.on("click", (async () => { + $loadMoreRelateds.text("加载中...").prop("disabled", !0); + currentPage++; + let moreData; + try { + moreData = await javDbApi.related(movieId, currentPage, 20); + } catch (e) { + console.error("加载更多清单失败:", e); + } finally { + $loadMoreRelateds.text("加载失败, 请点击重试").prop("disabled", !1); + } + if (moreData) { + this.displayRelateds(moreData, $relatedContainer); + if (moreData.length < 20) { + $loadMoreRelateds.remove(); + $("#relatedEnd").show(); + } else $loadMoreRelateds.text("加载更多清单").prop("disabled", !1); + } + })); + } else $relatedFooter.html('
    已加载全部清单
    '); + } else $relatedContainer.append('
    无清单
    '); else { + $relatedContainer.append('\n
    \n 获取清单失败\n 重试\n
    \n '); + $("#retryFetchRelateds").on("click", (async () => { + $("#retryFetchRelateds").parent().remove(); + await this.fetchAndDisplayRelateds(movieId); + })); + } + } + displayRelateds(dataList, $container) { + dataList.length && dataList.forEach((item => { + let commentHtml = `\n
    \n #${this.floorIndex++}\n 创建时间: ${item.createTime}\n

    ${item.name}

    \n

    视频个数: ${item.movieCount}

    \n

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

    \n
    \n `; + $container.append(commentHtml); + })); + } +} + + +export { RelatedPlugin }; \ No newline at end of file diff --git a/src/plugins/review.js b/src/plugins/review.js new file mode 100644 index 0000000..25e28ed --- /dev/null +++ b/src/plugins/review.js @@ -0,0 +1,153 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, isJavBus, isJavDb, NO, YES } from '../core/constants.js'; +import { javDbApi } from '../api/javdb.js'; + +class ReviewPlugin extends BasePlugin { + constructor() { + super(...arguments); + __publicField(this, "floorIndex", 1); + __publicField(this, "isInit", !1); + } + getName() { + return "ReviewPlugin"; + } + async handle() { + $(document).on("click", ".down-115", (async event => { + const magnet = $(event.currentTarget).data("magnet"); + let loadObj = loading(); + try { + await this.getBean("WangPan115TaskPlugin").handleAddTask(magnet); + } catch (e) { + show.error("发生错误:" + e); + console.error(e); + } finally { + loadObj.close(); + } + })); + if (window.isDetailPage) { + if (isJavDb) { + const movieId = this.parseMovieId(window.location.href); + await this.showReview(movieId); + await this.getBean("RelatedPlugin").showRelated($("#magnets-content"), movieId); + } + if (isJavBus) { + let carNum2 = this.getPageInfo().carNum; + const movies = await javDbApi.searchMovie(carNum2); + let movieId = null; + for (let i = 0; i < movies.length; i++) { + let item = movies[i]; + if (item.number.toLowerCase() === carNum2.toLowerCase()) { + movieId = item.id; + break; + } + } + if (!movieId) return; + this.showReview(movieId, $("#sample-waterfall")).then(); + } + } + } + async showReview(movieId, $eleBox) { + const enableLoadReview = await storageManager.getSetting("enableLoadReview", YES), $magnets = $eleBox || $("#magnets-content"); + $magnets.append(`\n
    \n \n ❓ 评论区\n \n ${enableLoadReview === YES ? "折叠" : "展开"}\n ${enableLoadReview === YES ? "▲" : "▼"}\n \n \n
    \n `); + $("#reviewsFold").on("click", (event => { + event.preventDefault(); + event.stopPropagation(); + const $text = $("#reviewsFold .toggle-text"), $icon = $("#reviewsFold .toggle-icon"), isFolded = "展开" === $text.text(); + $text.text(isFolded ? "折叠" : "展开"); + $icon.text(isFolded ? "▲" : "▼"); + if (isFolded) { + $("#reviewsContainer").show(); + $("#reviewsFooter").show(); + if (!this.isInit) { + this.fetchAndDisplayReviews(movieId); + this.isInit = !0; + } + storageManager.saveSettingItem("enableLoadReview", YES); + } else { + $("#reviewsContainer").hide(); + $("#reviewsFooter").hide(); + storageManager.saveSettingItem("enableLoadReview", NO); + } + })); + $magnets.append('
    '); + $magnets.append('
    '); + enableLoadReview === YES && await this.fetchAndDisplayReviews(movieId); + } + async fetchAndDisplayReviews(movieId) { + const $reviewsContainer = $("#reviewsContainer"), $reviewsFooter = $("#reviewsFooter"); + $reviewsContainer.append('
    获取评论中...
    '); + const reviewCount = await storageManager.getSetting("reviewCount", 20); + let dataList = null; + try { + dataList = await javDbApi.getReviews(movieId, 1, reviewCount); + } catch (e) { + e.toString().includes("簽名已過期") && show.error("生成签名失败, 请检查系统时间及时区是否正确!"); + clog.error("获取评论失败:", e); + console.error("获取评论失败:", e); + } finally { + $("#reviewsLoading").remove(); + } + if (!dataList) { + $reviewsContainer.append('\n
    \n 获取评论失败\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", !0); + currentPage++; + let moreData; + try { + moreData = await javDbApi.getReviews(movieId, currentPage, reviewCount); + } catch (e) { + console.error("加载更多评论失败:", e); + } finally { + $loadMoreReviews.text("加载失败, 请点击重试").prop("disabled", !1); + } + if (moreData) { + this.displayReviews(moreData, $reviewsContainer, reviewKeywordList); + if (moreData.length < reviewCount) { + $loadMoreReviews.remove(); + $("#reviewsEnd").show(); + } else $loadMoreReviews.text("加载更多评论").prop("disabled", !1); + } + })); + } else $reviewsFooter.html('
    已加载全部评论
    '); + } + displayReviews(dataList, $container, reviewKeywordList) { + if (dataList.length) { + dataList.forEach((item => { + if (reviewKeywordList.some((keyword => item.content.includes(keyword)))) return; + const starsHtml = Array(item.score).fill('').join(""), content = item.content.replace(/ed2k:\/\/\|file\|[^|]+\|\d+\|[a-fA-F0-9]{32}\|\/|magnet:\?[^\s"'<>`\u4e00-\u9fa5,。?!()【】]+|https?:\/\/[^\s"'<>`\u4e00-\u9fa5,。?!()【】]+/g, (match => match.startsWith("ed2k://") ? `\n ${match}\n \n ` : match.startsWith("magnet:") ? `\n ${match}\n \n ` : match.startsWith("http://") || match.startsWith("https://") ? `\n ${match}\n ` : match)), commentHtml = `\n
    \n #${this.floorIndex++}楼\n ${item.username}    ${starsHtml} \n ${utils.formatDate(item.created_at)} \n    点赞:${item.likes_count}\n

    ${content}

    \n
    \n `; + $container.append(commentHtml); + })); + this.rightClickFilter(); + } + } + async rightClickFilter() { + await storageManager.getSetting("enableTitleSelectFilter", YES) === YES && utils.rightClick(document.body, ".review-content", (async event => { + const selectedText = window.getSelection().toString(); + if (selectedText) { + event.preventDefault(); + await utils.q(event, `是否将 '${selectedText}' 加入评论区关键词?`, (async () => { + await storageManager.saveReviewFilterKeyword(selectedText); + show.ok("操作成功, 刷新页面后生效"); + })); + } + })); + } +} + + +export { ReviewPlugin }; \ No newline at end of file diff --git a/src/plugins/screenshot.js b/src/plugins/screenshot.js new file mode 100644 index 0000000..2a34a7e --- /dev/null +++ b/src/plugins/screenshot.js @@ -0,0 +1,153 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, isJavBus, isJavDb } from '../core/constants.js'; +import { searchFiles } from './wangpan-115-task.js'; + +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 && $(".preview-images .tile-item").first().before('
    正在加载缩略图
    '); + isJavBus && $("#sample-waterfall .sample-box:first").after('
    正在加载缩略图
    '); + try { + const imgUrl = await this.getScreenshot(carNum2); + this.addImg("缩略图", imgUrl); + clog.log("加载缩略图:", imgUrl); + } catch (e) { + this.showErrorFallback(carNum2, e); + } + } + async getScreenshot(carNum2) { + const cacheData = localStorage.getItem("jhs_screenShot") ? JSON.parse(localStorage.getItem("jhs_screenShot")) : {}; + if (cacheData[carNum2]) { + clog.debug("缓存中存在缩略图:", carNum2, cacheData[carNum2]); + return cacheData[carNum2]; + } + let imgUrl; + try { + imgUrl = await Promise.any([ this.getJavStoreScreenShot(carNum2) ]); + } catch (e) { + clog.error("获取缩略图资源失败:", imgUrl, e); + throw e; + } + if (!imgUrl) { + this.showErrorFallback(carNum2, null); + return null; + } + const httpsIndex = imgUrl.indexOf("https://"); + -1 !== httpsIndex && (imgUrl = imgUrl.substring(httpsIndex)); + cacheData[carNum2] = imgUrl; + clog.log("缩略图获取成功:", imgUrl); + localStorage.setItem("jhs_screenShot", JSON.stringify(cacheData)); + return imgUrl; + } + async getJavStoreScreenShot(carNum2) { + let url = `https://javstore.net/search/${carNum2}.html`; + clog.log("正在解析缩略图:", url); + let html = await gmHttp.get(url); + const $dom = utils.htmlTo$dom(html); + let detailPageUrl = null; + $dom.find("#content_news h3 span a").each((function() { + if ($(this).attr("title").toLowerCase().includes(carNum2.toLowerCase())) { + detailPageUrl = $(this).attr("href"); + return !1; + } + })); + if (!detailPageUrl) { + clog.error("JavStore, 查询番号失败:", url); + return null; + } + let detailPageHtml = await gmHttp.get(detailPageUrl); + const $detailPageDom = utils.htmlTo$dom(detailPageHtml); + let imgUrl = $detailPageDom.find("a:contains('CLICK HERE')").attr("href") || $detailPageDom.find("img[src*='_s.jpg']").attr("src"); + if (!imgUrl) { + clog.error("JavStore, 解析预览图失败:", url); + return null; + } + return imgUrl.replace(".th", ""); + } + async getJavBestScreenShot(carNum2) { + let url = `https://javbest.net/?s=${carNum2}`; + clog.log("正在解析缩略图:", url); + let html = await gmHttp.get(url); + const $dom = utils.htmlTo$dom(html), href = $dom.find(".app_loop_thumb a").first().attr("href"); + if (!href) { + clog.error("解析JavBest搜索页失败:", url); + throw new Error("解析JavBest搜索页失败"); + } + const title = $dom.find(".app_loop_thumb a").first().attr("title"); + if (!title.toLowerCase().includes(carNum2.toLowerCase())) { + clog.error("解析JavBest搜索页失败:", title); + throw new Error("解析JavBest搜索页失败"); + } + const detailPageHtml = await gmHttp.get(href); + let imgUrl = $(detailPageHtml).find('#content a img[src*="_t.jpg"]').attr("src"); + if (!imgUrl) { + clog.error("解析JavBest缩略图失败:", url); + throw new Error("解析JavBest缩略图失败"); + } + imgUrl = imgUrl.replace("_t", "").replace("http:", "https:"); + return imgUrl; + } + async getJavFreeScreenShot(carNum2) { + let url = `https://javfree.me/search/${carNum2}/`, html = await gmHttp.get(url); + const $itemList = utils.htmlTo$dom(html).find("article h2.entry-title a"); + if (!$itemList || 0 === $itemList.length) { + clog.error("解析JavFree搜索页失败:", url); + throw new Error("解析JavFree搜索页失败"); + } + let detailPageUrl = $($itemList[0]).attr("href"), detailPageHtml = await gmHttp.get(detailPageUrl); + const $imgList = utils.htmlTo$dom(detailPageHtml).find("#main > article > .entry-content > p img"); + if (!$imgList || 0 === $imgList.length) { + clog.error("解析JavFree详情页失败:", detailPageUrl); + throw new Error("解析JavFree详情页失败"); + } + const srcList = $imgList.filter((function() { + const src = $(this).attr("src"); + return src && src.toLowerCase().endsWith(".jpeg"); + })).map((function() { + return $(this).attr("src"); + })).get(); + console.log(srcList); + return srcList.at(-1); + } + addImg(title, imgUrl) { + if (imgUrl) { + isJavDb && $(".screen-container").html(`${title}`); + isJavBus && $(".screen-container").html(`
    ${title}
    `); + $(".screen-container").on("click", (event => { + event.stopPropagation(); + event.preventDefault(); + showImageViewer(event.currentTarget); + })); + } + } + showErrorFallback(carNum2, error) { + var _a2; + console.error("获取缩略图失败:", null == (_a2 = null == error ? void 0 : error.message) ? void 0 : _a2.substring(0, 100)); + let differentCss = isJavBus ? "margin-top: 30px" : "margin-top: 50px"; + $(".screen-container").html(`
    获取缩略图失败

    点击重试 或 前往确认`).off("click", ".retry-link").off("click", ".check-link").on("click", ".retry-link", (async e => { + e.stopPropagation(); + e.preventDefault(); + $(".screen-container").html(`
    正在重新加载...
    `); + try { + const imgUrl = await this.getScreenshot(carNum2); + this.addImg("缩略图", imgUrl); + } catch (err) { + this.showErrorFallback(carNum2, err); + } + })).on("click", ".check-link", (async e => { + e.stopPropagation(); + e.preventDefault(); + window.open(`https://javstore.net/search/${carNum2}.html`, "_blank"); + })); + } +} + +export { ScreenShotPlugin }; \ No newline at end of file diff --git a/src/plugins/setting.js b/src/plugins/setting.js new file mode 100644 index 0000000..ba84097 --- /dev/null +++ b/src/plugins/setting.js @@ -0,0 +1,867 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, isJavBus, isJavDb, NO, YES, qualityOptions } from '../core/constants.js'; +import { AliyunApi, WebDavApi, SALT, simpleEncrypt } from './auto-page.js'; + +class SettingPlugin extends BasePlugin { + constructor() { + super(...arguments); + __publicField(this, "folderName", "JHS-数据备份"); + __publicField(this, "cacheItems", [ { + key: "jhs_dmm_video", + text: "🎥 预览视频缓存", + title: "预览视频缓存" + }, { + key: "jhs_other_site", + text: "🌍 第三方站点缓存", + title: "第三方站点资源检测结果, 如missav,123Av等" + }, { + key: "jhs_screenShot", + text: "🖼️ 缩略图缓存", + title: "缩略图缓存" + }, { + key: "jhs_translate", + text: "🆎 标题翻译", + title: "标题翻译" + }, { + key: "jhs_actress_info", + text: "👩 演员信息", + title: "演员的年龄三围等数据信息" + }, { + key: "jhs_score_info", + text: "⭐ Top250|热播 评分数据", + title: "Top250及热播的评分数据" + } ]); + } + getName() { + return "SettingPlugin"; + } + async initCss() { + const settingObj = await storageManager.getSetting(); + let containerWidth = (null == settingObj ? void 0 : settingObj.containerWidth) ?? "100", containerColumns = utils.isMobile() && window.innerWidth < 1e3 ? 1 : (null == settingObj ? void 0 : settingObj.containerColumns) ?? 5; + this.applyImageMode().then(); + let containerWidthCss = `\n section .container{\n max-width: 1000px !important;\n min-width: ${containerWidth}%;\n }\n .movie-list, .movie-list.v{\n grid-template-columns: repeat(${containerColumns}, minmax(0, 1fr));\n }\n `; + isJavBus && (containerWidthCss = `\n .container-fluid .row{\n max-width: 1000px !important;\n min-width: ${containerWidth}%;\n margin: auto auto;\n }\n \n .container {\n max-width: 1000px !important;\n min-width: 80%;\n margin: auto auto;\n }\n \n .masonry {\n grid-template-columns: repeat(${containerColumns}, minmax(0, 1fr));\n }\n `); + return `\n \n `; + } + async handle() { + await storageManager.getSetting("enableClog", YES) === YES && clog.show(); + if (isJavDb) { + let handleResize2 = function() { + if ($(".navbar-search").is(":hidden")) { + $(".mini-setting-box").hide(); + $(".setting-box").show(); + } else { + $(".mini-setting-box").show(); + $(".setting-box").hide(); + } + }; + $("#navbar-menu-user .navbar-end").prepend(''); + utils.loopDetector((() => $("#miniHistoryBtn").length > 0), (() => { + $(".miniHistoryBtnBox").before('\n \n '); + handleResize2(); + })); + $(window).resize(handleResize2); + } + if (isJavBus) { + utils.loopDetector((() => $("#waitCheckBtn").length), (() => { + $("#waitCheckBtn").parent().append('\n
    \n
    \n \n 设置\n \n
    \n
    \n
    \n '); + }), 1, 1e4, !1); + 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 => `\n
    \n
    ${item.text}
    \n
    \n \n 清理\n \n \n 查看\n \n
    \n
    \n `)).join(""); + let videoQualityHtml = ""; + qualityOptions.forEach((option => { + option.canSelect && (videoQualityHtml += ``); + })); + const coverButtonPlugin = this.getBean("CoverButtonPlugin"); + let settingHtml = `\n
    \n
    \n
    💾 数据备份
    \n
    ⚙️ 基础配置
    \n
    🚫 屏蔽配置
    \n
    📋 定时任务
    \n
    🌐 外部网站
    \n
    ⌨️ 快捷键配置
    \n
    🧹 清理缓存
    \n
    💵 打赏作者
    \n
    \n \n
    \n
    \n \n \x3c!-- 阿里云盘面板 --\x3e\n
    \n \n \n
    \n 阿里云盘备份\n \n
    \n
    \n refresh_token:\n
    \n \n
    \n
    \n \n
    \n \n
    \n WebDav备份\n \n
    \n
    \n 服务地址:\n
    \n \n
    \n
    \n
    \n 用户名:\n
    \n \n
    \n
    \n
    \n 密码:\n
    \n \n
    \n
    \n
    \n \n \n \x3c!-- 基础设置面板 --\x3e\n
    \n
    \n 已鉴定标签展示位置:\n
    \n \n
    \n
    \n \n
    \n 已鉴定内容处理方式:\n
    \n \n
    \n
    \n \n
    \n \n 鉴定补录演员信息 ❓\n \n
    \n \n
    \n
    \n \n
    \n \n
    \n \n 列表页功能按钮\n \n
    \n \n
    \n 按钮-打开待鉴定:\n
    \n \n
    \n
    \n \n
    \n 按钮-打开已收藏:\n
    \n \n
    \n
    \n \n
    \n 打开待鉴定|已收藏 窗口数:\n
    \n \n
    \n
    \n \n
    \n 随机打开已收藏:\n
    \n \n
    \n
    \n \n \n
    \n \n
    \n \n 封面快捷按钮\n \n
    \n \n
    \n \n ${coverButtonPlugin.screenSvg}长缩略图:\n \n
    \n \n
    \n
    \n \n
    \n \n ${coverButtonPlugin.videoSvg}预览视频:\n \n
    \n \n
    \n
    \n \n
    \n \n ${coverButtonPlugin.handleSvg}鉴定按钮:\n \n
    \n \n
    \n
    \n \n
    \n \n ${coverButtonPlugin.siteSvg}第三方跳转:\n \n
    \n \n
    \n
    \n \n
    \n \n ${coverButtonPlugin.copySvg}复制按钮:\n \n
    \n \n
    \n
    \n \n
    \n\n
    \n 预览视频默认画质:\n
    \n \n
    \n
    \n \n
    \n 评论区条数:\n
    \n \n
    \n
    \n \n
    \n \n 高亮已收藏演员 ❓\n \n
    \n \n
    \n
    \n \n
    \n \n 分类标签|高亮演员-边框样式:\n \n
    \n \n \n
    \n
    \n\n
    \n \n
    \n 请求超时时间(毫秒):\n
    \n \n
    \n
    \n \n
    \n 请求失败重试次数:\n
    \n \n
    \n
    \n \n
    \n \n
    \n \n 启用控制台日志:\n \n
    \n \n
    \n
    \n\n
    \n 日志最大行数:\n
    \n \n
    \n
    \n
    \n \n \x3c!-- 定时任务 --\x3e\n
    \n \n
    \n 请求并发数量:\n
    \n \n
    \n
    \n
    \n 请求间隔时间(毫秒):\n
    \n \n
    \n
    \n \n
    \n \n
    \n 自动检测屏蔽黑名单演员\n
    \n \n 任务开关: ❓ \n \n
    \n \n
    \n
    \n
    \n 任务间隔时间:\n
    \n \n
    \n
    \n
    \n 检测规则:\n
    \n \n
    \n
    \n
    \n \n
    \n 自动同步已收藏的演员\n
    \n \n 任务开关: ❓ \n \n
    \n \n
    \n
    \n
    \n 任务间隔时间:\n
    \n \n
    \n
    \n
    \n \n
    \n 自动检测已收藏演员的最新作品\n
    \n \n 任务开关: ❓ \n \n
    \n \n
    \n
    \n
    \n 任务间隔时间:\n
    \n \n
    \n
    \n
    \n 检测规则:\n
    \n \n
    \n
    \n
    \n
    \n \n \x3c!-- 域名设置面板 --\x3e\n
    \n
    \n 域名 - MissAv:\n
    \n \n
    \n
    \n
    \n 域名 - Jable:\n
    \n \n
    \n
    \n
    \n 域名 - Avgle:\n
    \n \n
    \n
    \n
    \n 域名 - JavTrailer:\n
    \n \n
    \n
    \n
    \n 域名 - 123Av:\n
    \n \n
    \n
    \n
    \n 域名 - JavDb:\n
    \n \n
    \n
    \n
    \n 域名 - JavBus:\n
    \n \n
    \n
    \n
    \n 域名 - SupJav:\n
    \n \n
    \n
    \n
    \n \n \x3c!-- 快捷键 --\x3e\n
    \n

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

    \n
    \n
    \n 🚫 屏蔽:\n
    \n \n
    \n
    \n
    \n ⭐ 收藏:\n
    \n \n
    \n
    \n
    \n 📥️ 已下载:\n
    \n \n
    \n
    \n
    \n 🔍 已观看:\n
    \n \n
    \n
    \n \n
    \n \n ❓ 对视频列表页启用快捷键:\n \n
    \n \n
    \n
    \n
    \n \n
    \n
    \n ⏩ 快进:\n
    \n \n
    \n
    \n \n
    \n ▲ 折叠:\n
    \n \n
    \n
    \n \n
    \n 💻 控制台:\n
    \n \n
    \n
    \n
    \n\n\n
    \n 显示已鉴定内容\n
    \n 屏蔽单番号:\n
    \n \n
    \n
    \n
    \n 屏蔽演员:\n
    \n \n
    \n
    \n
    \n 屏蔽关键词:\n
    \n \n
    \n
    \n
    \n 收藏:\n
    \n \n
    \n
    \n
    \n 已下载:\n
    \n \n
    \n
    \n
    \n 已观看:\n
    \n \n
    \n
    \n
    \n 显示所有:\n
    \n \n
    \n
    \n
    \n\n
    \n \n \x3c!-- 屏蔽设置面板 --\x3e\n
    \n
    \n \n 启用划词屏蔽 ❓ \n \n
    \n \n
    \n
    \n \n
    \n \n
    \n
    \n 评论区屏蔽词:\n
    \n \n \n
    \n
    \n
    \n
    \n \n
    \n \n
    \n
    \n 视频标题屏蔽词:\n
    \n \n \n
    \n
    \n
    \n
    \n
    \n
    \n

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

    \n
    \n
    \n ${buttonsHTML}\n
    \n \n
    \n
    \n

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

    \n
    \n
    \n
    \n TRC20-USDT二维码\n

    TRC20-USDT

    \n \n

    点击地址可复制

    \n
    \n
    \n
    \n
    \n
    \n \n
    \n \n \n
    \n
    \n
    \n `; + layer.open({ + type: 1, + title: "设置", + content: settingHtml, + area: utils.getResponsiveArea([ "55%", "90%" ]), + scrollbar: !1, + success: (layero, index) => { + $(layero).find(".layui-layer-content").css("position", "relative"); + this.loadForm(); + this.bindClick(); + utils.setupEscClose(index); + fun && fun(); + }, + end: () => { + this.getBean("CoverButtonPlugin").enableSvgBtn(); + } + }); + } + async simpleSetting() { + let settingObj = await storageManager.getSetting(); + const showFilterItemHotKey = settingObj.showFilterItemHotKey, showFilterActorItemHotKey = settingObj.showFilterActorItemHotKey, showFilterKeywordItemHotKey = settingObj.showFilterKeywordItemHotKey, showFavoriteItemHotKey = settingObj.showFavoriteItemHotKey, showHasDownItemHotKey = settingObj.showHasDownItemHotKey, showHasWatchItemHotKey = settingObj.showHasWatchItemHotKey, showAllItemHotKey = settingObj.showAllItemHotKey; + return `\n
    \n
    \n
    \n \n 显示已鉴定内容:\n \n
    \n 屏蔽单番号${showFilterItemHotKey ? `(${showFilterItemHotKey})` : ""}:
    \n 屏蔽演员${showFilterActorItemHotKey ? `(${showFilterActorItemHotKey})` : ""}:
    \n 屏蔽关键词${showFilterKeywordItemHotKey ? `(${showFilterKeywordItemHotKey})` : ""}:
    \n 收藏${showFavoriteItemHotKey ? `(${showFavoriteItemHotKey})` : ""}:
    \n 已下载${showHasDownItemHotKey ? `(${showHasDownItemHotKey})` : ""}:
    \n 已观看${showHasWatchItemHotKey ? `(${showHasWatchItemHotKey})` : ""}:
    \n
    \n
    \n
    \n \n ❓ 显示所有${showAllItemHotKey ? `(${showAllItemHotKey})` : ""}:\n \n
    \n \n
    \n
    \n \n
    \n \n
    \n \n ❓ 弹窗方式打开页面:\n \n
    \n \n
    \n
    \n \n
    \n 鉴定后立即关闭页面:\n
    \n \n
    \n
    \n \n
    \n\n
    \n \n ❓ 瀑布流模式:\n \n
    \n \n
    \n
    \n \n
    \n 启用标题翻译:\n
    \n \n
    \n
    \n \n
    \n 启用悬浮大图:\n
    \n \n
    \n
    \n \n \n
    \n 启用115视频匹配: \n
    \n \n
    \n
    \n \n
    \n\n ${isJavDb ? '\n
    \n \n ❓ 加载女优信息:\n \n
    \n \n
    \n
    ' : ""}\n \n
    \n \n ❓ 加载第三方视频资源:\n \n
    \n \n
    \n
    \n \n
    \n \n ❓ 加载长缩略图:\n \n
    \n \n
    \n
    \n \n
    \n \n ❓ 更高画质预览视频:\n \n
    \n \n
    \n
    \n\n
    \n\n
    \n \n ❓ 竖图模式:\n \n
    \n \n
    \n
    \n \n
    \n 页面列数: \n
    \n \n
    \n
    \n \n
    \n 页面宽度: \n
    \n \n
    \n
    \n
    \n
    \n \n \n
    \n
    \n `; + } + async loadForm() { + let settingObj = await storageManager.getSetting(); + $("#videoQuality").val(settingObj.videoQuality); + $("#reviewCount").val(settingObj.reviewCount || 20); + $("#tagPosition").val(settingObj.tagPosition || "rightTop"); + $("#movieShowType").val(settingObj.movieShowType || "hide"); + $("#waitCheckCount").val(settingObj.waitCheckCount || 5); + $("#showWaitCheckBtn").prop("checked", !settingObj.showWaitCheckBtn || settingObj.showWaitCheckBtn === YES); + $("#showWaitDownBtn").prop("checked", !settingObj.showWaitDownBtn || settingObj.showWaitDownBtn === YES); + $("#randomOpenWaitDown").prop("checked", !!settingObj.randomOpenWaitDown && settingObj.randomOpenWaitDown === YES); + $("#checkConcurrencyCount").val(settingObj.checkConcurrencyCount || 2); + $("#checkRequestSleep").val(settingObj.checkRequestSleep || 100); + $("#enableCheckBlacklist").val(settingObj.enableCheckBlacklist || YES); + $("#checkBlacklist_intervalTime").val(settingObj.checkBlacklist_intervalTime || 12); + $("#checkBlacklist_ruleTime").val(settingObj.checkBlacklist_ruleTime || 8760); + $("#enableCheckFavoriteActress").val(settingObj.enableCheckFavoriteActress || YES); + $("#checkFavoriteActress_IntervalTime").val(settingObj.checkFavoriteActress_IntervalTime || 24); + $("#enableCheckNewVideo").val(settingObj.enableCheckNewVideo || YES); + $("#checkNewVideo_intervalTime").val(settingObj.checkNewVideo_intervalTime || 12); + $("#checkNewVideo_ruleTime").val(settingObj.checkNewVideo_ruleTime || 8760); + const highlightedTagNumber = settingObj.highlightedTagNumber || 1, highlightedTagColor = settingObj.highlightedTagColor || "#ce2222"; + $("#highlightedTagNumber").val(settingObj.highlightedTagNumber || 1); + $("#highlightedTagColor").val(settingObj.highlightedTagColor || "#ce2222"); + $("#highlightedTagLabel").css("border", `${highlightedTagNumber}px solid ${highlightedTagColor}`); + $("#enableClog").val(settingObj.enableClog || YES); + $("#clogMsgCount").val(settingObj.clogMsgCount || 2e3); + $("#refresh_token").val(settingObj.refresh_token || ""); + $("#httpTimeout").val(settingObj.httpTimeout || 5e3); + $("#httpRetryCount").val(settingObj.httpRetryCount || 3); + $("#webDavUrl").val(settingObj.webDavUrl || ""); + $("#webDavUsername").val(settingObj.webDavUsername || ""); + $("#webDavPassword").val(settingObj.webDavPassword || ""); + $("#enableTitleSelectFilter").prop("checked", !settingObj.enableTitleSelectFilter || settingObj.enableTitleSelectFilter === YES); + $("#enableFavoriteActresses").prop("checked", !settingObj.enableFavoriteActresses || settingObj.enableFavoriteActresses === YES); + $("#enableSaveActressCarInfo").prop("checked", !!settingObj.enableSaveActressCarInfo && settingObj.enableSaveActressCarInfo === YES); + $("#enableScreenSvg").prop("checked", !settingObj.enableScreenSvg || settingObj.enableScreenSvg === YES); + $("#enableVideoSvg").prop("checked", !settingObj.enableVideoSvg || settingObj.enableVideoSvg === YES); + $("#enableHandleSvg").prop("checked", !settingObj.enableHandleSvg || settingObj.enableHandleSvg === YES); + $("#enableSiteSvg").prop("checked", !settingObj.enableSiteSvg || settingObj.enableSiteSvg === YES); + $("#enableCopySvg").prop("checked", !settingObj.enableCopySvg || settingObj.enableCopySvg === YES); + const otherSitePlugin = this.getBean("OtherSitePlugin"), missAvUrl = await otherSitePlugin.getMissAvUrl(), jableUrl = await otherSitePlugin.getjableUrl(), avgleUrl = await otherSitePlugin.getAvgleUrl(), javTrailersUrl = await otherSitePlugin.getJavTrailersUrl(), av123Url = await otherSitePlugin.getAv123Url(), javDbUrl = await otherSitePlugin.getJavDbUrl(), javBusUrl = await otherSitePlugin.getJavBusUrl(), supJavUrl = await otherSitePlugin.getSupJavUrl(); + $("#missAvUrl").val(missAvUrl); + $("#jableUrl").val(jableUrl); + $("#avgleUrl").val(avgleUrl); + $("#javTrailersUrl").val(javTrailersUrl); + $("#av123Url").val(av123Url); + $("#javDbUrl").val(javDbUrl); + $("#javBusUrl").val(javBusUrl); + $("#supJavUrl").val(supJavUrl); + let reviewKeywordList = await storageManager.getReviewFilterKeywordList(), filterKeywordList = await storageManager.getTitleFilterKeyword(); + reviewKeywordList && reviewKeywordList.forEach((reviewKeyword => { + this.addLabelTag("#reviewKeywordContainer", reviewKeyword); + })); + filterKeywordList && filterKeywordList.forEach((reviewKeyword => { + this.addLabelTag("#filterKeywordContainer", reviewKeyword); + })); + [ "#reviewKeywordContainer", "#filterKeywordContainer" ].forEach((containerId => { + $(`${containerId} .add-tag-btn`).on("click", (event => this.addKeyword(event, containerId))); + $(`${containerId} .keyword-input`).on("keypress", (event => { + "Enter" === event.key && this.addKeyword(event, containerId); + })); + })); + $("#hotkey-panel [id]").map(((i, el) => el.id)).get().forEach((containerId => { + const $element = $(`#${containerId}`), defaultValue = void 0 !== settingObj[containerId] ? settingObj[containerId] : $element.attr("data-default-hotkey") || ""; + $element.val(defaultValue).on("input", (event => { + let value = $(event.target).val(); + if (/[\u4e00-\u9fa5]/.test(value) || /^Shift[a-zA-Z0-9]+$/.test(value)) { + $(event.target).val(""); + show.error("非法输入:不能输入中文或输入法转换错误"); + } + })).on("keydown", (event => this.handleHotkeyInput(event, $element))); + })); + $("#enableImageHotKey").prop("checked", !!settingObj.enableImageHotKey && settingObj.enableImageHotKey === YES); + } + handleHotkeyInput(event, $input) { + event.preventDefault(); + const hotkey = this.parseHotkey(event); + "" !== hotkey ? this.isDuplicateHotkey(hotkey, $input.attr("id")) ? show.error("该快捷键已被其他功能使用!") : $input.val(hotkey) : $input.val(""); + } + parseHotkey(event) { + if ("Backspace" === event.key || "Process" === event.key) return ""; + const keys = []; + event.ctrlKey && keys.push("Ctrl"); + event.shiftKey && keys.push("Shift"); + event.altKey && keys.push("Alt"); + event.metaKey && keys.push("Cmd"); + const key = { + " ": "Space", + Control: "Ctrl", + Meta: "Cmd", + ArrowUp: "Up", + ArrowDown: "Down", + ArrowLeft: "Left", + ArrowRight: "Right" + }[event.key] || (event.key.length > 1 ? event.key.replace("Arrow", "") : event.key); + [ "Control", "Shift", "Alt", "Meta" ].includes(event.key) || keys.push(key); + return keys.length > 0 ? keys.join("+") : ""; + } + isDuplicateHotkey(hotkey, currentInputId) { + let isDuplicate = !1; + $("#hotkey-panel [id]").each(((i, el) => { + if (el.id !== currentInputId && hotkey && hotkey === $(el).val()) { + isDuplicate = !0; + return !1; + } + })); + return isDuplicate; + } + async initSimpleSettingForm() { + let settingObj = await storageManager.getSetting(); + $("#containerColumns").val(settingObj.containerColumns || 5); + $("#showContainerColumns").text(settingObj.containerColumns || 5); + $("#containerWidth").val((settingObj.containerWidth || 100) - 70); + $("#showContainerWidth").text((settingObj.containerWidth || 100) + "%"); + $("#dialogOpenDetail").prop("checked", !settingObj.dialogOpenDetail || settingObj.dialogOpenDetail === YES); + $("#needClosePage").prop("checked", !settingObj.needClosePage || settingObj.needClosePage === YES); + $("#autoPage").prop("checked", !settingObj.autoPage || settingObj.autoPage === YES); + $("#translateTitle").prop("checked", !settingObj.translateTitle || settingObj.translateTitle === YES); + $("#enableLoadActressInfo").prop("checked", !settingObj.enableLoadActressInfo || settingObj.enableLoadActressInfo === YES); + $("#enableLoadOtherSite").prop("checked", !settingObj.enableLoadOtherSite || settingObj.enableLoadOtherSite === YES); + $("#containerColumns").on("input", (async event => { + let columns = $("#containerColumns").val(); + $("#showContainerColumns").text(columns); + if (isJavDb) { + document.querySelector(".movie-list").style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`; + } + if (isJavBus) { + document.querySelector(".masonry").style.gridTemplateColumns = `repeat(${columns}, minmax(0, 1fr))`; + } + await storageManager.saveSettingItem("containerColumns", columns); + this.applyImageMode(); + })); + $("#containerWidth").on("input", (async event => { + let containerWidth = parseInt($(event.target).val()); + const value = containerWidth + 70 + "%"; + $("#showContainerWidth").text(value); + if (isJavDb) { + document.querySelector("section .container").style.minWidth = value; + } + if (isJavBus) { + document.querySelector(".container-fluid .row").style.minWidth = value; + } + storageManager.saveSettingItem("containerWidth", containerWidth + 70); + })); + $("#dialogOpenDetail").on("change", (event => { + let dialogOpenDetail = $("#dialogOpenDetail").is(":checked") ? YES : NO; + storageManager.saveSettingItem("dialogOpenDetail", dialogOpenDetail); + })); + $("#showFilterItem").prop("checked", !!settingObj.showFilterItem && settingObj.showFilterItem === YES); + $("#showFilterActorItem").prop("checked", !!settingObj.showFilterActorItem && settingObj.showFilterActorItem === YES); + $("#showFilterKeywordItem").prop("checked", !!settingObj.showFilterKeywordItem && settingObj.showFilterKeywordItem === YES); + $("#showFavoriteItem").prop("checked", !settingObj.showFavoriteItem || settingObj.showFavoriteItem === YES); + $("#showHasDownItem").prop("checked", !settingObj.showHasDownItem || settingObj.showHasDownItem === YES); + $("#showHasWatchItem").prop("checked", !settingObj.showHasWatchItem || settingObj.showHasWatchItem === YES); + $("#showFilterItem").on("change", (async event => { + let showFilterItem = $("#showFilterItem").is(":checked") ? YES : NO; + await storageManager.saveSettingItem("showFilterItem", showFilterItem); + window.refresh(); + })); + $("#showFilterActorItem").on("change", (async event => { + let showFilterActorItem = $("#showFilterActorItem").is(":checked") ? YES : NO; + await storageManager.saveSettingItem("showFilterActorItem", showFilterActorItem); + window.refresh(); + })); + $("#showFilterKeywordItem").on("change", (async event => { + let showFilterKeywordItem = $("#showFilterKeywordItem").is(":checked") ? YES : NO; + await storageManager.saveSettingItem("showFilterKeywordItem", showFilterKeywordItem); + window.refresh(); + })); + $("#showFavoriteItem").on("change", (async event => { + let showFavoriteItem = $("#showFavoriteItem").is(":checked") ? YES : NO; + await storageManager.saveSettingItem("showFavoriteItem", showFavoriteItem); + window.refresh(); + })); + $("#showHasDownItem").on("change", (async event => { + let showHasDownItem = $("#showHasDownItem").is(":checked") ? YES : NO; + await storageManager.saveSettingItem("showHasDownItem", showHasDownItem); + window.refresh(); + })); + $("#showHasWatchItem").on("change", (async event => { + let showHasWatchItem = $("#showHasWatchItem").is(":checked") ? YES : NO; + await storageManager.saveSettingItem("showHasWatchItem", showHasWatchItem); + window.refresh(); + })); + const $otherCheckboxes = $("#showFilterItem, #showFilterActorItem, #showFilterKeywordItem, #showFavoriteItem, #showHasDownItem, #showHasWatchItem"), updateOtherCheckboxesState = () => { + const isShowAllChecked = $("#showAllItem").is(":checked"); + $otherCheckboxes.prop("disabled", isShowAllChecked); + isShowAllChecked ? $otherCheckboxes.attr("data-tip", "请先关闭显示所有才可点击") : $otherCheckboxes.removeAttr("data-tip"); + }; + $("#showAllItem").prop("checked", !!settingObj.showAllItem && settingObj.showAllItem === YES); + $("#showAllItem").on("change", (async event => { + let showAllItem = $("#showAllItem").is(":checked") ? YES : NO; + await storageManager.saveSettingItem("showAllItem", showAllItem); + updateOtherCheckboxesState(); + window.refresh(); + })); + updateOtherCheckboxesState(); + $("#needClosePage").on("change", (async event => { + await storageManager.saveSettingItem("needClosePage", $("#needClosePage").is(":checked") ? YES : NO); + window.refresh(); + })); + $("#autoPage").on("change", (async event => { + const autoPage = $("#autoPage").is(":checked") ? YES : NO; + await storageManager.saveSettingItem("autoPage", autoPage); + autoPage === YES ? $("#sort-toggle-btn").hide() : $("#sort-toggle-btn").show(); + })); + $("#translateTitle").on("change", (async event => { + const translateTitle = $("#translateTitle").is(":checked") ? YES : NO; + await storageManager.saveSettingItem("translateTitle", translateTitle); + if (translateTitle === YES) { + await this.getBean("ListPagePlugin").doFilter(); + 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: !0, + scrollbar: !1, + content: '\n\n\n
    \n

    使用说明

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

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

    \n

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

    \n

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

    \n

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

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

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

    \n

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

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

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

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

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

    \n \n
    \n
    \n

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

    \n \n
    \n
    \n

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

    \n \n
    \n
    \n
    \n', + area: utils.getResponsiveArea([ "50%", "90%" ]) + }); + })); + } + async applyImageMode() { + $("#verticalImgStyle").remove(); + if (await storageManager.getSetting("enableVerticalModel", NO) === YES) { + let imgPosition = "100% 50% !important"; + window.location.href.includes("/advanced_search?type=100") && (imgPosition = "50% 50% !important"); + const verticalStyle = `\n .cover {\n min-height: 350px !important;\n overflow: hidden !important;\n padding-top: 142% !important;\n }\n \n .cover img {\n object-fit: cover !important;\n object-position: ${imgPosition};\n }\n \n /* bus的 */\n .masonry .movie-box img {\n min-height: 500px !important;\n object-fit: cover !important;\n object-position: top right;\n }\n `; + $("\n "; + } + handle() { + window.isDetailPage && this.translate(); + } + async translate(carNum2, showCarNum = !0) { + if (await storageManager.getSetting("translateTitle", YES) !== YES) return; + isJavBus && (showCarNum = !1); + let $titleElement = $(".origin-title"); + $titleElement.length || ($titleElement = $(".current-title")); + $titleElement.length || ($titleElement = $("h3")); + if (!$titleElement.length) return; + const originalText = $titleElement.text().trim(); + if (!originalText) { + show.error("获取标题失败, 无法进行翻译"); + return; + } + $titleElement.after('
    翻译中...
    '); + const $loadingElement = $titleElement.next(".translated-title"); + carNum2 || (carNum2 = this.getPageInfo().carNum); + const cache = localStorage.getItem("jhs_translate") ? JSON.parse(localStorage.getItem("jhs_translate")) : {}; + cache[carNum2] ? $loadingElement.html(showCarNum ? carNum2 + "   " + cache[carNum2] : cache[carNum2]) : translateText(originalText, "ja", "zh-CN").then((translatedText => { + $loadingElement.html(showCarNum ? carNum2 + "   " + translatedText : translatedText); + })).catch((error => { + console.error("翻译失败:", error); + $loadingElement.replaceWith(`
    翻译失败: ${error.message}
    `); + })); + } +} + + +export { TranslatePlugin }; \ No newline at end of file diff --git a/src/plugins/wangpan-115-task.js b/src/plugins/wangpan-115-task.js new file mode 100644 index 0000000..1f510b3 --- /dev/null +++ b/src/plugins/wangpan-115-task.js @@ -0,0 +1,151 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, isJavBus } from '../core/constants.js'; + +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($("\n \n `; + document.body.appendChild(panel); + const header = document.getElementById("jhs-cookie-header"); + document.getElementById("jhs-cookie-content"); + const toggleIcon = document.getElementById("jhs-toggle-icon"), copyButton = document.getElementById("jhs-copy-btn"); + header.addEventListener("click", (() => { + const isExpanded = panel.classList.toggle("expanded"); + toggleIcon.textContent = isExpanded ? "▲" : "▼"; + })); + copyButton.addEventListener("click", (async e => { + e.stopPropagation(); + try { + await navigator.clipboard.writeText(cookieValue); + this.showMessage("Cookie 已成功复制到剪贴板!"); + } catch (err) { + console.error("Failed to copy text using clipboard API: ", err); + const tempTextArea = document.createElement("textarea"); + tempTextArea.value = cookieValue; + document.body.appendChild(tempTextArea); + tempTextArea.select(); + document.execCommand("copy"); + document.body.removeChild(tempTextArea); + this.showMessage("Cookie 已复制! (回退方案)"); + } + })); + panel.classList.remove("expanded"); + } +} + +const _WangPan115MatchPlugin = class _WangPan115MatchPlugin extends BasePlugin { + constructor() { + super(...arguments); + __publicField(this, "loginStatus", _WangPan115MatchPlugin.LoginStatus.UNCHECKED); + } + getName() { + return "WangPan115MatchPlugin"; + } + async initCss() { + return "\n \n "; + } + async handle() { + $(document).on("click", ".jhs-match-no-login-btn", (async event => { + event.preventDefault(); + event.stopPropagation(); + await this.handleLoginRedirect(); + })); + $(document).on("click", ".jhs-match-btn", (event => { + event.preventDefault(); + event.stopImmediatePropagation(); + this.showMatchDetail(event.currentTarget); + })); + $(document).on("click", ".jhs-match-error-btn", (async event => { + event.preventDefault(); + event.stopPropagation(); + await this.retryMatch(event.currentTarget); + })); + await this.matchDetailPage(); + $(document).on("click", ".jhs-match-detail-error-btn", (async event => { + event.preventDefault(); + event.stopPropagation(); + $(event.currentTarget).replaceWith("匹配中..."); + try { + const carNum2 = this.getPageInfo().carNum, matchList = await this.searchFiles(carNum2); + $(".jhs-115-match-detail").remove(); + await this.matchDetailPage(matchList); + } catch (error) { + console.error(`重新匹配失败 [${carNum}]:`, error); + this.showMatchError($box, carNum, error); + } + })); + } + async matchDetailPage(matchList) { + if (!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 === _WangPan115MatchPlugin.LoginStatus.LOGGED_OUT) $tbody.append(`\n 未登录\n `); else if (matchList.length > 0) { + const rowsHtml = matchList.map((match => `\n \n ${match.name}\n ${this.formatSize(match.size)}\n ${match.createTime}\n \n 播放\n \n \n `)).join(""); + $tbody.append(rowsHtml); + } else $tbody.append(`\n 未匹配\n `); + } catch (error) { + $tbody.append(`\n 加载失败,请重试\n `); + console.error("加载文件列表时发生错误:", error); + } + if (isJavDb) if ($("#all-match-box").length) $("#all-match-box").append($detail); else { + $("#tabs-container").before("
    "); + $("#all-match-box").append($detail); + } else $("#mag-submit-show").before($detail); + } + async matchMovieList(movieListElement) { + if (await storageManager.getSetting("enable115Match", NO) !== NO) { + await this.checkLoginStatus(); + await this.processMovieElements(movieListElement); + } else $(".video-title [class^='jhs-match-']").remove(); + } + showMatchDetail(buttonElement) { + const $el = $(buttonElement), matchData = $el.attr("data-match"); + $(".jhs-match-detail").remove(); + const matches = this.parseMatchData(matchData); + if (0 === matches.length) return; + if (1 === matches.length) { + const pickcode = matches[0].videoId; + window.open(`https://115vod.com/?pickcode=${pickcode}&share_id=0`, "_blank"); + return; + } + const $detail = this.createMatchDetailElement(matches); + this.positionDetailElement($detail, $el); + this.addOutsideClickHandler($detail); + $detail.on("click", (e => { + e.stopPropagation(); + })); + } + parseMatchData(matchData) { + try { + return JSON.parse(matchData) || []; + } catch (e) { + console.error("解析匹配数据失败:", e); + return []; + } + } + createMatchDetailElement(matches) { + const $detail = $(`\n
    \n \n \n \n \n \n \n \n \n \n \n ${matches.map((match => `\n \n \n \n \n \n \n `)).join("")}\n \n
    名称大小时间播放
    ${match.name}${this.formatSize(match.size)}${match.createTime}\n 播放\n
    \n
    \n `); + $("body").append($detail); + return $detail; + } + positionDetailElement($detail, $trigger) { + const offset = $trigger.offset(); + $detail.css({ + top: offset.top - $detail.outerHeight() + 20, + left: offset.left + }); + } + addOutsideClickHandler($detail) { + setTimeout((() => { + $(document).on("click.jhs-match-detail", (e => { + if (!$detail.is(e.target) && 0 === $detail.has(e.target).length) { + $detail.remove(); + $(document).off("click.jhs-match-detail"); + } + })); + }), 100); + } + async retryMatch(buttonElement) { + const $el = $(buttonElement), $box2 = $el.closest(".movie-box, .item"), carNum2 = $el.attr("data-keyword"); + $el.replaceWith("匹配中..."); + try { + const matchList = await this.searchFiles(carNum2); + this.updateMatchStatus($box2, carNum2, matchList); + } catch (error) { + console.error(`重新匹配失败 [${carNum2}]:`, error); + this.showMatchError($box2, carNum2, error); + } + } + updateMatchStatus($box2, carNum2, matchList) { + matchList.length > 0 ? $box2.find(".jhs-match-btn").replaceWith(`匹配${matchList.length}个`) : $box2.find(".jhs-match-btn").replaceWith(`未匹配`); + } + async handleLoginRedirect() { + window.open("https://115.com"); + } + async searchFiles(carNum2) { + var _a2; + let searchKeyword = carNum2.toLowerCase().replace("fc2-", ""); + return (null == (_a2 = (await searchFiles(searchKeyword)).data) ? void 0 : _a2.map((data => ({ + folderId: data.fid, + videoId: data.pc, + name: data.n, + createTime: utils.formatDate(new Date(1e3 * data.te)), + size: data.s, + isVideo: [ ".mp4", ".avi", ".mov", ".mkv", ".flv", ".wmv" ].some((ext => { + var _a3; + return null == (_a3 = data.n) ? void 0 : _a3.toLowerCase().endsWith(ext); + })) + }))).filter((x => x.folderId && x.isVideo && x.name.toLowerCase().includes(searchKeyword)))) || []; + } + showMatchError($box2, carNum2, error) { + $box2.find(".jhs-match-btn").replaceWith(`匹配失败`); + show.error(`${carNum2} 匹配失败: ${error.message || "网络错误"}`); + } + async checkLoginStatus() { + var _a2; + if (this.loginStatus === _WangPan115MatchPlugin.LoginStatus.UNCHECKED) try { + const testResult = await searchFiles("test"); + this.loginStatus = (null == (_a2 = testResult.error) ? void 0 : _a2.includes("登录")) ? _WangPan115MatchPlugin.LoginStatus.LOGGED_OUT : _WangPan115MatchPlugin.LoginStatus.LOGGED_IN; + } catch { + this.loginStatus = _WangPan115MatchPlugin.LoginStatus.LOGGED_OUT; + } + } + async processMovieElements(movieListElement) { + const promises = Array.from(movieListElement).filter((ele => !utils.isHidden(ele))).filter((ele => !(isJavBus && $(ele).find(".avatar-box").length > 0))).map((ele => this.processSingleMovieElement(ele))); + await Promise.all(promises); + } + async processSingleMovieElement(element) { + const $box2 = $(element), {carNum: carNum2} = this.getBoxCarInfo($box2); + if (!($box2.find("[class^='jhs-match-']").length > 0)) if (this.loginStatus !== _WangPan115MatchPlugin.LoginStatus.LOGGED_OUT) try { + const matchList = await this.searchFiles(carNum2); + this.addTag($box2, carNum2, matchList); + } catch (error) { + console.error(`搜索失败 [${carNum2}]:`, error); + this.addTag($box2, carNum2, []); + } else this.addTag($box2, carNum2, []); + } + addTag($box2, carNum2, matchList) { + if (!($box2.find("[class^='jhs-match-']").length > 0)) if (this.loginStatus === _WangPan115MatchPlugin.LoginStatus.LOGGED_OUT) $box2.find(".video-title").prepend(`未登录`); else if (matchList.length > 0) { + const title = 1 === matchList.length ? "点击直接播放" : `点击查看${matchList.length}个匹配结果`; + $box2.find(".video-title").prepend(`匹配${matchList.length}个`); + } else $box2.find(".video-title").prepend(`未匹配`); + } + formatSize(bytes) { + if (!bytes) return "-"; + const units = [ "B", "KB", "MB", "GB", "TB" ]; + let size = parseFloat(bytes), unit = 0; + for (;size >= 1024 && unit < units.length - 1; ) { + size /= 1024; + unit++; + } + return `${size.toFixed(2)} ${units[unit]}`; + } +}; + +__publicField(_WangPan115MatchPlugin, "LoginStatus", { + UNCHECKED: -1, + LOGGED_OUT: 0, + LOGGED_IN: 1 +}); + +let WangPan115MatchPlugin = _WangPan115MatchPlugin; + + +export { WangPan115Plugin, WangPan115MatchPlugin }; \ No newline at end of file diff --git a/src/plugins/want-watched.js b/src/plugins/want-watched.js new file mode 100644 index 0000000..3b597e0 --- /dev/null +++ b/src/plugins/want-watched.js @@ -0,0 +1,90 @@ +import { BasePlugin } from '../core/base-plugin.js'; +import { __publicField, Status_FAVORITE, Status_HAS_DOWN } from '../core/constants.js'; + +class WantAndWatchedVideosPlugin extends BasePlugin { + constructor() { + super(...arguments); + __publicField(this, "type", null); + } + getName() { + return "WantAndWatchedVideosPlugin"; + } + async handle() { + if (window.location.href.includes("/want_watch_videos")) { + $("h3").append('导入至 JHS'); + $("#wantWatchBtn").on("click", (event => { + this.type = Status_FAVORITE; + this.importWantWatchVideos(event, "是否将 想看的影片 导入到 JHS-收藏?"); + })); + } + if (window.location.href.includes("/watched_videos")) { + $("h3").append('导入至 JHS'); + $("#wantWatchBtn").on("click", (event => { + this.type = Status_HAS_DOWN; + this.importWantWatchVideos(event, "是否将 看过的影片 导入到 JHS-已下载?"); + })); + } + } + importWantWatchVideos(event, title) { + utils.q(null, `${title}
    执行此功能前请记得备份数据`, (async () => { + let loadObj = loading(); + try { + await this.parseMovieList(); + } catch (e) { + console.error(e); + } finally { + loadObj.close(); + } + })); + } + async parseMovieList($dom) { + let movieList, nextPageLink; + if ($dom) { + movieList = $dom.find(this.getSelector().itemSelector); + nextPageLink = $dom.find(".pagination-next").attr("href"); + } else { + movieList = $(this.getSelector().itemSelector); + nextPageLink = $(".pagination-next").attr("href"); + } + for (const element of movieList) { + const item = $(element), href = item.find("a").attr("href"), carNum2 = item.find(".video-title strong").text().trim(), publishTime = item.find(".meta").text().trim(); + if (href && carNum2) try { + if (await storageManager.getCar(carNum2)) { + show.info(`${carNum2} 已存在, 跳过`); + continue; + } + await storageManager.saveCar({ + carNum: carNum2, + url: href, + names: null, + actionType: this.type, + publishTime: publishTime + }); + } catch (error) { + console.error(`保存失败 [${carNum2}]:`, error); + } + } + if (nextPageLink) { + show.info("发现下一页,正在解析:", nextPageLink); + await new Promise((resolve => setTimeout(resolve, 1e3))); + $.ajax({ + url: nextPageLink, + method: "GET", + success: html => { + const parser = new DOMParser, next$dom = $(parser.parseFromString(html, "text/html")); + this.parseMovieList(next$dom); + }, + error: function(err) { + console.error(err); + show.error("加载下一页失败:" + err.message); + } + }); + } else { + show.ok("导入结束!"); + window.refresh(); + } + } +} + + +export { WantAndWatchedVideosPlugin };