mirror of
https://github.com/fireinrain/JHS-enhance.git
synced 2026-09-28 15:46:38 +08:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3287afe7ea | |||
| 06f7059f1b | |||
| c4d77e728a | |||
| a8fc1cde8a | |||
| c8bcac5f98 | |||
| 2643a3d636 |
@@ -39,15 +39,55 @@ git push → GitHub Actions 自动构建 → 发布 GitHub Release
|
||||
|
||||
## 功能概览
|
||||
|
||||
- **列表页增强**:作品状态标签(屏蔽/收藏/已下载/已观看)、一键筛选已收藏/新作品、演员黑名单过滤、标题关键词过滤
|
||||
- **详情页增强**:磁力链接高亮匹配、DMM 多画质预览视频、标题翻译(Google 翻译)、字幕搜索(整合迅雷 + SubTitleCat,支持 115
|
||||
网盘直传与多目录选择)、演员信息展示、相关清单、评论查看
|
||||
- **多源预览图**:javfree / projectjav / javstore 三源预览图,支持来源切换、下载、新窗口打开,带 sessionStorage 缓存
|
||||
- **115 网盘集成**:目录匹配与预加载、多目录选择上传、字幕一键直传
|
||||
- **数据管理**:IndexedDB 本地存储、云盘备份/恢复(阿里云盘 / 115 网盘 / WebDAV)、跨 Tab 数据同步
|
||||
- **自动化任务**:自动翻页、新作品检测、收藏演员同步、黑名单更新
|
||||
- **UI 优化**:分类折叠、热榜 Top250、免 VIP 查看热播、以图识图、FC2 支持
|
||||
- **多站点支持**:JavDB / JavBus / JavSee / SeeJav / FC2 / JavTrailers / SubTitleCat
|
||||
### 列表页增强
|
||||
|
||||
- 作品状态标签(屏蔽/收藏/已下载/已观看)
|
||||
- 一键筛选已收藏/新作品/待鉴定
|
||||
- 演员黑名单过滤、标题关键词过滤
|
||||
- 封面快捷按钮(长缩略图/预览视频/鉴定按钮/第三方跳转/复制番号)
|
||||
- 分类折叠、热榜 Top250、免 VIP 查看热播
|
||||
- 自动翻页
|
||||
|
||||
### 详情页增强
|
||||
|
||||
- **磁力排序与过滤(JavDB)**:按发布日期/文件大小/文件数量拖拽排序,勾选启用;高清/4K/字幕/无码多条件并集过滤,4K 磁力行高亮显示
|
||||
- 磁力链接高亮匹配已收藏/已下载状态
|
||||
- DMM 多画质预览视频
|
||||
- **标题翻译**:Google 翻译,支持开关多次切换不失效
|
||||
- **字幕搜索**:合并迅雷 + SubTitleCat 为单一入口,支持 115 网盘直传、详情页预匹配目录、多目录选择上传、长目录名截断展示、字幕预览绿色字体
|
||||
- **多源预览图**:javfree / projectjav / javstore 三源切换,来源按钮绿色高亮,sessionStorage 缓存,错误提示弹窗内显示
|
||||
- 演员信息展示、相关清单、评论查看
|
||||
- 以图识图、第三方站点跳转
|
||||
|
||||
### 115 网盘集成
|
||||
|
||||
- 作品目录匹配与详情页预加载
|
||||
- 多目录选择上传字幕
|
||||
- 字幕一键直传至匹配目录
|
||||
- **作品删除增强**:删除整个文件夹(非仅视频),支持标记删除批量操作,删除后字幕目录列表联动;批量删除含风控保护(1~3s
|
||||
随机延迟 + 触发风控自动重试退避)
|
||||
|
||||
### 数据管理
|
||||
|
||||
- IndexedDB 本地存储
|
||||
- 云盘备份/恢复(阿里云盘 / WebDAV)
|
||||
- 跨 Tab 数据同步(BroadcastChannel)
|
||||
|
||||
### 自动化任务
|
||||
|
||||
- 新作品检测
|
||||
- 收藏演员同步
|
||||
- 黑名单自动检测与更新
|
||||
- 后台定时任务调度
|
||||
|
||||
### 其他
|
||||
|
||||
- FC2 站点支持(含 123AV 补全)
|
||||
- MagnetHub 磁力搜索
|
||||
- JavTrailers 站点处理
|
||||
- 设置面板:快捷键配置、域名配置、缓存清理、标记删除列表、打赏作者(二维码/钱包地址可自定义)
|
||||
- 浏览历史、想看/已看列表
|
||||
- 多站点支持:JavDB / JavBus / JavSee / SeeJav / FC2 / JavTrailers / SubTitleCat
|
||||
|
||||
---
|
||||
|
||||
@@ -59,63 +99,65 @@ git push → GitHub Actions 自动构建 → 发布 GitHub Release
|
||||
```
|
||||
JHS-enhance/
|
||||
├── src/
|
||||
│ ├── main.js # 入口文件:全局初始化、插件注册、页面启动
|
||||
│ ├── core/ # 基础设施层
|
||||
│ │ ├── constants.js # 全局常量、CSS 样式、页面类型判断
|
||||
│ │ ├── base-plugin.js # 插件基类(含 SVG 图标、页面信息解析)
|
||||
│ │ ├── plugin-manager.js # 插件管理器(注册、CSS/插件生命周期)
|
||||
│ │ ├── globals.js # 全局依赖挂载(jQuery、localforage 等)
|
||||
│ │ ├── storage.js # IndexedDB 存储管理器(localforage 封装)
|
||||
│ │ ├── utils.js # 工具函数集(DOM 操作、弹窗、快捷键等)
|
||||
│ │ └── gmHttp.js # GM_xmlhttpRequest 封装(带重试/并发控制)
|
||||
│ ├── api/ # API 层
|
||||
│ │ ├── javdb.js # JavDB API 封装(签名、搜索、评论等)
|
||||
│ │ └── dmm.js # DMM 预览视频获取(多画质)
|
||||
│ └── plugins/ # 插件层(40+ 个功能插件)
|
||||
│ ├── list-page.js # 列表页过滤与状态标签
|
||||
│ ├── list-page-button.js # 列表页功能按钮(待鉴定/已收藏/新作品/黑名单)
|
||||
│ ├── detail-page.js # 详情页数据加载
|
||||
│ ├── detail-page-button.js# 详情页操作按钮(含 115 目录预加载)
|
||||
│ ├── setting.js # 设置面板(Tabulator 表格)
|
||||
│ ├── translate.js # 标题翻译(Google 翻译 API)
|
||||
│ ├── preview-video.js # DMM 预览视频
|
||||
│ ├── highlight-magnet.js # 磁力链接高亮匹配
|
||||
│ ├── auto-page.js # 自动翻页 / 云盘备份
|
||||
│ ├── blacklist.js # 黑名单管理(演员/番号)
|
||||
│ ├── favorite-actresses.js# 收藏演员同步
|
||||
│ ├── new-video.js # 新作品检测
|
||||
│ ├── task.js # 后台任务调度
|
||||
│ ├── history.js # 浏览历史
|
||||
│ ├── wangpan-115.js # 115 网盘匹配
|
||||
│ ├── wangpan-115-task.js # 115 网盘任务处理
|
||||
│ ├── aliyun-pan.js # 阿里云盘
|
||||
│ ├── main.js # 入口文件:全局初始化、插件注册、页面启动、loading/Toast/Viewer 全局函数
|
||||
│ ├── core/ # 基础设施层(7 个模块)
|
||||
│ │ ├── constants.js # 全局常量、CSS 样式、页面类型判断、枚举值
|
||||
│ │ ├── base-plugin.js # 插件基类(含 SVG 图标、页面信息解析、getBean 跨插件调用)
|
||||
│ │ ├── plugin-manager.js # 插件管理器(注册、CSS 注入、插件生命周期、按站点分组)
|
||||
│ │ ├── globals.js # 全局依赖挂载(jQuery、localforage、layer 等)
|
||||
│ │ ├── storage.js # IndexedDB 存储管理器(localforage 封装,含缓存)
|
||||
│ │ ├── utils.js # 工具函数集(DOM 操作、弹窗确认、快捷键、重试、日期格式化)
|
||||
│ │ └── gmHttp.js # GM_xmlhttpRequest 封装(GET/POST/Form/File、超时重试、分块下载)
|
||||
│ ├── api/ # API 层(2 个模块)
|
||||
│ │ ├── javdb.js # JavDB API 封装(签名算法、搜索、评论、磁力列表)
|
||||
│ │ └── dmm.js # DMM 预览视频获取(多画质、默认画质选择)
|
||||
│ ├── lib/ # 第三方补丁库
|
||||
│ │ └── gm-xhr-parallel.js # Tampermonkey MV3 并行请求修复(redirect: manual)
|
||||
│ └── plugins/ # 插件层(41 个功能插件)
|
||||
│ ├── list-page.js # 列表页过滤与状态标签渲染
|
||||
│ ├── list-page-button.js # 列表页功能按钮(待鉴定/已收藏/新作品/黑名单管理)
|
||||
│ ├── detail-page.js # 详情页数据加载与基础增强
|
||||
│ ├── detail-page-button.js# 详情页操作按钮(115 目录预加载 + 磁力排序过滤面板)
|
||||
│ ├── setting.js # 设置面板(Tab 页:基础/任务/域名/快捷键/屏蔽/缓存/标记删除/打赏作者)
|
||||
│ ├── translate.js # 标题翻译(Google 翻译 API,原始标题缓存)
|
||||
│ ├── preview-video.js # DMM 预览视频播放器
|
||||
│ ├── highlight-magnet.js # 磁力链接高亮 + 排序 + 过滤(多键排序、并集过滤、4K 高亮)
|
||||
│ ├── auto-page.js # 自动翻页
|
||||
│ ├── blacklist.js # 黑名单管理(屏蔽演员/番号/关键词)
|
||||
│ ├── favorite-actresses.js# 收藏演员同步与高亮
|
||||
│ ├── new-video.js # 新作品检测与通知
|
||||
│ ├── task.js # 后台任务调度(黑名单/收藏演员/新作品定时执行)
|
||||
│ ├── history.js # 浏览历史表格
|
||||
│ ├── wangpan-115.js # 115 网盘匹配/标签/删除(含风控保护的批量删除)
|
||||
│ ├── wangpan-115-task.js # 115 网盘任务处理(文件搜索 API 封装)
|
||||
│ ├── aliyun-pan.js # 阿里云盘备份/恢复
|
||||
│ ├── fc2.js # FC2 页面处理
|
||||
│ ├── fc2-by123av.js # FC2 通过 123AV 搜索
|
||||
│ ├── actress-info.js # 演员信息展示
|
||||
│ ├── related.js # 相关清单
|
||||
│ ├── review.js # 评论展示
|
||||
│ ├── cover-button.js # 封面按钮
|
||||
│ ├── fold-category.js # 分类折叠
|
||||
│ ├── hit-show.js # 热播榜单
|
||||
│ ├── fc2-by123av.js # FC2 通过 123AV 补全番号信息
|
||||
│ ├── actress-info.js # 演员信息展示(含头像、生日、三围等)
|
||||
│ ├── related.js # 相关作品清单
|
||||
│ ├── review.js # 评论区展示
|
||||
│ ├── cover-button.js # 封面快捷按钮(SVG 图标 + 5 种封面操作)
|
||||
│ ├── fold-category.js # 分类折叠展开
|
||||
│ ├── hit-show.js # 热播榜单 + 免 VIP 查看
|
||||
│ ├── top250.js # Top250 排行榜
|
||||
│ ├── nav-bar.js # JavDB 导航栏增强
|
||||
│ ├── bus-nav-bar.js # JavBus 导航栏增强
|
||||
│ ├── bus-detail-page.js # JavBus 详情页
|
||||
│ ├── bus-detail-page.js # JavBus 详情页专属处理
|
||||
│ ├── bus-img.js # JavBus 图片处理
|
||||
│ ├── bus-preview-video.js # JavBus 预览视频
|
||||
│ ├── image-recognition.js # 以图识图
|
||||
│ ├── screenshot.js # 多源预览图(javfree/projectjav/javstore)
|
||||
│ ├── subtitles.js # 字幕搜索(整合迅雷+SubTitleCat)+ 115 直传
|
||||
│ ├── req115.js # 115 网盘 API 封装(Req + Drive115 + Req115)
|
||||
│ ├── image-recognition.js # 以图识图(第三方识图接口)
|
||||
│ ├── screenshot.js # 多源预览图(javfree/projectjav/javstore + 来源切换弹窗)
|
||||
│ ├── subtitles.js # 字幕搜索(整合迅雷+SubTitleCat)+ 115 多目录直传
|
||||
│ ├── req115.js # 115 网盘 API 封装(Req 基类 + Drive115 + Req115)
|
||||
│ ├── magnet-hub.js # MagnetHub 磁力搜索
|
||||
│ ├── other-site.js # 第三方站点链接
|
||||
│ ├── jav-trailers.js # JavTrailers 站点
|
||||
│ ├── subtitle-cat.js # SubTitleCat 字幕搜索(已整合到 subtitles.js,保留文件)
|
||||
│ ├── other-site.js # 第三方站点跳转链接
|
||||
│ ├── jav-trailers.js # JavTrailers 站点处理
|
||||
│ ├── subtitle-cat.js # SubTitleCat 字幕搜索(已整合,保留原文件)
|
||||
│ ├── filter-title-keyword.js # 标题关键词过滤
|
||||
│ └── want-watched.js # 想看/已看视频列表
|
||||
├── vite.config.mjs # Vite + vite-plugin-monkey 配置
|
||||
├── vite.config.mjs # Vite + vite-plugin-monkey 配置(元数据/CDN 依赖/域名匹配)
|
||||
├── package.json
|
||||
└── JHS-origin.js # 原始单体脚本(参考)
|
||||
└── JHS-origin.js # 原始单体脚本(参考归档)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -245,6 +287,12 @@ window.show.ok('操作成功'); // Toast 通知
|
||||
|
||||
---
|
||||
|
||||
## 赞助
|
||||
|
||||
如果当前项目有帮到您的话,赞助我喝瓶水 :)
|
||||
|
||||

|
||||
|
||||
## 构建配置
|
||||
|
||||
`vite-plugin-monkey` 在 `vite.config.mjs` 中配置了:
|
||||
@@ -258,7 +306,21 @@ window.show.ok('操作成功'); // Toast 通知
|
||||
|
||||
## 版本历史
|
||||
|
||||
- **v3.3.6** — 工程化重构版本,模块化拆分,支持 HMR 开发
|
||||
- **v3.3.7**
|
||||
- 新增:JavDB 磁力排序与过滤(拖拽排序、多条件并集过滤、4K 高亮)
|
||||
- 新增:多源预览图(javfree/projectjav/javstore + 来源切换弹窗 + 绿色高亮)
|
||||
- 新增:字幕搜索合并为单一入口(迅雷+SubTitleCat),详情页预匹配 115 目录,多目录选择上传
|
||||
- 新增:标记删除批量 115 删除(含风控保护:1~3s 随机延迟 + 风控自动退避重试)
|
||||
- 优化:作品删除改为删除整个文件夹(非仅视频),删除后字幕目录列表联动更新
|
||||
- 修复:标题翻译开关多次切换后失效(原始标题 data-original-title 缓存)
|
||||
- 修复:115 目录名过长截断展示(最多 26 字符)
|
||||
- 修复:字幕预览区域字体改为绿色
|
||||
- 修复:JavBus/JavDB 磁力列表 DOM 精确提取(data-rank/data-size/data-files/data-date 属性)
|
||||
- 修复:无码过滤条件新增 -U/-UC/-u/-uc 匹配
|
||||
- 优化:删除作品 115 风控提示(标记删除面板)
|
||||
- 优化:设置面板打赏作者二维码/钱包地址可自定义
|
||||
|
||||
- **v3.3.6** — 工程化重构版本,模块化拆分(41 个插件),支持 Vite HMR 开发热更新
|
||||
|
||||
---
|
||||
|
||||
|
||||
Vendored
+585
-60
@@ -631,6 +631,198 @@
|
||||
background: rgba(59, 130, 246, 0.9);
|
||||
box-shadow: 0 4px 16px rgba(59, 130, 246, 0.4);
|
||||
}
|
||||
|
||||
.magnet-4k-highlight {
|
||||
background-color: #fff5f5 !important;
|
||||
}
|
||||
.magnet-4k-highlight .name {
|
||||
color: #f40 !important;
|
||||
}
|
||||
|
||||
.msf-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
z-index: 999997;
|
||||
}
|
||||
.msf-panel {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
|
||||
z-index: 999998;
|
||||
width: 380px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.msf-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
background: #f9fafb;
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
.msf-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
.msf-close {
|
||||
font-size: 22px;
|
||||
color: #9ca3af;
|
||||
cursor: pointer;
|
||||
padding: 0 6px;
|
||||
line-height: 1;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.msf-close:hover {
|
||||
color: #ef4444;
|
||||
}
|
||||
.msf-body {
|
||||
padding: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.msf-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.msf-section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
}
|
||||
.msf-sort-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.msf-sort-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
cursor: default;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
}
|
||||
.msf-sort-item:hover {
|
||||
border-color: #cbd5e1;
|
||||
background: #f1f5f9;
|
||||
}
|
||||
.msf-sort-item.msf-dragging {
|
||||
opacity: 0.5;
|
||||
border-color: #3b82f6;
|
||||
background: #eff6ff;
|
||||
}
|
||||
.msf-sort-item.msf-drag-over {
|
||||
border-color: #3b82f6;
|
||||
background: #eff6ff;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
.msf-drag-handle {
|
||||
color: #cbd5e1;
|
||||
font-size: 16px;
|
||||
cursor: grab;
|
||||
line-height: 1;
|
||||
letter-spacing: -2px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.msf-drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
.msf-sort-item:hover .msf-drag-handle {
|
||||
color: #94a3b8;
|
||||
}
|
||||
.msf-sort-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
}
|
||||
.msf-sort-checkbox {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.msf-sort-hint {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.msf-filter-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.msf-filter-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 14px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
}
|
||||
.msf-filter-label:hover {
|
||||
border-color: #cbd5e1;
|
||||
background: #f1f5f9;
|
||||
}
|
||||
.msf-filter-checkbox {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.msf-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 14px 18px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
.msf-btn {
|
||||
padding: 8px 22px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.msf-btn-cancel {
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
}
|
||||
.msf-btn-cancel:hover {
|
||||
background: #e5e7eb;
|
||||
}
|
||||
.msf-btn-confirm {
|
||||
background: #3b82f6;
|
||||
color: #fff;
|
||||
}
|
||||
.msf-btn-confirm:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
const subtitleModalCss = `
|
||||
@@ -3740,6 +3932,7 @@ ${value}\r
|
||||
<div id="markDeleteList" style="max-height: 450px; overflow-y: auto; border: 1px solid #e0e0e0; border-radius: 4px;">
|
||||
<div style="text-align: center; padding: 40px; color: #999;">加载中...</div>
|
||||
</div>
|
||||
<p style="color: #e74c3c; font-size: 12px; margin-top: 10px; text-align: left;">⚠ 作品删除目前暂无法删除115下载记录,如果需要重新下载,请去115云下载记录里删除或清空记录</p>
|
||||
</div>
|
||||
|
||||
<!-- 快捷键 -->
|
||||
@@ -3901,13 +4094,13 @@ ${value}\r
|
||||
</div>
|
||||
</div>
|
||||
<div id="tip-author-panel" class="content-panel" style="display: ${"tip-author-panel" === defaultActivePanel ? "block" : "none"};">
|
||||
<p style="color: #666; font-size: 0.9em;">如果JAV-JHS给您带来了便捷和价值,请考虑给予一点支持,您的鼓励是我持续创作的最大动力!感谢您的慷慨支持!</p>
|
||||
<p style="color: #666; font-size: 0.9em;">如果JHS-enhance给您带来了便捷和价值,请考虑给予一点支持,您的鼓励是我持续创作的最大动力!感谢您的慷慨支持!</p>
|
||||
<div>
|
||||
<div style="display: flex; justify-content: space-around; align-items: flex-start; margin-bottom: 20px; flex-wrap: wrap;">
|
||||
<div style="text-align: center; margin: 10px; flex: 1 1 30%; min-width: 150px;">
|
||||
<img src="https://imgur.com/AvF0r3r.png" alt="TRC20-USDT二维码" style="width: 350px; height: 350px; border: 1px solid #ddd; padding: 5px; display: block; margin: 0 auto 5px;">
|
||||
<p>TRC20-USDT</p>
|
||||
<input type="text" readonly value="TYphgzpJ2hoDTa3J7kzj5xaHWbcPAyhbd5" onclick="this.select();document.execCommand('copy');alert('地址已复制!');"
|
||||
<img src="https://i.imgur.com/KM5I6dD.jpeg" alt="SOL二维码" style="width: 350px; height: 350px; border: 1px solid #ddd; padding: 5px; display: block; margin: 0 auto 5px;">
|
||||
<p>SOL</p>
|
||||
<input type="text" readonly value="5xvRp1jhWkCasT1H7Nh8G5eAuvdCFD5s2Xux2w3rJ8RG" onclick="this.select();document.execCommand('copy');alert('地址已复制!');"
|
||||
style="width: 90%; padding: 5px; margin-top: 5px; border: 1px solid #a99087; background-color: #fff; text-align: center; font-size: 0.8em; cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
<p style="font-size: 0.75em; color: #5a504c; margin-top: 4px;">点击地址可复制</p>
|
||||
</div>
|
||||
@@ -8075,26 +8268,112 @@ ${value}\r
|
||||
getName() {
|
||||
return "HighlightMagnetPlugin";
|
||||
}
|
||||
doFilterMagnet() {
|
||||
this.handleDb();
|
||||
this.handleBus();
|
||||
|
||||
parseDateFromAttr(dateStr) {
|
||||
if (!dateStr || dateStr.length !== 8) {
|
||||
const d2 = new Date(dateStr);
|
||||
return isNaN(d2.getTime()) ? 0 : d2.getTime();
|
||||
}
|
||||
const y = parseInt(dateStr.substring(0, 4), 10);
|
||||
const m = parseInt(dateStr.substring(4, 6), 10) - 1;
|
||||
const d = parseInt(dateStr.substring(6, 8), 10);
|
||||
const date = new Date(y, m, d);
|
||||
return isNaN(date.getTime()) ? 0 : date.getTime();
|
||||
}
|
||||
handleDb() {
|
||||
|
||||
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("無碼")
|
||||
};
|
||||
}
|
||||
|
||||
async applySortAndFilter(settings) {
|
||||
if (!isJavDb$1) return;
|
||||
let magnetNameList = $("#magnets-content .name");
|
||||
if (0 === magnetNameList.length) return;
|
||||
const HIGH_QUALITY_KEYWORDS = ["4k", "-c", "-u", "-uc"];
|
||||
let hasHighQuality = false;
|
||||
magnetNameList.each(((_, el) => {
|
||||
const $el = $(el), text = $el.text().toLowerCase(), isHighQuality = HIGH_QUALITY_KEYWORDS.some(((keyword) => text.includes(keyword)));
|
||||
$el.parent().parent().parent().addClass("magnet-row");
|
||||
text.includes("4k") && $el.css("color", "#f40");
|
||||
if (isHighQuality) {
|
||||
hasHighQuality = true;
|
||||
$el.parent().parent().parent().addClass("high-quality");
|
||||
}
|
||||
}));
|
||||
hasHighQuality ? $("#magnets-content .magnet-row").not(".high-quality").hide() : $("#enable-magnets-filter").addClass("do-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;
|
||||
});
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
}
|
||||
handleBus() {
|
||||
isJavBus$1 && window.isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => {
|
||||
@@ -8114,12 +8393,6 @@ ${value}\r
|
||||
})) : $("#enable-magnets-filter").addClass("do-hide");
|
||||
}));
|
||||
}
|
||||
showAll() {
|
||||
if (isJavDb$1) {
|
||||
$("#magnets-content .item").toArray().forEach(((el) => $(el).show()));
|
||||
}
|
||||
isJavBus$1 && $("#magnet-table tr").toArray().forEach(((el) => $(el).show()));
|
||||
}
|
||||
}
|
||||
class FoldCategoryPlugin extends BasePlugin {
|
||||
getName() {
|
||||
@@ -10714,10 +10987,13 @@ ${err.stack}` : "");
|
||||
async batchDeleteMarked(selectedItems, onProgress) {
|
||||
let deletedCount = 0;
|
||||
let failedCount = 0;
|
||||
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
const randomDelay = () => new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 2e3));
|
||||
for (let i = 0; i < selectedItems.length; i++) {
|
||||
const item = selectedItems[i];
|
||||
try {
|
||||
if (i > 0) {
|
||||
await randomDelay();
|
||||
}
|
||||
const matchList = await this.searchFiles(item.carNum);
|
||||
if (matchList.length === 0) {
|
||||
failedCount++;
|
||||
@@ -10732,24 +11008,50 @@ ${err.stack}` : "");
|
||||
continue;
|
||||
}
|
||||
let itemDeleted = 0;
|
||||
for (const m of safeMatchList) {
|
||||
try {
|
||||
const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", {
|
||||
pid: m.dirId,
|
||||
"fid[0]": m.folderId
|
||||
});
|
||||
if (result && result.state) {
|
||||
itemDeleted++;
|
||||
deletedCount++;
|
||||
const uniqueDirIds = [...new Set(safeMatchList.map((m) => m.dirId))];
|
||||
for (let d = 0; d < uniqueDirIds.length; d++) {
|
||||
if (d > 0) {
|
||||
await randomDelay();
|
||||
}
|
||||
const dirId = uniqueDirIds[d];
|
||||
let retryCount = 0;
|
||||
const maxRetries = 3;
|
||||
while (retryCount <= maxRetries) {
|
||||
try {
|
||||
const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", {
|
||||
"fid[0]": dirId,
|
||||
ignore_warn: "1"
|
||||
});
|
||||
if (result && result.state) {
|
||||
itemDeleted++;
|
||||
deletedCount++;
|
||||
break;
|
||||
} else {
|
||||
const errMsg = result && result.error || "未知错误";
|
||||
if (retryCount < maxRetries && this.isRateLimited(result)) {
|
||||
retryCount++;
|
||||
const backoff = (retryCount + 1) * 2e3 + Math.random() * 1e3;
|
||||
onProgress && onProgress(i + 1, selectedItems.length, item, `风控触发,${backoff / 1e3}秒后重试(${retryCount}/${maxRetries})`);
|
||||
await new Promise((resolve) => setTimeout(resolve, backoff));
|
||||
continue;
|
||||
}
|
||||
console.error(`删除目录失败: ${dirId}`, errMsg);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
if (retryCount < maxRetries && this.isRateLimited(e)) {
|
||||
retryCount++;
|
||||
const backoff = (retryCount + 1) * 2e3 + Math.random() * 1e3;
|
||||
onProgress && onProgress(i + 1, selectedItems.length, item, `风控触发,${backoff / 1e3}秒后重试(${retryCount}/${maxRetries})`);
|
||||
await new Promise((resolve) => setTimeout(resolve, backoff));
|
||||
continue;
|
||||
}
|
||||
console.error(`删除目录失败: ${dirId}`, e);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`删除失败: ${m.name}`, e);
|
||||
}
|
||||
}
|
||||
onProgress && onProgress(i + 1, selectedItems.length, item, itemDeleted > 0 ? `已删除${itemDeleted}个` : "删除失败");
|
||||
if (i < selectedItems.length - 1) {
|
||||
await delay(800);
|
||||
}
|
||||
} catch (error) {
|
||||
failedCount++;
|
||||
onProgress && onProgress(i + 1, selectedItems.length, item, error.message || "出错");
|
||||
@@ -10758,6 +11060,20 @@ ${err.stack}` : "");
|
||||
}
|
||||
return {deletedCount, failedCount};
|
||||
}
|
||||
|
||||
isRateLimited(response) {
|
||||
if (!response) return false;
|
||||
if (response.state === false) {
|
||||
const msg = (response.error || response.msg || "").toLowerCase();
|
||||
if (msg.includes("频繁") || msg.includes("操作太频繁") || msg.includes("稍后再试") || msg.includes("rate") || msg.includes("limit") || msg.includes("too many")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (response.status === 403 || response.status === 429) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
showDeleteDialog(carNum2, matchList, afterDelete) {
|
||||
const upperCarNum = carNum2.toUpperCase();
|
||||
const safeMatchList = matchList.filter((m) => m.name.toUpperCase().includes(upperCarNum));
|
||||
@@ -10800,6 +11116,7 @@ ${err.stack}` : "");
|
||||
btn: ["确定删除", "取消"],
|
||||
shadeClose: false,
|
||||
yes: async (index) => {
|
||||
var _a, _b;
|
||||
const $layer = $(`#layui-layer${index}`);
|
||||
const checked = $layer.find(".jhs-delete-folder-cb:checked");
|
||||
if (0 === checked.length) {
|
||||
@@ -10813,19 +11130,26 @@ ${err.stack}` : "");
|
||||
let loadObj = loading();
|
||||
let deletedCount = 0;
|
||||
try {
|
||||
for (const i of selected) {
|
||||
const m = safeMatchList[i];
|
||||
const uniqueDirIds = [...new Set(selected.map((i) => safeMatchList[i].dirId))];
|
||||
for (const dirId of uniqueDirIds) {
|
||||
const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", {
|
||||
pid: m.dirId,
|
||||
"fid[0]": m.folderId
|
||||
"fid[0]": dirId,
|
||||
ignore_warn: "1"
|
||||
});
|
||||
result && result.state ? deletedCount++ : console.error(`删除失败: ${m.name}`, result);
|
||||
result && result.state ? deletedCount++ : console.error(`删除目录失败: ${dirId}`, result);
|
||||
}
|
||||
loadObj.close();
|
||||
show.ok(`作品 "${carNum2}" 已删除 ${deletedCount} 个文件夹`);
|
||||
const newMatchList = await this.searchFiles(carNum2);
|
||||
afterDelete && afterDelete(newMatchList);
|
||||
this.getBean("WangPan115TaskPlugin").deleteOfflineTasksByKeyword(carNum2);
|
||||
const remainingCids = new Set(newMatchList.map((m) => m.dirId));
|
||||
JavPackSubtitle.cached115Matches = JavPackSubtitle.cached115Matches.filter((m) => remainingCids.has(m.cid));
|
||||
JavPackSubtitle.current115Cids = JavPackSubtitle.current115Cids.filter((cid) => remainingCids.has(cid));
|
||||
try {
|
||||
(_b = (_a = this.getBean("WangPan115TaskPlugin")).deleteOfflineTasksByKeyword) == null ? void 0 : _b.call(_a, carNum2);
|
||||
} catch (e) {
|
||||
console.warn("删除离线任务暂不可用:", e.message);
|
||||
}
|
||||
} catch (error) {
|
||||
loadObj.close();
|
||||
console.error("删除115文件失败:", error);
|
||||
@@ -12887,7 +13211,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="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()));
|
||||
@@ -12907,19 +13231,38 @@ ${err.stack}` : "");
|
||||
}
|
||||
});
|
||||
}));
|
||||
const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin"), enableMagnetsFilter = await storageManager.getSetting("enableMagnetsFilter", YES);
|
||||
$("#magnets-span").text(enableMagnetsFilter === YES ? "关闭磁力过滤" : "开启磁力过滤");
|
||||
enableMagnetsFilter === YES && highlightMagnetPlugin.doFilterMagnet();
|
||||
const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin"),
|
||||
enableMagnetsFilter = await storageManager.getSetting("enableMagnetsFilter", NO);
|
||||
if (enableMagnetsFilter === YES) {
|
||||
const savedSettings = await storageManager.getSetting("magnetSortAndFilter", "{}");
|
||||
let settings;
|
||||
try {
|
||||
settings = JSON.parse(savedSettings);
|
||||
} catch (e) {
|
||||
settings = {};
|
||||
}
|
||||
const sortEnabled = settings.sortEnabled || {};
|
||||
const fullSortOrder = settings.sortOrder && settings.sortOrder.length === 3 ? settings.sortOrder : ["date", "size", "files"];
|
||||
const effectiveSortOrder = fullSortOrder.filter((key) => sortEnabled[key]);
|
||||
highlightMagnetPlugin.applySortAndFilter({
|
||||
filterHD: settings.filterHD || false,
|
||||
filter4K: settings.filter4K || false,
|
||||
filterSubtitle: settings.filterSubtitle || false,
|
||||
filterUncensored: settings.filterUncensored || false,
|
||||
sortOrder: effectiveSortOrder
|
||||
});
|
||||
$("#magnets-span").text("取消磁力过滤");
|
||||
} else {
|
||||
$("#magnets-span").text("排序与过滤");
|
||||
}
|
||||
$("#enable-magnets-filter").on("click", ((event) => {
|
||||
let $span = $("#magnets-span");
|
||||
if ("关闭磁力过滤" === $span.text()) {
|
||||
const $span = $("#magnets-span");
|
||||
if ("取消磁力过滤" === $span.text()) {
|
||||
highlightMagnetPlugin.showAll();
|
||||
$span.text("开启磁力过滤");
|
||||
$span.text("排序与过滤");
|
||||
storageManager.saveSettingItem("enableMagnetsFilter", NO);
|
||||
} else {
|
||||
highlightMagnetPlugin.doFilterMagnet();
|
||||
$span.text("关闭磁力过滤");
|
||||
storageManager.saveSettingItem("enableMagnetsFilter", YES);
|
||||
this.openMagnetSortFilterPanel(highlightMagnetPlugin);
|
||||
}
|
||||
}));
|
||||
$("#subtitleSearchBtn").on("click", (() => {
|
||||
@@ -12928,6 +13271,186 @@ ${err.stack}` : "");
|
||||
window.JavPackSubtitle.preload115Matches(carNum2);
|
||||
this.showStatus(carNum2).then();
|
||||
}
|
||||
|
||||
async openMagnetSortFilterPanel(highlightMagnetPlugin) {
|
||||
const existingPanel = document.getElementById("magnet-sort-filter-panel");
|
||||
if (existingPanel) {
|
||||
existingPanel.remove();
|
||||
return;
|
||||
}
|
||||
const savedSettings = await storageManager.getSetting("magnetSortAndFilter", "{}");
|
||||
let settings;
|
||||
try {
|
||||
settings = JSON.parse(savedSettings);
|
||||
} catch (e) {
|
||||
settings = {};
|
||||
}
|
||||
const sortEnabled = settings.sortEnabled || {date: false, size: false, files: false};
|
||||
const sortOrder = settings.sortOrder && settings.sortOrder.length === 3 ? settings.sortOrder : ["date", "size", "files"];
|
||||
const filterHD = settings.filterHD || false;
|
||||
const filter4K = settings.filter4K || false;
|
||||
const filterSubtitle = settings.filterSubtitle || false;
|
||||
const filterUncensored = settings.filterUncensored || false;
|
||||
const SORT_LABELS = {date: "发布日期", size: "文件大小", files: "文件数量"};
|
||||
const SORT_HINTS = {date: "从新到旧", size: "从大到小", files: "从少到多"};
|
||||
const FILTER_ITEMS = [
|
||||
{key: "HD", label: "高清", checked: filterHD},
|
||||
{key: "4K", label: "4K", checked: filter4K},
|
||||
{key: "Subtitle", label: "字幕", checked: filterSubtitle},
|
||||
{key: "Uncensored", label: "无码", checked: filterUncensored}
|
||||
];
|
||||
const currentSortOrder = [...sortOrder];
|
||||
const currentSortEnabled = {...sortEnabled};
|
||||
const currentFilters = {
|
||||
HD: filterHD,
|
||||
"4K": filter4K,
|
||||
Subtitle: filterSubtitle,
|
||||
Uncensored: filterUncensored
|
||||
};
|
||||
const buildSortItemsHTML = () => {
|
||||
return currentSortOrder.map((key) => {
|
||||
const checked = currentSortEnabled[key] ? "checked" : "";
|
||||
return `<div class="msf-sort-item" draggable="true" data-sort-key="${key}">
|
||||
<span class="msf-drag-handle">⋮⋮</span>
|
||||
<label class="msf-sort-label">
|
||||
<input type="checkbox" class="msf-sort-checkbox" data-sort-key="${key}" ${checked}>
|
||||
${SORT_LABELS[key]} <span class="msf-sort-hint">${SORT_HINTS[key]}</span>
|
||||
</label>
|
||||
</div>`;
|
||||
}).join("");
|
||||
};
|
||||
const buildFilterItemsHTML = () => {
|
||||
return FILTER_ITEMS.map((item) => {
|
||||
const checked = currentFilters[item.key] ? "checked" : "";
|
||||
return `<label class="msf-filter-label">
|
||||
<input type="checkbox" class="msf-filter-checkbox" data-filter-key="${item.key}" ${checked}>
|
||||
${item.label}
|
||||
</label>`;
|
||||
}).join("");
|
||||
};
|
||||
const panelHTML = `
|
||||
<div class="msf-overlay" id="msf-overlay"></div>
|
||||
<div class="msf-panel" id="magnet-sort-filter-panel">
|
||||
<div class="msf-header">
|
||||
<span class="msf-title">磁力排序与过滤</span>
|
||||
<span class="msf-close" id="msf-close-btn">×</span>
|
||||
</div>
|
||||
<div class="msf-body">
|
||||
<div class="msf-section">
|
||||
<div class="msf-section-title">排序优先级(拖动调整顺序,勾选启用)</div>
|
||||
<div class="msf-sort-list" id="msf-sort-list">
|
||||
${buildSortItemsHTML()}
|
||||
</div>
|
||||
</div>
|
||||
<div class="msf-section">
|
||||
<div class="msf-section-title">过滤条件(可多选,并集关系)</div>
|
||||
<div class="msf-filter-list">
|
||||
${buildFilterItemsHTML()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msf-footer">
|
||||
<button class="msf-btn msf-btn-cancel" id="msf-cancel-btn">取消</button>
|
||||
<button class="msf-btn msf-btn-confirm" id="msf-confirm-btn">确认</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.innerHTML = panelHTML;
|
||||
document.body.appendChild(wrapper);
|
||||
const panel = document.getElementById("magnet-sort-filter-panel");
|
||||
const overlay = document.getElementById("msf-overlay");
|
||||
const closePanel = () => {
|
||||
panel.remove();
|
||||
overlay.remove();
|
||||
};
|
||||
overlay.addEventListener("click", closePanel);
|
||||
document.getElementById("msf-close-btn").addEventListener("click", closePanel);
|
||||
document.getElementById("msf-cancel-btn").addEventListener("click", closePanel);
|
||||
const rebuildSortList = () => {
|
||||
const sortList = document.getElementById("msf-sort-list");
|
||||
sortList.innerHTML = buildSortItemsHTML();
|
||||
bindSortEvents();
|
||||
};
|
||||
const bindSortEvents = () => {
|
||||
const sortItems = document.querySelectorAll("#msf-sort-list .msf-sort-item");
|
||||
sortItems.forEach((item) => {
|
||||
item.addEventListener("dragstart", (e) => {
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
e.dataTransfer.setData("text/plain", item.dataset.sortKey);
|
||||
item.classList.add("msf-dragging");
|
||||
});
|
||||
item.addEventListener("dragend", () => {
|
||||
item.classList.remove("msf-dragging");
|
||||
document.querySelectorAll("#msf-sort-list .msf-sort-item").forEach((el) => el.classList.remove("msf-drag-over"));
|
||||
});
|
||||
item.addEventListener("dragover", (e) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "move";
|
||||
item.classList.add("msf-drag-over");
|
||||
});
|
||||
item.addEventListener("dragleave", () => {
|
||||
item.classList.remove("msf-drag-over");
|
||||
});
|
||||
item.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
item.classList.remove("msf-drag-over");
|
||||
const fromKey = e.dataTransfer.getData("text/plain");
|
||||
const toKey = item.dataset.sortKey;
|
||||
if (fromKey === toKey) return;
|
||||
const fromIdx = currentSortOrder.indexOf(fromKey);
|
||||
const toIdx = currentSortOrder.indexOf(toKey);
|
||||
currentSortOrder.splice(fromIdx, 1);
|
||||
currentSortOrder.splice(toIdx, 0, fromKey);
|
||||
rebuildSortList();
|
||||
});
|
||||
const checkbox = item.querySelector(".msf-sort-checkbox");
|
||||
checkbox.addEventListener("change", () => {
|
||||
currentSortEnabled[item.dataset.sortKey] = checkbox.checked;
|
||||
});
|
||||
});
|
||||
};
|
||||
bindSortEvents();
|
||||
document.querySelectorAll(".msf-filter-checkbox").forEach((cb) => {
|
||||
cb.addEventListener("change", () => {
|
||||
currentFilters[cb.dataset.filterKey] = cb.checked;
|
||||
});
|
||||
});
|
||||
document.getElementById("msf-confirm-btn").addEventListener("click", async () => {
|
||||
try {
|
||||
const enabledSortOrder = currentSortOrder.filter((key) => currentSortEnabled[key]);
|
||||
const newSettings = {
|
||||
sortOrder: currentSortOrder,
|
||||
sortEnabled: currentSortEnabled,
|
||||
filterHD: currentFilters.HD,
|
||||
filter4K: currentFilters["4K"],
|
||||
filterSubtitle: currentFilters.Subtitle,
|
||||
filterUncensored: currentFilters.Uncensored
|
||||
};
|
||||
await storageManager.saveSettingItem("magnetSortAndFilter", JSON.stringify(newSettings));
|
||||
const hasAnyFilter = currentFilters.HD || currentFilters["4K"] || currentFilters.Subtitle || currentFilters.Uncensored;
|
||||
const hasAnySort = enabledSortOrder.length > 0;
|
||||
if (hasAnyFilter || hasAnySort) {
|
||||
await highlightMagnetPlugin.applySortAndFilter({
|
||||
filterHD: currentFilters.HD,
|
||||
filter4K: currentFilters["4K"],
|
||||
filterSubtitle: currentFilters.Subtitle,
|
||||
filterUncensored: currentFilters.Uncensored,
|
||||
sortOrder: enabledSortOrder
|
||||
});
|
||||
$("#magnets-span").text("取消磁力过滤");
|
||||
await storageManager.saveSettingItem("enableMagnetsFilter", YES);
|
||||
} else {
|
||||
highlightMagnetPlugin.showAll();
|
||||
$("#magnets-span").text("排序与过滤");
|
||||
await storageManager.saveSettingItem("enableMagnetsFilter", NO);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[磁力排序] 应用失败:", e);
|
||||
}
|
||||
closePanel();
|
||||
});
|
||||
}
|
||||
async showStatus(carNum2) {
|
||||
const $filterBtn = $("#filterBtn span"), $favoriteBtn = $("#favoriteBtn span"), $hasDownBtn = $("#hasDownBtn span"), $hasWatchBtn = $("#hasWatchBtn span"), hotKeyDisplay = (hotKey) => hotKey ? `(${hotKey})` : "";
|
||||
$filterBtn.text(`🚫 屏蔽 ${hotKeyDisplay(this.filterHotKey)}`);
|
||||
@@ -14291,11 +14814,13 @@ ${err.stack}` : "");
|
||||
}
|
||||
if (this.cache[carNum2]) {
|
||||
let _this = this;
|
||||
$title.attr("data-original-title", content);
|
||||
$title.contents().each((function() {
|
||||
3 === this.nodeType && "" !== this.textContent.trim() && (this.textContent = " " + _this.cache[carNum2] + " ");
|
||||
}));
|
||||
$title.attr("title", _this.cache[carNum2]);
|
||||
} else translateText(content).then(((result) => {
|
||||
$title.attr("data-original-title", content);
|
||||
if (isJavDb$1) {
|
||||
$title.contents().each((function() {
|
||||
3 !== this.nodeType || "" === this.textContent.trim() || this.textContent.includes(carNum2) || (this.textContent = " " + result + " ");
|
||||
@@ -14313,7 +14838,7 @@ ${err.stack}` : "");
|
||||
async revertTranslation() {
|
||||
$(this.getSelector().itemSelector).toArray().forEach(((ele) => {
|
||||
let $box2 = $(ele);
|
||||
const originalContent = $box2.find(".box").attr("title") || $box2.find(".video-title").attr("title") || $box2.find("img").attr("data-title");
|
||||
const originalContent = $box2.find(".video-title").attr("data-original-title") || $box2.find(".box").attr("title") || $box2.find(".video-title").attr("title") || $box2.find("img").attr("data-title");
|
||||
let carNum2;
|
||||
isJavDb$1 && (carNum2 = $box2.find(".video-title strong").text().trim());
|
||||
const $title = $box2.find(".video-title");
|
||||
|
||||
@@ -569,6 +569,198 @@ const mainCss = `
|
||||
background: rgba(59, 130, 246, 0.9);
|
||||
box-shadow: 0 4px 16px rgba(59, 130, 246, 0.4);
|
||||
}
|
||||
|
||||
.magnet-4k-highlight {
|
||||
background-color: #fff5f5 !important;
|
||||
}
|
||||
.magnet-4k-highlight .name {
|
||||
color: #f40 !important;
|
||||
}
|
||||
|
||||
.msf-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
z-index: 999997;
|
||||
}
|
||||
.msf-panel {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.2);
|
||||
z-index: 999998;
|
||||
width: 380px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.msf-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 18px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
background: #f9fafb;
|
||||
border-radius: 10px 10px 0 0;
|
||||
}
|
||||
.msf-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #1e293b;
|
||||
}
|
||||
.msf-close {
|
||||
font-size: 22px;
|
||||
color: #9ca3af;
|
||||
cursor: pointer;
|
||||
padding: 0 6px;
|
||||
line-height: 1;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.msf-close:hover {
|
||||
color: #ef4444;
|
||||
}
|
||||
.msf-body {
|
||||
padding: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.msf-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
.msf-section-title {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
}
|
||||
.msf-sort-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.msf-sort-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 14px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
cursor: default;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
}
|
||||
.msf-sort-item:hover {
|
||||
border-color: #cbd5e1;
|
||||
background: #f1f5f9;
|
||||
}
|
||||
.msf-sort-item.msf-dragging {
|
||||
opacity: 0.5;
|
||||
border-color: #3b82f6;
|
||||
background: #eff6ff;
|
||||
}
|
||||
.msf-sort-item.msf-drag-over {
|
||||
border-color: #3b82f6;
|
||||
background: #eff6ff;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
.msf-drag-handle {
|
||||
color: #cbd5e1;
|
||||
font-size: 16px;
|
||||
cursor: grab;
|
||||
line-height: 1;
|
||||
letter-spacing: -2px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.msf-drag-handle:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
.msf-sort-item:hover .msf-drag-handle {
|
||||
color: #94a3b8;
|
||||
}
|
||||
.msf-sort-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
}
|
||||
.msf-sort-checkbox {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.msf-sort-hint {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.msf-filter-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
.msf-filter-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 14px;
|
||||
background: #f8fafc;
|
||||
border: 1px solid #e2e8f0;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
user-select: none;
|
||||
}
|
||||
.msf-filter-label:hover {
|
||||
border-color: #cbd5e1;
|
||||
background: #f1f5f9;
|
||||
}
|
||||
.msf-filter-checkbox {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.msf-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding: 14px 18px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
.msf-btn {
|
||||
padding: 8px 22px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.msf-btn-cancel {
|
||||
background: #f3f4f6;
|
||||
color: #374151;
|
||||
}
|
||||
.msf-btn-cancel:hover {
|
||||
background: #e5e7eb;
|
||||
}
|
||||
.msf-btn-confirm {
|
||||
background: #3b82f6;
|
||||
color: #fff;
|
||||
}
|
||||
.msf-btn-confirm:hover {
|
||||
background: #2563eb;
|
||||
}
|
||||
</style>
|
||||
`;
|
||||
|
||||
|
||||
@@ -23,7 +23,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="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()));
|
||||
@@ -43,19 +43,38 @@ class DetailPageButtonPlugin extends BasePlugin {
|
||||
}
|
||||
});
|
||||
}));
|
||||
const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin"), enableMagnetsFilter = await storageManager.getSetting("enableMagnetsFilter", YES);
|
||||
$("#magnets-span").text(enableMagnetsFilter === YES ? "关闭磁力过滤" : "开启磁力过滤");
|
||||
enableMagnetsFilter === YES && highlightMagnetPlugin.doFilterMagnet();
|
||||
const highlightMagnetPlugin = this.getBean("HighlightMagnetPlugin"),
|
||||
enableMagnetsFilter = await storageManager.getSetting("enableMagnetsFilter", NO);
|
||||
if (enableMagnetsFilter === YES) {
|
||||
const savedSettings = await storageManager.getSetting("magnetSortAndFilter", "{}");
|
||||
let settings;
|
||||
try {
|
||||
settings = JSON.parse(savedSettings);
|
||||
} catch (e) {
|
||||
settings = {};
|
||||
}
|
||||
const sortEnabled = settings.sortEnabled || {};
|
||||
const fullSortOrder = (settings.sortOrder && settings.sortOrder.length === 3) ? settings.sortOrder : ["date", "size", "files"];
|
||||
const effectiveSortOrder = fullSortOrder.filter(key => sortEnabled[key]);
|
||||
highlightMagnetPlugin.applySortAndFilter({
|
||||
filterHD: settings.filterHD || !1,
|
||||
filter4K: settings.filter4K || !1,
|
||||
filterSubtitle: settings.filterSubtitle || !1,
|
||||
filterUncensored: settings.filterUncensored || !1,
|
||||
sortOrder: effectiveSortOrder,
|
||||
});
|
||||
$("#magnets-span").text("取消磁力过滤");
|
||||
} else {
|
||||
$("#magnets-span").text("排序与过滤");
|
||||
}
|
||||
$("#enable-magnets-filter").on("click", (event => {
|
||||
let $span = $("#magnets-span");
|
||||
if ("关闭磁力过滤" === $span.text()) {
|
||||
const $span = $("#magnets-span");
|
||||
if ("取消磁力过滤" === $span.text()) {
|
||||
highlightMagnetPlugin.showAll();
|
||||
$span.text("开启磁力过滤");
|
||||
$span.text("排序与过滤");
|
||||
storageManager.saveSettingItem("enableMagnetsFilter", NO);
|
||||
} else {
|
||||
highlightMagnetPlugin.doFilterMagnet();
|
||||
$span.text("关闭磁力过滤");
|
||||
storageManager.saveSettingItem("enableMagnetsFilter", YES);
|
||||
this.openMagnetSortFilterPanel(highlightMagnetPlugin);
|
||||
}
|
||||
}));
|
||||
$("#subtitleSearchBtn").on("click", (() => {
|
||||
@@ -66,6 +85,186 @@ class DetailPageButtonPlugin extends BasePlugin {
|
||||
// $("#xunLeiSubtitleBtn").on("click", (() => this.searchXunLeiSubtitle(carNum2)));
|
||||
this.showStatus(carNum2).then();
|
||||
}
|
||||
|
||||
async openMagnetSortFilterPanel(highlightMagnetPlugin) {
|
||||
const existingPanel = document.getElementById("magnet-sort-filter-panel");
|
||||
if (existingPanel) {
|
||||
existingPanel.remove();
|
||||
return;
|
||||
}
|
||||
const savedSettings = await storageManager.getSetting("magnetSortAndFilter", "{}");
|
||||
let settings;
|
||||
try {
|
||||
settings = JSON.parse(savedSettings);
|
||||
} catch (e) {
|
||||
settings = {};
|
||||
}
|
||||
const sortEnabled = settings.sortEnabled || {date: !1, size: !1, files: !1};
|
||||
const sortOrder = (settings.sortOrder && settings.sortOrder.length === 3) ? settings.sortOrder : ["date", "size", "files"];
|
||||
const filterHD = settings.filterHD || !1;
|
||||
const filter4K = settings.filter4K || !1;
|
||||
const filterSubtitle = settings.filterSubtitle || !1;
|
||||
const filterUncensored = settings.filterUncensored || !1;
|
||||
const SORT_LABELS = {date: "发布日期", size: "文件大小", files: "文件数量"};
|
||||
const SORT_HINTS = {date: "从新到旧", size: "从大到小", files: "从少到多"};
|
||||
const FILTER_ITEMS = [
|
||||
{key: "HD", label: "高清", checked: filterHD},
|
||||
{key: "4K", label: "4K", checked: filter4K},
|
||||
{key: "Subtitle", label: "字幕", checked: filterSubtitle},
|
||||
{key: "Uncensored", label: "无码", checked: filterUncensored},
|
||||
];
|
||||
const currentSortOrder = [...sortOrder];
|
||||
const currentSortEnabled = {...sortEnabled};
|
||||
const currentFilters = {
|
||||
HD: filterHD,
|
||||
"4K": filter4K,
|
||||
Subtitle: filterSubtitle,
|
||||
Uncensored: filterUncensored,
|
||||
};
|
||||
const buildSortItemsHTML = () => {
|
||||
return currentSortOrder.map(key => {
|
||||
const checked = currentSortEnabled[key] ? "checked" : "";
|
||||
return `<div class="msf-sort-item" draggable="true" data-sort-key="${key}">
|
||||
<span class="msf-drag-handle">⋮⋮</span>
|
||||
<label class="msf-sort-label">
|
||||
<input type="checkbox" class="msf-sort-checkbox" data-sort-key="${key}" ${checked}>
|
||||
${SORT_LABELS[key]} <span class="msf-sort-hint">${SORT_HINTS[key]}</span>
|
||||
</label>
|
||||
</div>`;
|
||||
}).join("");
|
||||
};
|
||||
const buildFilterItemsHTML = () => {
|
||||
return FILTER_ITEMS.map(item => {
|
||||
const checked = currentFilters[item.key] ? "checked" : "";
|
||||
return `<label class="msf-filter-label">
|
||||
<input type="checkbox" class="msf-filter-checkbox" data-filter-key="${item.key}" ${checked}>
|
||||
${item.label}
|
||||
</label>`;
|
||||
}).join("");
|
||||
};
|
||||
const panelHTML = `
|
||||
<div class="msf-overlay" id="msf-overlay"></div>
|
||||
<div class="msf-panel" id="magnet-sort-filter-panel">
|
||||
<div class="msf-header">
|
||||
<span class="msf-title">磁力排序与过滤</span>
|
||||
<span class="msf-close" id="msf-close-btn">×</span>
|
||||
</div>
|
||||
<div class="msf-body">
|
||||
<div class="msf-section">
|
||||
<div class="msf-section-title">排序优先级(拖动调整顺序,勾选启用)</div>
|
||||
<div class="msf-sort-list" id="msf-sort-list">
|
||||
${buildSortItemsHTML()}
|
||||
</div>
|
||||
</div>
|
||||
<div class="msf-section">
|
||||
<div class="msf-section-title">过滤条件(可多选,并集关系)</div>
|
||||
<div class="msf-filter-list">
|
||||
${buildFilterItemsHTML()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="msf-footer">
|
||||
<button class="msf-btn msf-btn-cancel" id="msf-cancel-btn">取消</button>
|
||||
<button class="msf-btn msf-btn-confirm" id="msf-confirm-btn">确认</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.innerHTML = panelHTML;
|
||||
document.body.appendChild(wrapper);
|
||||
const panel = document.getElementById("magnet-sort-filter-panel");
|
||||
const overlay = document.getElementById("msf-overlay");
|
||||
const closePanel = () => {
|
||||
panel.remove();
|
||||
overlay.remove();
|
||||
};
|
||||
overlay.addEventListener("click", closePanel);
|
||||
document.getElementById("msf-close-btn").addEventListener("click", closePanel);
|
||||
document.getElementById("msf-cancel-btn").addEventListener("click", closePanel);
|
||||
const rebuildSortList = () => {
|
||||
const sortList = document.getElementById("msf-sort-list");
|
||||
sortList.innerHTML = buildSortItemsHTML();
|
||||
bindSortEvents();
|
||||
};
|
||||
const bindSortEvents = () => {
|
||||
const sortItems = document.querySelectorAll("#msf-sort-list .msf-sort-item");
|
||||
sortItems.forEach(item => {
|
||||
item.addEventListener("dragstart", (e) => {
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
e.dataTransfer.setData("text/plain", item.dataset.sortKey);
|
||||
item.classList.add("msf-dragging");
|
||||
});
|
||||
item.addEventListener("dragend", () => {
|
||||
item.classList.remove("msf-dragging");
|
||||
document.querySelectorAll("#msf-sort-list .msf-sort-item").forEach(el => el.classList.remove("msf-drag-over"));
|
||||
});
|
||||
item.addEventListener("dragover", (e) => {
|
||||
e.preventDefault();
|
||||
e.dataTransfer.dropEffect = "move";
|
||||
item.classList.add("msf-drag-over");
|
||||
});
|
||||
item.addEventListener("dragleave", () => {
|
||||
item.classList.remove("msf-drag-over");
|
||||
});
|
||||
item.addEventListener("drop", (e) => {
|
||||
e.preventDefault();
|
||||
item.classList.remove("msf-drag-over");
|
||||
const fromKey = e.dataTransfer.getData("text/plain");
|
||||
const toKey = item.dataset.sortKey;
|
||||
if (fromKey === toKey) return;
|
||||
const fromIdx = currentSortOrder.indexOf(fromKey);
|
||||
const toIdx = currentSortOrder.indexOf(toKey);
|
||||
currentSortOrder.splice(fromIdx, 1);
|
||||
currentSortOrder.splice(toIdx, 0, fromKey);
|
||||
rebuildSortList();
|
||||
});
|
||||
const checkbox = item.querySelector(".msf-sort-checkbox");
|
||||
checkbox.addEventListener("change", () => {
|
||||
currentSortEnabled[item.dataset.sortKey] = checkbox.checked;
|
||||
});
|
||||
});
|
||||
};
|
||||
bindSortEvents();
|
||||
document.querySelectorAll(".msf-filter-checkbox").forEach(cb => {
|
||||
cb.addEventListener("change", () => {
|
||||
currentFilters[cb.dataset.filterKey] = cb.checked;
|
||||
});
|
||||
});
|
||||
document.getElementById("msf-confirm-btn").addEventListener("click", async () => {
|
||||
try {
|
||||
const enabledSortOrder = currentSortOrder.filter(key => currentSortEnabled[key]);
|
||||
const newSettings = {
|
||||
sortOrder: currentSortOrder,
|
||||
sortEnabled: currentSortEnabled,
|
||||
filterHD: currentFilters.HD,
|
||||
filter4K: currentFilters["4K"],
|
||||
filterSubtitle: currentFilters.Subtitle,
|
||||
filterUncensored: currentFilters.Uncensored,
|
||||
};
|
||||
await storageManager.saveSettingItem("magnetSortAndFilter", JSON.stringify(newSettings));
|
||||
const hasAnyFilter = currentFilters.HD || currentFilters["4K"] || currentFilters.Subtitle || currentFilters.Uncensored;
|
||||
const hasAnySort = enabledSortOrder.length > 0;
|
||||
if (hasAnyFilter || hasAnySort) {
|
||||
await highlightMagnetPlugin.applySortAndFilter({
|
||||
filterHD: currentFilters.HD,
|
||||
filter4K: currentFilters["4K"],
|
||||
filterSubtitle: currentFilters.Subtitle,
|
||||
filterUncensored: currentFilters.Uncensored,
|
||||
sortOrder: enabledSortOrder,
|
||||
});
|
||||
$("#magnets-span").text("取消磁力过滤");
|
||||
await storageManager.saveSettingItem("enableMagnetsFilter", YES);
|
||||
} else {
|
||||
highlightMagnetPlugin.showAll();
|
||||
$("#magnets-span").text("排序与过滤");
|
||||
await storageManager.saveSettingItem("enableMagnetsFilter", NO);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[磁力排序] 应用失败:", e);
|
||||
}
|
||||
closePanel();
|
||||
});
|
||||
}
|
||||
async showStatus(carNum2) {
|
||||
const $filterBtn = $("#filterBtn span"), $favoriteBtn = $("#favoriteBtn span"), $hasDownBtn = $("#hasDownBtn span"), $hasWatchBtn = $("#hasWatchBtn span"), hotKeyDisplay = hotKey => hotKey ? `(${hotKey})` : "";
|
||||
$filterBtn.text(`🚫 屏蔽 ${hotKeyDisplay(this.filterHotKey)}`);
|
||||
|
||||
+105
-23
@@ -5,26 +5,114 @@ class HighlightMagnetPlugin extends BasePlugin {
|
||||
getName() {
|
||||
return "HighlightMagnetPlugin";
|
||||
}
|
||||
doFilterMagnet() {
|
||||
this.handleDb();
|
||||
this.handleBus();
|
||||
|
||||
parseDateFromAttr(dateStr) {
|
||||
if (!dateStr || dateStr.length !== 8) {
|
||||
const d = new Date(dateStr);
|
||||
return isNaN(d.getTime()) ? 0 : d.getTime();
|
||||
}
|
||||
const y = parseInt(dateStr.substring(0, 4), 10);
|
||||
const m = parseInt(dateStr.substring(4, 6), 10) - 1;
|
||||
const d = parseInt(dateStr.substring(6, 8), 10);
|
||||
const date = new Date(y, m, d);
|
||||
return isNaN(date.getTime()) ? 0 : date.getTime();
|
||||
}
|
||||
handleDb() {
|
||||
|
||||
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,
|
||||
$row: $row,
|
||||
nameText: nameText,
|
||||
date: this.parseDateFromAttr(dateStr),
|
||||
dateStr: dateStr,
|
||||
size: sizeMB * 1024 * 1024,
|
||||
files: files,
|
||||
tags: 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('無碼'),
|
||||
};
|
||||
}
|
||||
|
||||
async applySortAndFilter(settings) {
|
||||
if (!isJavDb) return;
|
||||
let magnetNameList = $("#magnets-content .name");
|
||||
if (0 === magnetNameList.length) return;
|
||||
const HIGH_QUALITY_KEYWORDS = [ "4k", "-c", "-u", "-uc" ];
|
||||
let hasHighQuality = !1;
|
||||
magnetNameList.each(((_, el) => {
|
||||
const $el = $(el), text = $el.text().toLowerCase(), isHighQuality = HIGH_QUALITY_KEYWORDS.some((keyword => text.includes(keyword)));
|
||||
$el.parent().parent().parent().addClass("magnet-row");
|
||||
text.includes("4k") && $el.css("color", "#f40");
|
||||
if (isHighQuality) {
|
||||
hasHighQuality = !0;
|
||||
$el.parent().parent().parent().addClass("high-quality");
|
||||
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 !0;
|
||||
if (filter4K && info.is4K) return !0;
|
||||
if (filterSubtitle && info.isSubtitle) return !0;
|
||||
if (filterUncensored && info.isUncensored) return !0;
|
||||
return !1;
|
||||
});
|
||||
}
|
||||
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');
|
||||
}
|
||||
}));
|
||||
hasHighQuality ? $("#magnets-content .magnet-row").not(".high-quality").hide() : $("#enable-magnets-filter").addClass("do-hide");
|
||||
});
|
||||
hiddenInfos.forEach(info => $(info.row).hide());
|
||||
}
|
||||
|
||||
showAll() {
|
||||
if (isJavDb) {
|
||||
$("#magnets-content .item[data-rank]").toArray().forEach((el => {
|
||||
$(el).show();
|
||||
$(el).removeClass('magnet-4k-highlight');
|
||||
}));
|
||||
}
|
||||
if (isJavBus) {
|
||||
$("#magnet-table tr").toArray().forEach((el => $(el).show()));
|
||||
}
|
||||
}
|
||||
handleBus() {
|
||||
isJavBus && window.isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => {
|
||||
@@ -44,12 +132,6 @@ class HighlightMagnetPlugin extends BasePlugin {
|
||||
})) : $("#enable-magnets-filter").addClass("do-hide");
|
||||
}));
|
||||
}
|
||||
showAll() {
|
||||
if (isJavDb) {
|
||||
$("#magnets-content .item").toArray().forEach((el => $(el).show()));
|
||||
}
|
||||
isJavBus && $("#magnet-table tr").toArray().forEach((el => $(el).show()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -402,11 +402,13 @@ class ListPagePlugin extends BasePlugin {
|
||||
}
|
||||
if (this.cache[carNum2]) {
|
||||
let _this = this;
|
||||
$title.attr("data-original-title", content);
|
||||
$title.contents().each((function() {
|
||||
3 === this.nodeType && "" !== this.textContent.trim() && (this.textContent = " " + _this.cache[carNum2] + " ");
|
||||
}));
|
||||
$title.attr("title", _this.cache[carNum2]);
|
||||
} else translateText(content).then((result => {
|
||||
$title.attr("data-original-title", content);
|
||||
if (isJavDb) {
|
||||
$title.contents().each((function() {
|
||||
3 !== this.nodeType || "" === this.textContent.trim() || this.textContent.includes(carNum2) || (this.textContent = " " + result + " ");
|
||||
@@ -424,7 +426,7 @@ class ListPagePlugin extends BasePlugin {
|
||||
async revertTranslation() {
|
||||
$(this.getSelector().itemSelector).toArray().forEach((ele => {
|
||||
let $box2 = $(ele);
|
||||
const originalContent = $box2.find(".box").attr("title") || $box2.find(".video-title").attr("title") || $box2.find("img").attr("data-title");
|
||||
const originalContent = $box2.find(".video-title").attr("data-original-title") || $box2.find(".box").attr("title") || $box2.find(".video-title").attr("title") || $box2.find("img").attr("data-title");
|
||||
let carNum2;
|
||||
isJavDb && (carNum2 = $box2.find(".video-title strong").text().trim());
|
||||
const $title = $box2.find(".video-title");
|
||||
|
||||
File diff suppressed because one or more lines are too long
+73
-21
@@ -1,6 +1,7 @@
|
||||
import { BasePlugin } from '../core/base-plugin.js';
|
||||
import { __publicField, isJavBus, isJavDb, currentHref, NO } from '../core/constants.js';
|
||||
import { searchFiles } from './wangpan-115-task.js';
|
||||
import {JavPackSubtitle} from './subtitles.js';
|
||||
|
||||
class WangPan115Plugin extends BasePlugin {
|
||||
constructor() {
|
||||
@@ -392,10 +393,13 @@ const _WangPan115MatchPlugin = class _WangPan115MatchPlugin extends BasePlugin {
|
||||
async batchDeleteMarked(selectedItems, onProgress) {
|
||||
let deletedCount = 0;
|
||||
let failedCount = 0;
|
||||
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
const randomDelay = () => new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 2000));
|
||||
for (let i = 0; i < selectedItems.length; i++) {
|
||||
const item = selectedItems[i];
|
||||
try {
|
||||
if (i > 0) {
|
||||
await randomDelay();
|
||||
}
|
||||
const matchList = await this.searchFiles(item.carNum);
|
||||
if (matchList.length === 0) {
|
||||
failedCount++;
|
||||
@@ -410,24 +414,50 @@ const _WangPan115MatchPlugin = class _WangPan115MatchPlugin extends BasePlugin {
|
||||
continue;
|
||||
}
|
||||
let itemDeleted = 0;
|
||||
for (const m of safeMatchList) {
|
||||
try {
|
||||
const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", {
|
||||
pid: m.dirId,
|
||||
"fid[0]": m.folderId
|
||||
});
|
||||
if (result && result.state) {
|
||||
itemDeleted++;
|
||||
deletedCount++;
|
||||
const uniqueDirIds = [...new Set(safeMatchList.map((m) => m.dirId))];
|
||||
for (let d = 0; d < uniqueDirIds.length; d++) {
|
||||
if (d > 0) {
|
||||
await randomDelay();
|
||||
}
|
||||
const dirId = uniqueDirIds[d];
|
||||
let retryCount = 0;
|
||||
const maxRetries = 3;
|
||||
while (retryCount <= maxRetries) {
|
||||
try {
|
||||
const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", {
|
||||
"fid[0]": dirId,
|
||||
ignore_warn: "1"
|
||||
});
|
||||
if (result && result.state) {
|
||||
itemDeleted++;
|
||||
deletedCount++;
|
||||
break;
|
||||
} else {
|
||||
const errMsg = (result && result.error) || "未知错误";
|
||||
if (retryCount < maxRetries && this.isRateLimited(result)) {
|
||||
retryCount++;
|
||||
const backoff = (retryCount + 1) * 2000 + Math.random() * 1000;
|
||||
onProgress && onProgress(i + 1, selectedItems.length, item, `风控触发,${backoff / 1000}秒后重试(${retryCount}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, backoff));
|
||||
continue;
|
||||
}
|
||||
console.error(`删除目录失败: ${dirId}`, errMsg);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
if (retryCount < maxRetries && this.isRateLimited(e)) {
|
||||
retryCount++;
|
||||
const backoff = (retryCount + 1) * 2000 + Math.random() * 1000;
|
||||
onProgress && onProgress(i + 1, selectedItems.length, item, `风控触发,${backoff / 1000}秒后重试(${retryCount}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, backoff));
|
||||
continue;
|
||||
}
|
||||
console.error(`删除目录失败: ${dirId}`, e);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`删除失败: ${m.name}`, e);
|
||||
}
|
||||
}
|
||||
onProgress && onProgress(i + 1, selectedItems.length, item, itemDeleted > 0 ? `已删除${itemDeleted}个` : "删除失败");
|
||||
if (i < selectedItems.length - 1) {
|
||||
await delay(800);
|
||||
}
|
||||
} catch (error) {
|
||||
failedCount++;
|
||||
onProgress && onProgress(i + 1, selectedItems.length, item, error.message || "出错");
|
||||
@@ -437,6 +467,21 @@ const _WangPan115MatchPlugin = class _WangPan115MatchPlugin extends BasePlugin {
|
||||
return {deletedCount, failedCount};
|
||||
}
|
||||
|
||||
isRateLimited(response) {
|
||||
if (!response) return false;
|
||||
if (response.state === false) {
|
||||
const msg = (response.error || response.msg || "").toLowerCase();
|
||||
if (msg.includes("频繁") || msg.includes("操作太频繁") || msg.includes("稍后再试")
|
||||
|| msg.includes("rate") || msg.includes("limit") || msg.includes("too many")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (response.status === 403 || response.status === 429) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
showDeleteDialog(carNum2, matchList, afterDelete) {
|
||||
const upperCarNum = carNum2.toUpperCase();
|
||||
const safeMatchList = matchList.filter(m => m.name.toUpperCase().includes(upperCarNum));
|
||||
@@ -492,19 +537,26 @@ const _WangPan115MatchPlugin = class _WangPan115MatchPlugin extends BasePlugin {
|
||||
let loadObj = loading();
|
||||
let deletedCount = 0;
|
||||
try {
|
||||
for (const i of selected) {
|
||||
const m = safeMatchList[i];
|
||||
const uniqueDirIds = [...new Set(selected.map((i) => safeMatchList[i].dirId))];
|
||||
for (const dirId of uniqueDirIds) {
|
||||
const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", {
|
||||
pid: m.dirId,
|
||||
"fid[0]": m.folderId
|
||||
"fid[0]": dirId,
|
||||
ignore_warn: "1"
|
||||
});
|
||||
result && result.state ? deletedCount++ : console.error(`删除失败: ${m.name}`, result);
|
||||
result && result.state ? deletedCount++ : console.error(`删除目录失败: ${dirId}`, result);
|
||||
}
|
||||
loadObj.close();
|
||||
show.ok(`作品 "${carNum2}" 已删除 ${deletedCount} 个文件夹`);
|
||||
const newMatchList = await this.searchFiles(carNum2);
|
||||
afterDelete && afterDelete(newMatchList);
|
||||
this.getBean("WangPan115TaskPlugin").deleteOfflineTasksByKeyword(carNum2);
|
||||
const remainingCids = new Set(newMatchList.map((m) => m.dirId));
|
||||
JavPackSubtitle.cached115Matches = JavPackSubtitle.cached115Matches.filter((m) => remainingCids.has(m.cid));
|
||||
JavPackSubtitle.current115Cids = JavPackSubtitle.current115Cids.filter((cid) => remainingCids.has(cid));
|
||||
try {
|
||||
this.getBean("WangPan115TaskPlugin").deleteOfflineTasksByKeyword?.(carNum2);
|
||||
} catch (e) {
|
||||
console.warn("删除离线任务暂不可用:", e.message);
|
||||
}
|
||||
} catch (error) {
|
||||
loadObj.close();
|
||||
console.error("删除115文件失败:", error);
|
||||
|
||||
Reference in New Issue
Block a user