新增下载高清封面

This commit is contained in:
lzyme.dev@gmail.com
2026-09-01 15:18:34 +08:00
parent d5c59ba6ac
commit 080cbb00ee
6 changed files with 167 additions and 7 deletions
+74 -3
View File
@@ -1253,6 +1253,15 @@ System.register("./__entry.js", ['jquery', 'localforage', 'toastify-js', 'blueim
);
insertStyle(mainCss);
insertStyle(subtitleModalCss);
const isEligibleDmmCoverCode = (value) => {
const raw = String(value || "").trim().toUpperCase();
if (!/^[A-Z0-9]{2,10}-\d{1,6}$/.test(raw)) return false;
const code = raw.replace(/\s+/g, "-").toUpperCase();
if (!/^[A-Z0-9]{2,10}-\d{1,6}$/.test(code) || /^FC2-/.test(code)) return false;
if (/^\d{6}[-_]\d{2,3}$/.test(code)) return false;
if (/^(DUGA|MYWIFE|HEYZO|PACO|10MU|1PONDO|CARIBBEAN|CARIB|TOKYO|GACHI|REAL|JUKU|AKA|NTR)-/.test(code)) return false;
return true;
};
window.$ = window.jQuery = $$1;
if (typeof $$1.trim !== "function") {
$$1.trim = function (text) {
@@ -10418,6 +10427,27 @@ ${err.stack}` : "");
return qualityMap;
}
return null;
};
const fromJavxyCover = async (code) => {
const query = String(code || "").trim();
if (!query) return null;
for (const endpoint of JAVXY_ENDPOINTS) {
const params = new URLSearchParams({client: "laosiji-new"});
const apiUrl2 = `https://${endpoint.host}/covers/${encodeURIComponent(query)}?${params}`;
let r;
try {
r = await gmHttp.get(apiUrl2, null, {
"Accept": "application/json,text/plain,*/*",
"X-Javxy-Token": JAVXY_TOKEN
}, {timeout: 15e3});
} catch (e) {
continue;
}
if (!r) continue;
if ((r == null ? void 0 : r.found) && (r.url || r.cover || r.highCover)) return r;
return null;
}
return null;
};
class JavxyPreviewVideoPlugin extends BasePlugin {
getName() {
@@ -12590,9 +12620,11 @@ ${err.stack}` : "");
$(".buttons button[data-clipboard-text*='magnet:']").each(((i, el) => {
const $copyBtn = $(el);
const magnet = $copyBtn.attr("data-clipboard-text");
$copyBtn.css("margin-right", parseInt($copyBtn.css("margin-right")) - 5 + "px");
$copyBtn.css("margin-right", parseInt($copyBtn.css("margin-right")) - 4 + "px");
const $downloadBtn = $copyBtn.siblings("a.button, button").first();
$downloadBtn.length && $downloadBtn.css("margin-right", parseInt($downloadBtn.css("margin-right")) - 5 + "px");
if ($downloadBtn.length) {
$downloadBtn.css("margin-right", parseInt($downloadBtn.css("margin-right")) + 3 + "px");
}
$("<button>").text("验车").addClass("button is-info is-small").click(((event) => {
event.stopPropagation();
event.preventDefault();
@@ -15618,7 +15650,7 @@ ${err.stack}` : "");
}
async createMenuBtn() {
const pageInfo = this.getPageInfo(), carNum2 = pageInfo.carNum,
buttonsHtml = '\n <div style="margin: 10px auto; display: flex; justify-content: space-between; align-items: center; flex-wrap:wrap;gap: 20px;">\n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="filterBtn" class="menu-btn" style="width: 120px; background-color:#de3333; color: white; text-align: center; padding: 8px 0;">\n <span>🚫 屏蔽</span>\n </a>\n <a id="favoriteBtn" class="menu-btn" style="width: 120px; background-color:#25b1dc; color: white; text-align: center; padding: 8px 0;">\n <span>⭐ 收藏</span>\n </a>\n <a id="hasDownBtn" class="menu-btn" style="width: 120px; background-color:#7bc73b; color: white; text-align: center; padding: 8px 0;">\n <span>📥️ 已下载</span>\n </a>\n <a id="hasWatchBtn" class="menu-btn" style="width: 120px; background-color:#d7a80c; color: white; text-align: center; padding: 8px 0;">\n <span>🔍 已观看</span>\n </a>\n </div>\n \n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="enable-magnets-filter" class="menu-btn" style="width: 140px; background-color: #c2bd4c; color: white; text-align: center; padding: 8px 0;">\n <span id="magnets-span">排序与过滤</span>\n </a>\n <a id="magnetSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to right, rgb(245,140,1), rgb(84,161,29)); color: white; text-align: center; padding: 8px 0;">\n <span>磁力搜索</span>\n </a>\n <a id="subtitleSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕搜索</span>\n </a>\n <!--\n <a id="xunLeiSubtitleBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (迅雷)</span>\n </a>\n <a id="search-subtitle-btn" class="menu-btn" style="width: 160px; background: linear-gradient(to bottom, #8d5656, rgb(196,159,91)); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (SubTitleCat)</span>\n </a>\n -->\n </div>\n </div>\n ';
buttonsHtml = '\n <div style="margin: 10px auto; display: flex; justify-content: space-between; align-items: center; flex-wrap:wrap;gap: 20px;">\n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="filterBtn" class="menu-btn" style="width: 120px; background-color:#de3333; color: white; text-align: center; padding: 8px 0;">\n <span>🚫 屏蔽</span>\n </a>\n <a id="favoriteBtn" class="menu-btn" style="width: 120px; background-color:#25b1dc; color: white; text-align: center; padding: 8px 0;">\n <span>⭐ 收藏</span>\n </a>\n <a id="hasDownBtn" class="menu-btn" style="width: 120px; background-color:#7bc73b; color: white; text-align: center; padding: 8px 0;">\n <span>📥️ 已下载</span>\n </a>\n <a id="hasWatchBtn" class="menu-btn" style="width: 120px; background-color:#d7a80c; color: white; text-align: center; padding: 8px 0;">\n <span>🔍 已观看</span>\n </a>\n </div>\n \n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="downloadCoverBtn" class="menu-btn" style="width: 140px; background-color: #9b59b6; color: white; text-align: center; padding: 8px 0;">\n <span>下载高清封面</span>\n </a>\n <a id="enable-magnets-filter" class="menu-btn" style="width: 140px; background-color: #c2bd4c; color: white; text-align: center; padding: 8px 0;">\n <span id="magnets-span">排序与过滤</span>\n </a>\n <a id="magnetSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to right, rgb(245,140,1), rgb(84,161,29)); color: white; text-align: center; padding: 8px 0;">\n <span>磁力搜索</span>\n </a>\n <a id="subtitleSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕搜索</span>\n </a>\n <!--\n <a id="xunLeiSubtitleBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (迅雷)</span>\n </a>\n <a id="search-subtitle-btn" class="menu-btn" style="width: 160px; background: linear-gradient(to bottom, #8d5656, rgb(196,159,91)); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (SubTitleCat)</span>\n </a>\n -->\n </div>\n </div>\n ';
isJavDb$1 && $(".tabs").after(buttonsHtml);
isJavBus$1 && $("#mag-submit-show").before(buttonsHtml);
$("#favoriteBtn").on("click", (() => this.favoriteOne()));
@@ -15679,6 +15711,45 @@ ${err.stack}` : "");
}));
$("#subtitleSearchBtn").on("click", (() => {
window.JavPackSubtitle.openSearchModal({details: {code: carNum2}});
}));
$("#downloadCoverBtn").on("click", (async () => {
if (!isEligibleDmmCoverCode(carNum2)) {
layer.msg("当前番号不支持下载高清封面");
return;
}
const $btn = $("#downloadCoverBtn");
$btn.addClass("is-loading").prop("disabled", true);
try {
const coverData = await fromJavxyCover(carNum2);
if (!coverData) {
layer.msg("未找到高清封面");
return;
}
const coverUrl = coverData.url || coverData.highCover || coverData.cover;
if (!coverUrl) {
layer.msg("封面链接为空");
return;
}
const fileName = `${carNum2}-cover.jpg`;
_GM_xmlhttpRequest({
method: "GET",
url: coverUrl,
responseType: "blob",
onload: (response) => {
if (response.status === 200) {
utils.download(response.response, fileName);
} else {
layer.msg(`封面下载失败: ${response.status}`);
}
},
onerror: () => layer.msg("封面下载网络错误"),
ontimeout: () => layer.msg("封面下载超时")
});
} catch (e) {
layer.msg("获取封面信息失败");
} finally {
$btn.removeClass("is-loading").prop("disabled", false);
}
}));
window.JavPackSubtitle.preload115Matches(carNum2);
this.showStatus(carNum2).then();
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jhs-enhance",
"version": "3.3.10",
"version": "3.3.11",
"description": "Jav-鉴黄师 油猴脚本 - 工程化版本",
"type": "module",
"scripts": {
+26
View File
@@ -211,10 +211,36 @@ const getJavxyVideoUrls = async (code, failedSources = []) => {
return null;
};
const fromJavxyCover = async (code) => {
const query = String(code || '').trim();
if (!query) return null;
for (const endpoint of JAVXY_ENDPOINTS) {
const params = new URLSearchParams({client: 'laosiji-new'});
const apiUrl = `https://${endpoint.host}/covers/${encodeURIComponent(query)}?${params}`;
let r;
try {
r = await gmHttp.get(apiUrl, null, {
'Accept': 'application/json,text/plain,*/*',
'X-Javxy-Token': JAVXY_TOKEN,
}, {timeout: 15000});
} catch (e) {
continue;
}
if (!r) continue;
if (r?.found && (r.url || r.cover || r.highCover)) return r;
return null;
}
return null;
};
export {
fromJavxyCcCd,
fallbackJavxyResult,
getJavxyVideoUrls,
fromJavxyCover,
normalizeJavxySource,
isJpSourceTemporarilyFailed,
markJpSourceTemporarilyFailed,
+11
View File
@@ -1043,6 +1043,16 @@ insertStyle(
insertStyle(mainCss);
insertStyle(subtitleModalCss);
const isEligibleDmmCoverCode = (value) => {
const raw = String(value || '').trim().toUpperCase();
if (!/^[A-Z0-9]{2,10}-\d{1,6}$/.test(raw)) return false;
const code = raw.replace(/\s+/g, '-').toUpperCase();
if (!/^[A-Z0-9]{2,10}-\d{1,6}$/.test(code) || /^FC2-/.test(code)) return false;
if (/^\d{6}[-_]\d{2,3}$/.test(code)) return false;
if (/^(DUGA|MYWIFE|HEYZO|PACO|10MU|1PONDO|CARIBBEAN|CARIB|TOKYO|GACHI|REAL|JUKU|AKA|NTR)-/.test(code)) return false;
return true;
};
export {
__defProp,
__typeError,
@@ -1052,6 +1062,7 @@ export {
isJavDb,
isJavBus,
isSearchPage,
isEligibleDmmCoverCode,
Status_RUNNING,
Status_SUCCESS,
Status_FAIL,
+54 -2
View File
@@ -1,6 +1,19 @@
import { BasePlugin } from '../core/base-plugin.js';
import { __publicField, isJavBus, isJavDb, Status_FILTER, Status_FAVORITE, Status_HAS_DOWN, Status_HAS_WATCH, NO, YES } from '../core/constants.js';
import {
__publicField,
isJavBus,
isJavDb,
isEligibleDmmCoverCode,
Status_FILTER,
Status_FAVORITE,
Status_HAS_DOWN,
Status_HAS_WATCH,
NO,
YES
} from '../core/constants.js';
import { HotkeyManager } from './preview-video.js';
import {GM_xmlhttpRequest} from 'vite-plugin-monkey/dist/client';
import {fromJavxyCover} from '../api/javxy.js';
class DetailPageButtonPlugin extends BasePlugin {
getName() {
@@ -23,7 +36,7 @@ class DetailPageButtonPlugin extends BasePlugin {
}
async createMenuBtn() {
const pageInfo = this.getPageInfo(), carNum2 = pageInfo.carNum,
buttonsHtml = '\n <div style="margin: 10px auto; display: flex; justify-content: space-between; align-items: center; flex-wrap:wrap;gap: 20px;">\n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="filterBtn" class="menu-btn" style="width: 120px; background-color:#de3333; color: white; text-align: center; padding: 8px 0;">\n <span>🚫 屏蔽</span>\n </a>\n <a id="favoriteBtn" class="menu-btn" style="width: 120px; background-color:#25b1dc; color: white; text-align: center; padding: 8px 0;">\n <span>⭐ 收藏</span>\n </a>\n <a id="hasDownBtn" class="menu-btn" style="width: 120px; background-color:#7bc73b; color: white; text-align: center; padding: 8px 0;">\n <span>📥️ 已下载</span>\n </a>\n <a id="hasWatchBtn" class="menu-btn" style="width: 120px; background-color:#d7a80c; color: white; text-align: center; padding: 8px 0;">\n <span>🔍 已观看</span>\n </a>\n </div>\n \n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="enable-magnets-filter" class="menu-btn" style="width: 140px; background-color: #c2bd4c; color: white; text-align: center; padding: 8px 0;">\n <span id="magnets-span">排序与过滤</span>\n </a>\n <a id="magnetSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to right, rgb(245,140,1), rgb(84,161,29)); color: white; text-align: center; padding: 8px 0;">\n <span>磁力搜索</span>\n </a>\n <a id="subtitleSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕搜索</span>\n </a>\n <!--\n <a id="xunLeiSubtitleBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (迅雷)</span>\n </a>\n <a id="search-subtitle-btn" class="menu-btn" style="width: 160px; background: linear-gradient(to bottom, #8d5656, rgb(196,159,91)); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (SubTitleCat)</span>\n </a>\n -->\n </div>\n </div>\n ';
buttonsHtml = '\n <div style="margin: 10px auto; display: flex; justify-content: space-between; align-items: center; flex-wrap:wrap;gap: 20px;">\n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="filterBtn" class="menu-btn" style="width: 120px; background-color:#de3333; color: white; text-align: center; padding: 8px 0;">\n <span>🚫 屏蔽</span>\n </a>\n <a id="favoriteBtn" class="menu-btn" style="width: 120px; background-color:#25b1dc; color: white; text-align: center; padding: 8px 0;">\n <span>⭐ 收藏</span>\n </a>\n <a id="hasDownBtn" class="menu-btn" style="width: 120px; background-color:#7bc73b; color: white; text-align: center; padding: 8px 0;">\n <span>📥️ 已下载</span>\n </a>\n <a id="hasWatchBtn" class="menu-btn" style="width: 120px; background-color:#d7a80c; color: white; text-align: center; padding: 8px 0;">\n <span>🔍 已观看</span>\n </a>\n </div>\n \n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="downloadCoverBtn" class="menu-btn" style="width: 140px; background-color: #9b59b6; color: white; text-align: center; padding: 8px 0;">\n <span>下载高清封面</span>\n </a>\n <a id="enable-magnets-filter" class="menu-btn" style="width: 140px; background-color: #c2bd4c; color: white; text-align: center; padding: 8px 0;">\n <span id="magnets-span">排序与过滤</span>\n </a>\n <a id="magnetSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to right, rgb(245,140,1), rgb(84,161,29)); color: white; text-align: center; padding: 8px 0;">\n <span>磁力搜索</span>\n </a>\n <a id="subtitleSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕搜索</span>\n </a>\n <!--\n <a id="xunLeiSubtitleBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (迅雷)</span>\n </a>\n <a id="search-subtitle-btn" class="menu-btn" style="width: 160px; background: linear-gradient(to bottom, #8d5656, rgb(196,159,91)); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (SubTitleCat)</span>\n </a>\n -->\n </div>\n </div>\n ';
isJavDb && $(".tabs").after(buttonsHtml);
isJavBus && $("#mag-submit-show").before(buttonsHtml);
$("#favoriteBtn").on("click", (() => this.favoriteOne()));
@@ -87,6 +100,45 @@ class DetailPageButtonPlugin extends BasePlugin {
$("#subtitleSearchBtn").on("click", (() => {
window.JavPackSubtitle.openSearchModal({details: {code: carNum2}});
}));
$("#downloadCoverBtn").on("click", (async () => {
if (!isEligibleDmmCoverCode(carNum2)) {
layer.msg("当前番号不支持下载高清封面");
return;
}
const $btn = $("#downloadCoverBtn");
$btn.addClass("is-loading").prop("disabled", true);
try {
const coverData = await fromJavxyCover(carNum2);
if (!coverData) {
layer.msg("未找到高清封面");
return;
}
const coverUrl = coverData.url || coverData.highCover || coverData.cover;
if (!coverUrl) {
layer.msg("封面链接为空");
return;
}
const fileName = `${carNum2}-cover.jpg`;
GM_xmlhttpRequest({
method: 'GET',
url: coverUrl,
responseType: 'blob',
onload: (response) => {
if (response.status === 200) {
utils.download(response.response, fileName);
} else {
layer.msg(`封面下载失败: ${response.status}`);
}
},
onerror: () => layer.msg("封面下载网络错误"),
ontimeout: () => layer.msg("封面下载超时"),
});
} catch (e) {
layer.msg("获取封面信息失败");
} finally {
$btn.removeClass("is-loading").prop("disabled", false);
}
}));
window.JavPackSubtitle.preload115Matches(carNum2);
// $("#search-subtitle-btn").on("click", (event => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum2}`, carNum2, !1, event)));
// $("#xunLeiSubtitleBtn").on("click", (() => this.searchXunLeiSubtitle(carNum2)));
+1 -1
View File
@@ -8,7 +8,7 @@ export default defineConfig({
userscript: {
name: 'JHS-enhance',
namespace: 'JHS-enhance',
version: '3.3.10',
version: '3.3.11',
author: 'xiebro,fireinrain',
description: 'Jav-鉴黄师 增强脚本。列表页:作品状态标签、一键筛选、新作品检测、演员黑名单过滤;详情页:磁力链接高亮、DMM 多画质预览视频、标题翻译、整合字幕搜索(迅雷+SubTitleCat)并支持 115 直传、多源预览图(javfree/projectjav/javstore)含来源切换;数据:115 网盘目录匹配与多目录选择、云盘备份/恢复、跨 Tab 同步;其他:自动翻页、分类折叠、Top250、以图识图、热门榜单、评论查看、相关清单。支持 JavDB / JavBus / JavSee / SeeJav / FC2 / JavTrailers',
license: 'MIT',