diff --git a/dist/jhs-enhance.user.js b/dist/jhs-enhance.user.js index fb9a072..819d0c8 100644 --- a/dist/jhs-enhance.user.js +++ b/dist/jhs-enhance.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name JHS-enhance // @namespace JHS-enhance -// @version 3.3.11 +// @version 3.3.13 // @author xiebro,fireinrain // @description Jav-鉴黄师 增强脚本。列表页:作品状态标签、一键筛选、新作品检测、演员黑名单过滤;详情页:磁力链接高亮、DMM 多画质预览视频、标题翻译、整合字幕搜索(迅雷+SubTitleCat)并支持 115 直传、多源预览图(javfree/projectjav/javstore)含来源切换;数据:115 网盘目录匹配与多目录选择、云盘备份/恢复、跨 Tab 同步;其他:自动翻页、分类折叠、Top250、以图识图、热门榜单、评论查看、相关清单。支持 JavDB / JavBus / JavSee / SeeJav / FC2 / JavTrailers // @license MIT @@ -284,6 +284,16 @@ System.register("./__entry.js", ['jquery', 'localforage', 'toastify-js', 'blueim quality: "mmb", text: "中画质 (432p)", canSelect: true + }, { + id: "video-dm", + quality: "dm", + text: "低画质 (1000kbps)", + canSelect: true + }, { + id: "video-dmb", + quality: "dmb", + text: "中画质 (1500kbps)", + canSelect: true }, { id: "video-mhb", quality: "mhb", @@ -10267,191 +10277,56 @@ ${err.stack}` : ""); videoEl._hls = null; } }; - const JAVXY_TOKEN = [118, 119, 112, 71, 97, 110, 28, 84, 124, 65, 76, 102, 65, 16, 77, 109, 64, 82, 85, 83, 67, 92, 125, 108, 83, 65, 124, 107, 84, 104, 71, 84, 17, 124, 118, 125, 104, 8, 125, 96, 112, 103, 29, 18, 82, 83, 87, 84].map((v) => String.fromCharCode(v ^ 37)).join(""); - const JAVXY_ENDPOINTS = [ - {host: String.fromCharCode(106, 97, 118, 120, 121, 46, 99, 99, 46, 99, 100), label: "Javxy"}, - { - host: String.fromCharCode(119, 111, 114, 107, 101, 114, 46, 106, 97, 118, 120, 121, 46, 99, 99, 46, 99, 100), - label: "Javxy Worker" - } - ]; - const JAVXY_SOURCE_LABELS = { - "Tokyo-Hot": "Javxy | Tokyo-Hot", - FC2: "Javxy | FC2", - Direct: "Javxy | Direct", - DMM: "Javxy | dmm", - MGStage: "Javxy | MGStage", - DUGA: "Javxy | DUGA", - MYWIFE: "Javxy | MyWife", - JavTrailers: "Javxy | JavTrailers", - JavDB: "Javxy | Javdb", - AVWikiDB: "Javxy | AVWikiDB", - JAVDatabase: "Javxy | JAVDatabase", - HEYZO: "Javxy | Heyzo", - HeyDouga: "Javxy | HeyDouga", - PACO: "Javxy | Paco", - "10MU": "Javxy | 10mu", - Caribbean: "Javxy | 加勒比", - "1Pondo": "Javxy | 一本道" - }; - const JAVXY_QUALITY_OPTIONS = [ - {quality: "4k", text: "4K"}, - {quality: "hhb", text: "1080p"}, - {quality: "1080p", text: "1080p"}, - {quality: "hmb", text: "720p"}, - {quality: "720p", text: "720p"}, - {quality: "mhb", text: "576p"}, - {quality: "540p", text: "540p"}, - {quality: "mmb", text: "432p"}, - {quality: "480p", text: "480p"}, - {quality: "396p", text: "396p"}, - {quality: "360p", text: "360p"}, - {quality: "240p", text: "240p"} - ]; - const selectHighestQuality = (qualityMap) => { - const rank = new Map(JAVXY_QUALITY_OPTIONS.map((item, index) => [item.quality, index])); - return Object.keys(qualityMap || {}).filter((key) => qualityMap[key]).sort((a, b) => (rank.get(a) ?? -1) - (rank.get(b) ?? -1))[0] || null; - }; - const sortQualityKeys = (qualityMap) => { - const rank = new Map(JAVXY_QUALITY_OPTIONS.map((item, index) => [item.quality, index])); - return Object.keys(qualityMap || {}).filter((key) => qualityMap[key]).sort((a, b) => (rank.get(a) ?? -1) - (rank.get(b) ?? -1)); - }; - const normalizeJavxySource = (value) => { - const raw = String(value || "").trim().toLowerCase(); - if (raw.includes("fc2")) return "FC2"; - if (raw.includes("mgstage")) return "MGStage"; - if (raw.includes("heydouga")) return "Direct"; - if (raw.includes("mywife")) return "MYWIFE"; - if (raw.includes("duga")) return "DUGA"; - if (raw.includes("javtrailers")) return "JavTrailers"; - if (raw.includes("javdb")) return "JavDB"; - if (raw.includes("avwikidb")) return "AVWikiDB"; - if (raw.includes("javdatabase")) return "JAVDatabase"; - if (raw.includes("dmm")) return "DMM"; - if (raw === "direct" || raw.includes("heyzo") || raw.includes("heydouga") || raw.includes("paco") || raw.includes("10musume") || raw.includes("10mu") || raw.includes("1pondo") || raw.includes("caribbean") || raw.includes("tokyo-hot") || raw.includes("tokyohot")) return "Direct"; - return String(value || "").trim(); - }; - const javxyQualityMapToDmmFormat = (javxyQualityMap) => { - const result = {}; - const qualityKeys = qualityOptions.map((o) => o.quality); - for (const [key, url] of Object.entries(javxyQualityMap || {})) { - if (!url) continue; - const lowerKey = key.toLowerCase(); - if (qualityKeys.includes(key)) { - result[key] = url; - } else if (qualityKeys.includes(lowerKey)) { - result[lowerKey] = url; - } - } - return result; - }; - const fromJavxyCcCd = async (id, rawCode = "", options = {}) => { - const query = String(id || rawCode || "").trim(); - if (!query) { - clog.debug("Javxy 跳过:查询词为空"); - return null; - } - for (const endpoint of JAVXY_ENDPOINTS) { - const params = new URLSearchParams({client: "laosiji-new"}); - if (Array.isArray(options.skip) && options.skip.length) params.set("skip", options.skip.join(",")); - if (Array.isArray(options.prefer) && options.prefer.length) params.set("prefer", options.prefer.join(",")); - if (Array.isArray(options.source) && options.source.length) params.set("source", options.source.join(",")); - if (options.playbackFallback) params.set("purpose", "playback-fallback"); - const apiUrl2 = `https://${endpoint.host}/trailers/${encodeURIComponent(query)}?${params}`; - clog.debug("Javxy 请求 API", {query, apiUrl: apiUrl2, endpoint: endpoint.label}); - let r; - try { - r = await gmHttp.get(apiUrl2, null, { - "Accept": "application/json,text/plain,*/*", - "X-Javxy-Token": JAVXY_TOKEN - }, {timeout: 8e3}); - } catch (e) { - clog.debug("Javxy API 网络失败,尝试下一个节点", {endpoint: endpoint.label, error: e.message}); - continue; - } - if (!r) { - clog.debug("Javxy API 无响应,尝试下一个节点", {endpoint: endpoint.label}); - continue; - } - const trailerUrl = String((r == null ? void 0 : r.trailer) || "").trim(); - if (!trailerUrl) { - clog.debug("Javxy 无 trailer 字段", {endpoint: endpoint.label, keys: Object.keys(r || {})}); - return null; - } - const qualityMap = (r == null ? void 0 : r.qualities) && typeof r.qualities === "object" ? r.qualities : {}; - const quality = (r == null ? void 0 : r.quality) && qualityMap[r.quality] ? r.quality : selectHighestQuality(qualityMap); - const sourceBase = JAVXY_SOURCE_LABELS[r == null ? void 0 : r.source] || `Javxy | ${(r == null ? void 0 : r.source) || "dmm"}`; - const directUrl = qualityMap[quality] || trailerUrl; - clog.debug("Javxy 返回结果", { - endpoint: endpoint.label, - source: r == null ? void 0 : r.source, - quality, - qualities: Object.keys(qualityMap), - url: directUrl - }); - const dmmQualityMap = javxyQualityMapToDmmFormat(qualityMap); - return { - url: directUrl, - source: sourceBase, - type: String((r == null ? void 0 : r.type) || "").trim() || "video", - qualities: qualityMap, - quality, - directUrl, - code: id, - rawCode, - javxySource: String((r == null ? void 0 : r.source) || "").trim(), - requiresJP: Boolean(r == null ? void 0 : r.requiresJP), - urls: Array.isArray(r == null ? void 0 : r.urls) && r.urls.length ? r.urls : sortQualityKeys(qualityMap).map((key) => qualityMap[key]), - dmmQualityMap - }; - } - return null; - }; - const fallbackJavxyResult = async (code, rawCode = "", failedSources = [], options = {}) => { - const skip = [...new Set((failedSources || []).map((source2) => normalizeJavxySource(source2)).filter(Boolean))]; - const source = [...new Set((options.source || []).map((s) => String(s || "").trim()).filter(Boolean))]; - if (!skip.length && !source.length) return null; - clog.debug("Javxy 播放失败回落查询", {code, skip, source}); - return fromJavxyCcCd(code, rawCode, {skip, source, playbackFallback: true}); - }; - const getJavxyVideoUrls = async (code, failedSources = []) => { - const result = await fallbackJavxyResult(code, code, failedSources, { - source: ["JavTrailers", "JavDB"] - }); - if ((result == null ? void 0 : result.dmmQualityMap) && Object.keys(result.dmmQualityMap).length > 0) { - return result.dmmQualityMap; - } - if (result == null ? void 0 : result.url) { - const qualityMap = {}; - qualityMap["720p"] = result.url; - return qualityMap; - } - return null; - }; - const getJavxyCover = async (code) => { + const COVER_API_URL = [77, 81, 81, 85, 86, 31, 10, 10, 68, 85, 76, 8, 65, 72, 72, 87, 86, 70, 11, 20, 22, 20, 17, 22, 22, 11, 93, 92, 95, 10, 68, 85, 76, 10, 70, 74, 83, 64, 87].map((v) => String.fromCharCode(v ^ 37)).join(""); + const TRAILER_API_URL = [77, 81, 81, 85, 86, 31, 10, 10, 68, 85, 76, 8, 65, 72, 72, 87, 86, 70, 11, 20, 22, 20, 17, 22, 22, 11, 93, 92, 95, 10, 68, 85, 76, 10, 81, 87, 68, 76, 73, 64, 87].map((v) => String.fromCharCode(v ^ 37)).join(""); + const BASE_URL = [77, 81, 81, 85, 86, 31, 10, 10, 68, 85, 76, 8, 65, 72, 72, 87, 86, 70, 11, 20, 22, 20, 17, 22, 22, 11, 93, 92, 95].map((v) => String.fromCharCode(v ^ 37)).join(""); + const COVER_API_TOKEN = [18, 28, 19, 19, 65, 65, 70, 28, 64, 29, 23, 21, 17, 29, 67, 70, 18, 64, 21, 22, 64, 16, 65, 29, 22, 19, 16, 28, 16, 17, 64, 29].map((v) => String.fromCharCode(v ^ 37)).join(""); + const getCover = async (code) => { + var _a; const query = String(code || "").trim(); if (!query) return null; - for (const endpoint of JAVXY_ENDPOINTS) { - const params = new URLSearchParams({client: "laosiji-new"}); - const apiUrl2 = `https://${endpoint.host}/covers/${encodeURIComponent(query)}?${params}`; - let r; - try { - r = await gmHttp.get(apiUrl2, null, { - "Accept": "application/json,text/plain,*/*", - "X-Javxy-Token": JAVXY_TOKEN - }, {timeout: 15e3}); - } catch (e) { - continue; - } - if (!r) continue; - if ((r == null ? void 0 : r.found) && (r.url || r.cover || r.highCover)) return r; + try { + const r = await gmHttp.get(`${COVER_API_URL}/${encodeURIComponent(query)}`, null, { + "Authorization": `Bearer ${COVER_API_TOKEN}` + }, {timeout: 15e3}); + if ((_a = r == null ? void 0 : r.cover) == null ? void 0 : _a.large) { + return r.cover.large; + } + return null; + } catch (e) { + clog.error("获取封面失败:", e.message); return null; } - return null; }; - class JavxyPreviewVideoPlugin extends BasePlugin { + const getTrailer = async (code) => { + const query = String(code || "").trim(); + if (!query) return null; + try { + const r = await gmHttp.get(`${TRAILER_API_URL}/${encodeURIComponent(query)}`, null, { + "Authorization": `Bearer ${COVER_API_TOKEN}` + }, {timeout: 15e3}); + if ((r == null ? void 0 : r.trailers) && Array.isArray(r.trailers) && r.trailers.length > 0) { + const qualityMap = {}; + r.trailers.forEach((item) => { + if (item.proxy && item.quality) { + qualityMap[item.quality] = `${BASE_URL}${item.proxy}`; + } + }); + if (Object.keys(qualityMap).length > 0) { + clog.log("预告片播放地址:", JSON.stringify(qualityMap)); + return qualityMap; + } + } + return null; + } catch (e) { + clog.error("获取预告片失败:", e.message); + return null; + } + }; + + class JavopenPreviewVideoPlugin extends BasePlugin { getName() { - return "JavxyPreviewVideoPlugin"; + return "JavopenPreviewVideoPlugin"; } _attachVideoSrc(videoEl, src) { if (!videoEl || !src) return; @@ -10461,20 +10336,13 @@ ${err.stack}` : ""); if (HlsClass) { attachHlsToVideo(videoEl, src).catch((err) => { clog.error("视频预览 HLS 加载失败:", err); - videoEl.src = src; - videoEl.load && videoEl.load(); }); } else { loadHlsLibrary().then((HlsClass2) => { if (videoEl && videoEl.isConnected && HlsClass2) { attachHlsToVideo(videoEl, src).catch((err) => { clog.error("视频预览 HLS 延迟加载失败:", err); - videoEl.src = src; - videoEl.load && videoEl.load(); }); - } else if (videoEl && videoEl.isConnected) { - videoEl.src = src; - videoEl.load && videoEl.load(); } }); } @@ -10493,38 +10361,38 @@ ${err.stack}` : ""); } async initCss() { return ` - .jhs-javxy-video-modal { + .jhs-javopen-video-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.95); z-index: 12345700; display: flex; justify-content: center; align-items: center; opacity: 0; visibility: hidden; transition: opacity 0.2s; } - .jhs-javxy-video-modal.is-open { opacity: 1; visibility: visible; } - .jhs-javxy-video-modal-inner { + .jhs-javopen-video-modal.is-open { opacity: 1; visibility: visible; } + .jhs-javopen-video-modal-inner { display: flex; flex-direction: column; align-items: center; gap: 12px; max-width: 90vw; max-height: 90vh; } - .jhs-javxy-video-wrapper { + .jhs-javopen-video-wrapper { width: 80vw; max-height: 80vh; aspect-ratio: 16/9; background: #000; position: relative; } - .jhs-javxy-video-wrapper video { + .jhs-javopen-video-wrapper video { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } - .jhs-javxy-quality-bar { + .jhs-javopen-quality-bar { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; } - .jhs-javxy-quality-btn { + .jhs-javopen-quality-btn { min-width: 60px; padding: 5px 10px; font-size: 13px; background: rgba(255,255,255,0.2); color: #fff; border: 1px solid rgba(255,255,255,0.5); border-radius: 4px; cursor: pointer; transition: background 0.2s; } - .jhs-javxy-quality-btn:hover { background: rgba(255,255,255,0.4); } - .jhs-javxy-quality-btn.active { + .jhs-javopen-quality-btn:hover { background: rgba(255,255,255,0.4); } + .jhs-javopen-quality-btn.active { background: #1890ff; border-color: #096dd9; font-weight: bold; } - .jhs-javxy-loading { + .jhs-javopen-loading { color: #fff; font-size: 16px; text-align: center; } `; @@ -10533,9 +10401,10 @@ ${err.stack}` : ""); if (!window.isDetailPage) return; const carNum2 = this.getPageInfo().carNum; if (!carNum2) return; - this._addJavxyButton(carNum2); + this._addJavopenButton(carNum2); } - _addJavxyButton(carNum2) { + + _addJavopenButton(carNum2) { let $target = null; if (isJavDb$1) { $target = $('a[title="複製番號"]'); @@ -10563,30 +10432,31 @@ ${err.stack}` : ""); e.stopPropagation(); $btn.css("pointer-events", "none").find("span").text("..."); try { - await this._openJavxyPlayer(carNum2); + await this._openJavopenPlayer(carNum2); } finally { $btn.css("pointer-events", "").find("span").text("视频预览"); } }); $target.after($btn); } - async _openJavxyPlayer(carNum2) { - if ($("#jhs-javxy-video-modal").length === 0) { + + async _openJavopenPlayer(carNum2) { + if ($("#jhs-javopen-video-modal").length === 0) { $("body").append(` -
-
-
- +
+
+
+
-
+
`); - const $modal2 = $("#jhs-javxy-video-modal"); + const $modal2 = $("#jhs-javopen-video-modal"); $modal2.on("click", (e) => { - if (e.target.id === "jhs-javxy-video-modal") { + if (e.target.id === "jhs-javopen-video-modal") { $modal2.removeClass("is-open"); - const videoEl2 = document.getElementById("jhs-javxy-video"); + const videoEl2 = document.getElementById("jhs-javopen-video"); if (videoEl2) { destroyHls(videoEl2); videoEl2.pause(); @@ -10596,7 +10466,7 @@ ${err.stack}` : ""); $(document).on("keydown", (e) => { if (e.key === "Escape" && $modal2.hasClass("is-open")) { $modal2.removeClass("is-open"); - const videoEl2 = document.getElementById("jhs-javxy-video"); + const videoEl2 = document.getElementById("jhs-javopen-video"); if (videoEl2) { destroyHls(videoEl2); videoEl2.pause(); @@ -10604,30 +10474,30 @@ ${err.stack}` : ""); } }); } - const $modal = $("#jhs-javxy-video-modal"); - const $wrapper = $modal.find(".jhs-javxy-video-wrapper"); - const $qualityBar = $modal.find(".jhs-javxy-quality-bar"); - $wrapper.html('
视频预览加载中...
'); + const $modal = $("#jhs-javopen-video-modal"); + const $wrapper = $modal.find(".jhs-javopen-video-wrapper"); + const $qualityBar = $modal.find(".jhs-javopen-quality-bar"); + $wrapper.html('
视频预览加载中...
'); $qualityBar.empty(); $modal.addClass("is-open"); let videoMap = null; try { - videoMap = await getJavxyVideoUrls(carNum2); + videoMap = await getTrailer(carNum2); } catch (err) { - $wrapper.html(`
视频预览请求失败: ${err.message}
`); + $wrapper.html(`
视频预览请求失败: ${err.message}
`); return; } if (!videoMap || Object.keys(videoMap).length === 0) { - $wrapper.html('
未找到可用的视频源
'); + $wrapper.html('
未找到可用的视频源
'); return; } const qualityList = Object.keys(videoMap); const defaultQuality = this._selectDefaultQuality(qualityList, "720p"); const defaultUrl = videoMap[defaultQuality]; - $wrapper.html(`