diff --git a/dist/jhs-enhance.user.js b/dist/jhs-enhance.user.js index f46dd5a..ba8266d 100644 --- a/dist/jhs-enhance.user.js +++ b/dist/jhs-enhance.user.js @@ -24,7 +24,7 @@ // @exclude https://*seejav*/forum/* // @exclude https://*seejav*/*actresses // @require https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js -// @require https://cdn.jsdelivr.net/npm/tabulator-tables@6.3.1/dist/js/tabulator.min.js +// @require https://cdn.jsdelivr.net/npm/tabulator-tables@6.5.2/dist/js/tabulator.min.js // @require https://cdn.jsdelivr.net/npm/layui-layer@1.0.9/dist/layer.min.js // @require https://cdn.jsdelivr.net/npm/blueimp-md5@2.19.0/js/md5.min.js // @require https://cdn.jsdelivr.net/npm/toastify-js@1.12.0/src/toastify.min.js @@ -73,12 +73,21 @@ // @run-at document-idle // ==/UserScript== -(function ($$1, localforage$1, Toastify$1, md5$1, tabulatorTables, Viewer$1, QRCode$1) { +(function ($$1, localforage$1, Toastify$1, md5$1, Viewer$1, QRCode$1) { 'use strict'; - var __defProp = Object.defineProperty, __typeError = (msg) => { + var __defProp = Object.defineProperty; + var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { + enumerable: true, + configurable: true, + writable: true, + value + }) : obj[key] = value; + var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); + var _a; + var __defProp2 = Object.defineProperty, __typeError = (msg) => { throw TypeError(msg); - }, __publicField = (obj, key, value) => ((obj2, key2, value2) => key2 in obj2 ? __defProp(obj2, key2, { + }, __publicField2 = (obj, key, value) => ((obj2, key2, value2) => key2 in obj2 ? __defProp2(obj2, key2, { enumerable: true, configurable: true, writable: true, @@ -506,6 +515,237 @@ z-index: 999998; } +`; + const subtitleModalCss = ` + `; function insertStyle(css) { if (css) if (css.includes("\n" ); insertStyle(mainCss); + insertStyle(subtitleModalCss); 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; @@ -695,24 +935,24 @@ 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({ + __publicField2(this, "car_list_key", "car_list"); + __publicField2(this, "filter_keyword_title_key", "filter_keyword_title"); + __publicField2(this, "filter_keyword_review_key", "filter_keyword_review"); + __publicField2(this, "setting_key", "setting"); + __publicField2(this, "blacklist_key", "blacklist"); + __publicField2(this, "blacklist_car_list_key", "blacklist_car_list"); + __publicField2(this, "favorite_actresses_key", "favorite_actresses"); + __publicField2(this, "highlighted_tags_key", "highlighted_tags"); + __publicField2(this, "forage", localforage.createInstance({ driver: localforage.INDEXEDDB, name: "JAV-JHS", version: 1, storeName: "appData" })); - __publicField(this, "cacheCarList", null); - __publicField(this, "cacheBlacklistCarList", null); - __publicField(this, "cacheFavoriteActressList", null); - __publicField(this, "cacheSettingObj", null); + __publicField2(this, "cacheCarList", null); + __publicField2(this, "cacheBlacklistCarList", null); + __publicField2(this, "cacheFavoriteActressList", null); + __publicField2(this, "cacheSettingObj", null); if (_StorageManager.instance) throw new Error("StorageManager已被实例化过了!"); _StorageManager.instance = this; } @@ -1273,8 +1513,8 @@ var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)(); class Utils { constructor() { - __publicField(this, "intervalContainer", {}); - __publicField(this, "mimeTypes", { + __publicField2(this, "intervalContainer", {}); + __publicField2(this, "mimeTypes", { txt: "text/plain", html: "text/html", css: "text/css", @@ -1303,14 +1543,14 @@ webm: "video/webm", ogg: "audio/ogg" }); - __publicField(this, "timers", /* @__PURE__ */ new Map()); - __publicField(this, "insertStyle", ((css) => { + __publicField2(this, "timers", /* @__PURE__ */ new Map()); + __publicField2(this, "insertStyle", ((css) => { if (css) { -1 === css.indexOf(""); $("head").append(css); } })); - __publicField(this, "layerIndexStack", []); + __publicField2(this, "layerIndexStack", []); Utils.instance || (Utils.instance = this); return Utils.instance; } @@ -1915,26 +2155,26 @@ ${value}\r } 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", ''); - __publicField(this, "markDeleteSvg", ''); + __publicField2(this, "pluginManager", null); + __publicField2(this, "settingSvg", ''); + __publicField2(this, "editSvg", ''); + __publicField2(this, "deleteSvg", ''); + __publicField2(this, "checkSvg", ''); + __publicField2(this, "actressSvg", ''); + __publicField2(this, "newSvg", ''); + __publicField2(this, "refreshSvg", ''); + __publicField2(this, "blacklistSvg", ''); + __publicField2(this, "removeSvg", ''); + __publicField2(this, "copySvg", ''); + __publicField2(this, "titleSvg", ''); + __publicField2(this, "carNumSvg", ''); + __publicField2(this, "downSvg", ''); + __publicField2(this, "handleSvg", ''); + __publicField2(this, "siteSvg", ''); + __publicField2(this, "videoSvg", ''); + __publicField2(this, "screenSvg", ''); + __publicField2(this, "recoveryVideoSvg", ''); + __publicField2(this, "markDeleteSvg", ''); } getName() { throw new Error(`${this.constructor.name} 未显示getName()`); @@ -2112,9 +2352,9 @@ ${value}\r class AutoPagePlugin extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "preloadDistance", 500); - __publicField(this, "currentPage", this.getInitialPageNumber()); - __publicField(this, "pageItems", []); + __publicField2(this, "preloadDistance", 500); + __publicField2(this, "currentPage", this.getInitialPageNumber()); + __publicField2(this, "pageItems", []); } getName() { return "AutoPagePlugin"; @@ -2543,8 +2783,8 @@ ${value}\r class SettingPlugin extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "folderName", "JHS-数据备份"); - __publicField(this, "cacheItems", [{ + __publicField2(this, "folderName", "JHS-数据备份"); + __publicField2(this, "cacheItems", [{ key: "jhs_dmm_video", text: "🎥 预览视频缓存", title: "预览视频缓存" @@ -4659,12 +4899,12 @@ ${value}\r class TaskPlugin extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "singleTaskKey", "checkNewActressActorFilterCar"); - __publicField(this, "taskConfig", null); - __publicField(this, "storageQueue", new AsyncQueue()); - __publicField(this, "lastCheckFavoriteActressTimeKey", "jhs_time_checkFavoriteActress"); - __publicField(this, "lastCheckBlacklistTimeKey", "jhs_time_checkBlacklist"); - __publicField(this, "lastCheckNewVideoTimeKey", "jhs_time_checkNewVideo"); + __publicField2(this, "singleTaskKey", "checkNewActressActorFilterCar"); + __publicField2(this, "taskConfig", null); + __publicField2(this, "storageQueue", new AsyncQueue()); + __publicField2(this, "lastCheckFavoriteActressTimeKey", "jhs_time_checkFavoriteActress"); + __publicField2(this, "lastCheckBlacklistTimeKey", "jhs_time_checkBlacklist"); + __publicField2(this, "lastCheckNewVideoTimeKey", "jhs_time_checkNewVideo"); } getName() { return "TaskPlugin"; @@ -5004,8 +5244,8 @@ ${value}\r class NewVideoPlugin extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "currentPage", 1); - __publicField(this, "pageSize", 30); + __publicField2(this, "currentPage", 1); + __publicField2(this, "pageSize", 30); } getName() { return "NewVideoPlugin"; @@ -5544,7 +5784,7 @@ ${value}\r class HistoryPlugin extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "tableObj", null); + __publicField2(this, "tableObj", null); } getName() { return "HistoryPlugin"; @@ -6287,15 +6527,15 @@ ${value}\r 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", /* @__PURE__ */ new Map()); - __publicField(_HotkeyManager, "handleKeydown", ((event) => { + __publicField2(_HotkeyManager, "isMac", 0 === navigator.platform.indexOf("Mac")); + __publicField2(_HotkeyManager, "registerHotKeyMap", /* @__PURE__ */ new Map()); + __publicField2(_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) => { + __publicField2(_HotkeyManager, "handleKeyup", ((event) => { for (const [id, data] of _HotkeyManager.registerHotKeyMap) { let hotkeyString = data.hotkeyString, keyupCallback = data.keyupCallback; keyupCallback && (_HotkeyManager.judgeHotkey(hotkeyString, event) && keyupCallback(event)); @@ -6406,6 +6646,497 @@ ${value}\r $secTitle.html(newHTML); } } + + window.JavPackSubtitle = (_a = class { + static escapeHtml(value = "") { + return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """); + } + + static sanitizeName(value = "") { + return String(value).replace(/[\\/:*?"<>|]/g, "").replace(/\s+/g, " ").trim(); + } + + static isSubtitleFile(file = {}) { + return /\.(srt|ass|ssa|vtt|sub)$/i.test(file.n || ""); + } + + static buildDefaultKeyword(details = {}) { + return this.sanitizeName(details.code || "") || this.sanitizeName([details.code, details.title].filter(Boolean).join(" ")) || document.title; + } + + static buildSearchKeywords(details = {}) { + return [this.buildDefaultKeyword(details)].filter(Boolean); + } + + static buildSubtitleBaseName(details = {}) { + var _a2; + if ((_a2 = window.JavPackMatch115Console) == null ? void 0 : _a2.buildRename) { + return window.JavPackMatch115Console.buildRename(details, [{n: `${details.code || "subtitle"}.mp4`}]); + } + return this.sanitizeName([details.code, details.title].filter(Boolean).join(" ")) || this.buildDefaultKeyword(details); + } + + static buildSubtitleFilename(details = {}, item = {}) { + const ext = this.sanitizeName(item.ext || "srt").replace(/^\.+/, "") || "srt"; + return `${this.buildSubtitleBaseName(details)}.${ext}`; + } + + static clearPreviewCache() { + this.previewCache.clear(); + } + + static async checkSubInCloud(req115, cid) { + if (!req115 || !cid) return false; + const res = await req115.filesAll(cid); + return ((res == null ? void 0 : res.data) || []).some((file) => this.isSubtitleFile(file)); + } + + static getReq115() { + return typeof Req115 !== "undefined" ? Req115 : window.Req115; + } + + static getGrant() { + return typeof Grant !== "undefined" ? Grant : window.Grant; + } + + static getTargetCid() { + var _a2, _b; + return ((_a2 = document.querySelector(".x-match-cont .zymatch-item [data-cid]")) == null ? void 0 : _a2.dataset.cid) || ((_b = document.querySelector(".x-match-cont .zymatch-item")) == null ? void 0 : _b.dataset.cid) || ""; + } + + static ensureDetailButton({details, getTargetCid, getSubtitleEntries} = {}) { + const panel = document.querySelector(".movie-panel-info"); + const buttons = panel == null ? void 0 : panel.querySelector(".panel-block:last-child .buttons"); + if (!buttons || document.getElementById(this.BTN_ID)) return; + const btn = document.createElement("button"); + btn.id = this.BTN_ID; + btn.type = "button"; + btn.className = "button is-small is-info x-subtitle-search"; + btn.textContent = "字幕搜索"; + const refreshSubtitleFileTip = () => { + const groups = /* @__PURE__ */ new Map(); + ((getSubtitleEntries == null ? void 0 : getSubtitleEntries()) || []).forEach(({ + directory, + files = [] + }) => { + const path = String(directory || "").trim(); + if (!path) return; + if (!groups.has(path)) groups.set(path, /* @__PURE__ */ new Set()); + files.map((file) => String((file == null ? void 0 : file.n) || (file == null ? void 0 : file.name) || file || "").trim()).filter(Boolean).forEach((name2) => groups.get(path).add(name2)); + }); + btn.title = groups.size ? ["已有字幕文件:", ...[...groups].flatMap(([path, files]) => [ + `目录:${path}`, + ...[...files].map((name2) => ` - ${name2}`) + ])].join("\n") : "暂无已匹配的字幕文件"; + btn.textContent = groups.size ? "已有字幕" : "字幕搜索"; + btn.classList.toggle("is-success", Boolean(groups.size)); + btn.classList.toggle("is-info", !groups.size); + }; + btn.addEventListener("mouseenter", refreshSubtitleFileTip); + btn.addEventListener("focus", refreshSubtitleFileTip); + btn.addEventListener("click", (e) => { + e.preventDefault(); + e.stopPropagation(); + this.openSearchModal({details, getTargetCid}); + }); + buttons.appendChild(btn); + refreshSubtitleFileTip(); + const matchRoot = document.querySelector(".x-match-cont"); + if (matchRoot) new MutationObserver(refreshSubtitleFileTip).observe(matchRoot, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ["data-has-subtitle", "data-subtitle-files"] + }); + } + + static modalTemplate(defaultKw) { + return ` +
+
+
+ 迅雷字幕检索: + + +
+ × +
+
+
+
+
+ 字幕内容预览暂无预览 +
+ +
+
+
+ `; + } + + static openSearchModal({details = {}, getTargetCid} = {}) { + var _a2; + const defaultKw = this.buildDefaultKeyword(details); + this.currentDetails = details; + (_a2 = document.getElementById(this.MODAL_ID)) == null ? void 0 : _a2.remove(); + const overlay = document.createElement("div"); + overlay.id = this.MODAL_ID; + overlay.className = "pdb-sub-overlay"; + overlay.innerHTML = this.modalTemplate(defaultKw); + document.body.appendChild(overlay); + const contentWrap = overlay.querySelector(".pdb-sub-content"); + const previewBox = overlay.querySelector(".pdb-sub-textarea"); + const statusNode = overlay.querySelector("#preview-status"); + const input = overlay.querySelector("#sub-search-input"); + const closeModal = () => { + this.clearPreviewCache(); + previewBox.value = ""; + overlay.remove(); + }; + overlay.querySelector("#sub-close-btn").addEventListener("click", closeModal); + overlay.addEventListener("click", (e) => { + if (e.target === overlay) closeModal(); + }); + const performSearch = async (kw) => { + if (!kw) return; + contentWrap.innerHTML = '
正在搜索字幕来源,请稍候...
'; + previewBox.value = ""; + statusNode.textContent = "暂无预览"; + const results = await Promise.allSettled([ + this.searchXunlei(kw), + this.searchSubtitleCat(kw) + ]); + const dataList = results.flatMap((result) => result.status === "fulfilled" ? result.value : []); + const chineseResults = this.sortResults(dataList, kw, details); + if (!chineseResults.length) { + contentWrap.innerHTML = '
未找到简中或繁中字幕,请尝试使用完整番号搜索
'; + return; + } + this.renderTable({ + container: contentWrap, + dataList: chineseResults, + previewBox, + statusNode, + overlay, + details, + getTargetCid, + kw + }); + }; + overlay.querySelector("#sub-search-btn").addEventListener("click", () => performSearch(input.value.trim())); + input.addEventListener("keypress", (e) => { + if (e.key === "Enter") performSearch(input.value.trim()); + }); + performSearch(defaultKw); + } + + static requestText(url) { + return new Promise((resolve, reject) => { + GM_xmlhttpRequest({ + method: "GET", + url, + onload: (res) => { + if (res.status >= 200 && res.status < 300) resolve(res.responseText || ""); + else reject(new Error(`HTTP ${res.status}`)); + }, + onerror: () => reject(new Error("网络请求失败")) + }); + }); + } + + static async searchXunlei(kw) { + const text = await this.requestText(`https://api-shoulei-ssl.xunlei.com/oracle/subtitle?name=${encodeURIComponent(kw)}`); + const root = JSON.parse(text); + if (root.code !== 0 || !Array.isArray(root.data)) return []; + const results = root.data.slice(0, 20).map((item) => this.normalizeXunleiResult(item)); + const labeledResults = await Promise.all(results.map((item) => this.labelXunleiChineseSubtitle(item))); + return labeledResults.filter(Boolean); + } + + static normalizeXunleiResult(item = {}) { + const rawLanguages = item.languages ?? item.Languages ?? item.langs ?? item.Langs ?? item.language ?? item.Language ?? []; + return { + ...item, + name: item.name || item.Name || item.extra_name || item.Title || "", + languages: Array.isArray(rawLanguages) ? rawLanguages : rawLanguages ? [String(rawLanguages)] : [], + ext: item.ext || item.Ext || item.format || item.Format || "srt", + url: item.url || item.Url || "", + provider: "迅雷" + }; + } + + static async labelXunleiChineseSubtitle(item) { + if (this.isWantedChineseSubtitle(item)) return item; + if (!item.url) return null; + try { + const language = this.detectChineseSubtitleLanguage(this.decodeSubtitle(await this.fetchBinaryCached(item.url))); + return language ? {...item, languages: [language]} : null; + } catch (_) { + return null; + } + } + + static detectChineseSubtitleLanguage(text = "") { + const sample = String(text).slice(0, 12e3); + if (!/[\u3400-\u9fff]/.test(sample)) return ""; + const simplifiedCount = (sample.match(/[为与个么开关东风头后发这边过还进时样气应实话说问题现经体万台农国门书车云]/g) || []).length; + const traditionalCount = (sample.match(/[為與個麼開關東風頭後發這邊過還進時樣氣應實話說問題現經體萬臺農國門書車雲]/g) || []).length; + if (simplifiedCount === 0 && traditionalCount === 0) return ""; + return traditionalCount > simplifiedCount ? "繁中" : "简中"; + } + + static async searchSubtitleCat(kw) { + const searchUrl = `${this.SUBTITLECAT_ORIGIN}/index.php?search=${encodeURIComponent(kw)}`; + const searchHtml = await this.requestText(searchUrl); + const entries = this.parseSubtitleCatSearch(searchHtml).slice(0, 20); + const languageGroups = await Promise.all(entries.map(async (entry) => { + try { + return this.parseSubtitleCatLanguages(await this.requestText(entry.detailUrl), entry); + } catch (_) { + return []; + } + })); + return languageGroups.flat(); + } + + static parseSubtitleCatSearch(html) { + const doc = new DOMParser().parseFromString(html, "text/html"); + return [...doc.querySelectorAll("table.sub-table tbody tr")].flatMap((row) => { + var _a2; + const link = row.querySelector('a[href*="subs/"]'); + const name2 = (_a2 = link == null ? void 0 : link.textContent) == null ? void 0 : _a2.trim(); + if (!link || !name2) return []; + return [{ + name: name2, + detailUrl: new URL(link.getAttribute("href"), this.SUBTITLECAT_ORIGIN).href + }]; + }); + } + + static parseSubtitleCatLanguages(html, entry) { + const doc = new DOMParser().parseFromString(html, "text/html"); + return [...doc.querySelectorAll(".sub-single")].flatMap((node) => { + var _a2; + const languageCode = ((_a2 = node.querySelector("img.flag")) == null ? void 0 : _a2.getAttribute("alt")) || ""; + const language = this.SUBTITLECAT_CHINESE_LANGUAGES.get(languageCode); + const link = node.querySelector("a.green-link[href$='.srt']"); + if (!language || !link) return []; + return [{ + name: entry.name, + languageCode, + languages: [language], + ext: "srt", + provider: "SubtitleCat", + url: new URL(link.getAttribute("href"), this.SUBTITLECAT_ORIGIN).href + }]; + }); + } + + static isWantedChineseSubtitle(item = {}) { + if (item.provider === "SubtitleCat") return this.SUBTITLECAT_CHINESE_LANGUAGES.has(item.languageCode); + const language = Array.isArray(item.languages) ? item.languages.join(" ") : String(item.languages || ""); + const name2 = `${item.name || ""} ${item.extra_name || ""}`; + return /(zh[-_]?cn|zh[-_]?tw|chs|cht|chinese\s*(simplified|traditional)|简体|繁体|繁體|简中|繁中)/i.test(`${language} ${name2}`); + } + + static sortResults(dataList, kw = "", details = {}) { + const kwClean = kw.toLowerCase().replace(/[-_.\s]/g, ""); + const tokens = kw.toLowerCase().split(/[-_.\s]+/).filter((word) => word.length > 1); + const codeClean = String(details.code || kw).toLowerCase().replace(/[-_.\s]/g, ""); + return [...dataList].filter((item) => this.isWantedChineseSubtitle(item)).sort((a, b) => this.scoreResult(b, kwClean, tokens, codeClean) - this.scoreResult(a, kwClean, tokens, codeClean)); + } + + static scoreResult(item, kwClean, tokens, codeClean = "") { + const name2 = (item.name || item.extra_name || "").toLowerCase(); + const compactName = name2.replace(/[-_.\s]/g, ""); + const lang = (item.languages && item.languages[0] || "").toLowerCase(); + let score = 0; + if (codeClean && compactName.includes(codeClean)) score += 800; + tokens.forEach((token) => { + if (name2.includes(token)) score += 50; + }); + if (kwClean && compactName.includes(kwClean)) score += 500; + if (/(zh|cn|chs|cht|chinese|简中|繁中)/i.test(lang) || /(zh|cn|chs|cht|chinese|简中|繁中)/i.test(name2)) score += 100; + if (item.ext === "srt" || item.ext === "ass") score += 20; + return score; + } + + static renderTable({container, dataList, previewBox, statusNode, overlay, details, getTargetCid, kw = ""}) { + const words = kw.split(/[-_.\s]+/).filter((word) => word.length > 1); + const highlightRegex = words.length ? new RegExp(`(${words.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})`, "gi") : null; + container.innerHTML = ` + + + + + + + + + + + + ${dataList.map((item, index) => this.renderRow(item, index, highlightRegex)).join("")} + +
来源原始字幕名称语言格式操作
+ `; + container.querySelectorAll(".sub-action-btn").forEach((btn) => { + btn.addEventListener("click", () => this.handleAction({ + btn, + item: dataList[btn.dataset.idx], + previewBox, + statusNode, + overlay, + details, + getTargetCid + })); + }); + } + + static renderRow(item, index, highlightRegex) { + var _a2; + const lang = ((_a2 = item.languages) == null ? void 0 : _a2[0]) || "未知"; + const name2 = item.name || item.extra_name || "未知字幕"; + const provider = item.provider || "迅雷"; + const providerClass = provider === "SubtitleCat" ? "pdb-sub-provider--subtitlecat" : "pdb-sub-provider--xunlei"; + const displayName = highlightRegex ? this.escapeHtml(name2).replace(highlightRegex, '$1') : this.escapeHtml(name2); + return ` + + ${this.escapeHtml(provider)} + ${displayName} + ${this.escapeHtml(lang)} + ${this.escapeHtml(item.ext || "srt")} + + + + + + + `; + } + + static async handleAction({btn, item, previewBox, statusNode, overlay, details, getTargetCid}) { + var _a2, _b, _c; + const action = btn.dataset.action; + const oldText = btn.textContent; + const url = item.url; + if (!url) return alert("无效的字幕下载直链"); + const filename = this.buildSubtitleFilename(details, item); + btn.textContent = "获取中..."; + btn.style.opacity = "0.6"; + try { + const buffer = await this.fetchBinaryCached(url); + const text = this.decodeSubtitle(buffer); + if (text.includes("NoSuchKey")) throw new Error("该字幕在迅雷云端已失效丢失 (NoSuchKey)"); + if (action === "preview") { + previewBox.value = text; + statusNode.textContent = filename; + this.setActivePreviewRow(overlay, btn); + } else if (action === "download") { + this.downloadBuffer(buffer, filename); + } else if (action === "upload") { + const cid = (getTargetCid == null ? void 0 : getTargetCid()) || this.getTargetCid(); + const req115 = this.getReq115(); + if (!cid || !req115) throw new Error("未检测到 115 目标目录,请先等待匹配完成"); + btn.textContent = "直传中..."; + await this.uploadSubtitle({req115, cid, filename, buffer}); + (_b = (_a2 = this.getGrant()) == null ? void 0 : _a2.notify) == null ? void 0 : _b.call(_a2, { + icon: "success", + msg: "字幕已上传到 115" + }); + (_c = document.getElementById(this.BTN_ID)) == null ? void 0 : _c.classList.add("is-success"); + document.getElementById(this.BTN_ID).textContent = "已有字幕"; + window.dispatchEvent(new CustomEvent("JavDB_SubtitleUploaded", { + detail: { + code: details.code || "", + cid: String(cid), + subtitle: {n: filename, s: buffer.byteLength} + } + })); + overlay.remove(); + this.clearPreviewCache(); + } + } catch (err) { + alert(`执行中止: ${err.message}`); + } finally { + btn.textContent = oldText; + btn.style.opacity = "1"; + } + } + + static setActivePreviewRow(overlay, btn) { + overlay.querySelectorAll(".pdb-sub-tr.is-previewing").forEach((row2) => { + row2.classList.remove("is-previewing"); + row2.removeAttribute("aria-current"); + }); + const row = btn.closest(".pdb-sub-tr"); + if (!row) return; + row.classList.add("is-previewing"); + row.setAttribute("aria-current", "true"); + } + + static decodeSubtitle(buffer) { + let text = new TextDecoder("utf-8").decode(buffer); + if ((text.match(/\uFFFD/g) || []).length > 3) text = new TextDecoder("gbk").decode(buffer); + return text; + } + + static fetchBinaryCached(url) { + if (this.previewCache.has(url)) return Promise.resolve(this.previewCache.get(url)); + return this.fetchBinary(url).then((buffer) => { + this.previewCache.set(url, buffer); + return buffer; + }); + } + + static fetchBinary(url) { + return new Promise((resolve, reject) => { + GM_xmlhttpRequest({ + method: "GET", + url, + responseType: "arraybuffer", + onload: (res) => { + if (res.status === 200 && res.response) resolve(res.response); + else reject(new Error(`字幕流获取失败,HTTP_CODE: ${res.status}`)); + }, + onerror: () => reject(new Error("跨域网络请求被阻断")) + }); + }); + } + + static downloadBuffer(buffer, filename) { + const blob = new Blob([buffer], {type: "application/octet-stream"}); + const link = document.createElement("a"); + link.href = URL.createObjectURL(blob); + link.download = filename; + link.click(); + URL.revokeObjectURL(link.href); + } + + static uploadSubtitle(args) { + return args.req115.queueMutation("上传字幕", () => this.uploadSubtitleNow(args)); + } + + static async uploadSubtitleNow({req115, cid, filename, buffer}) { + const blob = new Blob([buffer], {type: "application/octet-stream"}); + const file = new File([blob], filename, {type: "application/octet-stream"}); + const initRes = await req115.sampleInitUpload({filename, filesize: file.size, cid}); + if (!initRes || !initRes.host && initRes.status !== 2 && initRes.statuscode !== 0) { + throw new Error((initRes == null ? void 0 : initRes.error_msg) || "获取115上传安全凭证被拦截"); + } + if (!initRes.host) return initRes; + let uploadRes = null; + for (let retry = 0; retry < 3; retry++) { + uploadRes = await req115.upload({...initRes, filename, file}); + if (uploadRes && uploadRes.state !== false) return uploadRes; + await new Promise((resolve) => setTimeout(resolve, 1500)); + } + throw new Error((uploadRes == null ? void 0 : uploadRes.error_msg) || (uploadRes == null ? void 0 : uploadRes.error) || "115 服务器拒绝接收回调"); + } + }, __publicField(_a, "BTN_ID", "x-subtitle-search-btn"), __publicField(_a, "MODAL_ID", "x-subtitle-modal"), __publicField(_a, "previewCache", /* @__PURE__ */ new Map()), __publicField(_a, "SUBTITLECAT_ORIGIN", "https://www.subtitlecat.com"), __publicField(_a, "SUBTITLECAT_CHINESE_LANGUAGES", /* @__PURE__ */ new Map([ + ["zh-CN", "简中"], + ["zh-TW", "繁中"] + ])), _a); class Fc2Plugin extends BasePlugin { getName() { return "Fc2Plugin"; @@ -6437,7 +7168,7 @@ ${value}\r 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 ', + content: '\n
\n \n \n
\n
加载中...
\n
\n \n
第三方资源:
\n \n
\n 🚫 屏蔽\n ⭐ 收藏\n 📥️ 已下载\n 🔍 已观看\n \n \n 字幕搜索\n \n \n \n 磁力搜索\n \n
\n
\n \n
\n
\n
\n \n \n \n
\n ', area: utils.getResponsiveArea(["70%", "90%"]), skin: "movie-detail-layer", scrollbar: false, @@ -6494,8 +7225,9 @@ ${value}\r 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))); + $("#subtitleSearchBtn").on("click", (() => { + window.JavPackSubtitle.openSearchModal({details: {code: carNum2}}); + })); $("#magnetSearchBtn").on("click", (() => { let magnetHub = this.getBean("MagnetHubPlugin").createMagnetHub(carNum2); layer.open({ @@ -6817,7 +7549,7 @@ ${value}\r class ActressInfoPlugin extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "apiUrl", "https://ja.wikipedia.org/wiki/"); + __publicField2(this, "apiUrl", "https://ja.wikipedia.org/wiki/"); } getName() { return "ActressInfoPlugin"; @@ -6951,7 +7683,7 @@ ${value}\r class HitShowPlugin extends BasePlugin { constructor() { super(); - __publicField(this, "$contentBox", $(".section .container")); + __publicField2(this, "$contentBox", $(".section .container")); } getName() { return "HitShowPlugin"; @@ -7053,9 +7785,9 @@ ${err.stack}` : ""); class TOP250Plugin extends BasePlugin { constructor() { super(); - __publicField(this, "has_cnsub", ""); - __publicField(this, "$contentBox", $(".section .container")); - __publicField(this, "movies", []); + __publicField2(this, "has_cnsub", ""); + __publicField2(this, "$contentBox", $(".section .container")); + __publicField2(this, "movies", []); } getName() { return "TOP250Plugin"; @@ -7571,7 +8303,7 @@ ${err.stack}` : ""); class ImageRecognitionPlugin extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "siteList", [{ + __publicField2(this, "siteList", [{ name: "Google旧版", url: "https://www.google.com/searchbyimage?image_url={占位符}&client=firefox-b-d", ico: "https://www.google.com/favicon.ico" @@ -7584,7 +8316,7 @@ ${err.stack}` : ""); url: "https://yandex.ru/images/search?rpt=imageview&url={占位符}", ico: "https://yandex.ru/favicon.ico" }]); - __publicField(this, "isUploading", false); + __publicField2(this, "isUploading", false); } getName() { return "ImageRecognitionPlugin"; @@ -7778,8 +8510,8 @@ ${err.stack}` : ""); class RelatedPlugin extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "floorIndex", 1); - __publicField(this, "isInit", false); + __publicField2(this, "floorIndex", 1); + __publicField2(this, "isInit", false); } getName() { return "RelatedPlugin"; @@ -7886,7 +8618,7 @@ ${err.stack}` : ""); class WantAndWatchedVideosPlugin extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "type", null); + __publicField2(this, "type", null); } getName() { return "WantAndWatchedVideosPlugin"; @@ -7970,8 +8702,8 @@ ${err.stack}` : ""); class MagnetHubPlugin extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "currentEngine", null); - __publicField(this, "searchEngines", [{ + __publicField2(this, "currentEngine", null); + __publicField2(this, "searchEngines", [{ name: "U9A9", id: "u9a9", url: "https://u9a9.com/?type=2&search={keyword}", @@ -8519,8 +9251,8 @@ ${err.stack}` : ""); class WangPan115Plugin extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "JHS_115_COOKIE", "jhs_115_cookie"); - __publicField(this, "JHS_115_MAX_AGE", "jhs_115_max_age"); + __publicField2(this, "JHS_115_COOKIE", "jhs_115_cookie"); + __publicField2(this, "JHS_115_MAX_AGE", "jhs_115_max_age"); } getName() { return "WangPan115Plugin"; @@ -8774,7 +9506,7 @@ ${err.stack}` : ""); const _WangPan115MatchPlugin = class _WangPan115MatchPlugin2 extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "loginStatus", _WangPan115MatchPlugin2.LoginStatus.UNCHECKED); + __publicField2(this, "loginStatus", _WangPan115MatchPlugin2.LoginStatus.UNCHECKED); } getName() { return "WangPan115MatchPlugin"; @@ -9217,7 +9949,7 @@ ${err.stack}` : ""); return `${size.toFixed(2)} ${units[unit]}`; } }; - __publicField(_WangPan115MatchPlugin, "LoginStatus", { + __publicField2(_WangPan115MatchPlugin, "LoginStatus", { UNCHECKED: -1, LOGGED_OUT: 0, LOGGED_IN: 1 @@ -9771,12 +10503,12 @@ ${err.stack}` : ""); 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", [{ + __publicField2(this, "okBackgroundColor", "#7bc73b"); + __publicField2(this, "errorBackgroundColor", "#de3333"); + __publicField2(this, "warnBackgroundColor", "#d7a80c"); + __publicField2(this, "domainErrorBackgroundColor", "#d7780c"); + __publicField2(this, "asyncQueue", new AsyncQueue()); + __publicField2(this, "siteConfigs", [{ id: "javTrailersBtn", getBaseUrl: async () => await this.getJavTrailersUrl(), itemSelector: ".videos-list .video-link", @@ -9840,9 +10572,9 @@ ${err.stack}` : ""); 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); + __publicField2(this, "settingCache", null); + __publicField2(this, "lastFetchTime", 0); + __publicField2(this, "CACHE_DURATION", 1e4); } getName() { return "OtherSitePlugin"; @@ -10058,8 +10790,8 @@ ${err.stack}` : ""); class ReviewPlugin extends BasePlugin { constructor() { super(...arguments); - __publicField(this, "floorIndex", 1); - __publicField(this, "isInit", false); + __publicField2(this, "floorIndex", 1); + __publicField2(this, "isInit", false); } getName() { return "ReviewPlugin"; @@ -10731,12 +11463,12 @@ ${err.stack}` : ""); 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); + __publicField2(this, "$contentBox", $(".section .container")); + __publicField2(this, "urlParams", new URLSearchParams(window.location.search)); + __publicField2(this, "sortVal", this.urlParams.get("sort") || "release_date"); + __publicField2(this, "currentPage", this.urlParams.get("page") ? parseInt(this.urlParams.get("page")) : 1); + __publicField2(this, "maxPage", null); + __publicField2(this, "keyword", this.urlParams.get("keyword") || null); } getName() { return "Fc2By123AvPlugin"; @@ -10916,12 +11648,17 @@ ${err.stack}` : ""); 📥️ 已下载 🔍 已观看 + + 字幕搜索 + +