mirror of
https://github.com/fireinrain/JHS-enhance.git
synced 2026-09-28 23:56:36 +08:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4026b84fc2 | |||
| 76b608ccc0 | |||
| 6c9a4743bb | |||
| ce6cedf164 | |||
| 7840ca6389 | |||
| d03a9d1462 | |||
| b7cfc8541c | |||
| ce59d82ed2 | |||
| 37cf4e1183 | |||
| 8829af0fc3 | |||
| 9db06fba3b | |||
| 06b2497ee1 | |||
| 50581be5a6 | |||
| e92991c365 | |||
| 03700906e5 |
@@ -48,11 +48,16 @@
|
||||
|
||||
### 115 网盘集成
|
||||
|
||||
- **番号智能匹配**:双阶段搜索策略 — 第一阶段精准搜索(完整番号 + `type:4`),未命中时第二阶段模糊兜底(尾号 +
|
||||
正则匹配),支持不规则文件名(如 `MIVR00345-5.mp4`、`ABP001.mp4`)
|
||||
- **不规则文件名自动重命名**
|
||||
:检测到需第二阶段才能匹配的作品时,自动将文件重命名为标准番号格式(如 `mdvr00317-3.mp4` → `MDVR-317-3.mp4`
|
||||
),设置面板可开关 + 选择范围(仅VR / 全部)
|
||||
- 作品目录匹配与详情页预加载
|
||||
- 多目录选择上传字幕
|
||||
- 字幕一键直传至匹配目录
|
||||
- **作品删除增强**:删除整个文件夹(非仅视频),支持标记删除批量操作,删除后字幕目录列表联动;批量删除含风控保护(1~3s
|
||||
随机延迟 + 触发风控自动重试退避)
|
||||
随机延迟 + 触发风控自动重试退避);删除安全检查使用 `codeParse` 正则匹配番号
|
||||
|
||||
### 数据管理
|
||||
|
||||
@@ -108,7 +113,7 @@ JHS-enhance/
|
||||
│ ├── list-page-button.js # 列表页功能按钮(待鉴定/已收藏/新作品/黑名单管理)
|
||||
│ ├── detail-page.js # 详情页数据加载与基础增强
|
||||
│ ├── detail-page-button.js# 详情页操作按钮(115 目录预加载 + 磁力排序过滤面板)
|
||||
│ ├── setting.js # 设置面板(Tab 页:基础/任务/域名/快捷键/屏蔽/缓存/标记删除/打赏作者)
|
||||
│ ├── setting.js # 设置面板(Tab 页:基础/任务/域名/快捷键/屏蔽/缓存/标记删除/打赏作者;含 115 自动重命名开关 + 范围选择)
|
||||
│ ├── translate.js # 标题翻译(Google 翻译 API,原始标题缓存)
|
||||
│ ├── preview-video.js # DMM 预览视频播放器
|
||||
│ ├── highlight-magnet.js # 磁力链接高亮 + 排序 + 过滤(多键排序、并集过滤、4K 高亮)
|
||||
@@ -118,7 +123,7 @@ JHS-enhance/
|
||||
│ ├── new-video.js # 新作品检测与通知
|
||||
│ ├── task.js # 后台任务调度(黑名单/收藏演员/新作品定时执行)
|
||||
│ ├── history.js # 浏览历史表格
|
||||
│ ├── wangpan-115.js # 115 网盘匹配/标签/删除(含风控保护的批量删除)
|
||||
│ ├── wangpan-115.js # 115 网盘匹配/标签/删除(含 codeParse 正则匹配、双阶段搜索、自动重命名)
|
||||
│ ├── wangpan-115-task.js # 115 网盘任务处理(文件搜索 API 封装)
|
||||
│ ├── aliyun-pan.js # 阿里云盘备份/恢复
|
||||
│ ├── fc2.js # FC2 页面处理
|
||||
@@ -297,6 +302,15 @@ window.show.ok('操作成功'); // Toast 通知
|
||||
|
||||
## 版本历史
|
||||
|
||||
- **v3.3.9**
|
||||
- 新增:115 番号智能匹配 — `codeParse` 正则匹配引擎,双阶段搜索策略(精准 →
|
||||
模糊兜底),支持不规则文件名(如 `MIVR00345-5.mp4`、`ABP001.mp4`)
|
||||
- 新增:115 不规则文件名自动重命名 —
|
||||
检测到需模糊匹配的作品时自动批量重命名为标准格式(如 `mdvr00317-3.mp4` → `MDVR-317-3.mp4`),设置面板可开关 +
|
||||
范围选择(仅VR / 全部)
|
||||
- 优化:删除安全检查改用 `codeParse` 正则匹配,消除 `includes` 方式对不规则文件名的误判
|
||||
- 优化:`searchFiles` 增加 `type:4` 参数,搜索仅返回视频文件,避免目录占用 30 条槽位
|
||||
|
||||
- **v3.3.8**
|
||||
- 新增:Javxy 多源视频预览独立按钮(番号/识别码右侧),基于 Javxy 聚合 API,支持 JavTrailers / JavDB 等多源回退
|
||||
- 新增:HLS 视频播放运行时(hls.js 封装),支持 .m3u8 格式、播放进度记忆、画质切换
|
||||
|
||||
Vendored
+14774
-12483
File diff suppressed because one or more lines are too long
+4
-3
@@ -12,6 +12,9 @@ if (typeof $.trim !== 'function') {
|
||||
return text == null ? '' : (text + '').trim();
|
||||
};
|
||||
}
|
||||
if (typeof jQuery !== 'undefined' && typeof layer === 'undefined') {
|
||||
await import('layui-layer');
|
||||
}
|
||||
window.localforage = localforage;
|
||||
window.Toastify = Toastify;
|
||||
window.md5 = md5;
|
||||
@@ -31,6 +34,4 @@ window.Status_FAVORITE = Status_FAVORITE;
|
||||
window.Status_HAS_DOWN = Status_HAS_DOWN;
|
||||
window.Status_HAS_WATCH = Status_HAS_WATCH;
|
||||
window.NO = NO;
|
||||
window.YES = YES;
|
||||
|
||||
import 'layui-layer';
|
||||
window.YES = YES;
|
||||
+1
-1
@@ -98,7 +98,7 @@ class Utils {
|
||||
}
|
||||
if (!viewerExists) {
|
||||
this.layerIndexStack.pop();
|
||||
layer.close(topLayerIndex);
|
||||
typeof layer !== 'undefined' && layer.close(topLayerIndex);
|
||||
}
|
||||
}
|
||||
setupEscClose(layerIndex) {
|
||||
|
||||
+23
-21
@@ -820,29 +820,31 @@ window.ImageHoverPreview = class {
|
||||
}));
|
||||
}();
|
||||
|
||||
const originalLayerClose = layer.close;
|
||||
if (typeof layer !== 'undefined') {
|
||||
const originalLayerClose = layer.close;
|
||||
|
||||
layer.close = function(index) {
|
||||
const result = originalLayerClose.call(this, index);
|
||||
!function(waitTime = 10) {
|
||||
setTimeout((() => {
|
||||
const openLayerCount = document.querySelectorAll(".layui-layer-shade").length;
|
||||
document.documentElement.style.overflow = openLayerCount > 0 ? "hidden" : "";
|
||||
}), waitTime);
|
||||
}();
|
||||
return result;
|
||||
};
|
||||
|
||||
const originalLayerOpen = layer.open;
|
||||
|
||||
layer.open = function(options) {
|
||||
const originalSuccess = (options = options || {}).success;
|
||||
options.success = function(layero, index) {
|
||||
"function" == typeof originalSuccess && originalSuccess.call(this, layero, index);
|
||||
utils.setupEscClose(index);
|
||||
layer.close = function (index) {
|
||||
const result = originalLayerClose.call(this, index);
|
||||
!function (waitTime = 10) {
|
||||
setTimeout((() => {
|
||||
const openLayerCount = document.querySelectorAll(".layui-layer-shade").length;
|
||||
document.documentElement.style.overflow = openLayerCount > 0 ? "hidden" : "";
|
||||
}), waitTime);
|
||||
}();
|
||||
return result;
|
||||
};
|
||||
return originalLayerOpen.call(this, options);
|
||||
};
|
||||
|
||||
const originalLayerOpen = layer.open;
|
||||
|
||||
layer.open = function (options) {
|
||||
const originalSuccess = (options = options || {}).success;
|
||||
options.success = function (layero, index) {
|
||||
"function" == typeof originalSuccess && originalSuccess.call(this, layero, index);
|
||||
utils.setupEscClose(index);
|
||||
};
|
||||
return originalLayerOpen.call(this, options);
|
||||
};
|
||||
}
|
||||
|
||||
utils.importResource("https://cdn.jsdelivr.net/npm/layui-layer@1.0.9/layer.min.css");
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1051
-138
File diff suppressed because it is too large
Load Diff
+95
-96
@@ -1,6 +1,6 @@
|
||||
import { BasePlugin } from '../core/base-plugin.js';
|
||||
import { __publicField, isJavBus, isJavDb } from '../core/constants.js';
|
||||
import { AsyncQueue } from '../core/utils.js';
|
||||
import {BasePlugin} from '../core/base-plugin.js';
|
||||
import {__publicField, isJavBus, isJavDb} from '../core/constants.js';
|
||||
import {AsyncQueue} from '../core/utils.js';
|
||||
|
||||
class OtherSitePlugin extends BasePlugin {
|
||||
constructor() {
|
||||
@@ -10,98 +10,97 @@ class OtherSitePlugin extends BasePlugin {
|
||||
__publicField(this, "warnBackgroundColor", "#d7a80c");
|
||||
__publicField(this, "domainErrorBackgroundColor", "#d7780c");
|
||||
__publicField(this, "asyncQueue", new AsyncQueue);
|
||||
__publicField(this, "siteConfigs", [ {
|
||||
id: "javTrailersBtn",
|
||||
getBaseUrl: async () => await this.getJavTrailersUrl(),
|
||||
itemSelector: ".videos-list .video-link",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/search/${carNum2}`,
|
||||
getDetailPageHref: $box2 => $box2.attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.find("p.card-text").text()
|
||||
}, {
|
||||
id: "123AvBtn",
|
||||
getBaseUrl: async () => await this.getAv123Url() + "/ja",
|
||||
itemSelector: ".box-item",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/search?keyword=${carNum2}`,
|
||||
getDetailPageHref: $box2 => $box2.find(".detail a").attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.find("img").attr("title")
|
||||
}, {
|
||||
id: "jableBtn",
|
||||
getBaseUrl: async () => await this.getjableUrl(),
|
||||
itemSelector: "#list_videos_videos_list_search_result .detail .title a",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/search/${carNum2}/`,
|
||||
getDetailPageHref: $box2 => $box2.attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.text()
|
||||
}, {
|
||||
id: "avgleBtn",
|
||||
getBaseUrl: async () => await this.getAvgleUrl(),
|
||||
itemSelector: ".text-secondary",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/vod/search.html?wd=${carNum2}`,
|
||||
getDetailPageHref: $box2 => $box2.attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.text()
|
||||
}, {
|
||||
id: "missAvBtn",
|
||||
getBaseUrl: async () => await this.getMissAvUrl(),
|
||||
itemSelector: ".text-secondary",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/search/${carNum2}`,
|
||||
getDetailPageHref: $box2 => $box2.attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.text()
|
||||
}, {
|
||||
id: "supJavBtn",
|
||||
getBaseUrl: async () => await this.getSupJavUrl(),
|
||||
itemSelector: ".posts post",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/?s=${carNum2}`,
|
||||
getDetailPageHref: ($box2, baseUrl, carNum2) => $box2.attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.attr("title")
|
||||
}, {
|
||||
id: "javDbBtn",
|
||||
getBaseUrl: async () => await this.getJavDbUrl(),
|
||||
itemSelector: ".movie-list .item",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/search?q=${carNum2}`,
|
||||
getDetailPageHref: $box2 => $box2.find("a").attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.find(".video-title").text(),
|
||||
condition: sourceCarNum => isJavBus
|
||||
}, {
|
||||
id: "javBusBtn",
|
||||
getBaseUrl: async () => await this.getJavBusUrl(),
|
||||
itemSelector: ".container h3",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/${carNum2}`,
|
||||
getDetailPageHref: ($box2, baseUrl, carNum2) => `${baseUrl}/${carNum2}`,
|
||||
findCarNumOrTitle: $box2 => $box2.text(),
|
||||
condition: sourceCarNum => isJavDb && sourceCarNum && !sourceCarNum.includes("FC2")
|
||||
}, {
|
||||
id: "fanzaBtn",
|
||||
noHandle: !0,
|
||||
initUrl: carNum2 => `https://www.dmm.co.jp/search/=/searchstr=${carNum2}`,
|
||||
condition: sourceCarNum => sourceCarNum && !sourceCarNum.includes("FC2")
|
||||
} ]);
|
||||
__publicField(this, "siteConfigs", [
|
||||
{
|
||||
id: "MissAvBtn",
|
||||
getBaseUrl: async () => await this.getMissAvUrl(),
|
||||
itemSelector: ".text-secondary",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/search/${carNum2}`,
|
||||
getDetailPageHref: $box2 => $box2.attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.text()
|
||||
}, {
|
||||
id: "123AvBtn",
|
||||
getBaseUrl: async () => await this.getAv123Url() + "/ja",
|
||||
itemSelector: ".box-item",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/search?keyword=${carNum2}`,
|
||||
getDetailPageHref: $box2 => $box2.find(".detail a").attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.find("img").attr("title")
|
||||
}, {
|
||||
id: "JableBtn",
|
||||
getBaseUrl: async () => await this.getjableUrl(),
|
||||
itemSelector: "#list_videos_videos_list_search_result .detail .title a",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/search/${carNum2}/`,
|
||||
getDetailPageHref: $box2 => $box2.attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.text()
|
||||
}, {
|
||||
id: "SupJavBtn",
|
||||
getBaseUrl: async () => await this.getSupJavUrl(),
|
||||
itemSelector: ".posts post",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/?s=${carNum2}`,
|
||||
getDetailPageHref: ($box2, baseUrl, carNum2) => $box2.attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.attr("title")
|
||||
},
|
||||
{
|
||||
id: "JavTrailersBtn",
|
||||
getBaseUrl: async () => await this.getJavTrailersUrl(),
|
||||
itemSelector: ".videos-list .video-link",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/search/${carNum2}`,
|
||||
getDetailPageHref: $box2 => $box2.attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.find("p.card-text").text()
|
||||
},
|
||||
|
||||
{
|
||||
id: "JavDbBtn",
|
||||
getBaseUrl: async () => await this.getJavDbUrl(),
|
||||
itemSelector: ".movie-list .item",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/search?q=${carNum2}`,
|
||||
getDetailPageHref: $box2 => $box2.find("a").attr("href"),
|
||||
findCarNumOrTitle: $box2 => $box2.find(".video-title").text(),
|
||||
condition: sourceCarNum => isJavBus
|
||||
}, {
|
||||
id: "JavBusBtn",
|
||||
getBaseUrl: async () => await this.getJavBusUrl(),
|
||||
itemSelector: ".container h3",
|
||||
searchPath: (baseUrl, carNum2) => `${baseUrl}/${carNum2}`,
|
||||
getDetailPageHref: ($box2, baseUrl, carNum2) => `${baseUrl}/${carNum2}`,
|
||||
findCarNumOrTitle: $box2 => $box2.text(),
|
||||
condition: sourceCarNum => isJavDb && sourceCarNum && !sourceCarNum.includes("FC2")
|
||||
}]);
|
||||
__publicField(this, "settingCache", null);
|
||||
__publicField(this, "lastFetchTime", 0);
|
||||
__publicField(this, "CACHE_DURATION", 1e4);
|
||||
}
|
||||
|
||||
getName() {
|
||||
return "OtherSitePlugin";
|
||||
}
|
||||
|
||||
async initCss() {
|
||||
return "\n <style>\n .site-btn {\n position: relative !important;\n min-width: 80px;\n display: inline-block;\n padding: 5px 10px;\n color: white !important;\n background-color:#938585;\n text-decoration: none;\n border-radius: 4px;\n text-align: center;\n margin-bottom: 5px;\n }\n .site-btn:hover {\n color: white;\n transform: translateY(-2px);\n box-shadow: 0 2px 5px rgba(0,0,0,0.1);\n }\n .site-tag {\n position: absolute; \n top: -15px; \n right: 0; \n background-color: #ffc107; \n color: #333; \n font-size: 12px; \n padding: 2px 6px; \n border-radius: 4px;\n }\n </style>\n ";
|
||||
}
|
||||
|
||||
async handle() {
|
||||
window.isDetailPage && this.loadOtherSite().then();
|
||||
}
|
||||
|
||||
async loadOtherSite(carNum2, sourceCarNum) {
|
||||
if ("yes" !== await storageManager.getSetting("enableLoadOtherSite", "yes")) return;
|
||||
carNum2 || (carNum2 = this.getPageInfo().carNum);
|
||||
const enabledSites = this.getEnabledSites(), html = `\n <div id="otherSiteBox" class="panel-block" style="${isJavDb ? "margin-top:8px;font-size:13px" : "margin-top:10px;font-size:13px"}; user-select: none; ">\n <div style="display: flex;gap: 5px;flex-wrap: wrap">\n ${this.siteConfigs.map((config => {
|
||||
config.sourceCarNum = sourceCarNum;
|
||||
if (config.condition && !1 === config.condition(config.sourceCarNum)) return "";
|
||||
return `<a target="_blank" class="site-btn" style="${enabledSites.includes(config.id) ? "" : "display:none"}" id="${config.id}"><span>${config.id.replace("Btn", "")}</span></a>`;
|
||||
})).join("")}\n <a id="settingSiteBtn" class="site-btn"><span>设置</span></a>\n </div>\n </div>\n \n <div id="settingsArea" class="panel-block" style="display: none; margin-top:10px; margin-bottom: 10px; user-select: none; ">\n <div id="siteCheckboxes" style="display: flex;gap: 5px;flex-wrap: wrap">\n </div>\n </div>\n `;
|
||||
const enabledSites = this.getEnabledSites(),
|
||||
html = `\n <div id="otherSiteBox" class="panel-block" style="${isJavDb ? "margin-top:8px;font-size:13px" : "margin-top:10px;font-size:13px"}; user-select: none; ">\n <div style="display: flex;gap: 5px;flex-wrap: wrap">\n ${this.siteConfigs.map((config => {
|
||||
config.sourceCarNum = sourceCarNum;
|
||||
if (config.condition && !1 === config.condition(config.sourceCarNum)) return "";
|
||||
return `<a target="_blank" class="site-btn" style="${enabledSites.includes(config.id) ? "" : "display:none"}" id="${config.id}"><span>${config.id.replace("Btn", "")}</span></a>`;
|
||||
})).join("")}\n <a id="settingSiteBtn" class="site-btn"><span>跳转设置</span></a>\n </div>\n </div>\n \n <div id="settingsArea" class="panel-block" style="display: none; margin-top:10px; margin-bottom: 10px; user-select: none; ">\n <div id="siteCheckboxes" style="display: flex;gap: 5px;flex-wrap: wrap">\n </div>\n </div>\n `;
|
||||
$(".movie-panel-info").append(html);
|
||||
$(".container .info").append(html);
|
||||
$("#javTrailersBtn").on("click", (async event => {
|
||||
event.preventDefault();
|
||||
let settingObj = await storageManager.getSetting();
|
||||
const filterHotKey = settingObj.filterHotKey, favoriteHotKey = settingObj.favoriteHotKey, speedVideoHotKey = settingObj.speedVideoHotKey;
|
||||
let href = $("#javTrailersBtn").attr("href"), url = href + `?handle=1&filterHotKey=${filterHotKey}&favoriteHotKey=${favoriteHotKey}&speedVideoHotKey=${speedVideoHotKey}`;
|
||||
const filterHotKey = settingObj.filterHotKey, favoriteHotKey = settingObj.favoriteHotKey,
|
||||
speedVideoHotKey = settingObj.speedVideoHotKey;
|
||||
let href = $("#javTrailersBtn").attr("href"),
|
||||
url = href + `?handle=1&filterHotKey=${filterHotKey}&favoriteHotKey=${favoriteHotKey}&speedVideoHotKey=${speedVideoHotKey}`;
|
||||
event && (event.ctrlKey || event.metaKey) && (url = href);
|
||||
utils.openPage(url, carNum2, !1, event);
|
||||
}));
|
||||
@@ -111,26 +110,15 @@ class OtherSitePlugin extends BasePlugin {
|
||||
this.renderSettingsArea();
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
async handleSite(carNum2, config) {
|
||||
const $btn = $(`#${config.id}`);
|
||||
if (config.initUrl) {
|
||||
$btn.attr("href", config.initUrl(carNum2));
|
||||
$btn.css("backgroundColor", this.warnBackgroundColor);
|
||||
}
|
||||
if (config.noHandle && !0 === config.noHandle) {
|
||||
const dmmCacheKey = "jhs_other_site_dmm", dmmCachedResult = (localStorage.getItem(dmmCacheKey) ? JSON.parse(localStorage.getItem(dmmCacheKey)) : {})[carNum2];
|
||||
if (dmmCachedResult) if ("single" === dmmCachedResult.type) {
|
||||
$btn.attr("href", dmmCachedResult.url);
|
||||
$btn.css("backgroundColor", this.okBackgroundColor);
|
||||
} else if ("multiple" === dmmCachedResult.type) {
|
||||
$btn.attr("href", dmmCachedResult.url);
|
||||
$btn.append('<span class="site-tag" style="top:-15px">多结果</span>');
|
||||
$btn.css("backgroundColor", this.okBackgroundColor);
|
||||
}
|
||||
} else try {
|
||||
try {
|
||||
if ($btn.attr("href")) return;
|
||||
if (utils.isHidden($btn)) return;
|
||||
const cacheKey = "jhs_other_site", cacheData = localStorage.getItem(cacheKey) ? JSON.parse(localStorage.getItem(cacheKey)) : {}, siteKey = carNum2 + "_" + config.id.replace("Btn", ""), cachedResult = cacheData[siteKey];
|
||||
const cacheKey = "jhs_other_site",
|
||||
cacheData = localStorage.getItem(cacheKey) ? JSON.parse(localStorage.getItem(cacheKey)) : {},
|
||||
siteKey = carNum2 + "_" + config.id.replace("Btn", ""), cachedResult = cacheData[siteKey];
|
||||
if (cachedResult) {
|
||||
if ("single" === cachedResult.type) {
|
||||
$btn.attr("href", cachedResult.url);
|
||||
@@ -144,7 +132,8 @@ class OtherSitePlugin extends BasePlugin {
|
||||
}
|
||||
const baseUrl = await config.getBaseUrl(), searchUrl = config.searchPath(baseUrl, carNum2);
|
||||
$btn.attr("href", searchUrl);
|
||||
const html = await gmHttp.get(searchUrl, null, config.headers, !0), $dom = utils.htmlTo$dom(html), resultUrlList = [];
|
||||
const html = await gmHttp.get(searchUrl, null, config.headers, !0), $dom = utils.htmlTo$dom(html),
|
||||
resultUrlList = [];
|
||||
$dom.find(config.itemSelector).each(((index, element) => {
|
||||
const $box2 = $(element);
|
||||
if (!config.findCarNumOrTitle($box2).toLowerCase().includes(carNum2.toLowerCase())) return;
|
||||
@@ -202,6 +191,7 @@ class OtherSitePlugin extends BasePlugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getSettingCache() {
|
||||
const now = Date.now();
|
||||
if (!this.settingCache || now - this.lastFetchTime > this.CACHE_DURATION) {
|
||||
@@ -210,37 +200,44 @@ class OtherSitePlugin extends BasePlugin {
|
||||
}
|
||||
return this.settingCache;
|
||||
}
|
||||
|
||||
async getMissAvUrl() {
|
||||
return (await this.getSettingCache()).missAvUrl || "https://missav.live";
|
||||
}
|
||||
|
||||
async getjableUrl() {
|
||||
return (await this.getSettingCache()).jableUrl || "https://jable.tv";
|
||||
}
|
||||
async getAvgleUrl() {
|
||||
return (await this.getSettingCache()).avgleUrl || "https://jav.rs";
|
||||
}
|
||||
|
||||
async getJavTrailersUrl() {
|
||||
return (await this.getSettingCache()).javTrailersUrl || "https://javtrailers.com";
|
||||
}
|
||||
|
||||
async getAv123Url() {
|
||||
return (await this.getSettingCache()).av123Url || "https://123av.com";
|
||||
}
|
||||
|
||||
async getJavDbUrl() {
|
||||
return (await this.getSettingCache()).javDbUrl || "https://javdb.com";
|
||||
}
|
||||
|
||||
async getJavBusUrl() {
|
||||
return (await this.getSettingCache()).javBusUrl || "https://www.javbus.com";
|
||||
}
|
||||
|
||||
async getSupJavUrl() {
|
||||
return (await this.getSettingCache()).supJavUrl || "https://supjav.com";
|
||||
}
|
||||
|
||||
getEnabledSites() {
|
||||
const enabledSites = localStorage.getItem("jhs_enabled_sites");
|
||||
return enabledSites ? JSON.parse(enabledSites) : this.siteConfigs.map((c => c.id));
|
||||
}
|
||||
|
||||
saveEnabledSites(sites) {
|
||||
localStorage.setItem("jhs_enabled_sites", JSON.stringify(sites));
|
||||
}
|
||||
|
||||
renderSettingsArea() {
|
||||
const enabledSites = this.getEnabledSites(), checkboxesDiv = document.getElementById("siteCheckboxes");
|
||||
checkboxesDiv && (checkboxesDiv.innerHTML = this.siteConfigs.map((config => {
|
||||
@@ -248,6 +245,7 @@ class OtherSitePlugin extends BasePlugin {
|
||||
return `\n <div style="margin-right: 15px; display: flex; align-items: ${isJavDb ? "center" : "flex-start"};">\n <input type="checkbox" id="checkbox-${config.id}" data-site-id="${config.id}" ${isEnabled ? "checked" : ""} style="margin-right: 8px; cursor: pointer;">\n <label for="checkbox-${config.id}" style="color: #333; font-weight: 500; cursor: pointer;">${config.id.replace("Btn", "")}</label>\n </div>\n `;
|
||||
})).join(""));
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
const settingsArea = document.getElementById("settingsArea");
|
||||
document.addEventListener("click", (event => {
|
||||
@@ -261,7 +259,8 @@ class OtherSitePlugin extends BasePlugin {
|
||||
const siteId = event.target.getAttribute("data-site-id");
|
||||
if (event.target.checked) {
|
||||
$(`#${siteId}`).show();
|
||||
const carNum2 = this.getPageInfo().carNum, config = this.siteConfigs.find((item => item.id === siteId));
|
||||
const carNum2 = this.getPageInfo().carNum,
|
||||
config = this.siteConfigs.find((item => item.id === siteId));
|
||||
this.handleSite(carNum2, config).then();
|
||||
} else $(`#${siteId}`).hide();
|
||||
const enabledSites = Array.from(settingsArea.querySelectorAll('input[type="checkbox"]:checked')).map((checkbox => checkbox.getAttribute("data-site-id")));
|
||||
@@ -272,4 +271,4 @@ class OtherSitePlugin extends BasePlugin {
|
||||
}
|
||||
|
||||
|
||||
export { OtherSitePlugin };
|
||||
export {OtherSitePlugin};
|
||||
+17
-5
File diff suppressed because one or more lines are too long
@@ -6,8 +6,9 @@ const getDownPathList = async () => {
|
||||
return "object" == typeof res ? res.data : null;
|
||||
};
|
||||
|
||||
const searchFiles = async (search_value, offset = 0, limit = 30) => {
|
||||
const url = `https://webapi.115.com/files/search?search_value=${encodeURIComponent(search_value)}&offset=${offset}&limit=${limit}`;
|
||||
const searchFiles = async (search_value, offset = 0, limit = 30, params = {}) => {
|
||||
const queryParams = new URLSearchParams({search_value, offset, limit, ...params});
|
||||
const url = `https://webapi.115.com/files/search?${queryParams}`;
|
||||
return await gmHttp.get(url);
|
||||
};
|
||||
|
||||
|
||||
+100
-18
@@ -275,7 +275,13 @@ const _WangPan115MatchPlugin = class _WangPan115MatchPlugin extends BasePlugin {
|
||||
const $detail = $('\n <div class="jhs-match-detail jhs-115-match-detail" id="115-match-table">\n <table>\n <thead>\n <tr style="text-align: center">\n <th colspan="4">115匹配</th>\n </tr>\n <tr>\n <th>名称</th>\n <th>大小</th>\n <th>时间</th>\n <th>播放</th>\n </tr>\n </thead>\n <tbody>\n </tbody>\n </table>\n </div>\n '), $tbody = $detail.find("tbody");
|
||||
try {
|
||||
const carNum2 = this.getPageInfo().carNum;
|
||||
matchList || (matchList = await this.searchFiles(carNum2));
|
||||
if (!matchList) {
|
||||
const {results, usedPhase2} = await this._searchFilesWithPhase(carNum2);
|
||||
matchList = results;
|
||||
if (usedPhase2 && matchList.length > 0) {
|
||||
await this.autoRenameFiles(carNum2, matchList);
|
||||
}
|
||||
}
|
||||
await this.checkLoginStatus();
|
||||
if (this.loginStatus === _WangPan115MatchPlugin.LoginStatus.LOGGED_OUT) $tbody.append(`<tr><td colspan="4">\n <a class='jhs-match-no-login-btn a-info'\n data-keyword="${carNum2}"\n title="未登录115网盘">未登录</a>\n </td></tr>`); else if (matchList.length > 0) {
|
||||
const rowsHtml = matchList.map((match => `\n <tr>\n <td>${match.name}</td>\n <td>${this.formatSize(match.size)}</td>\n <td>${match.createTime}</td>\n <td>\n <a href="https://115vod.com/?pickcode=${match.videoId}&share_id=0"\n target="_blank"\n class="a-success"\n title="播放">播放</a>\n </td>\n </tr>\n `)).join("");
|
||||
@@ -406,8 +412,8 @@ const _WangPan115MatchPlugin = class _WangPan115MatchPlugin extends BasePlugin {
|
||||
onProgress && onProgress(i + 1, selectedItems.length, item, "未匹配到115文件");
|
||||
continue;
|
||||
}
|
||||
const upperCarNum = item.carNum.toUpperCase();
|
||||
const safeMatchList = matchList.filter(m => m.name.toUpperCase().includes(upperCarNum));
|
||||
const {regex: safeRegex} = codeParse(item.carNum);
|
||||
const safeMatchList = matchList.filter(m => safeRegex.test(m.name));
|
||||
if (safeMatchList.length === 0) {
|
||||
failedCount++;
|
||||
onProgress && onProgress(i + 1, selectedItems.length, item, "无安全可删除目录");
|
||||
@@ -507,8 +513,8 @@ const _WangPan115MatchPlugin = class _WangPan115MatchPlugin extends BasePlugin {
|
||||
}
|
||||
|
||||
showDeleteDialog(carNum2, matchList, afterDelete) {
|
||||
const upperCarNum = carNum2.toUpperCase();
|
||||
const safeMatchList = matchList.filter(m => m.name.toUpperCase().includes(upperCarNum));
|
||||
const {regex: safeRegex} = codeParse(carNum2);
|
||||
const safeMatchList = matchList.filter(m => safeRegex.test(m.name));
|
||||
const unsafeCount = matchList.length - safeMatchList.length;
|
||||
if (safeMatchList.length === 0) {
|
||||
show.error(`作品 "${carNum2}" 匹配到的 ${matchList.length} 个项目均不含番号,无法安全删除。请手动在115中删除。`);
|
||||
@@ -616,20 +622,76 @@ const _WangPan115MatchPlugin = class _WangPan115MatchPlugin extends BasePlugin {
|
||||
window.open("https://115.com");
|
||||
}
|
||||
async searchFiles(carNum2) {
|
||||
return (await this._searchFilesWithPhase(carNum2)).results;
|
||||
}
|
||||
|
||||
async _searchFilesWithPhase(carNum2) {
|
||||
const {regex} = codeParse(carNum2);
|
||||
const searchKeyword = carNum2.toLowerCase().replace("fc2-", "");
|
||||
|
||||
const mapAndFilter = (rawData) => {
|
||||
return rawData.map((data => ({
|
||||
folderId: data.fid,
|
||||
dirId: data.cid,
|
||||
videoId: data.pc,
|
||||
name: data.n,
|
||||
createTime: utils.formatDate(new Date(1e3 * data.te)),
|
||||
size: data.s,
|
||||
isVideo: true
|
||||
}))).filter((x => x.folderId && regex.test(x.name)));
|
||||
};
|
||||
|
||||
var _a2;
|
||||
let searchKeyword = carNum2.toLowerCase().replace("fc2-", "");
|
||||
return (null == (_a2 = (await searchFiles(searchKeyword)).data) ? void 0 : _a2.map((data => ({
|
||||
folderId: data.fid,
|
||||
dirId: data.cid,
|
||||
videoId: data.pc,
|
||||
name: data.n,
|
||||
createTime: utils.formatDate(new Date(1e3 * data.te)),
|
||||
size: data.s,
|
||||
isVideo: [ ".mp4", ".avi", ".mov", ".mkv", ".flv", ".wmv" ].some((ext => {
|
||||
var _a3;
|
||||
return null == (_a3 = data.n) ? void 0 : _a3.toLowerCase().endsWith(ext);
|
||||
}))
|
||||
}))).filter((x => x.folderId && x.isVideo && x.name.toLowerCase().includes(searchKeyword)))) || [];
|
||||
const preciseData = (null == (_a2 = (await searchFiles(searchKeyword, 0, 30, {type: 4})).data) ? void 0 : _a2) || [];
|
||||
const preciseResults = mapAndFilter(preciseData);
|
||||
if (preciseResults.length > 0) return {results: preciseResults, usedPhase2: false};
|
||||
|
||||
const numberPart = extractNumberPart(carNum2);
|
||||
if (numberPart === searchKeyword) return {results: preciseResults, usedPhase2: false};
|
||||
|
||||
const fuzzyData = (null == (_a2 = (await searchFiles(numberPart, 0, 30, {type: 4})).data) ? void 0 : _a2) || [];
|
||||
return {results: mapAndFilter(fuzzyData), usedPhase2: true};
|
||||
}
|
||||
|
||||
buildRenameTarget(carNum, originalName) {
|
||||
const {regex} = codeParse(carNum);
|
||||
const match = regex.exec(originalName);
|
||||
if (!match) return null;
|
||||
const before = originalName.slice(0, match.index);
|
||||
const after = originalName.slice(match.index + match[0].length);
|
||||
return before + carNum + after;
|
||||
}
|
||||
|
||||
async autoRenameFiles(carNum, matchList) {
|
||||
try {
|
||||
const enableAutoRename = await storageManager.getSetting("enable115AutoRename", NO);
|
||||
if (enableAutoRename !== YES) return;
|
||||
const scope = await storageManager.getSetting("autoRenameScope", "vr_only");
|
||||
if (scope === "vr_only" && !carNum.toUpperCase().includes("VR")) return;
|
||||
const renameObj = {};
|
||||
matchList.forEach(match => {
|
||||
const newName = this.buildRenameTarget(carNum, match.name);
|
||||
if (newName && newName !== match.name) {
|
||||
renameObj[match.folderId] = newName;
|
||||
}
|
||||
});
|
||||
if (Object.keys(renameObj).length === 0) return;
|
||||
const formData = {};
|
||||
Object.entries(renameObj).forEach(([fid, newName]) => {
|
||||
formData[`files_new_name[${fid}]`] = newName;
|
||||
});
|
||||
const result = await gmHttp.postForm("https://webapi.115.com/files/batch_rename", formData);
|
||||
if (result && result.state) {
|
||||
Object.entries(renameObj).forEach(([fid, newName]) => {
|
||||
const match = matchList.find(m => m.folderId === fid);
|
||||
console.log(`[115重命名] ${match.name} → ${newName}`);
|
||||
});
|
||||
} else {
|
||||
console.warn("[115重命名] 重命名失败:", (result && result.error) || "未知错误");
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("[115重命名] 重命名失败,跳过:", e.message);
|
||||
}
|
||||
}
|
||||
showMatchError($box2, carNum2, error) {
|
||||
$box2.find(".jhs-match-btn").replaceWith(`<a class='jhs-match-error-btn' data-keyword="${carNum2}" \n title="匹配失败,点击重试">匹配失败</a>`);
|
||||
@@ -694,5 +756,25 @@ __publicField(_WangPan115MatchPlugin, "LoginStatus", {
|
||||
|
||||
let WangPan115MatchPlugin = _WangPan115MatchPlugin;
|
||||
|
||||
const extractNumberPart = (carNum) => {
|
||||
let cleaned = carNum.replace(/^fc2[-_]?(ppv[-_])?/i, "");
|
||||
const match = cleaned.match(/(\d+)$/);
|
||||
return match ? match[1] : cleaned;
|
||||
};
|
||||
|
||||
const codeParse = (code) => {
|
||||
const codes = code.split(/-|_/);
|
||||
const sep = "\\s?(0|-|_){0,2}\\s?";
|
||||
|
||||
let pattern = codes.join(sep);
|
||||
if (/^fc2/i.test(code)) pattern = `${codes[0]}${sep}(ppv)?${sep}${codes.at(-1)}`;
|
||||
|
||||
return {
|
||||
code,
|
||||
codes,
|
||||
prefix: codes[0],
|
||||
regex: new RegExp(`(?<![a-z])${pattern}(?!\\d)`, "i"),
|
||||
};
|
||||
};
|
||||
|
||||
export { WangPan115Plugin, WangPan115MatchPlugin };
|
||||
+3
-4
@@ -8,8 +8,8 @@ export default defineConfig({
|
||||
userscript: {
|
||||
name: 'JHS-enhance',
|
||||
namespace: 'JHS-enhance',
|
||||
version: '3.3.7',
|
||||
author: 'xie bro,fireinrain',
|
||||
version: '3.3.10',
|
||||
author: 'xiebro,fireinrain',
|
||||
description: 'Jav-鉴黄师 增强脚本。列表页:作品状态标签、一键筛选、新作品检测、演员黑名单过滤;详情页:磁力链接高亮、DMM 多画质预览视频、标题翻译、整合字幕搜索(迅雷+SubTitleCat)并支持 115 直传、多源预览图(javfree/projectjav/javstore)含来源切换;数据:115 网盘目录匹配与多目录选择、云盘备份/恢复、跨 Tab 同步;其他:自动翻页、分类折叠、Top250、以图识图、热门榜单、评论查看、相关清单。支持 JavDB / JavBus / JavSee / SeeJav / FC2 / JavTrailers',
|
||||
license: 'MIT',
|
||||
icon: 'https://www.google.com/s2/favicons?sz=64&domain=javdb.com',
|
||||
@@ -92,14 +92,13 @@ export default defineConfig({
|
||||
build: {
|
||||
fileName: 'jhs-enhance.user.js',
|
||||
externalGlobals: {
|
||||
'jquery': '$',
|
||||
'jquery': 'jQuery',
|
||||
'localforage': 'localforage',
|
||||
'toastify-js': 'Toastify',
|
||||
'blueimp-md5': 'md5',
|
||||
'tabulator-tables': 'Tabulator',
|
||||
'viewerjs': 'Viewer',
|
||||
'qrcodejs': 'QRCode',
|
||||
'layui-layer': 'layer',
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user