+ async loadMarkDeleteList() {
+ const markedList = await storageManager.forage.getItem("markedDeleteList") || [];
+ const $list = $("#markDeleteList");
+ const $count = $("#markDeleteCount");
+ $count.text(`共 ${markedList.length} 个作品`);
+ if (markedList.length === 0) {
+ $list.html('
');
+ return;
+ }
+ let html = "";
+ for (let i = 0; i < markedList.length; i++) {
+ const item = markedList[i];
+ const truncatedTitle = item.title && item.title.length > 30 ? item.title.substring(0, 30) + "..." : item.title;
+ const detailUrl = item.url ? item.url.startsWith("http") ? item.url : window.location.origin + item.url : "#";
+ html += `
`;
- $list.html(html);
- $("#markDeleteSelectAll").prop("checked", true);
}
+ html += `
`;
+ $list.html(html);
+ $("#markDeleteSelectAll").prop("checked", true);
+ }
addLabelTag(containerId, keyword) {
const $tagBox = $(`${containerId} .tag-box`);
let $label, color = "#333";
@@ -5606,12 +5606,12 @@ ${value}\r
class TaskPlugin extends BasePlugin {
constructor() {
super(...arguments);
- __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");
+ __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";
@@ -5951,8 +5951,8 @@ ${value}\r
class NewVideoPlugin extends BasePlugin {
constructor() {
super(...arguments);
- __publicField2(this, "currentPage", 1);
- __publicField2(this, "pageSize", 30);
+ __publicField2(this, "currentPage", 1);
+ __publicField2(this, "pageSize", 30);
}
getName() {
return "NewVideoPlugin";
@@ -6491,7 +6491,7 @@ ${value}\r
class HistoryPlugin extends BasePlugin {
constructor() {
super(...arguments);
- __publicField2(this, "tableObj", null);
+ __publicField2(this, "tableObj", null);
}
getName() {
return "HistoryPlugin";
@@ -7105,42 +7105,42 @@ ${value}\r
if (!$videoEl.length) return;
const $videoContainer = $videoEl.parent();
$videoContainer.css("position", "relative");
- const videoEl = $videoEl[0];
- let carNum2 = this.getPageInfo().carNum;
- const jhs_videoMuted = localStorage.getItem("jhs_videoMuted");
- if (jhs_videoMuted) videoEl.muted = "yes" === jhs_videoMuted;
+ const videoEl = $videoEl[0];
+ let carNum2 = this.getPageInfo().carNum;
+ const jhs_videoMuted = localStorage.getItem("jhs_videoMuted");
+ if (jhs_videoMuted) videoEl.muted = "yes" === jhs_videoMuted;
videoEl.addEventListener("volumechange", (function() {
localStorage.setItem("jhs_videoMuted", videoEl.muted ? "yes" : "no");
}));
- const CACHE_KEY2 = "jhs_dmm_video";
- let dmmErrorRetryCount = 0;
- const attachVideoErrorHandler = () => {
- videoEl.addEventListener("error", async function onVideoError(e) {
- if (dmmErrorRetryCount >= 2) return;
- dmmErrorRetryCount++;
- clog.warn(`预览视频加载失败(第${dmmErrorRetryCount}次),清除缓存后重试...`);
- const cachedData = localStorage.getItem(CACHE_KEY2) ? JSON.parse(localStorage.getItem(CACHE_KEY2)) : {};
- delete cachedData[carNum2];
- localStorage.setItem(CACHE_KEY2, JSON.stringify(cachedData));
- const freshMap = await getDmmVideo(carNum2);
- if (!freshMap) {
- show.error("预览视频获取失败,请稍后重试");
- return;
- }
- const storedQuality = await storageManager.getSetting("videoQuality");
- const quality = selectDefaultQuality(Object.keys(freshMap), storedQuality);
- const freshUrl = freshMap[quality];
- const savedTime = videoEl.currentTime;
- $videoEl.attr("src", freshUrl);
- videoEl.load();
- if (savedTime > 0 && Number.isFinite(savedTime)) {
- videoEl.currentTime = savedTime;
- }
- videoEl.play();
- }, {once: false});
- };
- attachVideoErrorHandler();
- videoEl.play();
+ const CACHE_KEY2 = "jhs_dmm_video";
+ let dmmErrorRetryCount = 0;
+ const attachVideoErrorHandler = () => {
+ videoEl.addEventListener("error", async function onVideoError(e) {
+ if (dmmErrorRetryCount >= 2) return;
+ dmmErrorRetryCount++;
+ clog.warn(`预览视频加载失败(第${dmmErrorRetryCount}次),清除缓存后重试...`);
+ const cachedData = localStorage.getItem(CACHE_KEY2) ? JSON.parse(localStorage.getItem(CACHE_KEY2)) : {};
+ delete cachedData[carNum2];
+ localStorage.setItem(CACHE_KEY2, JSON.stringify(cachedData));
+ const freshMap = await getDmmVideo(carNum2);
+ if (!freshMap) {
+ show.error("预览视频获取失败,请稍后重试");
+ return;
+ }
+ const storedQuality = await storageManager.getSetting("videoQuality");
+ const quality = selectDefaultQuality(Object.keys(freshMap), storedQuality);
+ const freshUrl = freshMap[quality];
+ const savedTime = videoEl.currentTime;
+ $videoEl.attr("src", freshUrl);
+ videoEl.load();
+ if (savedTime > 0 && Number.isFinite(savedTime)) {
+ videoEl.currentTime = savedTime;
+ }
+ videoEl.play();
+ }, {once: false});
+ };
+ attachVideoErrorHandler();
+ videoEl.play();
const dmmVideoMap = await getDmmVideo(carNum2);
let $bottomToolbar = $("
").attr("id", "video-bottom-toolbar").css({
display: "flex",
@@ -7198,9 +7198,9 @@ ${value}\r
const currentTime = videoEl.currentTime;
$videoEl.attr("src", videoSrc);
videoEl.load();
- if (currentTime > 0 && Number.isFinite(currentTime)) {
- videoEl.currentTime = currentTime;
- }
+ if (currentTime > 0 && Number.isFinite(currentTime)) {
+ videoEl.currentTime = currentTime;
+ }
await videoEl.play();
$bottomToolbar.find(".video-control-btn").removeClass("active").css({
"background-color": "#fff",
@@ -7265,15 +7265,15 @@ ${value}\r
return (this.isMac ? event.metaKey : event.ctrlKey) === ctrl && event.shiftKey === shift && event.altKey === alt && event.key.toLowerCase() === key;
}
};
- __publicField2(_HotkeyManager, "isMac", 0 === navigator.platform.indexOf("Mac"));
- __publicField2(_HotkeyManager, "registerHotKeyMap", /* @__PURE__ */ new Map());
- __publicField2(_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);
}
}));
- __publicField2(_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));
@@ -7385,154 +7385,151 @@ ${value}\r
}
}
- class JavPackSubtitle {
- static escapeHtml(value = "") {
- return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
- }
+ class JavPackSubtitle {
+ static escapeHtml(value = "") {
+ return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
+ }
- static sanitizeName(value = "") {
- return String(value).replace(/[\\/:*?"<>|]/g, "").replace(/\s+/g, " ").trim();
- }
+ 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 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 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 buildSearchKeywords(details = {}) {
+ return [this.buildDefaultKeyword(details)].filter(Boolean);
+ }
- static buildSubtitleBaseName(details = {}) {
- var _a;
- if ((_a = window.JavPackMatch115Console) == null ? void 0 : _a.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 buildSubtitleBaseName(details = {}) {
+ var _a;
+ if ((_a = window.JavPackMatch115Console) == null ? void 0 : _a.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 buildSubtitleFilename(details = {}, item = {}) {
+ const ext = this.sanitizeName(item.ext || "srt").replace(/^\.+/, "") || "srt";
+ return `${this.buildSubtitleBaseName(details)}.${ext}`;
+ }
- static clearPreviewCache() {
- this.previewCache.clear();
- }
+ 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 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 window.Req115 || null;
- }
+ static getReq115() {
+ return window.Req115 || null;
+ }
- static getTargetCid() {
- var _a, _b;
- return ((_a = document.querySelector(".x-match-cont .zymatch-item [data-cid]")) == null ? void 0 : _a.dataset.cid) || ((_b = document.querySelector(".x-match-cont .zymatch-item")) == null ? void 0 : _b.dataset.cid) || "";
- }
+ static getTargetCid() {
+ var _a, _b;
+ return ((_a = document.querySelector(".x-match-cont .zymatch-item [data-cid]")) == null ? void 0 : _a.dataset.cid) || ((_b = document.querySelector(".x-match-cont .zymatch-item")) == null ? void 0 : _b.dataset.cid) || "";
+ }
- static async find115Cid(code) {
- if (!code) return "";
- const req115 = this.getReq115();
- if (!req115) return "";
- try {
- const {data} = await req115.filesSearchAllFolders(code);
- if (!(data == null ? void 0 : data.length)) return "";
- const exact = data.find((f) => f.n === code);
- if (exact) return exact.cid;
- const upper = data.find((f) => f.n.toUpperCase() === code.toUpperCase());
- if (upper) return upper.cid;
- const contains = data.find((f) => f.n.toUpperCase().includes(code.toUpperCase()));
- return (contains == null ? void 0 : contains.cid) || "";
- } catch (e) {
- console.warn("[JavPackSubtitle] 115搜索目录失败:", e.message);
- return "";
- }
- }
+ static async find115Cid(code) {
+ if (!code) return "";
+ const req115 = this.getReq115();
+ if (!req115) return "";
+ try {
+ const {data} = await req115.filesSearchAllFolders(code);
+ if (!(data == null ? void 0 : data.length)) return "";
+ const exact = data.find((f) => f.n === code);
+ if (exact) return exact.cid;
+ const upper = data.find((f) => f.n.toUpperCase() === code.toUpperCase());
+ if (upper) return upper.cid;
+ const contains = data.find((f) => f.n.toUpperCase().includes(code.toUpperCase()));
+ return (contains == null ? void 0 : contains.cid) || "";
+ } catch (e) {
+ console.warn("[JavPackSubtitle] 115搜索目录失败:", e.message);
+ return "";
+ }
+ }
- static async preload115Matches(code) {
- this.cached115Matches = [];
- this.current115Cids = [];
- if (!code) return;
- const req115 = this.getReq115();
- if (!req115) return;
- try {
- const {data} = await req115.filesSearchAllFolders(code);
- if (!(data == null ? void 0 : data.length)) return;
- const codeUpper = code.toUpperCase();
- const matches = data.filter((f) => f.n.toUpperCase().includes(codeUpper)).map((f) => ({
- cid: f.cid,
- name: f.n
- })).sort((a, b) => {
- const aExact = a.name.toUpperCase() === codeUpper;
- const bExact = b.name.toUpperCase() === codeUpper;
- if (aExact && !bExact) return -1;
- if (!aExact && bExact) return 1;
- return a.name.localeCompare(b.name);
- });
- this.cached115Matches = matches;
- this.current115Cids = matches.length === 1 ? [matches[0].cid] : matches.map((m) => m.cid);
- this.last115Code = code;
- } catch (e) {
- console.warn("[JavPackSubtitle] 115预加载目录失败:", e.message);
- }
- }
+ static async preload115Matches(code) {
+ this.cached115Matches = [];
+ this.current115Cids = [];
+ if (!code) return;
+ const req115 = this.getReq115();
+ if (!req115) return;
+ try {
+ const {data} = await req115.filesSearchAllFolders(code);
+ if (!(data == null ? void 0 : data.length)) return;
+ const codeUpper = code.toUpperCase();
+ const matches = data.filter((f) => f.n.toUpperCase().includes(codeUpper)).map((f) => ({
+ cid: f.cid,
+ name: f.n
+ })).sort((a, b) => {
+ const aExact = a.name.toUpperCase() === codeUpper;
+ const bExact = b.name.toUpperCase() === codeUpper;
+ if (aExact && !bExact) return -1;
+ if (!aExact && bExact) return 1;
+ return a.name.localeCompare(b.name);
+ });
+ this.cached115Matches = matches;
+ this.current115Cids = matches.length === 1 ? [matches[0].cid] : matches.map((m) => m.cid);
+ this.last115Code = code;
+ } catch (e) {
+ console.warn("[JavPackSubtitle] 115预加载目录失败:", e.message);
+ }
+ }
- 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 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 modalTemplate(defaultKw) {
+ return `
`;
- }
+ }
- static openSearchModal({details = {}, getTargetCid} = {}) {
- var _a;
- const defaultKw = this.buildDefaultKeyword(details);
- this.currentDetails = details;
- (_a = document.getElementById(this.MODAL_ID)) == null ? void 0 : _a.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 dirWrap = overlay.querySelector("#sub-115-wrap");
- const dirSelect = overlay.querySelector("#sub-115-select");
- const refresh115DirUI = () => {
- if (!this.cached115Matches.length) {
- dirWrap.style.display = "none";
- return;
- }
- dirWrap.style.display = "";
- dirSelect.innerHTML = this.cached115Matches.map((m) => {
- const checked = this.current115Cids.includes(m.cid) ? " checked" : "";
- const displayName = m.name.length > 26 ? m.name.slice(0, 26) + "..." : m.name;
- return `
`;
- }).join("");
- };
- dirSelect.addEventListener("change", () => {
- this.current115Cids = Array.from(dirSelect.querySelectorAll("input:checked")).map((cb) => cb.value);
- });
- if (this.last115Code === details.code && this.cached115Matches.length) {
- refresh115DirUI();
- } else {
- this.preload115Matches(details.code).then(refresh115DirUI);
- }
- 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 = "暂无预览";
- try {
- 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
- });
- if (kw.toUpperCase() !== (details.code || "").toUpperCase()) {
- this.preload115Matches(kw).then(refresh115DirUI);
- }
- } catch (e) {
- console.error("字幕搜索失败:", e);
- contentWrap.innerHTML = '
搜索失败,请稍后重试
';
- }
- };
- 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 openSearchModal({details = {}, getTargetCid} = {}) {
+ var _a;
+ const defaultKw = this.buildDefaultKeyword(details);
+ this.currentDetails = details;
+ (_a = document.getElementById(this.MODAL_ID)) == null ? void 0 : _a.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 dirWrap = overlay.querySelector("#sub-115-wrap");
+ const dirSelect = overlay.querySelector("#sub-115-select");
+ const refresh115DirUI = () => {
+ if (!this.cached115Matches.length) {
+ dirWrap.style.display = "none";
+ return;
}
-
- static requestText(url) {
- return window.gmHttp.get(url);
+ dirWrap.style.display = "";
+ dirSelect.innerHTML = this.cached115Matches.map((m) => {
+ const checked = this.current115Cids.includes(m.cid) ? " checked" : "";
+ const displayName = m.name.length > 26 ? m.name.slice(0, 26) + "..." : m.name;
+ return `
`;
+ }).join("");
+ };
+ dirSelect.addEventListener("change", () => {
+ this.current115Cids = Array.from(dirSelect.querySelectorAll("input:checked")).map((cb) => cb.value);
+ });
+ if (this.last115Code === details.code && this.cached115Matches.length) {
+ refresh115DirUI();
+ } else {
+ this.preload115Matches(details.code).then(refresh115DirUI);
+ }
+ 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 = "暂无预览";
+ try {
+ 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
+ });
+ if (kw.toUpperCase() !== (details.code || "").toUpperCase()) {
+ this.preload115Matches(kw).then(refresh115DirUI);
+ }
+ } catch (e) {
+ console.error("字幕搜索失败:", e);
+ contentWrap.innerHTML = '
搜索失败,请稍后重试
';
}
+ };
+ 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 async searchXunlei(kw) {
- const root = await this.requestText(`https://api-shoulei-ssl.xunlei.com/oracle/subtitle?name=${encodeURIComponent(kw)}`);
- if (!root || 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 requestText(url) {
+ return window.gmHttp.get(url);
+ }
+
+ static async searchXunlei(kw) {
+ const root = await this.requestText(`https://api-shoulei-ssl.xunlei.com/oracle/subtitle?name=${encodeURIComponent(kw)}`);
+ if (!root || 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 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 parseSubtitleCatSearch(html) {
+ const doc = new DOMParser().parseFromString(html, "text/html");
+ return [...doc.querySelectorAll("table.sub-table tbody tr")].flatMap((row) => {
+ var _a;
+ const link = row.querySelector('a[href*="subs/"]');
+ const name2 = (_a = link == null ? void 0 : link.textContent) == null ? void 0 : _a.trim();
+ if (!link || !name2) return [];
+ return [{
+ name: name2,
+ detailUrl: new URL(link.getAttribute("href"), this.SUBTITLECAT_ORIGIN).href
+ }];
+ });
+ }
- 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 parseSubtitleCatLanguages(html, entry) {
+ const doc = new DOMParser().parseFromString(html, "text/html");
+ return [...doc.querySelectorAll(".sub-single")].flatMap((node) => {
+ var _a;
+ const languageCode = ((_a = node.querySelector("img.flag")) == null ? void 0 : _a.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 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 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 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 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 parseSubtitleCatSearch(html) {
- const doc = new DOMParser().parseFromString(html, "text/html");
- return [...doc.querySelectorAll("table.sub-table tbody tr")].flatMap((row) => {
- var _a;
- const link = row.querySelector('a[href*="subs/"]');
- const name2 = (_a = link == null ? void 0 : link.textContent) == null ? void 0 : _a.trim();
- if (!link || !name2) return [];
- return [{
- name: name2,
- detailUrl: new URL(link.getAttribute("href"), this.SUBTITLECAT_ORIGIN).href
- }];
- });
- }
+ 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 parseSubtitleCatLanguages(html, entry) {
- const doc = new DOMParser().parseFromString(html, "text/html");
- return [...doc.querySelectorAll(".sub-single")].flatMap((node) => {
- var _a;
- const languageCode = ((_a = node.querySelector("img.flag")) == null ? void 0 : _a.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 = `
+ 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 = `
@@ -7784,27 +7781,27 @@ ${value}\r
`;
- container.querySelectorAll(".sub-action-btn").forEach((btn) => {
- btn.addEventListener("click", () => this.handleAction({
- btn,
- item: dataList[btn.dataset.idx],
- previewBox,
- statusNode,
- overlay,
- details,
- getTargetCid
- }));
- });
- }
+ 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 _a;
- const lang = ((_a = item.languages) == null ? void 0 : _a[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 `
+ static renderRow(item, index, highlightRegex) {
+ var _a;
+ const lang = ((_a = item.languages) == null ? void 0 : _a[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} |
@@ -7817,614 +7814,614 @@ ${value}\r
`;
- }
-
- static async handleAction({btn, item, previewBox, statusNode, overlay, details, getTargetCid}) {
- 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 jdbCid = (getTargetCid == null ? void 0 : getTargetCid()) || this.getTargetCid();
- let cids = jdbCid ? [jdbCid] : [...this.current115Cids];
- const req115 = this.getReq115();
- if (!cids.length || !req115) throw new Error("未在 115 找到匹配目录,请确认作品已下载到 115");
- btn.textContent = "直传中...";
- for (const cid of cids) {
- await this.uploadSubtitle({req115, cid, filename, buffer});
- }
- show.ok(`字幕已上传到 115(${cids.length}个目录)`);
- const detailBtn = document.getElementById(this.BTN_ID);
- if (detailBtn) {
- detailBtn.classList.add("is-success");
- detailBtn.textContent = "已有字幕";
- }
- window.dispatchEvent(new CustomEvent("JavDB_SubtitleUploaded", {
- detail: {
- code: details.code || "",
- cid: String(cids[0]),
- 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(JavPackSubtitle, "BTN_ID", "x-subtitle-search-btn");
- __publicField(JavPackSubtitle, "MODAL_ID", "x-subtitle-modal");
- __publicField(JavPackSubtitle, "previewCache", /* @__PURE__ */ new Map());
- __publicField(JavPackSubtitle, "cached115Matches", []);
- __publicField(JavPackSubtitle, "current115Cids", []);
- __publicField(JavPackSubtitle, "last115Code", "");
- __publicField(JavPackSubtitle, "SUBTITLECAT_ORIGIN", "https://www.subtitlecat.com");
- __publicField(JavPackSubtitle, "SUBTITLECAT_CHINESE_LANGUAGES", /* @__PURE__ */ new Map([
- ["zh-CN", "简中"],
- ["zh-TW", "繁中"]
- ]));
- window.JavPackSubtitle = JavPackSubtitle;
-
- class Req {
- static _serializeToFormData(formData, data, parentKey = "") {
- Object.entries(data).forEach(([key, val]) => {
- const fullKey = parentKey ? `${parentKey}[${key}]` : key;
- if (val === void 0) return;
- if (val !== null && typeof val === "object" && !(val instanceof Blob || val instanceof Date)) {
- this._serializeToFormData(formData, val, fullKey);
- } else {
- formData.append(fullKey, val instanceof Date ? val.toISOString() : val);
- }
- });
- }
-
- static request(config) {
- let options;
- if (typeof config === "string") {
- options = {url: config};
- } else if (config !== null && typeof config === "object") {
- options = {...config};
- } else {
- return Promise.reject(new TypeError("Request config must be a string or an object"));
- }
- if (!options.url) return Promise.reject(new Error("Request URL is required"));
- options.method = (options.method || this.DEFAULT_METHOD).toUpperCase();
- options.timeout || (options.timeout = this.DEFAULT_TIMEOUT);
- const {params, method, data, signal} = options;
- try {
- const urlInstance = new URL(options.url, location.origin);
- if (params) {
- const cleanParams = this._isPlainObject(params) ? Object.fromEntries(Object.entries(params).filter(([_, v]) => v != null)) : params;
- new URLSearchParams(cleanParams).forEach((v, k) => urlInstance.searchParams.set(k, v));
- }
- options.url = urlInstance.toString();
- } catch {
- return Promise.reject(new Error(`Invalid Request URL: ${options.url}`));
- }
- if (method === "POST") {
- options.responseType ?? (options.responseType = this.DEFAULT_POST_RESPONSE_TYPE);
- if (this._isPlainObject(data) && !this._isRawBody(data)) {
- const formData = new FormData();
- this._serializeToFormData(formData, data);
- options.data = formData;
- }
- } else if (method === "GET") {
- options.responseType ?? (options.responseType = options.url.split(/[?#]/)[0].endsWith(".json") ? "json" : this.DEFAULT_GET_RESPONSE_TYPE);
- }
- return new Promise((resolve, reject) => {
- if (signal == null ? void 0 : signal.aborted) return reject(new DOMException("Aborted", "AbortError"));
- const {url: targetUrl} = options;
- let xhrHandler;
- const handleAbort = () => xhrHandler == null ? void 0 : xhrHandler.abort();
- const finalize = (settleFn, ...args) => {
- signal == null ? void 0 : signal.removeEventListener("abort", handleAbort);
- settleFn(...args);
- };
- xhrHandler = _GM_xmlhttpRequest({
- onabort: () => reject(new DOMException("Aborted", "AbortError")),
- ontimeout: () => finalize(reject, new Error(`Timeout: ${targetUrl}`)),
- onerror: () => finalize(reject, new Error(`Network Error: ${targetUrl}`)),
- onload: ({
- status,
- finalUrl,
- response
- }) => status >= 400 ? finalize(reject, new Error(`HTTP ${status}: ${targetUrl}`)) : finalize(resolve, method === "HEAD" ? finalUrl : response),
- ...options,
- params: void 0
- });
- signal == null ? void 0 : signal.addEventListener("abort", handleAbort, {once: true});
- });
- }
-
- static async tasks(initial, steps, signal) {
- let ctx = initial;
- for (const step of steps) {
- if (signal == null ? void 0 : signal.aborted) throw new DOMException("Aborted", "AbortError");
- if (typeof ctx === "string" || (ctx == null ? void 0 : ctx.url)) ctx = await this.request(typeof ctx === "string" ? {
- url: ctx,
- signal
- } : {...ctx, signal});
- ctx = await step(ctx);
- }
- return ctx;
+ static async handleAction({btn, item, previewBox, statusNode, overlay, details, getTargetCid}) {
+ 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 jdbCid = (getTargetCid == null ? void 0 : getTargetCid()) || this.getTargetCid();
+ let cids = jdbCid ? [jdbCid] : [...this.current115Cids];
+ const req115 = this.getReq115();
+ if (!cids.length || !req115) throw new Error("未在 115 找到匹配目录,请确认作品已下载到 115");
+ btn.textContent = "直传中...";
+ for (const cid of cids) {
+ await this.uploadSubtitle({req115, cid, filename, buffer});
+ }
+ show.ok(`字幕已上传到 115(${cids.length}个目录)`);
+ const detailBtn = document.getElementById(this.BTN_ID);
+ if (detailBtn) {
+ detailBtn.classList.add("is-success");
+ detailBtn.textContent = "已有字幕";
+ }
+ window.dispatchEvent(new CustomEvent("JavDB_SubtitleUploaded", {
+ detail: {
+ code: details.code || "",
+ cid: String(cids[0]),
+ subtitle: {n: filename, s: buffer.byteLength}
+ }
+ }));
+ overlay.remove();
+ this.clearPreviewCache();
}
+ } catch (err) {
+ alert(`执行中止: ${err.message}`);
+ } finally {
+ btn.textContent = oldText;
+ btn.style.opacity = "1";
+ }
}
- __publicField(Req, "DEFAULT_GET_RESPONSE_TYPE", "document");
- __publicField(Req, "DEFAULT_POST_RESPONSE_TYPE", "json");
- __publicField(Req, "DEFAULT_TIMEOUT", 3e4);
- __publicField(Req, "DEFAULT_METHOD", "GET");
- __publicField(Req, "_isRawBody", (v) => v instanceof FormData || v instanceof Blob || v instanceof ArrayBuffer || v instanceof URLSearchParams);
- __publicField(Req, "_isPlainObject", (v) => Object.prototype.toString.call(v) === "[object Object]");
+ 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");
+ }
- class Drive115 extends Req {
- static files(cid = "0", params = {}) {
- return this.request({
- url: "https://webapi.115.com/files",
- params: {cid, ...params}
- });
+ 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(JavPackSubtitle, "BTN_ID", "x-subtitle-search-btn");
+ __publicField(JavPackSubtitle, "MODAL_ID", "x-subtitle-modal");
+ __publicField(JavPackSubtitle, "previewCache", /* @__PURE__ */ new Map());
+ __publicField(JavPackSubtitle, "cached115Matches", []);
+ __publicField(JavPackSubtitle, "current115Cids", []);
+ __publicField(JavPackSubtitle, "last115Code", "");
+ __publicField(JavPackSubtitle, "SUBTITLECAT_ORIGIN", "https://www.subtitlecat.com");
+ __publicField(JavPackSubtitle, "SUBTITLECAT_CHINESE_LANGUAGES", /* @__PURE__ */ new Map([
+ ["zh-CN", "简中"],
+ ["zh-TW", "繁中"]
+ ]));
+ window.JavPackSubtitle = JavPackSubtitle;
+
+ class Req {
+ static _serializeToFormData(formData, data, parentKey = "") {
+ Object.entries(data).forEach(([key, val]) => {
+ const fullKey = parentKey ? `${parentKey}[${key}]` : key;
+ if (val === void 0) return;
+ if (val !== null && typeof val === "object" && !(val instanceof Blob || val instanceof Date)) {
+ this._serializeToFormData(formData, val, fullKey);
+ } else {
+ formData.append(fullKey, val instanceof Date ? val.toISOString() : val);
}
+ });
+ }
- static filesSearch(search_value, params = {}) {
- return this.request({
- url: "https://webapi.115.com/files/search",
- params: {search_value, ...params}
- });
+ static request(config) {
+ let options;
+ if (typeof config === "string") {
+ options = {url: config};
+ } else if (config !== null && typeof config === "object") {
+ options = {...config};
+ } else {
+ return Promise.reject(new TypeError("Request config must be a string or an object"));
+ }
+ if (!options.url) return Promise.reject(new Error("Request URL is required"));
+ options.method = (options.method || this.DEFAULT_METHOD).toUpperCase();
+ options.timeout || (options.timeout = this.DEFAULT_TIMEOUT);
+ const {params, method, data, signal} = options;
+ try {
+ const urlInstance = new URL(options.url, location.origin);
+ if (params) {
+ const cleanParams = this._isPlainObject(params) ? Object.fromEntries(Object.entries(params).filter(([_, v]) => v != null)) : params;
+ new URLSearchParams(cleanParams).forEach((v, k) => urlInstance.searchParams.set(k, v));
}
-
- static lixianTaskLists(page = 1) {
- return this.request({
- url: "https://115.com/web/lixian/",
- params: {ct: "lixian", ac: "task_lists", page}
- });
+ options.url = urlInstance.toString();
+ } catch {
+ return Promise.reject(new Error(`Invalid Request URL: ${options.url}`));
+ }
+ if (method === "POST") {
+ options.responseType ?? (options.responseType = this.DEFAULT_POST_RESPONSE_TYPE);
+ if (this._isPlainObject(data) && !this._isRawBody(data)) {
+ const formData = new FormData();
+ this._serializeToFormData(formData, data);
+ options.data = formData;
}
+ } else if (method === "GET") {
+ options.responseType ?? (options.responseType = options.url.split(/[?#]/)[0].endsWith(".json") ? "json" : this.DEFAULT_GET_RESPONSE_TYPE);
+ }
+ return new Promise((resolve, reject) => {
+ if (signal == null ? void 0 : signal.aborted) return reject(new DOMException("Aborted", "AbortError"));
+ const {url: targetUrl} = options;
+ let xhrHandler;
+ const handleAbort = () => xhrHandler == null ? void 0 : xhrHandler.abort();
+ const finalize = (settleFn, ...args) => {
+ signal == null ? void 0 : signal.removeEventListener("abort", handleAbort);
+ settleFn(...args);
+ };
+ xhrHandler = _GM_xmlhttpRequest({
+ onabort: () => reject(new DOMException("Aborted", "AbortError")),
+ ontimeout: () => finalize(reject, new Error(`Timeout: ${targetUrl}`)),
+ onerror: () => finalize(reject, new Error(`Network Error: ${targetUrl}`)),
+ onload: ({
+ status,
+ finalUrl,
+ response
+ }) => status >= 400 ? finalize(reject, new Error(`HTTP ${status}: ${targetUrl}`)) : finalize(resolve, method === "HEAD" ? finalUrl : response),
+ ...options,
+ params: void 0
+ });
+ signal == null ? void 0 : signal.addEventListener("abort", handleAbort, {once: true});
+ });
+ }
- static labelList() {
- return this.request({
- url: "https://webapi.115.com/label/list",
- params: {keyword: "", limit: 11150}
- });
- }
+ static async tasks(initial, steps, signal) {
+ let ctx = initial;
+ for (const step of steps) {
+ if (signal == null ? void 0 : signal.aborted) throw new DOMException("Aborted", "AbortError");
+ if (typeof ctx === "string" || (ctx == null ? void 0 : ctx.url)) ctx = await this.request(typeof ctx === "string" ? {
+ url: ctx,
+ signal
+ } : {...ctx, signal});
+ ctx = await step(ctx);
+ }
+ return ctx;
+ }
+ }
- /**
- * @connect 115vod.com
- */
- static filesVideo(pickcode) {
- return this.request({
- url: "https://115vod.com/webapi/files/video",
- params: {pickcode, local: 1}
- });
- }
+ __publicField(Req, "DEFAULT_GET_RESPONSE_TYPE", "document");
+ __publicField(Req, "DEFAULT_POST_RESPONSE_TYPE", "json");
+ __publicField(Req, "DEFAULT_TIMEOUT", 3e4);
+ __publicField(Req, "DEFAULT_METHOD", "GET");
+ __publicField(Req, "_isRawBody", (v) => v instanceof FormData || v instanceof Blob || v instanceof ArrayBuffer || v instanceof URLSearchParams);
+ __publicField(Req, "_isPlainObject", (v) => Object.prototype.toString.call(v) === "[object Object]");
- static post(details) {
- return this.request({method: "POST", ...details});
- }
+ class Drive115 extends Req {
+ static files(cid = "0", params = {}) {
+ return this.request({
+ url: "https://webapi.115.com/files",
+ params: {cid, ...params}
+ });
+ }
- static filesAdd(cname, pid) {
- return this.post({
- url: "https://webapi.115.com/files/add",
- data: {cname, pid}
- });
- }
+ static filesSearch(search_value, params = {}) {
+ return this.request({
+ url: "https://webapi.115.com/files/search",
+ params: {search_value, ...params}
+ });
+ }
- static lixianAddTaskUrl(url, wp_path_id) {
- return this.post({
- url: "https://115.com/web/lixian/",
- params: {ct: "lixian", ac: "add_task_url"},
- data: {url, wp_path_id}
- });
- }
+ static lixianTaskLists(page = 1) {
+ return this.request({
+ url: "https://115.com/web/lixian/",
+ params: {ct: "lixian", ac: "task_lists", page}
+ });
+ }
- /**
- * Bulk delete offline tasks and source files
- * @param {string[]} hash Array of info_hashes
- */
- static lixianTaskDel(hash) {
- return this.post({
- url: "https://115.com/web/lixian/",
- params: {ct: "lixian", ac: "task_del"},
- data: {hash}
- });
- }
+ static labelList() {
+ return this.request({
+ url: "https://webapi.115.com/label/list",
+ params: {keyword: "", limit: 11150}
+ });
+ }
- /**
- * Bulk delete files
- * @param {string[]} fid Array of file IDs
- * @param {string} pid Parent folder ID
- */
- static rbDelete(fid, pid = "") {
- return this.post({
- url: "https://webapi.115.com/rb/delete",
- data: {fid, pid, ignore_warn: 1}
- });
- }
+ /**
+ * @connect 115vod.com
+ */
+ static filesVideo(pickcode) {
+ return this.request({
+ url: "https://115vod.com/webapi/files/video",
+ params: {pickcode, local: 1}
+ });
+ }
- /**
- * Batch move files
- * @param {string[]} fid Array of file IDs
- * @param {string} pid Destination folder ID
- */
- static filesMove(fid, pid) {
- return this.post({
- url: "https://webapi.115.com/files/move",
- data: {fid, pid, move_proid: ""}
- });
- }
+ static post(details) {
+ return this.request({method: "POST", ...details});
+ }
- /**
- * Bulk label files
- * @param {string} file_ids fid1,fid2,fid3...
- * @param {string} file_label label_id1,label_id2,label_id3...
- */
- static filesBatchLabel(file_ids, file_label, action = "add") {
- return this.post({
- url: "https://webapi.115.com/files/batch_label",
- data: {file_ids, file_label, action}
- });
- }
+ static filesAdd(cname, pid) {
+ return this.post({
+ url: "https://webapi.115.com/files/add",
+ data: {cname, pid}
+ });
+ }
- /**
- * Bulk rename files
- * @param {object} files_new_name { [fid]: rename }
- */
- static filesBatchRename(files_new_name) {
- return this.post({
- url: "https://webapi.115.com/files/batch_rename",
- data: {files_new_name}
- });
- }
+ static lixianAddTaskUrl(url, wp_path_id) {
+ return this.post({
+ url: "https://115.com/web/lixian/",
+ params: {ct: "lixian", ac: "add_task_url"},
+ data: {url, wp_path_id}
+ });
+ }
- static sampleInitUpload({filename, filesize, cid}) {
- return this.post({
- url: "https://uplb.115.com/3.0/sampleinitupload.php",
- data: {filename, filesize, target: `U_1_${cid}`}
- });
- }
+ /**
+ * Bulk delete offline tasks and source files
+ * @param {string[]} hash Array of info_hashes
+ */
+ static lixianTaskDel(hash) {
+ return this.post({
+ url: "https://115.com/web/lixian/",
+ params: {ct: "lixian", ac: "task_del"},
+ data: {hash}
+ });
+ }
- /**
- * @connect aliyuncs.com
- */
- static upload({
- host: url,
- filename: name2,
- object: key,
- policy,
- accessid: OSSAccessKeyId,
- callback,
- signature,
- file
- }) {
- return this.post({
- url,
- data: {
- name: name2,
- key,
+ /**
+ * Bulk delete files
+ * @param {string[]} fid Array of file IDs
+ * @param {string} pid Parent folder ID
+ */
+ static rbDelete(fid, pid = "") {
+ return this.post({
+ url: "https://webapi.115.com/rb/delete",
+ data: {fid, pid, ignore_warn: 1}
+ });
+ }
+
+ /**
+ * Batch move files
+ * @param {string[]} fid Array of file IDs
+ * @param {string} pid Destination folder ID
+ */
+ static filesMove(fid, pid) {
+ return this.post({
+ url: "https://webapi.115.com/files/move",
+ data: {fid, pid, move_proid: ""}
+ });
+ }
+
+ /**
+ * Bulk label files
+ * @param {string} file_ids fid1,fid2,fid3...
+ * @param {string} file_label label_id1,label_id2,label_id3...
+ */
+ static filesBatchLabel(file_ids, file_label, action = "add") {
+ return this.post({
+ url: "https://webapi.115.com/files/batch_label",
+ data: {file_ids, file_label, action}
+ });
+ }
+
+ /**
+ * Bulk rename files
+ * @param {object} files_new_name { [fid]: rename }
+ */
+ static filesBatchRename(files_new_name) {
+ return this.post({
+ url: "https://webapi.115.com/files/batch_rename",
+ data: {files_new_name}
+ });
+ }
+
+ static sampleInitUpload({filename, filesize, cid}) {
+ return this.post({
+ url: "https://uplb.115.com/3.0/sampleinitupload.php",
+ data: {filename, filesize, target: `U_1_${cid}`}
+ });
+ }
+
+ /**
+ * @connect aliyuncs.com
+ */
+ static upload({
+ host: url,
+ filename: name2,
+ object: key,
policy,
- OSSAccessKeyId,
- success_action_status: "200",
+ accessid: OSSAccessKeyId,
callback,
signature,
file
- }
- });
- }
-
- static filesEdit(fid, fid_cover) {
- return this.post({
- url: "https://webapi.115.com/files/edit",
- data: {fid, fid_cover}
- });
+ }) {
+ return this.post({
+ url,
+ data: {
+ name: name2,
+ key,
+ policy,
+ OSSAccessKeyId,
+ success_action_status: "200",
+ callback,
+ signature,
+ file
}
+ });
}
- __publicField(Drive115, "DEFAULT_GET_RESPONSE_TYPE", "json");
+ static filesEdit(fid, fid_cover) {
+ return this.post({
+ url: "https://webapi.115.com/files/edit",
+ data: {fid, fid_cover}
+ });
+ }
+ }
- class Req115 extends Drive115 {
- static async filesAll(cid, params = {}) {
- const res = await this.files(cid, params);
- const {count, page_size, data} = res;
- return count > page_size && data.length ? this.files(cid, {...params, limit: count}) : res;
- }
+ __publicField(Drive115, "DEFAULT_GET_RESPONSE_TYPE", "json");
- static filesAllVideos(cid, params = {}) {
- return this.filesAll(cid, {...params, type: 4});
- }
-
- static filesAllSRTs(cid, params = {}) {
- return this.filesAll(cid, {...params, suffix: "srt"});
- }
-
- static async filesSearchAll(search_value, params = {}) {
- const res = await this.filesSearch(search_value, params);
- const {count, page_size, data} = res;
- return count > page_size && data.length ? this.filesSearch(search_value, {...params, limit: count}) : res;
- }
-
- static filesSearchAllVideos(search_value, params = {}) {
- return this.filesSearchAll(search_value, {...params, type: 4});
- }
-
- static filesSearchAllFolders(search_value, params = {}) {
- return this.filesSearchAll(search_value, {...params, fc: 1});
- }
-
- static async handleDir(routes) {
- var _a;
- if (routes.length === 1 && /^\d{5,}$/.test(routes[0])) return routes[0];
- let cid;
- const routesStr = routes.join("/");
- const cachedCid = localStorage.getItem(routesStr);
- if (cachedCid) {
- const res = await this.files(cachedCid);
- if ((_a = res == null ? void 0 : res.path) == null ? void 0 : _a.length) {
- const path = res.path.slice(1).map((p) => p.name);
- if (path.join("/") === routesStr) cid = cachedCid;
- }
- }
- if (!cid) {
- cid = "0";
- for (const route of routes) {
- const {data} = await this.filesSearchAllFolders(route, {cid});
- let folder = data.find((folder2) => folder2.n === route);
- if (!folder) folder = await this.filesAdd(route, cid);
- cid = folder == null ? void 0 : folder.cid;
- if (!cid) break;
- }
- }
- const month = (/* @__PURE__ */ new Date()).getMonth().toString();
- if (localStorage.getItem("115_CD") !== month) {
- localStorage.clear();
- localStorage.setItem("115_CD", month);
- }
- if (cid) localStorage.setItem(routesStr, cid);
- return cid;
- }
-
- static async handleVerify(info_hash, {regex, codes}, {max, filter}) {
- const sleep = () => {
- return new Promise((r) => {
- setTimeout(r, 1e3);
- });
- };
- let file_id = "";
- let videos = [];
- for (let index = 0; index < max; index++) {
- if (index) await sleep();
- const {tasks} = await this.lixianTaskLists();
- const task = tasks.find((task2) => task2.info_hash === info_hash);
- if (!task || task.status === -1) break;
- file_id = task.file_id;
- if (file_id) break;
- }
- if (!file_id) return {file_id, videos};
- for (let index = 0; index < max; index++) {
- if (index) await sleep();
- const {data} = await this.filesAllVideos(file_id);
- videos = data.filter((item) => regex.test(item.n));
- if (videos.length) break;
- }
- if (!videos.length) {
- const {tasks} = await this.lixianTaskLists();
- const task = tasks.find((task2) => task2.info_hash === info_hash);
- if (task.status === 2) {
- const {data} = await this.filesAllVideos(file_id);
- codes = codes.map((code) => code.toUpperCase());
- videos = data.filter((item) => {
- const name2 = item.n.toUpperCase();
- return codes.some((code) => name2.includes(code));
- });
- }
- }
- return {videos: videos.filter(filter), file_id};
- }
-
- static async handleClean(keepFiles, cid) {
- const needMove = keepFiles.filter((file) => file.cid !== cid).map((file) => file.fid);
- if (needMove.length) await this.filesMove(needMove, cid);
- const {data} = await this.filesAll(cid);
- const needRemove = data.filter((item) => !keepFiles.some((file) => file.fid === item.fid)).map((item) => item.fid ?? item.cid);
- if (needRemove.length) return this.rbDelete(needRemove, cid);
- }
-
- static async handleTags(files, tags) {
- var _a;
- const {data} = await this.labelList();
- if (!((_a = data == null ? void 0 : data.list) == null ? void 0 : _a.length)) return;
- const {list} = data;
- const labels = [];
- tags.forEach((tag) => {
- const item = list.find((item2) => item2.name === tag);
- if (item) labels.push(item.id);
- });
- if (labels.length) return this.filesBatchLabel(files.map((it) => it.fid).toString(), labels.toString());
- }
-
- static handleRename(files, cid, {rename, renameTxt, zh, crack}) {
- rename = rename.replaceAll("$zh", zh ? renameTxt.zh : "");
- rename = rename.replaceAll("$crack", crack ? renameTxt.crack : "");
- rename = rename.split("$sep").filter(Boolean).join(renameTxt.sep);
- rename = rename.trim();
- const renameObj = {[cid]: rename};
- if (files.length === 1) {
- const {fid, ico} = files[0];
- renameObj[fid] = `${rename}.${ico}`;
- return this.filesBatchRename(renameObj);
- }
- const icoMap = files.reduce((acc, {ico, ...item}) => {
- acc[ico] ?? (acc[ico] = []);
- acc[ico].push(item);
- return acc;
- }, {});
- const noTxt = renameTxt.no;
- for (const [ico, items] of Object.entries(icoMap)) {
- if (items.length === 1) {
- renameObj[items[0].fid] = `${rename}.${ico}`;
- continue;
- }
- items.toSorted((a, b) => a.n.localeCompare(b.n)).forEach(({fid}, idx) => {
- const no = noTxt.replaceAll(`\${no}`, `${idx + 1}`.padStart(2, "0"));
- renameObj[fid] = `${rename}${no}.${ico}`;
- });
- }
- return this.filesBatchRename(renameObj);
- }
-
- static async handleCover(url, cid, filename) {
- const file = await this.request({url, timeout: 6e4, responseType: "blob"});
- if (!file) return;
- const res = await this.sampleInitUpload({cid, filename, filesize: file.size});
- if (res == null ? void 0 : res.host) return this.upload({...res, filename, file});
- }
-
- static async handleOffline({
- dir,
- regex,
- codes,
- verifyOptions,
- code,
- rename,
- renameTxt,
- tags,
- clean,
- cover
- }, magnets) {
- const res = {status: "error", msg: `获取目录失败: ${dir.join("/")}`};
- const cid = await this.handleDir(dir);
- if (!cid) return res;
- for (let index = 0, {length} = magnets; index < length; index++) {
- const {url, zh, crack} = magnets[index];
- const {state, error_msg, errcode, info_hash} = await this.lixianAddTaskUrl(url, cid);
- if (!state) {
- res.msg = error_msg;
- res.status = "error";
- res.currIdx = index;
- if (errcode === 10008) continue;
- if (errcode === 911) res.status = "warn";
- break;
- }
- const {videos, file_id} = await this.handleVerify(info_hash, {regex, codes}, verifyOptions);
- if (!videos.length) {
- if (verifyOptions.clean) this.lixianTaskDel([info_hash]);
- if (file_id && clean) this.rbDelete([file_id], cid);
- res.msg = `${code} 离线验证失败`;
- res.status = "error";
- continue;
- }
- const {data: srts = []} = await this.filesAllSRTs(file_id);
- const files = [...videos, ...srts];
- if (clean) await this.handleClean(files, file_id);
- if (tags.length) this.handleTags(videos, tags);
- if (rename) this.handleRename(files, file_id, {rename, renameTxt, zh: zh || srts.length, crack});
- if (cover) {
- try {
- const {data} = await this.handleCover(cover, file_id, `${code}.cover.jpg`);
- if (data == null ? void 0 : data.file_id) this.filesEdit(file_id, data.file_id);
- } catch (err) {
- console.warn("[Req115.handleCover]", err == null ? void 0 : err.message);
- }
- }
- res.msg = `${code} 离线任务成功`;
- res.status = "success";
- break;
- }
- return res;
- }
-
- static async queueMutation(name2, fn) {
- return fn();
- }
+ class Req115 extends Drive115 {
+ static async filesAll(cid, params = {}) {
+ const res = await this.files(cid, params);
+ const {count, page_size, data} = res;
+ return count > page_size && data.length ? this.files(cid, {...params, limit: count}) : res;
}
- window.Req115 = Req115;
+ static filesAllVideos(cid, params = {}) {
+ return this.filesAll(cid, {...params, type: 4});
+ }
+
+ static filesAllSRTs(cid, params = {}) {
+ return this.filesAll(cid, {...params, suffix: "srt"});
+ }
+
+ static async filesSearchAll(search_value, params = {}) {
+ const res = await this.filesSearch(search_value, params);
+ const {count, page_size, data} = res;
+ return count > page_size && data.length ? this.filesSearch(search_value, {...params, limit: count}) : res;
+ }
+
+ static filesSearchAllVideos(search_value, params = {}) {
+ return this.filesSearchAll(search_value, {...params, type: 4});
+ }
+
+ static filesSearchAllFolders(search_value, params = {}) {
+ return this.filesSearchAll(search_value, {...params, fc: 1});
+ }
+
+ static async handleDir(routes) {
+ var _a;
+ if (routes.length === 1 && /^\d{5,}$/.test(routes[0])) return routes[0];
+ let cid;
+ const routesStr = routes.join("/");
+ const cachedCid = localStorage.getItem(routesStr);
+ if (cachedCid) {
+ const res = await this.files(cachedCid);
+ if ((_a = res == null ? void 0 : res.path) == null ? void 0 : _a.length) {
+ const path = res.path.slice(1).map((p) => p.name);
+ if (path.join("/") === routesStr) cid = cachedCid;
+ }
+ }
+ if (!cid) {
+ cid = "0";
+ for (const route of routes) {
+ const {data} = await this.filesSearchAllFolders(route, {cid});
+ let folder = data.find((folder2) => folder2.n === route);
+ if (!folder) folder = await this.filesAdd(route, cid);
+ cid = folder == null ? void 0 : folder.cid;
+ if (!cid) break;
+ }
+ }
+ const month = (/* @__PURE__ */ new Date()).getMonth().toString();
+ if (localStorage.getItem("115_CD") !== month) {
+ localStorage.clear();
+ localStorage.setItem("115_CD", month);
+ }
+ if (cid) localStorage.setItem(routesStr, cid);
+ return cid;
+ }
+
+ static async handleVerify(info_hash, {regex, codes}, {max, filter}) {
+ const sleep = () => {
+ return new Promise((r) => {
+ setTimeout(r, 1e3);
+ });
+ };
+ let file_id = "";
+ let videos = [];
+ for (let index = 0; index < max; index++) {
+ if (index) await sleep();
+ const {tasks} = await this.lixianTaskLists();
+ const task = tasks.find((task2) => task2.info_hash === info_hash);
+ if (!task || task.status === -1) break;
+ file_id = task.file_id;
+ if (file_id) break;
+ }
+ if (!file_id) return {file_id, videos};
+ for (let index = 0; index < max; index++) {
+ if (index) await sleep();
+ const {data} = await this.filesAllVideos(file_id);
+ videos = data.filter((item) => regex.test(item.n));
+ if (videos.length) break;
+ }
+ if (!videos.length) {
+ const {tasks} = await this.lixianTaskLists();
+ const task = tasks.find((task2) => task2.info_hash === info_hash);
+ if (task.status === 2) {
+ const {data} = await this.filesAllVideos(file_id);
+ codes = codes.map((code) => code.toUpperCase());
+ videos = data.filter((item) => {
+ const name2 = item.n.toUpperCase();
+ return codes.some((code) => name2.includes(code));
+ });
+ }
+ }
+ return {videos: videos.filter(filter), file_id};
+ }
+
+ static async handleClean(keepFiles, cid) {
+ const needMove = keepFiles.filter((file) => file.cid !== cid).map((file) => file.fid);
+ if (needMove.length) await this.filesMove(needMove, cid);
+ const {data} = await this.filesAll(cid);
+ const needRemove = data.filter((item) => !keepFiles.some((file) => file.fid === item.fid)).map((item) => item.fid ?? item.cid);
+ if (needRemove.length) return this.rbDelete(needRemove, cid);
+ }
+
+ static async handleTags(files, tags) {
+ var _a;
+ const {data} = await this.labelList();
+ if (!((_a = data == null ? void 0 : data.list) == null ? void 0 : _a.length)) return;
+ const {list} = data;
+ const labels = [];
+ tags.forEach((tag) => {
+ const item = list.find((item2) => item2.name === tag);
+ if (item) labels.push(item.id);
+ });
+ if (labels.length) return this.filesBatchLabel(files.map((it) => it.fid).toString(), labels.toString());
+ }
+
+ static handleRename(files, cid, {rename, renameTxt, zh, crack}) {
+ rename = rename.replaceAll("$zh", zh ? renameTxt.zh : "");
+ rename = rename.replaceAll("$crack", crack ? renameTxt.crack : "");
+ rename = rename.split("$sep").filter(Boolean).join(renameTxt.sep);
+ rename = rename.trim();
+ const renameObj = {[cid]: rename};
+ if (files.length === 1) {
+ const {fid, ico} = files[0];
+ renameObj[fid] = `${rename}.${ico}`;
+ return this.filesBatchRename(renameObj);
+ }
+ const icoMap = files.reduce((acc, {ico, ...item}) => {
+ acc[ico] ?? (acc[ico] = []);
+ acc[ico].push(item);
+ return acc;
+ }, {});
+ const noTxt = renameTxt.no;
+ for (const [ico, items] of Object.entries(icoMap)) {
+ if (items.length === 1) {
+ renameObj[items[0].fid] = `${rename}.${ico}`;
+ continue;
+ }
+ items.toSorted((a, b) => a.n.localeCompare(b.n)).forEach(({fid}, idx) => {
+ const no = noTxt.replaceAll(`\${no}`, `${idx + 1}`.padStart(2, "0"));
+ renameObj[fid] = `${rename}${no}.${ico}`;
+ });
+ }
+ return this.filesBatchRename(renameObj);
+ }
+
+ static async handleCover(url, cid, filename) {
+ const file = await this.request({url, timeout: 6e4, responseType: "blob"});
+ if (!file) return;
+ const res = await this.sampleInitUpload({cid, filename, filesize: file.size});
+ if (res == null ? void 0 : res.host) return this.upload({...res, filename, file});
+ }
+
+ static async handleOffline({
+ dir,
+ regex,
+ codes,
+ verifyOptions,
+ code,
+ rename,
+ renameTxt,
+ tags,
+ clean,
+ cover
+ }, magnets) {
+ const res = {status: "error", msg: `获取目录失败: ${dir.join("/")}`};
+ const cid = await this.handleDir(dir);
+ if (!cid) return res;
+ for (let index = 0, {length} = magnets; index < length; index++) {
+ const {url, zh, crack} = magnets[index];
+ const {state, error_msg, errcode, info_hash} = await this.lixianAddTaskUrl(url, cid);
+ if (!state) {
+ res.msg = error_msg;
+ res.status = "error";
+ res.currIdx = index;
+ if (errcode === 10008) continue;
+ if (errcode === 911) res.status = "warn";
+ break;
+ }
+ const {videos, file_id} = await this.handleVerify(info_hash, {regex, codes}, verifyOptions);
+ if (!videos.length) {
+ if (verifyOptions.clean) this.lixianTaskDel([info_hash]);
+ if (file_id && clean) this.rbDelete([file_id], cid);
+ res.msg = `${code} 离线验证失败`;
+ res.status = "error";
+ continue;
+ }
+ const {data: srts = []} = await this.filesAllSRTs(file_id);
+ const files = [...videos, ...srts];
+ if (clean) await this.handleClean(files, file_id);
+ if (tags.length) this.handleTags(videos, tags);
+ if (rename) this.handleRename(files, file_id, {rename, renameTxt, zh: zh || srts.length, crack});
+ if (cover) {
+ try {
+ const {data} = await this.handleCover(cover, file_id, `${code}.cover.jpg`);
+ if (data == null ? void 0 : data.file_id) this.filesEdit(file_id, data.file_id);
+ } catch (err) {
+ console.warn("[Req115.handleCover]", err == null ? void 0 : err.message);
+ }
+ }
+ res.msg = `${code} 离线任务成功`;
+ res.status = "success";
+ break;
+ }
+ return res;
+ }
+
+ static async queueMutation(name2, fn) {
+ return fn();
+ }
+ }
+
+ window.Req115 = Req115;
class Fc2Plugin extends BasePlugin {
getName() {
return "Fc2Plugin";
@@ -8456,7 +8453,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
\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 ',
area: utils.getResponsiveArea(["70%", "90%"]),
skin: "movie-detail-layer",
scrollbar: false,
@@ -8513,10 +8510,10 @@ ${value}\r
window.refresh();
layer.closeAll();
}));
- $("#subtitleSearchBtn").on("click", (() => {
- window.JavPackSubtitle.openSearchModal({details: {code: carNum2}});
- }));
- window.JavPackSubtitle.preload115Matches(carNum2);
+ $("#subtitleSearchBtn").on("click", (() => {
+ window.JavPackSubtitle.openSearchModal({details: {code: carNum2}});
+ }));
+ window.JavPackSubtitle.preload115Matches(carNum2);
$("#magnetSearchBtn").on("click", (() => {
let magnetHub = this.getBean("MagnetHubPlugin").createMagnetHub(carNum2);
layer.open({
@@ -8601,7 +8598,7 @@ ${value}\r
utils.loopDetector((() => $(".movie-gallery .image-list").length > 0), (async () => {
$(".movie-gallery .image-list").prepend('
正在加载缩略图
');
const screenShotPlugin = this.getBean("ScreenShotPlugin"), imgUrl = await screenShotPlugin.getScreenshot(carNum2);
- imgUrl && await screenShotPlugin.addImg("缩略图", imgUrl, carNum2);
+ imgUrl && await screenShotPlugin.addImg("缩略图", imgUrl, carNum2);
}));
}
handleMagnets(movieId) {
@@ -8668,208 +8665,208 @@ ${value}\r
return "HighlightMagnetPlugin";
}
- parseDateFromAttr(dateStr) {
- if (!dateStr) return 0;
- if (dateStr.length === 8) {
- const y = parseInt(dateStr.substring(0, 4), 10);
- const m = parseInt(dateStr.substring(4, 6), 10) - 1;
- const d2 = parseInt(dateStr.substring(6, 8), 10);
- const date = new Date(y, m, d2);
- return isNaN(date.getTime()) ? 0 : date.getTime();
- }
- const d = new Date(dateStr);
- return isNaN(d.getTime()) ? 0 : d.getTime();
+ parseDateFromAttr(dateStr) {
+ if (!dateStr) return 0;
+ if (dateStr.length === 8) {
+ const y = parseInt(dateStr.substring(0, 4), 10);
+ const m = parseInt(dateStr.substring(4, 6), 10) - 1;
+ const d2 = parseInt(dateStr.substring(6, 8), 10);
+ const date = new Date(y, m, d2);
+ return isNaN(date.getTime()) ? 0 : date.getTime();
}
+ const d = new Date(dateStr);
+ return isNaN(d.getTime()) ? 0 : d.getTime();
+ }
- parseSizeToBytes(sizeText) {
- if (!sizeText) return 0;
- const s = sizeText.trim().toUpperCase();
- const num = parseFloat(s);
- if (isNaN(num)) return 0;
- if (s.includes("TB")) return num * 1024 * 1024 * 1024 * 1024;
- if (s.includes("GB")) return num * 1024 * 1024 * 1024;
- if (s.includes("MB")) return num * 1024 * 1024;
- if (s.includes("KB")) return num * 1024;
- return num;
+ parseSizeToBytes(sizeText) {
+ if (!sizeText) return 0;
+ const s = sizeText.trim().toUpperCase();
+ const num = parseFloat(s);
+ if (isNaN(num)) return 0;
+ if (s.includes("TB")) return num * 1024 * 1024 * 1024 * 1024;
+ if (s.includes("GB")) return num * 1024 * 1024 * 1024;
+ if (s.includes("MB")) return num * 1024 * 1024;
+ if (s.includes("KB")) return num * 1024;
+ return num;
+ }
+
+ getRowInfo(row) {
+ const $row = $(row);
+ const nameText = $row.find(".name").text().toLowerCase();
+ const tags = [];
+ $row.find(".tags .tag").each((_, tag) => tags.push($(tag).text().toLowerCase()));
+ const sizeMB = parseInt($row.attr("data-size"), 10) || 0;
+ const files = parseInt($row.attr("data-files"), 10) || 0;
+ const dateStr = $row.attr("data-date") || "";
+ return {
+ row,
+ $row,
+ nameText,
+ date: this.parseDateFromAttr(dateStr),
+ dateStr,
+ size: sizeMB * 1024 * 1024,
+ files,
+ tags,
+ is4K: nameText.includes("4k") || nameText.includes("[4k]") || tags.includes("4k"),
+ isHD: nameText.includes("高清") || tags.includes("高清"),
+ isSubtitle: nameText.includes("字幕") || nameText.includes("-c") || nameText.includes(".chs") || nameText.includes(".cht") || nameText.includes(".chi") || tags.includes("字幕"),
+ isUncensored: nameText.includes("无码") || nameText.includes("無碼") || nameText.includes("-u") || nameText.includes("-uc") || tags.includes("无码") || tags.includes("無碼")
+ };
+ }
+
+ getRowInfoForBus(row) {
+ const $row = $(row);
+ const $nameCell = $row.find("td:first-child");
+ const nameText = ($nameCell.find("a:first-child").text() || "").toLowerCase().trim();
+ const sizeText = ($row.find("td:nth-child(2)").text() || "").trim();
+ const dateText = ($row.find("td:nth-child(3)").text() || "").trim();
+ const hasHDButton = $nameCell.find("a.btn-primary").text().includes("高清");
+ return {
+ row,
+ $row,
+ nameText,
+ date: this.parseDateFromAttr(dateText),
+ dateStr: dateText,
+ size: this.parseSizeToBytes(sizeText),
+ files: 0,
+ tags: [],
+ is4K: nameText.includes("4k") || nameText.includes("[4k]"),
+ isHD: hasHDButton || nameText.includes("高清"),
+ isSubtitle: nameText.includes("字幕") || nameText.includes("-c") || nameText.includes(".chs") || nameText.includes(".cht") || nameText.includes(".chi"),
+ isUncensored: nameText.includes("无码") || nameText.includes("無碼") || nameText.includes("-u") || nameText.includes("-uc")
+ };
+ }
+
+ async applySortAndFilter(settings) {
+ if (isJavBus$1) {
+ return this.applySortAndFilterForBus(settings);
}
-
- getRowInfo(row) {
- const $row = $(row);
- const nameText = $row.find(".name").text().toLowerCase();
- const tags = [];
- $row.find(".tags .tag").each((_, tag) => tags.push($(tag).text().toLowerCase()));
- const sizeMB = parseInt($row.attr("data-size"), 10) || 0;
- const files = parseInt($row.attr("data-files"), 10) || 0;
- const dateStr = $row.attr("data-date") || "";
- return {
- row,
- $row,
- nameText,
- date: this.parseDateFromAttr(dateStr),
- dateStr,
- size: sizeMB * 1024 * 1024,
- files,
- tags,
- is4K: nameText.includes("4k") || nameText.includes("[4k]") || tags.includes("4k"),
- isHD: nameText.includes("高清") || tags.includes("高清"),
- isSubtitle: nameText.includes("字幕") || nameText.includes("-c") || nameText.includes(".chs") || nameText.includes(".cht") || nameText.includes(".chi") || tags.includes("字幕"),
- isUncensored: nameText.includes("无码") || nameText.includes("無碼") || nameText.includes("-u") || nameText.includes("-uc") || tags.includes("无码") || tags.includes("無碼")
- };
- }
-
- getRowInfoForBus(row) {
- const $row = $(row);
- const $nameCell = $row.find("td:first-child");
- const nameText = ($nameCell.find("a:first-child").text() || "").toLowerCase().trim();
- const sizeText = ($row.find("td:nth-child(2)").text() || "").trim();
- const dateText = ($row.find("td:nth-child(3)").text() || "").trim();
- const hasHDButton = $nameCell.find("a.btn-primary").text().includes("高清");
- return {
- row,
- $row,
- nameText,
- date: this.parseDateFromAttr(dateText),
- dateStr: dateText,
- size: this.parseSizeToBytes(sizeText),
- files: 0,
- tags: [],
- is4K: nameText.includes("4k") || nameText.includes("[4k]"),
- isHD: hasHDButton || nameText.includes("高清"),
- isSubtitle: nameText.includes("字幕") || nameText.includes("-c") || nameText.includes(".chs") || nameText.includes(".cht") || nameText.includes(".chi"),
- isUncensored: nameText.includes("无码") || nameText.includes("無碼") || nameText.includes("-u") || nameText.includes("-uc")
- };
- }
-
- async applySortAndFilter(settings) {
- if (isJavBus$1) {
- return this.applySortAndFilterForBus(settings);
- }
if (!isJavDb$1) return;
- const $container = $("#magnets-content");
- const $rows = $container.find(".item[data-rank]");
- if (0 === $rows.length) return;
- const rowInfos = $rows.toArray().map((row) => this.getRowInfo(row));
- const {filterHD, filter4K, filterSubtitle, filterUncensored, sortOrder} = settings;
- const anyFilterEnabled = filterHD || filter4K || filterSubtitle || filterUncensored;
- let visibleInfos = rowInfos;
- if (anyFilterEnabled) {
- visibleInfos = rowInfos.filter((info) => {
- if (filterHD && info.isHD) return true;
- if (filter4K && info.is4K) return true;
- if (filterSubtitle && info.isSubtitle) return true;
- if (filterUncensored && info.isUncensored) return true;
- return false;
- });
- }
- if (sortOrder && sortOrder.length > 0) {
- visibleInfos.sort((a, b) => {
- for (const key of sortOrder) {
- let cmp = 0;
- switch (key) {
- case "date":
- cmp = b.date - a.date;
- break;
- case "size":
- cmp = b.size - a.size;
- break;
- case "files":
- cmp = a.files - b.files;
- break;
- }
- if (0 !== cmp) return cmp;
- }
- return 0;
- });
- }
- const container = document.getElementById("magnets-content");
- if (!container) return;
- const hiddenInfos = rowInfos.filter((info) => !visibleInfos.includes(info));
- const reviewsContainer = document.getElementById("reviewsContainer");
- const insertRef = reviewsContainer && reviewsContainer.parentNode === container ? reviewsContainer.previousElementSibling || reviewsContainer : null;
- const fragment = document.createDocumentFragment();
- visibleInfos.forEach((info) => fragment.appendChild(info.row));
- hiddenInfos.forEach((info) => fragment.appendChild(info.row));
- if (insertRef) {
- container.insertBefore(fragment, insertRef);
- } else {
- container.appendChild(fragment);
- }
- visibleInfos.forEach((info) => {
- if (info.is4K) {
- info.$row.addClass("magnet-4k-highlight");
- }
- });
- hiddenInfos.forEach((info) => $(info.row).hide());
+ const $container = $("#magnets-content");
+ const $rows = $container.find(".item[data-rank]");
+ if (0 === $rows.length) return;
+ const rowInfos = $rows.toArray().map((row) => this.getRowInfo(row));
+ const {filterHD, filter4K, filterSubtitle, filterUncensored, sortOrder} = settings;
+ const anyFilterEnabled = filterHD || filter4K || filterSubtitle || filterUncensored;
+ let visibleInfos = rowInfos;
+ if (anyFilterEnabled) {
+ visibleInfos = rowInfos.filter((info) => {
+ if (filterHD && info.isHD) return true;
+ if (filter4K && info.is4K) return true;
+ if (filterSubtitle && info.isSubtitle) return true;
+ if (filterUncensored && info.isUncensored) return true;
+ return false;
+ });
}
-
- applySortAndFilterForBus(settings) {
- const $table = $("#magnet-table");
- if (!$table.length) return;
- const allRows = $table.find("tr").toArray();
- const headerRow = allRows.find((r) => $(r).find("td").length === 0 || $(r).css("font-weight") === "bold" || $(r).css("fontWeight") === "bold" || $(r).css("font-weight") === "700");
- const dataRows = headerRow ? allRows.filter((r) => r !== headerRow) : allRows.slice(1);
- if (0 === dataRows.length) return;
- const rowInfos = dataRows.map((row) => this.getRowInfoForBus(row));
- const {filterHD, filter4K, filterSubtitle, filterUncensored, sortOrder} = settings;
- const anyFilterEnabled = filterHD || filter4K || filterSubtitle || filterUncensored;
- let visibleInfos = rowInfos;
- if (anyFilterEnabled) {
- visibleInfos = rowInfos.filter((info) => {
- if (filterHD && info.isHD) return true;
- if (filter4K && info.is4K) return true;
- if (filterSubtitle && info.isSubtitle) return true;
- if (filterUncensored && info.isUncensored) return true;
- return false;
- });
+ if (sortOrder && sortOrder.length > 0) {
+ visibleInfos.sort((a, b) => {
+ for (const key of sortOrder) {
+ let cmp = 0;
+ switch (key) {
+ case "date":
+ cmp = b.date - a.date;
+ break;
+ case "size":
+ cmp = b.size - a.size;
+ break;
+ case "files":
+ cmp = a.files - b.files;
+ break;
+ }
+ if (0 !== cmp) return cmp;
}
- if (sortOrder && sortOrder.length > 0) {
- visibleInfos.sort((a, b) => {
- for (const key of sortOrder) {
- let cmp = 0;
- switch (key) {
- case "date":
- cmp = b.date - a.date;
- break;
- case "size":
- cmp = b.size - a.size;
- break;
- }
- if (0 !== cmp) return cmp;
- }
- return 0;
- });
- }
- const hiddenInfos = rowInfos.filter((info) => !visibleInfos.includes(info));
- const fragment = document.createDocumentFragment();
- visibleInfos.forEach((info) => fragment.appendChild(info.row));
- hiddenInfos.forEach((info) => fragment.appendChild(info.row));
- const insertAnchor = headerRow || $table.find("tr").first()[0];
- if (insertAnchor && insertAnchor.nextSibling) {
- $(insertAnchor).after(fragment);
- } else {
- $table.append(fragment);
- }
- visibleInfos.forEach((info) => {
- if (info.is4K) {
- info.$row.addClass("magnet-4k-highlight");
- }
- });
- hiddenInfos.forEach((info) => $(info.row).hide());
+ return 0;
+ });
}
+ const container = document.getElementById("magnets-content");
+ if (!container) return;
+ const hiddenInfos = rowInfos.filter((info) => !visibleInfos.includes(info));
+ const reviewsContainer = document.getElementById("reviewsContainer");
+ const insertRef = reviewsContainer && reviewsContainer.parentNode === container ? reviewsContainer.previousElementSibling || reviewsContainer : null;
+ const fragment = document.createDocumentFragment();
+ visibleInfos.forEach((info) => fragment.appendChild(info.row));
+ hiddenInfos.forEach((info) => fragment.appendChild(info.row));
+ if (insertRef) {
+ container.insertBefore(fragment, insertRef);
+ } else {
+ container.appendChild(fragment);
+ }
+ visibleInfos.forEach((info) => {
+ if (info.is4K) {
+ info.$row.addClass("magnet-4k-highlight");
+ }
+ });
+ hiddenInfos.forEach((info) => $(info.row).hide());
+ }
- showAll() {
- if (isJavDb$1) {
- $("#magnets-content .item[data-rank]").toArray().forEach(((el) => {
- $(el).show();
- $(el).removeClass("magnet-4k-highlight");
- }));
- }
- if (isJavBus$1) {
- $("#magnet-table tr").toArray().forEach(((el) => {
- $(el).show();
- $(el).removeClass("magnet-4k-highlight");
- }));
+ applySortAndFilterForBus(settings) {
+ const $table = $("#magnet-table");
+ if (!$table.length) return;
+ const allRows = $table.find("tr").toArray();
+ const headerRow = allRows.find((r) => $(r).find("td").length === 0 || $(r).css("font-weight") === "bold" || $(r).css("fontWeight") === "bold" || $(r).css("font-weight") === "700");
+ const dataRows = headerRow ? allRows.filter((r) => r !== headerRow) : allRows.slice(1);
+ if (0 === dataRows.length) return;
+ const rowInfos = dataRows.map((row) => this.getRowInfoForBus(row));
+ const {filterHD, filter4K, filterSubtitle, filterUncensored, sortOrder} = settings;
+ const anyFilterEnabled = filterHD || filter4K || filterSubtitle || filterUncensored;
+ let visibleInfos = rowInfos;
+ if (anyFilterEnabled) {
+ visibleInfos = rowInfos.filter((info) => {
+ if (filterHD && info.isHD) return true;
+ if (filter4K && info.is4K) return true;
+ if (filterSubtitle && info.isSubtitle) return true;
+ if (filterUncensored && info.isUncensored) return true;
+ return false;
+ });
+ }
+ if (sortOrder && sortOrder.length > 0) {
+ visibleInfos.sort((a, b) => {
+ for (const key of sortOrder) {
+ let cmp = 0;
+ switch (key) {
+ case "date":
+ cmp = b.date - a.date;
+ break;
+ case "size":
+ cmp = b.size - a.size;
+ break;
+ }
+ if (0 !== cmp) return cmp;
}
+ return 0;
+ });
+ }
+ const hiddenInfos = rowInfos.filter((info) => !visibleInfos.includes(info));
+ const fragment = document.createDocumentFragment();
+ visibleInfos.forEach((info) => fragment.appendChild(info.row));
+ hiddenInfos.forEach((info) => fragment.appendChild(info.row));
+ const insertAnchor = headerRow || $table.find("tr").first()[0];
+ if (insertAnchor && insertAnchor.nextSibling) {
+ $(insertAnchor).after(fragment);
+ } else {
+ $table.append(fragment);
+ }
+ visibleInfos.forEach((info) => {
+ if (info.is4K) {
+ info.$row.addClass("magnet-4k-highlight");
+ }
+ });
+ hiddenInfos.forEach((info) => $(info.row).hide());
+ }
+
+ showAll() {
+ if (isJavDb$1) {
+ $("#magnets-content .item[data-rank]").toArray().forEach(((el) => {
+ $(el).show();
+ $(el).removeClass("magnet-4k-highlight");
+ }));
+ }
+ if (isJavBus$1) {
+ $("#magnet-table tr").toArray().forEach(((el) => {
+ $(el).show();
+ $(el).removeClass("magnet-4k-highlight");
+ }));
+ }
}
handleBus() {
isJavBus$1 && window.isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => {
@@ -9015,7 +9012,7 @@ ${value}\r
class ActressInfoPlugin extends BasePlugin {
constructor() {
super(...arguments);
- __publicField2(this, "apiUrl", "https://ja.wikipedia.org/wiki/");
+ __publicField2(this, "apiUrl", "https://ja.wikipedia.org/wiki/");
}
getName() {
return "ActressInfoPlugin";
@@ -9149,7 +9146,7 @@ ${value}\r
class HitShowPlugin extends BasePlugin {
constructor() {
super();
- __publicField2(this, "$contentBox", $(".section .container"));
+ __publicField2(this, "$contentBox", $(".section .container"));
}
getName() {
return "HitShowPlugin";
@@ -9251,9 +9248,9 @@ ${err.stack}` : "");
class TOP250Plugin extends BasePlugin {
constructor() {
super();
- __publicField2(this, "has_cnsub", "");
- __publicField2(this, "$contentBox", $(".section .container"));
- __publicField2(this, "movies", []);
+ __publicField2(this, "has_cnsub", "");
+ __publicField2(this, "$contentBox", $(".section .container"));
+ __publicField2(this, "movies", []);
}
getName() {
return "TOP250Plugin";
@@ -9659,10 +9656,10 @@ ${err.stack}` : "");
}
closeVideoModal() {
const $previewVideo = $("#preview-video");
- if ($previewVideo.length > 0) {
- const videoEl = $previewVideo[0];
- videoEl.pause();
- }
+ if ($previewVideo.length > 0) {
+ const videoEl = $previewVideo[0];
+ videoEl.pause();
+ }
$("#bus-preview-modal").removeClass("is-open");
}
async handle() {
@@ -9701,8 +9698,8 @@ ${err.stack}` : "");
let $previewVideo = $("#preview-video");
if ($previewVideo.length > 0) {
$modal.addClass("is-open");
- const videoEl = $previewVideo[0];
- videoEl.play().catch(((e) => console.warn("尝试播放失败 (可能被浏览器阻止):", e)));
+ const videoEl = $previewVideo[0];
+ videoEl.play().catch(((e) => console.warn("尝试播放失败 (可能被浏览器阻止):", e)));
return;
}
let carNum2 = this.getPageInfo().carNum;
@@ -9729,10 +9726,10 @@ ${err.stack}` : "");
`);
- const $videoEl = $("#preview-video"), $qualityControlsBox = $container.find(".video-control-box");
- if (!$videoEl.length) return;
- const videoEl = $videoEl[0];
- const jhs_videoMuted = localStorage.getItem("jhs_videoMuted");
+ const $videoEl = $("#preview-video"), $qualityControlsBox = $container.find(".video-control-box");
+ if (!$videoEl.length) return;
+ const videoEl = $videoEl[0];
+ const jhs_videoMuted = localStorage.getItem("jhs_videoMuted");
videoEl.muted = !jhs_videoMuted || "yes" === jhs_videoMuted;
videoEl.addEventListener("volumechange", (function() {
localStorage.setItem("jhs_videoMuted", videoEl.muted ? "yes" : "no");
@@ -9759,11 +9756,11 @@ ${err.stack}` : "");
if ($button.hasClass("active")) return;
let videoSrc = $button.attr("data-video-src");
const currentTime = videoEl.currentTime;
- videoEl.src = videoSrc;
+ videoEl.src = videoSrc;
videoEl.load();
- if (currentTime > 0 && Number.isFinite(currentTime)) {
- videoEl.currentTime = currentTime;
- }
+ if (currentTime > 0 && Number.isFinite(currentTime)) {
+ videoEl.currentTime = currentTime;
+ }
await videoEl.play();
$buttons.removeClass("active");
$button.addClass("active");
@@ -9774,456 +9771,456 @@ ${err.stack}` : "");
}
}
- const HLS_SCRIPT_URL = "https://cdn.jsdelivr.net/npm/hls.js@1.5.18/dist/hls.min.js";
- let hlsLoadPromise = null;
- const getHlsClass = () => {
- const HlsClass = window.Hls || globalThis.Hls || (typeof Hls !== "undefined" ? Hls : null);
- return HlsClass && HlsClass.isSupported && HlsClass.isSupported() ? HlsClass : null;
- };
- const binaryTextToArrayBuffer = (value) => {
- const text = String(value || "");
- const bytes = new Uint8Array(text.length);
- for (let index = 0; index < text.length; index += 1) {
- bytes[index] = text.charCodeAt(index) & 255;
- }
- return bytes.buffer;
- };
- const loadHlsLibrary = () => {
- const readyHls = getHlsClass();
- if (readyHls) return Promise.resolve(readyHls);
- if (hlsLoadPromise) return hlsLoadPromise;
- const loadByGm = () => new Promise((resolve) => {
- _GM_xmlhttpRequest({
- method: "GET",
- url: HLS_SCRIPT_URL,
- timeout: 15e3,
- onload: (r) => {
- if (r.status >= 200 && r.status < 300 && r.responseText) {
- try {
- Function(`${r.responseText}
+ const HLS_SCRIPT_URL = "https://cdn.jsdelivr.net/npm/hls.js@1.5.18/dist/hls.min.js";
+ let hlsLoadPromise = null;
+ const getHlsClass = () => {
+ const HlsClass = window.Hls || globalThis.Hls || (typeof Hls !== "undefined" ? Hls : null);
+ return HlsClass && HlsClass.isSupported && HlsClass.isSupported() ? HlsClass : null;
+ };
+ const binaryTextToArrayBuffer = (value) => {
+ const text = String(value || "");
+ const bytes = new Uint8Array(text.length);
+ for (let index = 0; index < text.length; index += 1) {
+ bytes[index] = text.charCodeAt(index) & 255;
+ }
+ return bytes.buffer;
+ };
+ const loadHlsLibrary = () => {
+ const readyHls = getHlsClass();
+ if (readyHls) return Promise.resolve(readyHls);
+ if (hlsLoadPromise) return hlsLoadPromise;
+ const loadByGm = () => new Promise((resolve) => {
+ _GM_xmlhttpRequest({
+ method: "GET",
+ url: HLS_SCRIPT_URL,
+ timeout: 15e3,
+ onload: (r) => {
+ if (r.status >= 200 && r.status < 300 && r.responseText) {
+ try {
+ Function(`${r.responseText}
//# sourceURL=${HLS_SCRIPT_URL}`).call(globalThis);
- } catch (err) {
- clog.error("HLS: hls.js 执行失败", err);
- }
- }
- resolve(getHlsClass());
- },
- onerror: () => resolve(getHlsClass()),
- ontimeout: () => resolve(getHlsClass())
- });
- });
- hlsLoadPromise = new Promise((resolve) => {
- const existing = document.querySelector('script[data-jhs-hls="1"]');
- if (existing) {
- existing.addEventListener("load", () => resolve(getHlsClass()), {once: true});
- existing.addEventListener("error", () => loadByGm().then(resolve), {once: true});
- setTimeout(() => {
- if (!getHlsClass()) loadByGm().then(resolve);
- }, 4e3);
- return;
- }
- const hlsScript = document.createElement("script");
- hlsScript.src = HLS_SCRIPT_URL;
- hlsScript.async = true;
- hlsScript.dataset.jhsHls = "1";
- hlsScript.onload = () => resolve(getHlsClass());
- hlsScript.onerror = () => loadByGm().then(resolve);
- document.head.appendChild(hlsScript);
- }).then((HlsClass) => {
- if (!HlsClass) hlsLoadPromise = null;
- return HlsClass;
- });
- return hlsLoadPromise;
- };
- const createHlsLoader = () => {
- class GMHlsLoader {
- constructor(config) {
- this.config = config;
- this.context = null;
- this.callbacks = null;
- this.loader = null;
- this.stats = this.createStats();
+ } catch (err) {
+ clog.error("HLS: hls.js 执行失败", err);
}
+ }
+ resolve(getHlsClass());
+ },
+ onerror: () => resolve(getHlsClass()),
+ ontimeout: () => resolve(getHlsClass())
+ });
+ });
+ hlsLoadPromise = new Promise((resolve) => {
+ const existing = document.querySelector('script[data-jhs-hls="1"]');
+ if (existing) {
+ existing.addEventListener("load", () => resolve(getHlsClass()), {once: true});
+ existing.addEventListener("error", () => loadByGm().then(resolve), {once: true});
+ setTimeout(() => {
+ if (!getHlsClass()) loadByGm().then(resolve);
+ }, 4e3);
+ return;
+ }
+ const hlsScript = document.createElement("script");
+ hlsScript.src = HLS_SCRIPT_URL;
+ hlsScript.async = true;
+ hlsScript.dataset.jhsHls = "1";
+ hlsScript.onload = () => resolve(getHlsClass());
+ hlsScript.onerror = () => loadByGm().then(resolve);
+ document.head.appendChild(hlsScript);
+ }).then((HlsClass) => {
+ if (!HlsClass) hlsLoadPromise = null;
+ return HlsClass;
+ });
+ return hlsLoadPromise;
+ };
+ const createHlsLoader = () => {
+ class GMHlsLoader {
+ constructor(config) {
+ this.config = config;
+ this.context = null;
+ this.callbacks = null;
+ this.loader = null;
+ this.stats = this.createStats();
+ }
- createStats() {
- return {
- aborted: false,
- loaded: 0,
- retry: 0,
- total: 0,
- chunkCount: 0,
- bwEstimate: 0,
- trequest: 0,
- tfirst: 0,
- tload: 0,
- loading: {start: 0, first: 0, end: 0},
- parsing: {start: 0, end: 0},
- buffering: {start: 0, first: 0, end: 0}
- };
- }
+ createStats() {
+ return {
+ aborted: false,
+ loaded: 0,
+ retry: 0,
+ total: 0,
+ chunkCount: 0,
+ bwEstimate: 0,
+ trequest: 0,
+ tfirst: 0,
+ tload: 0,
+ loading: {start: 0, first: 0, end: 0},
+ parsing: {start: 0, end: 0},
+ buffering: {start: 0, first: 0, end: 0}
+ };
+ }
- destroy() {
- this.abort();
- }
+ destroy() {
+ this.abort();
+ }
- abort() {
- if (this.stats) this.stats.aborted = true;
- if (this.loader) {
- try {
- this.loader.abort();
- } catch (e) {
- }
- }
- this.loader = null;
- }
+ abort() {
+ if (this.stats) this.stats.aborted = true;
+ if (this.loader) {
+ try {
+ this.loader.abort();
+ } catch (e) {
+ }
+ }
+ this.loader = null;
+ }
- load(context, config, callbacks) {
- this.context = context;
- this.callbacks = callbacks;
- const requestUrl = context.url;
- const wantsArrayBuffer = context.responseType === "arraybuffer" || /\.(?:ts|m4s|mp4|key)(?:[?#]|$)/i.test(requestUrl);
- const startedAt = performance.now();
- const stats = this.stats = this.createStats();
- stats.trequest = startedAt;
- stats.tfirst = startedAt;
- stats.tload = startedAt;
- stats.loading.start = startedAt;
- this.loader = _GM_xmlhttpRequest({
- method: "GET",
- url: requestUrl,
- responseType: "text",
- overrideMimeType: wantsArrayBuffer ? "text/plain; charset=x-user-defined" : void 0,
- timeout: config && config.timeout ? config.timeout : 2e4,
- headers: {
- Accept: wantsArrayBuffer ? "*/*" : "application/vnd.apple.mpegurl, application/x-mpegURL, */*"
- },
- onprogress: (event) => {
- stats.loaded = Number(event && event.loaded ? event.loaded : stats.loaded || 0);
- stats.total = Number(event && event.total ? event.total : stats.total || stats.loaded || 0);
- if (!stats.loading.first && stats.loaded > 0) {
- stats.loading.first = performance.now();
- }
- },
- onload: (r) => {
- const status = Number(r.status || 0);
- const response = {
- code: status,
- text: r.statusText || "",
- url: r.finalUrl || requestUrl
- };
- stats.tfirst = stats.tfirst || performance.now();
- stats.tload = performance.now();
- stats.loading.first = stats.loading.first || stats.tload;
- stats.loading.end = stats.tload;
- if (status < 200 || status >= 300) {
- callbacks.onError && callbacks.onError(response, context, null, stats);
- return;
- }
- const responseText = r.responseText || r.response || "";
- const data = wantsArrayBuffer ? binaryTextToArrayBuffer(responseText) : responseText;
- stats.loaded = data && data.byteLength ? data.byteLength : data && data.length ? data.length : stats.loaded || 0;
- stats.total = stats.total || stats.loaded;
- stats.bwEstimate = stats.loading.end > stats.loading.first ? Math.round(stats.total * 8e3 / (stats.loading.end - stats.loading.first)) : 0;
- callbacks.onSuccess && callbacks.onSuccess({data, url: response.url}, stats, context, response);
- },
- onerror: () => {
- callbacks.onError && callbacks.onError({
- code: 0,
- text: "network error",
- url: requestUrl
- }, context, null, stats);
- },
- ontimeout: () => {
- stats.tload = performance.now();
- stats.loading.end = stats.tload;
- callbacks.onTimeout && callbacks.onTimeout(stats, context, null);
- }
- });
+ load(context, config, callbacks) {
+ this.context = context;
+ this.callbacks = callbacks;
+ const requestUrl = context.url;
+ const wantsArrayBuffer = context.responseType === "arraybuffer" || /\.(?:ts|m4s|mp4|key)(?:[?#]|$)/i.test(requestUrl);
+ const startedAt = performance.now();
+ const stats = this.stats = this.createStats();
+ stats.trequest = startedAt;
+ stats.tfirst = startedAt;
+ stats.tload = startedAt;
+ stats.loading.start = startedAt;
+ this.loader = _GM_xmlhttpRequest({
+ method: "GET",
+ url: requestUrl,
+ responseType: "text",
+ overrideMimeType: wantsArrayBuffer ? "text/plain; charset=x-user-defined" : void 0,
+ timeout: config && config.timeout ? config.timeout : 2e4,
+ headers: {
+ Accept: wantsArrayBuffer ? "*/*" : "application/vnd.apple.mpegurl, application/x-mpegURL, */*"
+ },
+ onprogress: (event) => {
+ stats.loaded = Number(event && event.loaded ? event.loaded : stats.loaded || 0);
+ stats.total = Number(event && event.total ? event.total : stats.total || stats.loaded || 0);
+ if (!stats.loading.first && stats.loaded > 0) {
+ stats.loading.first = performance.now();
}
- }
-
- return GMHlsLoader;
- };
- const isM3U8Url = (url) => /\.m3u8(?:[?#].*)?$/i.test(String(url || ""));
- const attachHlsToVideo = (videoEl, src) => {
- return new Promise((resolve, reject) => {
- if (!videoEl || !src) {
- reject(new Error("video element or source missing"));
- return;
- }
- const HlsClass = getHlsClass();
- if (!HlsClass) {
- videoEl.src = src;
- videoEl.load && videoEl.load();
- resolve(false);
- return;
- }
- const hls = new HlsClass({
- enableWorker: false,
- lowLatencyMode: true,
- loader: createHlsLoader(),
- autoStartLoad: true,
- startPosition: 0,
- capLevelToPlayerSize: true,
- testBandwidth: false,
- preferManagedMediaSource: false,
- maxBufferLength: 6,
- maxMaxBufferLength: 12,
- backBufferLength: 30,
- maxBufferHole: 0.5,
- nudgeOffset: 0.1,
- manifestLoadingMaxRetry: 2,
- levelLoadingMaxRetry: 2,
- fragLoadingMaxRetry: 2,
- manifestLoadingTimeOut: 12e3,
- levelLoadingTimeOut: 12e3,
- fragLoadingTimeOut: 12e3,
- abrEwmaFastLive: 3,
- abrEwmaSlowLive: 9
- });
- hls.on(HlsClass.Events.MANIFEST_PARSED, () => {
- hls.startLoad(0);
- videoEl.play().catch(() => {
- });
- resolve(true);
- });
- hls.on(HlsClass.Events.ERROR, (_, data) => {
- if (!data || !data.fatal) return;
- clog.error("HLS: 播放失败", data);
- if (data.type === HlsClass.ErrorTypes.MEDIA_ERROR) {
- if (videoEl.readyState >= 2) {
- try {
- hls.recoverMediaError && hls.recoverMediaError();
- } catch (e) {
- }
- return;
- }
- }
- reject(new Error(data.details || "HLS fatal error"));
- });
- hls.loadSource(src);
- hls.attachMedia(videoEl);
- videoEl._hls = hls;
- });
- };
- const destroyHls = (videoEl) => {
- if (videoEl && videoEl._hls) {
- try {
- videoEl._hls.destroy();
- } catch (e) {
- }
- videoEl._hls = null;
- }
- };
- const JAVXY_TOKEN = [118, 119, 112, 71, 97, 110, 28, 84, 124, 65, 76, 102, 65, 16, 77, 109, 64, 82, 85, 83, 67, 92, 125, 108, 83, 65, 124, 107, 84, 104, 71, 84, 17, 124, 118, 125, 104, 8, 125, 96, 112, 103, 29, 18, 82, 83, 87, 84].map((v) => String.fromCharCode(v ^ 37)).join("");
- const JAVXY_ENDPOINTS = [
- {host: String.fromCharCode(106, 97, 118, 120, 121, 46, 99, 99, 46, 99, 100), label: "Javxy"},
- {
- host: String.fromCharCode(119, 111, 114, 107, 101, 114, 46, 106, 97, 118, 120, 121, 46, 99, 99, 46, 99, 100),
- label: "Javxy Worker"
- }
- ];
- const JAVXY_SOURCE_LABELS = {
- "Tokyo-Hot": "Javxy | Tokyo-Hot",
- FC2: "Javxy | FC2",
- Direct: "Javxy | Direct",
- DMM: "Javxy | dmm",
- MGStage: "Javxy | MGStage",
- DUGA: "Javxy | DUGA",
- MYWIFE: "Javxy | MyWife",
- JavTrailers: "Javxy | JavTrailers",
- JavDB: "Javxy | Javdb",
- AVWikiDB: "Javxy | AVWikiDB",
- JAVDatabase: "Javxy | JAVDatabase",
- HEYZO: "Javxy | Heyzo",
- HeyDouga: "Javxy | HeyDouga",
- PACO: "Javxy | Paco",
- "10MU": "Javxy | 10mu",
- Caribbean: "Javxy | 加勒比",
- "1Pondo": "Javxy | 一本道"
- };
- const JAVXY_QUALITY_OPTIONS = [
- {quality: "4k", text: "4K"},
- {quality: "hhb", text: "1080p"},
- {quality: "1080p", text: "1080p"},
- {quality: "hmb", text: "720p"},
- {quality: "720p", text: "720p"},
- {quality: "mhb", text: "576p"},
- {quality: "540p", text: "540p"},
- {quality: "mmb", text: "432p"},
- {quality: "480p", text: "480p"},
- {quality: "396p", text: "396p"},
- {quality: "360p", text: "360p"},
- {quality: "240p", text: "240p"}
- ];
- const selectHighestQuality = (qualityMap) => {
- const rank = new Map(JAVXY_QUALITY_OPTIONS.map((item, index) => [item.quality, index]));
- return Object.keys(qualityMap || {}).filter((key) => qualityMap[key]).sort((a, b) => (rank.get(a) ?? -1) - (rank.get(b) ?? -1))[0] || null;
- };
- const sortQualityKeys = (qualityMap) => {
- const rank = new Map(JAVXY_QUALITY_OPTIONS.map((item, index) => [item.quality, index]));
- return Object.keys(qualityMap || {}).filter((key) => qualityMap[key]).sort((a, b) => (rank.get(a) ?? -1) - (rank.get(b) ?? -1));
- };
- const normalizeJavxySource = (value) => {
- const raw = String(value || "").trim().toLowerCase();
- if (raw.includes("fc2")) return "FC2";
- if (raw.includes("mgstage")) return "MGStage";
- if (raw.includes("heydouga")) return "Direct";
- if (raw.includes("mywife")) return "MYWIFE";
- if (raw.includes("duga")) return "DUGA";
- if (raw.includes("javtrailers")) return "JavTrailers";
- if (raw.includes("javdb")) return "JavDB";
- if (raw.includes("avwikidb")) return "AVWikiDB";
- if (raw.includes("javdatabase")) return "JAVDatabase";
- if (raw.includes("dmm")) return "DMM";
- if (raw === "direct" || raw.includes("heyzo") || raw.includes("heydouga") || raw.includes("paco") || raw.includes("10musume") || raw.includes("10mu") || raw.includes("1pondo") || raw.includes("caribbean") || raw.includes("tokyo-hot") || raw.includes("tokyohot")) return "Direct";
- return String(value || "").trim();
- };
- const javxyQualityMapToDmmFormat = (javxyQualityMap) => {
- const result = {};
- const qualityKeys = qualityOptions.map((o) => o.quality);
- for (const [key, url] of Object.entries(javxyQualityMap || {})) {
- if (!url) continue;
- const lowerKey = key.toLowerCase();
- if (qualityKeys.includes(key)) {
- result[key] = url;
- } else if (qualityKeys.includes(lowerKey)) {
- result[lowerKey] = url;
- }
- }
- return result;
- };
- const fromJavxyCcCd = async (id, rawCode = "", options = {}) => {
- const query = String(id || rawCode || "").trim();
- if (!query) {
- clog.debug("Javxy 跳过:查询词为空");
- return null;
- }
- for (const endpoint of JAVXY_ENDPOINTS) {
- const params = new URLSearchParams({client: "laosiji-new"});
- if (Array.isArray(options.skip) && options.skip.length) params.set("skip", options.skip.join(","));
- if (Array.isArray(options.prefer) && options.prefer.length) params.set("prefer", options.prefer.join(","));
- if (Array.isArray(options.source) && options.source.length) params.set("source", options.source.join(","));
- if (options.playbackFallback) params.set("purpose", "playback-fallback");
- const apiUrl2 = `https://${endpoint.host}/trailers/${encodeURIComponent(query)}?${params}`;
- clog.debug("Javxy 请求 API", {query, apiUrl: apiUrl2, endpoint: endpoint.label});
- let r;
- try {
- r = await gmHttp.get(apiUrl2, null, {
- "Accept": "application/json,text/plain,*/*",
- "X-Javxy-Token": JAVXY_TOKEN
- }, {timeout: 8e3});
- } catch (e) {
- clog.debug("Javxy API 网络失败,尝试下一个节点", {endpoint: endpoint.label, error: e.message});
- continue;
- }
- if (!r) {
- clog.debug("Javxy API 无响应,尝试下一个节点", {endpoint: endpoint.label});
- continue;
- }
- const trailerUrl = String((r == null ? void 0 : r.trailer) || "").trim();
- if (!trailerUrl) {
- clog.debug("Javxy 无 trailer 字段", {endpoint: endpoint.label, keys: Object.keys(r || {})});
- return null;
- }
- const qualityMap = (r == null ? void 0 : r.qualities) && typeof r.qualities === "object" ? r.qualities : {};
- const quality = (r == null ? void 0 : r.quality) && qualityMap[r.quality] ? r.quality : selectHighestQuality(qualityMap);
- const sourceBase = JAVXY_SOURCE_LABELS[r == null ? void 0 : r.source] || `Javxy | ${(r == null ? void 0 : r.source) || "dmm"}`;
- const directUrl = qualityMap[quality] || trailerUrl;
- clog.debug("Javxy 返回结果", {
- endpoint: endpoint.label,
- source: r == null ? void 0 : r.source,
- quality,
- qualities: Object.keys(qualityMap),
- url: directUrl
- });
- const dmmQualityMap = javxyQualityMapToDmmFormat(qualityMap);
- return {
- url: directUrl,
- source: sourceBase,
- type: String((r == null ? void 0 : r.type) || "").trim() || "video",
- qualities: qualityMap,
- quality,
- directUrl,
- code: id,
- rawCode,
- javxySource: String((r == null ? void 0 : r.source) || "").trim(),
- requiresJP: Boolean(r == null ? void 0 : r.requiresJP),
- urls: Array.isArray(r == null ? void 0 : r.urls) && r.urls.length ? r.urls : sortQualityKeys(qualityMap).map((key) => qualityMap[key]),
- dmmQualityMap
+ },
+ onload: (r) => {
+ const status = Number(r.status || 0);
+ const response = {
+ code: status,
+ text: r.statusText || "",
+ url: r.finalUrl || requestUrl
};
- }
- 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"]
+ stats.tfirst = stats.tfirst || performance.now();
+ stats.tload = performance.now();
+ stats.loading.first = stats.loading.first || stats.tload;
+ stats.loading.end = stats.tload;
+ if (status < 200 || status >= 300) {
+ callbacks.onError && callbacks.onError(response, context, null, stats);
+ return;
+ }
+ const responseText = r.responseText || r.response || "";
+ const data = wantsArrayBuffer ? binaryTextToArrayBuffer(responseText) : responseText;
+ stats.loaded = data && data.byteLength ? data.byteLength : data && data.length ? data.length : stats.loaded || 0;
+ stats.total = stats.total || stats.loaded;
+ stats.bwEstimate = stats.loading.end > stats.loading.first ? Math.round(stats.total * 8e3 / (stats.loading.end - stats.loading.first)) : 0;
+ callbacks.onSuccess && callbacks.onSuccess({data, url: response.url}, stats, context, response);
+ },
+ onerror: () => {
+ callbacks.onError && callbacks.onError({
+ code: 0,
+ text: "network error",
+ url: requestUrl
+ }, context, null, stats);
+ },
+ ontimeout: () => {
+ stats.tload = performance.now();
+ stats.loading.end = stats.tload;
+ callbacks.onTimeout && callbacks.onTimeout(stats, context, null);
+ }
});
- 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 GMHlsLoader;
+ };
+ const isM3U8Url = (url) => /\.m3u8(?:[?#].*)?$/i.test(String(url || ""));
+ const attachHlsToVideo = (videoEl, src) => {
+ return new Promise((resolve, reject) => {
+ if (!videoEl || !src) {
+ reject(new Error("video element or source missing"));
+ return;
+ }
+ const HlsClass = getHlsClass();
+ if (!HlsClass) {
+ videoEl.src = src;
+ videoEl.load && videoEl.load();
+ resolve(false);
+ return;
+ }
+ const hls = new HlsClass({
+ enableWorker: false,
+ lowLatencyMode: true,
+ loader: createHlsLoader(),
+ autoStartLoad: true,
+ startPosition: 0,
+ capLevelToPlayerSize: true,
+ testBandwidth: false,
+ preferManagedMediaSource: false,
+ maxBufferLength: 6,
+ maxMaxBufferLength: 12,
+ backBufferLength: 30,
+ maxBufferHole: 0.5,
+ nudgeOffset: 0.1,
+ manifestLoadingMaxRetry: 2,
+ levelLoadingMaxRetry: 2,
+ fragLoadingMaxRetry: 2,
+ manifestLoadingTimeOut: 12e3,
+ levelLoadingTimeOut: 12e3,
+ fragLoadingTimeOut: 12e3,
+ abrEwmaFastLive: 3,
+ abrEwmaSlowLive: 9
+ });
+ hls.on(HlsClass.Events.MANIFEST_PARSED, () => {
+ hls.startLoad(0);
+ videoEl.play().catch(() => {
+ });
+ resolve(true);
+ });
+ hls.on(HlsClass.Events.ERROR, (_, data) => {
+ if (!data || !data.fatal) return;
+ clog.error("HLS: 播放失败", data);
+ if (data.type === HlsClass.ErrorTypes.MEDIA_ERROR) {
+ if (videoEl.readyState >= 2) {
+ try {
+ hls.recoverMediaError && hls.recoverMediaError();
+ } catch (e) {
+ }
+ return;
+ }
}
+ reject(new Error(data.details || "HLS fatal error"));
+ });
+ hls.loadSource(src);
+ hls.attachMedia(videoEl);
+ videoEl._hls = hls;
+ });
+ };
+ const destroyHls = (videoEl) => {
+ if (videoEl && videoEl._hls) {
+ try {
+ videoEl._hls.destroy();
+ } catch (e) {
+ }
+ videoEl._hls = null;
+ }
+ };
+ const JAVXY_TOKEN = [118, 119, 112, 71, 97, 110, 28, 84, 124, 65, 76, 102, 65, 16, 77, 109, 64, 82, 85, 83, 67, 92, 125, 108, 83, 65, 124, 107, 84, 104, 71, 84, 17, 124, 118, 125, 104, 8, 125, 96, 112, 103, 29, 18, 82, 83, 87, 84].map((v) => String.fromCharCode(v ^ 37)).join("");
+ const JAVXY_ENDPOINTS = [
+ {host: String.fromCharCode(106, 97, 118, 120, 121, 46, 99, 99, 46, 99, 100), label: "Javxy"},
+ {
+ host: String.fromCharCode(119, 111, 114, 107, 101, 114, 46, 106, 97, 118, 120, 121, 46, 99, 99, 46, 99, 100),
+ label: "Javxy Worker"
+ }
+ ];
+ const JAVXY_SOURCE_LABELS = {
+ "Tokyo-Hot": "Javxy | Tokyo-Hot",
+ FC2: "Javxy | FC2",
+ Direct: "Javxy | Direct",
+ DMM: "Javxy | dmm",
+ MGStage: "Javxy | MGStage",
+ DUGA: "Javxy | DUGA",
+ MYWIFE: "Javxy | MyWife",
+ JavTrailers: "Javxy | JavTrailers",
+ JavDB: "Javxy | Javdb",
+ AVWikiDB: "Javxy | AVWikiDB",
+ JAVDatabase: "Javxy | JAVDatabase",
+ HEYZO: "Javxy | Heyzo",
+ HeyDouga: "Javxy | HeyDouga",
+ PACO: "Javxy | Paco",
+ "10MU": "Javxy | 10mu",
+ Caribbean: "Javxy | 加勒比",
+ "1Pondo": "Javxy | 一本道"
+ };
+ const JAVXY_QUALITY_OPTIONS = [
+ {quality: "4k", text: "4K"},
+ {quality: "hhb", text: "1080p"},
+ {quality: "1080p", text: "1080p"},
+ {quality: "hmb", text: "720p"},
+ {quality: "720p", text: "720p"},
+ {quality: "mhb", text: "576p"},
+ {quality: "540p", text: "540p"},
+ {quality: "mmb", text: "432p"},
+ {quality: "480p", text: "480p"},
+ {quality: "396p", text: "396p"},
+ {quality: "360p", text: "360p"},
+ {quality: "240p", text: "240p"}
+ ];
+ const selectHighestQuality = (qualityMap) => {
+ const rank = new Map(JAVXY_QUALITY_OPTIONS.map((item, index) => [item.quality, index]));
+ return Object.keys(qualityMap || {}).filter((key) => qualityMap[key]).sort((a, b) => (rank.get(a) ?? -1) - (rank.get(b) ?? -1))[0] || null;
+ };
+ const sortQualityKeys = (qualityMap) => {
+ const rank = new Map(JAVXY_QUALITY_OPTIONS.map((item, index) => [item.quality, index]));
+ return Object.keys(qualityMap || {}).filter((key) => qualityMap[key]).sort((a, b) => (rank.get(a) ?? -1) - (rank.get(b) ?? -1));
+ };
+ const normalizeJavxySource = (value) => {
+ const raw = String(value || "").trim().toLowerCase();
+ if (raw.includes("fc2")) return "FC2";
+ if (raw.includes("mgstage")) return "MGStage";
+ if (raw.includes("heydouga")) return "Direct";
+ if (raw.includes("mywife")) return "MYWIFE";
+ if (raw.includes("duga")) return "DUGA";
+ if (raw.includes("javtrailers")) return "JavTrailers";
+ if (raw.includes("javdb")) return "JavDB";
+ if (raw.includes("avwikidb")) return "AVWikiDB";
+ if (raw.includes("javdatabase")) return "JAVDatabase";
+ if (raw.includes("dmm")) return "DMM";
+ if (raw === "direct" || raw.includes("heyzo") || raw.includes("heydouga") || raw.includes("paco") || raw.includes("10musume") || raw.includes("10mu") || raw.includes("1pondo") || raw.includes("caribbean") || raw.includes("tokyo-hot") || raw.includes("tokyohot")) return "Direct";
+ return String(value || "").trim();
+ };
+ const javxyQualityMapToDmmFormat = (javxyQualityMap) => {
+ const result = {};
+ const qualityKeys = qualityOptions.map((o) => o.quality);
+ for (const [key, url] of Object.entries(javxyQualityMap || {})) {
+ if (!url) continue;
+ const lowerKey = key.toLowerCase();
+ if (qualityKeys.includes(key)) {
+ result[key] = url;
+ } else if (qualityKeys.includes(lowerKey)) {
+ result[lowerKey] = url;
+ }
+ }
+ return result;
+ };
+ const fromJavxyCcCd = async (id, rawCode = "", options = {}) => {
+ const query = String(id || rawCode || "").trim();
+ if (!query) {
+ clog.debug("Javxy 跳过:查询词为空");
+ return null;
+ }
+ for (const endpoint of JAVXY_ENDPOINTS) {
+ const params = new URLSearchParams({client: "laosiji-new"});
+ if (Array.isArray(options.skip) && options.skip.length) params.set("skip", options.skip.join(","));
+ if (Array.isArray(options.prefer) && options.prefer.length) params.set("prefer", options.prefer.join(","));
+ if (Array.isArray(options.source) && options.source.length) params.set("source", options.source.join(","));
+ if (options.playbackFallback) params.set("purpose", "playback-fallback");
+ const apiUrl2 = `https://${endpoint.host}/trailers/${encodeURIComponent(query)}?${params}`;
+ clog.debug("Javxy 请求 API", {query, apiUrl: apiUrl2, endpoint: endpoint.label});
+ let r;
+ try {
+ r = await gmHttp.get(apiUrl2, null, {
+ "Accept": "application/json,text/plain,*/*",
+ "X-Javxy-Token": JAVXY_TOKEN
+ }, {timeout: 8e3});
+ } catch (e) {
+ clog.debug("Javxy API 网络失败,尝试下一个节点", {endpoint: endpoint.label, error: e.message});
+ continue;
+ }
+ if (!r) {
+ clog.debug("Javxy API 无响应,尝试下一个节点", {endpoint: endpoint.label});
+ continue;
+ }
+ const trailerUrl = String((r == null ? void 0 : r.trailer) || "").trim();
+ if (!trailerUrl) {
+ clog.debug("Javxy 无 trailer 字段", {endpoint: endpoint.label, keys: Object.keys(r || {})});
return null;
- };
+ }
+ const qualityMap = (r == null ? void 0 : r.qualities) && typeof r.qualities === "object" ? r.qualities : {};
+ const quality = (r == null ? void 0 : r.quality) && qualityMap[r.quality] ? r.quality : selectHighestQuality(qualityMap);
+ const sourceBase = JAVXY_SOURCE_LABELS[r == null ? void 0 : r.source] || `Javxy | ${(r == null ? void 0 : r.source) || "dmm"}`;
+ const directUrl = qualityMap[quality] || trailerUrl;
+ clog.debug("Javxy 返回结果", {
+ endpoint: endpoint.label,
+ source: r == null ? void 0 : r.source,
+ quality,
+ qualities: Object.keys(qualityMap),
+ url: directUrl
+ });
+ const dmmQualityMap = javxyQualityMapToDmmFormat(qualityMap);
+ return {
+ url: directUrl,
+ source: sourceBase,
+ type: String((r == null ? void 0 : r.type) || "").trim() || "video",
+ qualities: qualityMap,
+ quality,
+ directUrl,
+ code: id,
+ rawCode,
+ javxySource: String((r == null ? void 0 : r.source) || "").trim(),
+ requiresJP: Boolean(r == null ? void 0 : r.requiresJP),
+ urls: Array.isArray(r == null ? void 0 : r.urls) && r.urls.length ? r.urls : sortQualityKeys(qualityMap).map((key) => qualityMap[key]),
+ dmmQualityMap
+ };
+ }
+ return null;
+ };
+ const fallbackJavxyResult = async (code, rawCode = "", failedSources = [], options = {}) => {
+ const skip = [...new Set((failedSources || []).map((source2) => normalizeJavxySource(source2)).filter(Boolean))];
+ const source = [...new Set((options.source || []).map((s) => String(s || "").trim()).filter(Boolean))];
+ if (!skip.length && !source.length) return null;
+ clog.debug("Javxy 播放失败回落查询", {code, skip, source});
+ return fromJavxyCcCd(code, rawCode, {skip, source, playbackFallback: true});
+ };
+ const getJavxyVideoUrls = async (code, failedSources = []) => {
+ const result = await fallbackJavxyResult(code, code, failedSources, {
+ source: ["JavTrailers", "JavDB"]
+ });
+ if ((result == null ? void 0 : result.dmmQualityMap) && Object.keys(result.dmmQualityMap).length > 0) {
+ return result.dmmQualityMap;
+ }
+ if (result == null ? void 0 : result.url) {
+ const qualityMap = {};
+ qualityMap["720p"] = result.url;
+ return qualityMap;
+ }
+ return null;
+ };
- class JavxyPreviewVideoPlugin extends BasePlugin {
- getName() {
- return "JavxyPreviewVideoPlugin";
- }
+ class JavxyPreviewVideoPlugin extends BasePlugin {
+ getName() {
+ return "JavxyPreviewVideoPlugin";
+ }
- _attachVideoSrc(videoEl, src) {
- if (!videoEl || !src) return;
- destroyHls(videoEl);
- if (isM3U8Url(src)) {
- const HlsClass = getHlsClass();
- if (HlsClass) {
- attachHlsToVideo(videoEl, src).catch((err) => {
- clog.error("视频预览 HLS 加载失败:", err);
- videoEl.src = src;
- videoEl.load && videoEl.load();
- });
- } else {
- loadHlsLibrary().then((HlsClass2) => {
- if (videoEl && videoEl.isConnected && HlsClass2) {
- attachHlsToVideo(videoEl, src).catch((err) => {
- clog.error("视频预览 HLS 延迟加载失败:", err);
- videoEl.src = src;
- videoEl.load && videoEl.load();
- });
- } else if (videoEl && videoEl.isConnected) {
- videoEl.src = src;
- videoEl.load && videoEl.load();
- }
- });
- }
- } else {
+ _attachVideoSrc(videoEl, src) {
+ if (!videoEl || !src) return;
+ destroyHls(videoEl);
+ if (isM3U8Url(src)) {
+ const HlsClass = getHlsClass();
+ if (HlsClass) {
+ attachHlsToVideo(videoEl, src).catch((err) => {
+ clog.error("视频预览 HLS 加载失败:", err);
+ videoEl.src = src;
+ videoEl.load && videoEl.load();
+ });
+ } else {
+ loadHlsLibrary().then((HlsClass2) => {
+ if (videoEl && videoEl.isConnected && HlsClass2) {
+ attachHlsToVideo(videoEl, src).catch((err) => {
+ clog.error("视频预览 HLS 延迟加载失败:", err);
videoEl.src = src;
videoEl.load && videoEl.load();
+ });
+ } else if (videoEl && videoEl.isConnected) {
+ videoEl.src = src;
+ videoEl.load && videoEl.load();
}
+ });
}
+ } else {
+ videoEl.src = src;
+ videoEl.load && videoEl.load();
+ }
+ }
- _selectDefaultQuality(qualityList, intendedDefault) {
- if (!qualityList || qualityList.length === 0) return null;
- const availableSet = new Set(qualityList);
- if (availableSet.has(intendedDefault)) return intendedDefault;
- const priorityOrder = qualityOptions.map((o) => o.quality).reverse();
- for (const q of priorityOrder) if (availableSet.has(q)) return q;
- return qualityList[0];
- }
+ _selectDefaultQuality(qualityList, intendedDefault) {
+ if (!qualityList || qualityList.length === 0) return null;
+ const availableSet = new Set(qualityList);
+ if (availableSet.has(intendedDefault)) return intendedDefault;
+ const priorityOrder = qualityOptions.map((o) => o.quality).reverse();
+ for (const q of priorityOrder) if (availableSet.has(q)) return q;
+ return qualityList[0];
+ }
- async initCss() {
- return `
+ async initCss() {
+ return `
.jhs-javxy-video-modal {
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.95); z-index: 12345700;
@@ -10259,54 +10256,54 @@ ${err.stack}` : "");
color: #fff; font-size: 16px; text-align: center;
}
`;
- }
+ }
- async handle() {
- if (!window.isDetailPage) return;
- const carNum2 = this.getPageInfo().carNum;
- if (!carNum2) return;
- this._addJavxyButton(carNum2);
- }
+ async handle() {
+ if (!window.isDetailPage) return;
+ const carNum2 = this.getPageInfo().carNum;
+ if (!carNum2) return;
+ this._addJavxyButton(carNum2);
+ }
- _addJavxyButton(carNum2) {
- let $target = null;
- if (isJavDb$1) {
- $target = $('a[title="複製番號"]');
- } else if (isJavBus$1) {
- const headerSpans = document.querySelectorAll("span.header");
- for (const span of headerSpans) {
- if (span.textContent.trim() === "識別碼:") {
- const nextSpan = span.nextElementSibling;
- if (nextSpan && nextSpan.tagName === "SPAN") {
- const copyBtn = nextSpan.nextElementSibling;
- if (copyBtn && copyBtn.tagName === "BUTTON" && copyBtn.textContent.trim() === "复制") {
- $target = $(copyBtn);
- } else {
- $target = $(nextSpan);
- }
- }
- break;
- }
- }
+ _addJavxyButton(carNum2) {
+ let $target = null;
+ if (isJavDb$1) {
+ $target = $('a[title="複製番號"]');
+ } else if (isJavBus$1) {
+ const headerSpans = document.querySelectorAll("span.header");
+ for (const span of headerSpans) {
+ if (span.textContent.trim() === "識別碼:") {
+ const nextSpan = span.nextElementSibling;
+ if (nextSpan && nextSpan.tagName === "SPAN") {
+ const copyBtn = nextSpan.nextElementSibling;
+ if (copyBtn && copyBtn.tagName === "BUTTON" && copyBtn.textContent.trim() === "复制") {
+ $target = $(copyBtn);
+ } else {
+ $target = $(nextSpan);
+ }
}
- if (!$target || !$target.length) return;
- const $btn = $(`
视频预览`);
- $btn.on("click", async (e) => {
- e.preventDefault();
- e.stopPropagation();
- $btn.css("pointer-events", "none").find("span").text("...");
- try {
- await this._openJavxyPlayer(carNum2);
- } finally {
- $btn.css("pointer-events", "").find("span").text("视频预览");
- }
- });
- $target.after($btn);
+ break;
+ }
}
+ }
+ if (!$target || !$target.length) return;
+ const $btn = $(`
视频预览`);
+ $btn.on("click", async (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ $btn.css("pointer-events", "none").find("span").text("...");
+ try {
+ await this._openJavxyPlayer(carNum2);
+ } finally {
+ $btn.css("pointer-events", "").find("span").text("视频预览");
+ }
+ });
+ $target.after($btn);
+ }
- async _openJavxyPlayer(carNum2) {
- if ($("#jhs-javxy-video-modal").length === 0) {
- $("body").append(`
+ async _openJavxyPlayer(carNum2) {
+ if ($("#jhs-javxy-video-modal").length === 0) {
+ $("body").append(`
@@ -10316,88 +10313,88 @@ ${err.stack}` : "");
`);
- const $modal2 = $("#jhs-javxy-video-modal");
- $modal2.on("click", (e) => {
- if (e.target.id === "jhs-javxy-video-modal") {
- $modal2.removeClass("is-open");
- const videoEl2 = document.getElementById("jhs-javxy-video");
- if (videoEl2) {
- destroyHls(videoEl2);
- videoEl2.pause();
- }
- }
- });
- $(document).on("keydown", (e) => {
- if (e.key === "Escape" && $modal2.hasClass("is-open")) {
- $modal2.removeClass("is-open");
- const videoEl2 = document.getElementById("jhs-javxy-video");
- if (videoEl2) {
- destroyHls(videoEl2);
- videoEl2.pause();
- }
- }
- });
+ const $modal2 = $("#jhs-javxy-video-modal");
+ $modal2.on("click", (e) => {
+ if (e.target.id === "jhs-javxy-video-modal") {
+ $modal2.removeClass("is-open");
+ const videoEl2 = document.getElementById("jhs-javxy-video");
+ if (videoEl2) {
+ destroyHls(videoEl2);
+ videoEl2.pause();
}
- const $modal = $("#jhs-javxy-video-modal");
- const $wrapper = $modal.find(".jhs-javxy-video-wrapper");
- const $qualityBar = $modal.find(".jhs-javxy-quality-bar");
- $wrapper.html('
视频预览加载中...
');
- $qualityBar.empty();
- $modal.addClass("is-open");
- let videoMap = null;
- try {
- videoMap = await getJavxyVideoUrls(carNum2);
- } catch (err) {
- $wrapper.html(`
视频预览请求失败: ${err.message}
`);
- return;
+ }
+ });
+ $(document).on("keydown", (e) => {
+ if (e.key === "Escape" && $modal2.hasClass("is-open")) {
+ $modal2.removeClass("is-open");
+ const videoEl2 = document.getElementById("jhs-javxy-video");
+ if (videoEl2) {
+ destroyHls(videoEl2);
+ videoEl2.pause();
}
- if (!videoMap || Object.keys(videoMap).length === 0) {
- $wrapper.html('
未找到可用的视频源
');
- return;
- }
- const qualityList = Object.keys(videoMap);
- const defaultQuality = this._selectDefaultQuality(qualityList, "720p");
- const defaultUrl = videoMap[defaultQuality];
- $wrapper.html(`