10 Commits

Author SHA1 Message Date
lzyme.dev@gmail.com b5bdec2c35 更新文档 2026-09-01 15:28:19 +08:00
lzyme.dev@gmail.com 11c5650efb 新增下载高清封面 2026-09-01 15:24:40 +08:00
lzyme.dev@gmail.com 080cbb00ee 新增下载高清封面 2026-09-01 15:18:34 +08:00
lzyme.dev@gmail.com d5c59ba6ac 新增磁力列表验车按钮 2026-09-01 14:24:54 +08:00
lzyme.dev@gmail.com 6a12b45f86 Merge remote-tracking branch 'origin/main' 2026-09-01 00:53:11 +08:00
lzyme.dev@gmail.com 1e3c4d9718 更新文档 2026-09-01 00:50:56 +08:00
FireInRain 2b8c925fed Merge pull request #2 from anupamme/fix-repo-jhs-enhance-remove-hardcoded-imgur-client-id
fix: the userscript embeds imgur api credentials dir... in...
2026-08-31 21:42:16 +08:00
lzyme.dev@gmail.com a9e5286c53 更新文档 2026-08-31 14:31:53 +08:00
anupamme 7005b528a3 fix: V-001 security vulnerability
Automated security fix generated by OrbisAI Security
2026-08-31 06:14:02 +00:00
lzyme.dev@gmail.com 4026b84fc2 更新版本3.3.10 2026-08-31 14:04:44 +08:00
10 changed files with 260 additions and 25 deletions
+10 -4
View File
@@ -36,6 +36,9 @@
### 详情页增强
- **磁力搜索聚合(MagnetHub)**:多引擎聚合搜索(Sukebei / U9A9 / U3C3 / BTSearch / SoKitty / ØMagnet),Tab
切换引擎,结果表格展示(名称/大小/日期/操作),支持按大小/最新/最旧排序,质量标签自动识别(中字/4K/破解),一键复制磁力链接(含番号提取码),验车功能(Whatslink
API 截图预览、键盘/缩放导航),115 离线下载快捷按钮
- **磁力排序与过滤(JavDB)**:按发布日期/文件大小/文件数量拖拽排序,勾选启用;高清/4K/字幕/无码多条件并集过滤,4K 磁力行高亮显示
- 磁力链接高亮匹配已收藏/已下载状态
- DMM 多画质预览视频
@@ -44,12 +47,14 @@
- **字幕搜索**:合并迅雷 + SubTitleCat 为单一入口,支持 115 网盘直传、详情页预匹配目录、多目录选择上传、长目录名截断展示、字幕预览绿色字体
- **多源预览图**:javfree / projectjav / javstore 三源切换,来源按钮绿色高亮,sessionStorage 缓存,错误提示弹窗内显示
- 演员信息展示、相关清单、评论查看
- **下载高清封面**:获取 DMM 高清封面直链并下载,支持番号格式校验(排除 FC2/无码作品)
- 以图识图、第三方站点跳转
### 115 网盘集成
- **番号智能匹配**:双阶段搜索策略 — 第一阶段精准搜索(完整番号 + `type:4`),未命中时第二阶段模糊兜底(尾号 +
正则匹配),支持不规则文件名(如 `MIVR00345-5.mp4`、`ABP001.mp4`)
- **磁力离线下载**:MagnetHub 磁力搜索结果支持一键添加至 115 离线下载
- **不规则文件名自动重命名**
:检测到需第二阶段才能匹配的作品时,自动将文件重命名为标准番号格式(如 `mdvr00317-3.mp4` → `MDVR-317-3.mp4`
),设置面板可开关 + 选择范围(仅VR / 全部)
@@ -75,7 +80,8 @@
### 其他
- FC2 站点支持(含 123AV 补全)
- MagnetHub 磁力搜索
- **MagnetHub 磁力搜索**:6 引擎聚合(Sukebei/U9A9/U3C3/BTSearch/SoKitty/ØMagnet),质量标签(中字/4K/破解),验车截图预览(Whatslink
API,键盘导航),排序切换,115 离线一键下载
- JavTrailers 站点处理
- 设置面板:快捷键配置、域名配置、缓存清理、标记删除列表、打赏作者(二维码/钱包地址可自定义)
- 浏览历史、想看/已看列表
@@ -104,7 +110,7 @@ JHS-enhance/
│ │ ├── javdb.js # JavDB API 封装(签名算法、搜索、评论、磁力列表)
│ │ ├── dmm.js # DMM 预览视频获取(多画质、默认画质选择)
│ │ ├── dmm-graphql.js # DMM GraphQL API(备选视频源回退)
│ │ └── javxy.js # Javxy 聚合视频 API(多源解析、签名生成)
│ │ └── javxy.js # Javxy 聚合 API(视频多源解析、高清封面获取、签名生成)
│ ├── lib/ # 第三方补丁库
│ │ ├── gm-xhr-parallel.js # Tampermonkey MV3 并行请求修复(redirect: manual)
│ │ └── hls-runtime.js # HLS 视频播放运行时(hls.js 封装、进度记忆、画质切换)
@@ -112,7 +118,7 @@ JHS-enhance/
│ ├── list-page.js # 列表页过滤与状态标签渲染
│ ├── list-page-button.js # 列表页功能按钮(待鉴定/已收藏/新作品/黑名单管理)
│ ├── detail-page.js # 详情页数据加载与基础增强
│ ├── detail-page-button.js# 详情页操作按钮(115 目录预加载 + 磁力排序过滤面板)
│ ├── detail-page-button.js# 详情页操作按钮(下载高清封面 + 115 目录预加载 + 磁力排序过滤面板)
│ ├── setting.js # 设置面板(Tab 页:基础/任务/域名/快捷键/屏蔽/缓存/标记删除/打赏作者;含 115 自动重命名开关 + 范围选择)
│ ├── translate.js # 标题翻译(Google 翻译 API,原始标题缓存)
│ ├── preview-video.js # DMM 预览视频播放器
@@ -144,7 +150,7 @@ JHS-enhance/
│ ├── screenshot.js # 多源预览图(javfree/projectjav/javstore + 来源切换弹窗)
│ ├── subtitles.js # 字幕搜索(整合迅雷+SubTitleCat)+ 115 多目录直传
│ ├── req115.js # 115 网盘 API 封装(Req 基类 + Drive115 + Req115)
│ ├── magnet-hub.js # MagnetHub 磁力搜索
│ ├── magnet-hub.js # MagnetHub 磁力搜索(6 引擎聚合、质量标签、验车截图、115 离线)
│ ├── other-site.js # 第三方站点跳转链接
│ ├── jav-trailers.js # JavTrailers 站点处理
│ ├── javxy-preview-video.js# Javxy 多源视频预览(独立按钮、聚合 API、HLS 支持)
+113 -10
View File
@@ -1,7 +1,7 @@
// ==UserScript==
// @name JHS-enhance
// @namespace JHS-enhance
// @version 3.3.9
// @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
@@ -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 getJavxyCover = 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() {
@@ -10809,10 +10839,17 @@ ${err.stack}` : "");
type: mimeType
}), formData = new FormData();
formData.append("image", blob);
const idKey = "jhs_imgurClientId";
let clientId = localStorage.getItem(idKey);
if (!clientId) {
clientId = window.prompt("请输入您自己的Imgur Client-ID(可前往 https://api.imgur.com/oauth2/addclient 免费申请)用于图片上传搜索:");
if (!clientId) throw new Error("未提供Imgur Client-ID,无法上传图片");
localStorage.setItem(idKey, clientId);
}
const response = await fetch("https://api.imgur.com/3/image", {
method: "POST",
headers: {
Authorization: "Client-ID d70305e7c3ac5c6"
Authorization: `Client-ID ${clientId}`
},
body: formData
}), data = await response.json();
@@ -11362,16 +11399,16 @@ ${err.stack}` : "");
box-shadow: 0 1px 2px rgba(0,0,0,.1);
}
.magnet-hub-btn.copied {
background-color: #4CAF50;
background-color: #66bb6a;
}
.magnet-hub-btn.copy-btn {
background-color: #7c3aed;
background-color: #8b5cf6;
}
.magnet-hub-btn.check-btn {
background-color: #be185d;
background-color: #e11d48;
}
.magnet-hub-btn.down-btn {
background-color: #0d9488;
background-color: #14b8a6;
}
.whatslink-overlay {
position: fixed;
@@ -12581,19 +12618,31 @@ ${err.stack}` : "");
}
async handle() {
$(".buttons button[data-clipboard-text*='magnet:']").each(((i, el) => {
$(el).parent().append($("<button>").text("115离线下载").addClass("button is-info is-small").click((async (event) => {
const $copyBtn = $(el);
const magnet = $copyBtn.attr("data-clipboard-text");
$copyBtn.css("margin-right", parseInt($copyBtn.css("margin-right")) - 4 + "px");
const $downloadBtn = $copyBtn.siblings("a.button, button").first();
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();
this.getBean("MagnetHubPlugin").checkWhatslink(magnet);
})).appendTo($copyBtn.parent());
$("<button>").text("115离线下载").addClass("button is-info is-small").click((async (event) => {
event.stopPropagation();
event.preventDefault();
let loadObj = loading();
try {
await this.handleAddTask($(el).attr("data-clipboard-text"));
await this.handleAddTask(magnet);
} catch (e) {
show.error("发生错误:" + e);
console.error(e);
} finally {
loadObj.close();
}
})));
})).appendTo($copyBtn.parent());
}));
isJavBus$1 && window.isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => {
this.bus115Down();
@@ -12616,6 +12665,11 @@ ${err.stack}` : "");
} finally {
loadObj.close();
}
})).appendTo(actionCell);
$("<button>").text("验车").addClass("button is-info is-small").click(((event) => {
event.stopPropagation();
event.preventDefault();
this.getBean("MagnetHubPlugin").checkWhatslink(magnetLink);
})).appendTo(actionCell);
$(row).append(actionCell);
}
@@ -15596,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()));
@@ -15657,6 +15711,55 @@ ${err.stack}` : "");
}));
$("#subtitleSearchBtn").on("click", (() => {
window.JavPackSubtitle.openSearchModal({details: {code: carNum2}});
}));
$("#downloadCoverBtn").on("click", (async () => {
if (!isEligibleDmmCoverCode(carNum2)) {
show.error("当前番号不支持下载高清封面");
return;
}
const $btn = $("#downloadCoverBtn");
$btn.addClass("is-loading").prop("disabled", true);
try {
const coverData = await getJavxyCover(carNum2);
if (!coverData) {
show.error("未找到高清封面");
return;
}
const coverUrl = coverData.url || coverData.highCover || coverData.cover;
if (!coverUrl) {
show.error("封面链接为空");
return;
}
const fileName = `${carNum2}-cover.jpg`;
_GM_xmlhttpRequest({
method: "GET",
url: coverUrl,
responseType: "blob",
onload: (response) => {
if (response.status === 200) {
const blobUrl = URL.createObjectURL(response.response);
const a = document.createElement("a");
a.href = blobUrl;
a.download = fileName;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(blobUrl);
}, 100);
show.ok("封面下载已开始");
} else {
show.error(`封面下载失败: ${response.status}`);
}
},
onerror: () => show.error("封面下载网络错误"),
ontimeout: () => show.error("封面下载超时")
});
} catch (e) {
show.error("获取封面信息失败");
} finally {
$btn.removeClass("is-loading").prop("disabled", false);
}
}));
window.JavPackSubtitle.preload115Matches(carNum2);
this.showStatus(carNum2).then();
+5 -3
View File
@@ -1,17 +1,16 @@
{
"name": "jhs-enhance",
"version": "3.3.6",
"version": "3.3.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "jhs-enhance",
"version": "3.3.6",
"version": "3.3.10",
"license": "MIT",
"dependencies": {
"blueimp-md5": "^2.19.0",
"jquery": "^4.0.0",
"layui-layer": "^1.0.9",
"localforage": "^1.10.0",
"qrcodejs": "^1.0.0",
"tabulator-tables": "^6.5.2",
@@ -19,6 +18,7 @@
"viewerjs": "^1.12.0"
},
"devDependencies": {
"layui-layer": "^1.0.9",
"vite": "^6.0.0",
"vite-plugin-monkey": "^5.0.0"
}
@@ -1204,6 +1204,7 @@
"version": "1.0.9",
"resolved": "https://registry.npmmirror.com/layui-layer/-/layui-layer-1.0.9.tgz",
"integrity": "sha512-CzPVtG0D4IvmISUyLTzzbz2Uxfaoqch+87lHqVmO53ie8ZKOfOkENbBl7ssiLna1lAnv4M1SycmH62NbtndtDQ==",
"dev": true,
"license": "ISC",
"dependencies": {
"jquery": "^3.1.1"
@@ -1213,6 +1214,7 @@
"version": "3.7.1",
"resolved": "https://registry.npmmirror.com/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
"dev": true,
"license": "MIT"
},
"node_modules/lie": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "jhs-enhance",
"version": "3.3.6",
"version": "3.3.11",
"description": "Jav-鉴黄师 油猴脚本 - 工程化版本",
"type": "module",
"scripts": {
+26
View File
@@ -211,10 +211,36 @@ const getJavxyVideoUrls = async (code, failedSources = []) => {
return null;
};
const getJavxyCover = 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,
getJavxyCover,
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,
+64 -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 {getJavxyCover} 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,55 @@ class DetailPageButtonPlugin extends BasePlugin {
$("#subtitleSearchBtn").on("click", (() => {
window.JavPackSubtitle.openSearchModal({details: {code: carNum2}});
}));
$("#downloadCoverBtn").on("click", (async () => {
if (!isEligibleDmmCoverCode(carNum2)) {
show.error("当前番号不支持下载高清封面");
return;
}
const $btn = $("#downloadCoverBtn");
$btn.addClass("is-loading").prop("disabled", true);
try {
const coverData = await getJavxyCover(carNum2);
if (!coverData) {
show.error("未找到高清封面");
return;
}
const coverUrl = coverData.url || coverData.highCover || coverData.cover;
if (!coverUrl) {
show.error("封面链接为空");
return;
}
const fileName = `${carNum2}-cover.jpg`;
GM_xmlhttpRequest({
method: 'GET',
url: coverUrl,
responseType: 'blob',
onload: (response) => {
if (response.status === 200) {
const blobUrl = URL.createObjectURL(response.response);
const a = document.createElement('a');
a.href = blobUrl;
a.download = fileName;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(blobUrl);
}, 100);
show.ok("封面下载已开始");
} else {
show.error(`封面下载失败: ${response.status}`);
}
},
onerror: () => show.error("封面下载网络错误"),
ontimeout: () => show.error("封面下载超时"),
});
} catch (e) {
show.error("获取封面信息失败");
} 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)));
+8 -1
View File
@@ -162,10 +162,17 @@ class ImageRecognitionPlugin extends BasePlugin {
type: mimeType
}), formData = new FormData;
formData.append("image", blob);
const idKey = "jhs_imgurClientId";
let clientId = localStorage.getItem(idKey);
if (!clientId) {
clientId = window.prompt("请输入您自己的Imgur Client-ID(可前往 https://api.imgur.com/oauth2/addclient 免费申请)用于图片上传搜索:");
if (!clientId) throw new Error("未提供Imgur Client-ID,无法上传图片");
localStorage.setItem(idKey, clientId);
}
const response = await fetch("https://api.imgur.com/3/image", {
method: "POST",
headers: {
Authorization: "Client-ID d70305e7c3ac5c6"
Authorization: `Client-ID ${clientId}`
},
body: formData
}), data = await response.json();
+21 -3
View File
@@ -18,19 +18,32 @@ class WangPan115TaskPlugin extends BasePlugin {
}
async handle() {
$(".buttons button[data-clipboard-text*='magnet:']").each(((i, el) => {
$(el).parent().append($("<button>").text("115离线下载").addClass("button is-info is-small").click((async event => {
const $copyBtn = $(el);
const magnet = $copyBtn.attr("data-clipboard-text");
//对齐按钮间距
$copyBtn.css("margin-right", (parseInt($copyBtn.css("margin-right")) - 4) + "px");
const $downloadBtn = $copyBtn.siblings("a.button, button").first();
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();
this.getBean("MagnetHubPlugin").checkWhatslink(magnet);
})).appendTo($copyBtn.parent());
$("<button>").text("115离线下载").addClass("button is-info is-small").click((async event => {
event.stopPropagation();
event.preventDefault();
let loadObj = loading();
try {
await this.handleAddTask($(el).attr("data-clipboard-text"));
await this.handleAddTask(magnet);
} catch (e) {
show.error("发生错误:" + e);
console.error(e);
} finally {
loadObj.close();
}
})));
})).appendTo($copyBtn.parent());
}));
isJavBus && window.isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => {
this.bus115Down();
@@ -54,6 +67,11 @@ class WangPan115TaskPlugin extends BasePlugin {
loadObj.close();
}
})).appendTo(actionCell);
$("<button>").text("验车").addClass("button is-info is-small").click((event => {
event.stopPropagation();
event.preventDefault();
this.getBean("MagnetHubPlugin").checkWhatslink(magnetLink);
})).appendTo(actionCell);
$(row).append(actionCell);
}
}));
+1 -1
View File
@@ -8,7 +8,7 @@ export default defineConfig({
userscript: {
name: 'JHS-enhance',
namespace: 'JHS-enhance',
version: '3.3.9',
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',