mirror of
https://github.com/fireinrain/JHS-enhance.git
synced 2026-09-28 15:46:38 +08:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e74f2cbdc8 | |||
| 8396e3825d | |||
| e4792cd55f | |||
| 72ec9a0e83 | |||
| 4339f1b80f | |||
| 20d199f53a | |||
| a9e88cda88 | |||
| 032dcf1fdc | |||
| 38221bc78d | |||
| 4230176ce4 | |||
| b5bdec2c35 | |||
| 11c5650efb | |||
| 080cbb00ee | |||
| d5c59ba6ac | |||
| 6a12b45f86 | |||
| 1e3c4d9718 | |||
| 2b8c925fed | |||
| a9e5286c53 | |||
| 7005b528a3 | |||
| 4026b84fc2 | |||
| 76b608ccc0 | |||
| 6c9a4743bb | |||
| ce6cedf164 | |||
| 7840ca6389 | |||
| d03a9d1462 | |||
| b7cfc8541c | |||
| ce59d82ed2 | |||
| 37cf4e1183 | |||
| 8829af0fc3 | |||
| 9db06fba3b | |||
| 06b2497ee1 | |||
| 50581be5a6 | |||
| e92991c365 | |||
| 03700906e5 | |||
| e61739bd17 |
@@ -27,11 +27,15 @@ jobs:
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Get version
|
||||
id: version
|
||||
run: echo "VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Upload to Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: v${{ github.run_number }}
|
||||
name: Release v${{ github.run_number }}
|
||||
tag_name: v${{ steps.version.outputs.VERSION }}
|
||||
name: Release v${{ steps.version.outputs.VERSION }}
|
||||
files: dist/jhs-enhance.user.js
|
||||
draft: false
|
||||
prerelease: false
|
||||
|
||||
@@ -36,23 +36,33 @@
|
||||
|
||||
### 详情页增强
|
||||
|
||||
- **磁力搜索聚合(MagnetHub)**:多引擎聚合搜索(Sukebei / U9A9 / U3C3 / BTSearch / SoKitty / ØMagnet),Tab
|
||||
切换引擎,结果表格展示(名称/大小/日期/操作),支持按大小/最新/最旧排序,质量标签自动识别(中字/4K/破解),一键复制磁力链接(含番号提取码),验车功能(Whatslink
|
||||
API 截图预览、键盘/缩放导航),115 离线下载快捷按钮
|
||||
- **磁力排序与过滤(JavDB)**:按发布日期/文件大小/文件数量拖拽排序,勾选启用;高清/4K/字幕/无码多条件并集过滤,4K 磁力行高亮显示
|
||||
- 磁力链接高亮匹配已收藏/已下载状态
|
||||
- DMM 多画质预览视频
|
||||
- **多源视频预览**:Javxy 聚合 API 独立按钮(番号/识别码右侧),多源 fallback(JavTrailers / JavDB 等),HLS 支持、播放进度记忆、画质切换
|
||||
- **多源视频预览**:DMMRSC 聚合 API 独立按钮(番号/识别码右侧),HLS 支持、播放进度记忆、画质切换
|
||||
- **标题翻译**:Google 翻译,支持开关多次切换不失效
|
||||
- **字幕搜索**:合并迅雷 + SubTitleCat 为单一入口,支持 115 网盘直传、详情页预匹配目录、多目录选择上传、长目录名截断展示、字幕预览绿色字体
|
||||
- **多源预览图**:javfree / projectjav / javstore 三源切换,来源按钮绿色高亮,sessionStorage 缓存,错误提示弹窗内显示
|
||||
- 演员信息展示、相关清单、评论查看
|
||||
- **下载高清封面**:获取 DMM 高清封面直链并下载,支持番号格式校验(排除 FC2/无码作品)
|
||||
- 以图识图、第三方站点跳转
|
||||
|
||||
### 115 网盘集成
|
||||
|
||||
- **番号智能匹配**:双阶段搜索策略 — 第一阶段精准搜索(完整番号 + `type:4`),未命中时第二阶段模糊兜底(尾号 +
|
||||
正则匹配),支持不规则文件名(如 `MIVR00345-5.mp4`、`ABP001.mp4`)
|
||||
- **磁力离线下载**:MagnetHub 磁力搜索结果支持一键添加至 115 离线下载
|
||||
- **不规则文件名自动重命名**
|
||||
:检测到需第二阶段才能匹配的作品时,自动将文件重命名为标准番号格式(如 `mdvr00317-3.mp4` → `MDVR-317-3.mp4`
|
||||
),设置面板可开关 + 选择范围(仅VR / 全部)
|
||||
- 作品目录匹配与详情页预加载
|
||||
- 多目录选择上传字幕
|
||||
- 字幕一键直传至匹配目录
|
||||
- **作品删除增强**:删除整个文件夹(非仅视频),支持标记删除批量操作,删除后字幕目录列表联动;批量删除含风控保护(1~3s
|
||||
随机延迟 + 触发风控自动重试退避)
|
||||
随机延迟 + 触发风控自动重试退避);删除安全检查使用 `codeParse` 正则匹配番号
|
||||
|
||||
### 数据管理
|
||||
|
||||
@@ -70,7 +80,8 @@
|
||||
### 其他
|
||||
|
||||
- FC2 站点支持(含 123AV 补全)
|
||||
- MagnetHub 磁力搜索
|
||||
- **MagnetHub 磁力搜索**:6 引擎聚合(Sukebei/U9A9/U3C3/BTSearch/SoKitty/ØMagnet),质量标签(中字/4K/破解),验车截图预览(Whatslink
|
||||
API,键盘导航),排序切换,115 离线一键下载
|
||||
- JavTrailers 站点处理
|
||||
- 设置面板:快捷键配置、域名配置、缓存清理、标记删除列表、打赏作者(二维码/钱包地址可自定义)
|
||||
- 浏览历史、想看/已看列表
|
||||
@@ -99,7 +110,7 @@ JHS-enhance/
|
||||
│ │ ├── javdb.js # JavDB API 封装(签名算法、搜索、评论、磁力列表)
|
||||
│ │ ├── dmm.js # DMM 预览视频获取(多画质、默认画质选择)
|
||||
│ │ ├── dmm-graphql.js # DMM GraphQL API(备选视频源回退)
|
||||
│ │ └── javxy.js # Javxy 聚合视频 API(多源解析、签名生成)
|
||||
│ │ └── dmmrsc.js # DMMRSC API(视频多源解析、高清封面获取)
|
||||
│ ├── lib/ # 第三方补丁库
|
||||
│ │ ├── gm-xhr-parallel.js # Tampermonkey MV3 并行请求修复(redirect: manual)
|
||||
│ │ └── hls-runtime.js # HLS 视频播放运行时(hls.js 封装、进度记忆、画质切换)
|
||||
@@ -107,8 +118,8 @@ JHS-enhance/
|
||||
│ ├── list-page.js # 列表页过滤与状态标签渲染
|
||||
│ ├── list-page-button.js # 列表页功能按钮(待鉴定/已收藏/新作品/黑名单管理)
|
||||
│ ├── detail-page.js # 详情页数据加载与基础增强
|
||||
│ ├── detail-page-button.js# 详情页操作按钮(115 目录预加载 + 磁力排序过滤面板)
|
||||
│ ├── setting.js # 设置面板(Tab 页:基础/任务/域名/快捷键/屏蔽/缓存/标记删除/打赏作者)
|
||||
│ ├── detail-page-button.js# 详情页操作按钮(下载高清封面 + 115 目录预加载 + 磁力排序过滤面板)
|
||||
│ ├── setting.js # 设置面板(Tab 页:基础/任务/域名/快捷键/屏蔽/缓存/标记删除/打赏作者;含 115 自动重命名开关 + 范围选择)
|
||||
│ ├── translate.js # 标题翻译(Google 翻译 API,原始标题缓存)
|
||||
│ ├── preview-video.js # DMM 预览视频播放器
|
||||
│ ├── highlight-magnet.js # 磁力链接高亮 + 排序 + 过滤(多键排序、并集过滤、4K 高亮)
|
||||
@@ -118,7 +129,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 页面处理
|
||||
@@ -139,10 +150,10 @@ JHS-enhance/
|
||||
│ ├── screenshot.js # 多源预览图(javfree/projectjav/javstore + 来源切换弹窗)
|
||||
│ ├── subtitles.js # 字幕搜索(整合迅雷+SubTitleCat)+ 115 多目录直传
|
||||
│ ├── req115.js # 115 网盘 API 封装(Req 基类 + Drive115 + Req115)
|
||||
│ ├── magnet-hub.js # MagnetHub 磁力搜索
|
||||
│ ├── magnet-hub.js # MagnetHub 磁力搜索(6 引擎聚合、质量标签、验车截图、115 离线)
|
||||
│ ├── other-site.js # 第三方站点跳转链接
|
||||
│ ├── jav-trailers.js # JavTrailers 站点处理
|
||||
│ ├── javxy-preview-video.js# Javxy 多源视频预览(独立按钮、聚合 API、HLS 支持)
|
||||
│ ├── javxy-preview-video.js# DMMRSC 多源视频预览(独立按钮、聚合 API、HLS 支持)
|
||||
│ ├── subtitle-cat.js # SubTitleCat 字幕搜索(已整合,保留原文件)
|
||||
│ ├── filter-title-keyword.js # 标题关键词过滤
|
||||
│ └── want-watched.js # 想看/已看视频列表
|
||||
@@ -297,12 +308,21 @@ 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 等多源回退
|
||||
- 新增:DMMRSC 多源视频预览独立按钮(番号/识别码右侧),基于 DMMRSC 聚合 API
|
||||
- 新增:HLS 视频播放运行时(hls.js 封装),支持 .m3u8 格式、播放进度记忆、画质切换
|
||||
- 新增:DMM GraphQL API 模块,作为备选视频源回退方案
|
||||
- 优化:`bus-preview-video.js` 和 `preview-video.js` 清理移植代码,保持原有播放逻辑不变
|
||||
- 优化:`dmm.js` 回退至仅 DMM Affiliate API + GraphQL API 双通道,移除 Javxy 耦合
|
||||
- 优化:`dmm.js` 回退至仅 DMM Affiliate API + GraphQL API 双通道
|
||||
|
||||
- **v3.3.7**
|
||||
- 新增:JavDB 磁力排序与过滤(拖拽排序、多条件并集过滤、4K 高亮)
|
||||
|
||||
-251
@@ -1,251 +0,0 @@
|
||||
好的,让我先全面分析这个 11000 行的脚本。我会分段读取,理解它的整体结构和功能模块。
|
||||
|
||||
|
||||
|
||||
|
||||
The user wants me to analyze the JHS-origin.js file (about 11,000 lines) and provide a detailed report on how to modularize it. Let me first read through the entire file to understand its structure, then provide a comprehensive refactoring plan.
|
||||
|
||||
Since the file is 10,962 lines, I need to read it in chunks. Let me start by reading several sections to understand the code structure.
|
||||
|
||||
Let me read the file in multiple chunks to understand:
|
||||
1. The beginning (already read some)
|
||||
2. Key sections throughout
|
||||
3. The end
|
||||
|
||||
I'll read multiple sections in parallel.
|
||||
|
||||
Let me continue reading more sections to get a complete picture of the codebase.
|
||||
|
||||
Let me also read the end of the file to see the initialization/entry point.
|
||||
|
||||
Now I have a very comprehensive understanding of the codebase. Let me also read the very end to see the initialization.
|
||||
|
||||
Now I have a comprehensive understanding of the entire codebase. Let me write a detailed analysis report.
|
||||
|
||||
The code is approximately 10,962 lines, with a well-defined plugin architecture:
|
||||
- PluginManager: manages plugins
|
||||
- BasePlugin: base class for all plugins
|
||||
- ~40 plugins extending BasePlugin
|
||||
|
||||
The code also contains:
|
||||
- Utility classes (Utils, gmHttp, StorageManager, show, clog, etc.)
|
||||
- CSS styles
|
||||
- Constants and configuration
|
||||
- API interfaces (JavDbApi, etc.)
|
||||
- A plugin system with registration and execution
|
||||
|
||||
Let me now write a comprehensive refactoring report.
|
||||
现在我已经完整分析了整个代码库。以下是详细的结构分析报告:
|
||||
|
||||
---
|
||||
|
||||
## 🔍 JHS-enhance 代码结构分析报告
|
||||
|
||||
### 📊 总体概况
|
||||
|
||||
| 指标 | 数据 |
|
||||
|------|------|
|
||||
| 总行数 | **10,962 行** |
|
||||
| 核心类/插件 | **40+ 个** |
|
||||
| 支持站点 | JavDB、JavBus、JavTrailers、SubTitleCat、阿里云盘、115网盘 |
|
||||
| 外部依赖 | jQuery、Tabulator、layui-layer、MD5、Toastify、localForage、ViewerJS、QRCodeJS |
|
||||
|
||||
---
|
||||
|
||||
### 🏗️ 现有架构
|
||||
|
||||
代码已经有一个**不错的插件架构**,只是全部写在一个文件里:
|
||||
|
||||
```
|
||||
入口(最后100行)
|
||||
├── 初始化 pluginManager
|
||||
├── 检测当前页面类型(isDetailPage / isListPage / isFc2Page)
|
||||
├── 按站点注册插件
|
||||
├── pluginManager.processCss() → 加载所有插件CSS
|
||||
└── pluginManager.processPlugins() → 执行所有插件
|
||||
```
|
||||
|
||||
插件基类 `BasePlugin` 提供了:
|
||||
- `getName()` — 插件名
|
||||
- `initCss()` — 返回CSS字符串
|
||||
- `handle()` — 主逻辑入口
|
||||
- `getPageInfo()` — 获取当前页面信息
|
||||
- `getBean(name)` — 获取其他插件实例
|
||||
|
||||
---
|
||||
|
||||
### 📦 代码功能模块划分
|
||||
|
||||
我将 11000 行代码按功能分为以下 **8 大模块**,建议拆分为独立文件:
|
||||
|
||||
---
|
||||
|
||||
#### 模块一:🛠️ 基础设施层(~1500 行)
|
||||
**建议目录:`src/core/`**
|
||||
|
||||
| 文件 | 内容 | 说明 |
|
||||
|------|------|------|
|
||||
| `utils.js` | `Utils` 类(~300行) | 通用工具函数:下载、滚动、剪贴板、URL解析、排序等 |
|
||||
| `gmHttp.js` | `gmHttp` 类(~200行) | 基于 `GM_xmlhttpRequest` 的 HTTP 封装 |
|
||||
| `storage.js` | `StorageManager` 类(~500行) | IndexedDB 存储管理:收藏、屏蔽、设置、演员等 |
|
||||
| `show.js` | `show` / `clog` 对象(~100行) | Toast 消息提示、控制台日志 |
|
||||
| `constants.js` | 所有常量(~200行) | 状态枚举、画质选项、翻译映射表、标签样式等 |
|
||||
| `styles.js` | CSS 样式(~300行) | JavDB/JavBus 站点样式、全局样式、滚动条样式 |
|
||||
|
||||
---
|
||||
|
||||
#### 模块二:🔌 插件系统(~200 行)
|
||||
**建议目录:`src/core/`**
|
||||
|
||||
| 文件 | 内容 | 说明 |
|
||||
|------|------|------|
|
||||
| `plugin-manager.js` | `PluginManager` 类 | 插件注册、CSS加载、插件执行 |
|
||||
| `base-plugin.js` | `BasePlugin` 类 | 插件基类,提供 `getName()`、`handle()`、`initCss()` 等 |
|
||||
|
||||
---
|
||||
|
||||
#### 模块三:🌐 API 接口层(~200 行)
|
||||
**建议目录:`src/api/`**
|
||||
|
||||
| 文件 | 内容 | 说明 |
|
||||
|------|------|------|
|
||||
| `javdb-api.js` | `javDbApi` 对象 | JavDB API:获取评论、搜索电影、电影详情、磁力链接 |
|
||||
|
||||
---
|
||||
|
||||
#### 模块四:📋 列表页插件(~2500 行)
|
||||
**建议目录:`src/plugins/list/`**
|
||||
|
||||
| 文件 | 类名 | 功能 |
|
||||
|------|------|------|
|
||||
| `list-page.js` | `ListPagePlugin` | 列表页核心:状态标签渲染、屏蔽过滤、演员屏蔽、排序 |
|
||||
| `list-page-button.js` | `ListPageButtonPlugin` | 列表页工具栏按钮:一键屏蔽、收藏、随机打开等 |
|
||||
| `auto-page.js` | `AutoPagePlugin` | 自动翻页 |
|
||||
| `fold-category.js` | `FoldCategoryPlugin` | 折叠/展开分类标签区域 |
|
||||
| `cover-button.js` | `CoverButtonPlugin` | 封面图上的操作按钮(屏蔽、收藏等) |
|
||||
| `history.js` | `HistoryPlugin` | 鉴定记录面板 |
|
||||
| `filter-title-keyword.js` | `FilterTitleKeywordPlugin` | 标题关键词屏蔽 |
|
||||
| `blacklist.js` | `BlacklistPlugin` | 黑名单管理(屏蔽演员/标签) |
|
||||
| `hit-show.js` | `HitShowPlugin` | 热播榜 |
|
||||
| `top250.js` | `TOP250Plugin` | TOP250 排行榜 |
|
||||
|
||||
---
|
||||
|
||||
#### 模块五:🎬 详情页插件(~2000 行)
|
||||
**建议目录:`src/plugins/detail/`**
|
||||
|
||||
| 文件 | 类名 | 功能 |
|
||||
|------|------|------|
|
||||
| `detail-page.js` | `DetailPagePlugin` | 详情页核心:FC2/普通影片信息展示 |
|
||||
| `detail-page-button.js` | `DetailPageButtonPlugin` | 详情页按钮:屏蔽、收藏、已下载、已观看、磁力搜索、字幕 |
|
||||
| `bus-detail-page.js` | `BusDetailPagePlugin` | JavBus 详情页 |
|
||||
| `review.js` | `ReviewPlugin` | 评论查看 |
|
||||
| `related.js` | `RelatedPlugin` | 相关清单 |
|
||||
| `highlight-magnet.js` | `HighlightMagnetPlugin` | 磁力链接高亮过滤 |
|
||||
| `translate.js` | `TranslatePlugin` | 标题翻译(日→中) |
|
||||
| `want-watched.js` | `WantAndWatchedVideosPlugin` | 想看/已看视频 |
|
||||
|
||||
---
|
||||
|
||||
#### 模块六:🎥 视频与媒体插件(~1500 行)
|
||||
**建议目录:`src/plugins/media/`**
|
||||
|
||||
| 文件 | 类名 | 功能 |
|
||||
|------|------|------|
|
||||
| `preview-video.js` | `PreviewVideoPlugin` | JavDB 预览视频播放 |
|
||||
| `bus-preview-video.js` | `BusPreviewVideoPlugin` | JavBus 预览视频播放 |
|
||||
| `screen-shot.js` | `ScreenShotPlugin` | 缩略图加载 |
|
||||
| `bus-img.js` | `BusImgPlugin` | JavBus 图片处理 |
|
||||
| `image-recognition.js` | `ImageRecognitionPlugin` | 以图识图 |
|
||||
|
||||
---
|
||||
|
||||
#### 模块七:🔗 外部站点与工具插件(~2000 行)
|
||||
**建议目录:`src/plugins/external/`**
|
||||
|
||||
| 文件 | 类名 | 功能 |
|
||||
|------|------|------|
|
||||
| `magnet-hub.js` | `MagnetHubPlugin` | 多站点磁力搜索聚合 |
|
||||
| `other-site.js` | `OtherSitePlugin` | 其他站点入口(JavStore、MissAV等) |
|
||||
| `fc2.js` | `Fc2Plugin` | FC2 影片页面 |
|
||||
| `fc2-123av.js` | `Fc2By123AvPlugin` | 123AV 的 FC2 页面 |
|
||||
| `wangpan-115.js` | `WangPan115Plugin` / `WangPan115TaskPlugin` / `WangPan115MatchPlugin` | 115网盘离线下载 |
|
||||
| `aliyun-pan.js` | `AliyunPanPlugin` | 阿里云盘 |
|
||||
| `jav-trailers.js` | `JavTrailersPlugin` | JavTrailers 站点 |
|
||||
| `subtitle-cat.js` | `SubTitleCatPlugin` | SubTitleCat 字幕搜索 |
|
||||
|
||||
---
|
||||
|
||||
#### 模块八:👤 演员与任务插件(~2000 行)
|
||||
**建议目录:`src/plugins/actress/`**
|
||||
|
||||
| 文件 | 类名 | 功能 |
|
||||
|------|------|------|
|
||||
| `actress-info.js` | `ActressInfoPlugin` | 演员信息面板 |
|
||||
| `favorite-actresses.js` | `FavoriteActressesPlugin` | 收藏演员管理 |
|
||||
| `new-video.js` | `NewVideoPlugin` | 新作品检测 |
|
||||
| `task.js` | `TaskPlugin` | 定时任务:检测黑名单、同步演员、新作品检测 |
|
||||
| `nav-bar.js` | `NavBarPlugin` / `BusNavBarPlugin` | 导航栏增强 |
|
||||
| `setting.js` | `SettingPlugin` | 设置面板 |
|
||||
|
||||
---
|
||||
|
||||
### 🎯 建议的项目目录结构
|
||||
|
||||
```
|
||||
JHS-enhance/
|
||||
├── src/
|
||||
│ ├── main.js # 入口:初始化 + 插件注册
|
||||
│ ├── core/
|
||||
│ │ ├── utils.js # 通用工具函数
|
||||
│ │ ├── gmHttp.js # GM_xmlhttpRequest 封装
|
||||
│ │ ├── storage.js # IndexedDB 存储管理
|
||||
│ │ ├── show.js # Toast 消息提示
|
||||
│ │ ├── constants.js # 所有常量
|
||||
│ │ ├── styles.js # CSS 样式
|
||||
│ │ ├── plugin-manager.js # 插件管理器
|
||||
│ │ └── base-plugin.js # 插件基类
|
||||
│ ├── api/
|
||||
│ │ └── javdb-api.js # JavDB API 接口
|
||||
│ ├── plugins/
|
||||
│ │ ├── list/ # 列表页插件 (10个)
|
||||
│ │ ├── detail/ # 详情页插件 (8个)
|
||||
│ │ ├── media/ # 视频/媒体插件 (5个)
|
||||
│ │ ├── external/ # 外部站点插件 (8个)
|
||||
│ │ └── actress/ # 演员/任务插件 (6个)
|
||||
│ └── styles/
|
||||
│ └── ... (可选,如果CSS拆出来)
|
||||
├── vite.config.mjs
|
||||
├── package.json
|
||||
└── JHS-origin.js # 保留原始文件作为参考
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ⚠️ 拆分时的注意事项
|
||||
|
||||
1. **全局变量依赖**:当前代码大量使用 `unsafeWindow.xxx = window.xxx`,拆分为 ES Module 后需要改为 `export`/`import`,同时通过 `window` 暴露给油猴全局作用域
|
||||
|
||||
2. **插件间依赖**:插件通过 `this.getBean(name)` 互相调用,拆分后需确保 `PluginManager` 先初始化
|
||||
|
||||
3. **CDN 外部依赖**:jQuery、Tabulator 等通过 `@require` 加载,在模块中直接使用 `$`、`Tabulator` 等全局变量即可
|
||||
|
||||
4. **CSS 注入**:`insertStyle()` 和 `initCss()` 需要 `document.head` 可用后才能执行
|
||||
|
||||
5. **循环依赖**:注意 `TaskPlugin` ↔ `BlacklistPlugin`、`ListPagePlugin` ↔ `BlacklistPlugin` 等之间的双向引用
|
||||
|
||||
---
|
||||
|
||||
### 🚀 建议的实施顺序
|
||||
|
||||
| 优先级 | 步骤 | 预计工作量 |
|
||||
|--------|------|-----------|
|
||||
| P0 | 拆分 `core/` 基础设施(utils, gmHttp, storage, show, constants, styles) | 1-2小时 |
|
||||
| P1 | 拆分插件系统(plugin-manager, base-plugin)和入口 `main.js` | 30分钟 |
|
||||
| P2 | 拆分 API 层(javdb-api) | 15分钟 |
|
||||
| P3 | 分批拆分插件,每批 3-5 个,逐个验证构建 | 3-5小时 |
|
||||
| P4 | 清理全局变量,统一为 ES Module 导入导出 | 1小时 |
|
||||
|
||||
---
|
||||
|
||||
需要我开始执行拆分吗?建议从 **P0(基础设施层)** 开始,这是最独立、最不容易出错的模块。
|
||||
Vendored
+14861
-12527
File diff suppressed because one or more lines are too long
Generated
+5
-3
@@ -1,17 +1,16 @@
|
||||
{
|
||||
"name": "jhs-enhance",
|
||||
"version": "3.3.6",
|
||||
"version": "3.3.10",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "jhs-enhance",
|
||||
"version": "3.3.6",
|
||||
"version": "3.3.10",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"jquery": "^4.0.0",
|
||||
"layui-layer": "^1.0.9",
|
||||
"localforage": "^1.10.0",
|
||||
"qrcodejs": "^1.0.0",
|
||||
"tabulator-tables": "^6.5.2",
|
||||
@@ -19,6 +18,7 @@
|
||||
"viewerjs": "^1.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"layui-layer": "^1.0.9",
|
||||
"vite": "^6.0.0",
|
||||
"vite-plugin-monkey": "^5.0.0"
|
||||
}
|
||||
@@ -1204,6 +1204,7 @@
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmmirror.com/layui-layer/-/layui-layer-1.0.9.tgz",
|
||||
"integrity": "sha512-CzPVtG0D4IvmISUyLTzzbz2Uxfaoqch+87lHqVmO53ie8ZKOfOkENbBl7ssiLna1lAnv4M1SycmH62NbtndtDQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"jquery": "^3.1.1"
|
||||
@@ -1213,6 +1214,7 @@
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmmirror.com/jquery/-/jquery-3.7.1.tgz",
|
||||
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lie": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jhs-enhance",
|
||||
"version": "3.3.6",
|
||||
"version": "3.3.15",
|
||||
"description": "Jav-鉴黄师 油猴脚本 - 工程化版本",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
const COVER_API_URL = [77, 81, 81, 85, 86, 31, 10, 10, 68, 85, 76, 8, 65, 72, 72, 87, 86, 70, 11, 20, 22, 20, 17, 22, 22, 11, 93, 92, 95, 10, 68, 85, 76, 10, 70, 74, 83, 64, 87]
|
||||
.map(v => String.fromCharCode(v ^ 0x25)).join('');
|
||||
|
||||
const TRAILER_API_URL = [77, 81, 81, 85, 86, 31, 10, 10, 68, 85, 76, 8, 65, 72, 72, 87, 86, 70, 11, 20, 22, 20, 17, 22, 22, 11, 93, 92, 95, 10, 68, 85, 76, 10, 81, 87, 68, 76, 73, 64, 87]
|
||||
.map(v => String.fromCharCode(v ^ 0x25)).join('');
|
||||
|
||||
const BASE_URL = [77, 81, 81, 85, 86, 31, 10, 10, 68, 85, 76, 8, 65, 72, 72, 87, 86, 70, 11, 20, 22, 20, 17, 22, 22, 11, 93, 92, 95]
|
||||
.map(v => String.fromCharCode(v ^ 0x25)).join('');
|
||||
|
||||
const COVER_API_TOKEN = [18, 28, 19, 19, 65, 65, 70, 28, 64, 29, 23, 21, 17, 29, 67, 70, 18, 64, 21, 22, 64, 16, 65, 29, 22, 19, 16, 28, 16, 17, 64, 29]
|
||||
.map(v => String.fromCharCode(v ^ 0x25)).join('');
|
||||
|
||||
const getCover = async (code) => {
|
||||
const query = String(code || '').trim();
|
||||
if (!query) return null;
|
||||
|
||||
try {
|
||||
const r = await gmHttp.get(`${COVER_API_URL}/${encodeURIComponent(query)}`, null, {
|
||||
'Authorization': `Bearer ${COVER_API_TOKEN}`,
|
||||
}, {timeout: 15000});
|
||||
|
||||
if (r?.cover?.large) {
|
||||
//clog.log('高清封面地址:', r.cover.large);
|
||||
return r.cover.large;
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
clog.error('获取封面失败:', e.message);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getTrailer = async (code) => {
|
||||
const query = String(code || '').trim();
|
||||
if (!query) return null;
|
||||
|
||||
try {
|
||||
const r = await gmHttp.get(`${TRAILER_API_URL}/${encodeURIComponent(query)}`, null, {
|
||||
'Authorization': `Bearer ${COVER_API_TOKEN}`,
|
||||
}, {timeout: 15000});
|
||||
|
||||
if (r?.trailers && Array.isArray(r.trailers) && r.trailers.length > 0) {
|
||||
const qualityMap = {};
|
||||
r.trailers.forEach((item) => {
|
||||
if (item.proxy && item.quality) {
|
||||
qualityMap[item.quality] = `${BASE_URL}${item.proxy}`;
|
||||
}
|
||||
});
|
||||
if (Object.keys(qualityMap).length > 0) {
|
||||
clog.log('预告片播放地址:', JSON.stringify(qualityMap));
|
||||
return qualityMap;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch (e) {
|
||||
clog.error('获取预告片失败:', e.message);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export {getCover, getTrailer};
|
||||
@@ -211,10 +211,36 @@ const getJavxyVideoUrls = async (code, failedSources = []) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const getJavxyCover = async (code) => {
|
||||
const query = String(code || '').trim();
|
||||
if (!query) return null;
|
||||
|
||||
for (const endpoint of JAVXY_ENDPOINTS) {
|
||||
const params = new URLSearchParams({client: 'laosiji-new'});
|
||||
const apiUrl = `https://${endpoint.host}/covers/${encodeURIComponent(query)}?${params}`;
|
||||
|
||||
let r;
|
||||
try {
|
||||
r = await gmHttp.get(apiUrl, null, {
|
||||
'Accept': 'application/json,text/plain,*/*',
|
||||
'X-Javxy-Token': JAVXY_TOKEN,
|
||||
}, {timeout: 15000});
|
||||
} catch (e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!r) continue;
|
||||
if (r?.found && (r.url || r.cover || r.highCover)) return r;
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export {
|
||||
fromJavxyCcCd,
|
||||
fallbackJavxyResult,
|
||||
getJavxyVideoUrls,
|
||||
getJavxyCover,
|
||||
normalizeJavxySource,
|
||||
isJpSourceTemporarilyFailed,
|
||||
markJpSourceTemporarilyFailed,
|
||||
|
||||
@@ -63,6 +63,16 @@ const currentHref = window.location.href,
|
||||
quality: "mmb",
|
||||
text: "中画质 (432p)",
|
||||
canSelect: !0
|
||||
}, {
|
||||
id: "video-dm",
|
||||
quality: "dm",
|
||||
text: "低画质 (1000kbps)",
|
||||
canSelect: !0
|
||||
}, {
|
||||
id: "video-dmb",
|
||||
quality: "dmb",
|
||||
text: "中画质 (1500kbps)",
|
||||
canSelect: !0
|
||||
}, {
|
||||
id: "video-mhb",
|
||||
quality: "mhb",
|
||||
@@ -577,6 +587,23 @@ const mainCss = `
|
||||
color: #f40 !important;
|
||||
}
|
||||
|
||||
#magnets-content .item[data-rank] {
|
||||
position: relative !important;
|
||||
}
|
||||
#magnets-content .item[data-rank] .buttons {
|
||||
position: absolute !important;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 0 !important;
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
#magnets-content .item[data-rank] .name {
|
||||
display: block;
|
||||
padding-right: 260px;
|
||||
}
|
||||
|
||||
.msf-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
@@ -1043,6 +1070,16 @@ insertStyle(
|
||||
insertStyle(mainCss);
|
||||
insertStyle(subtitleModalCss);
|
||||
|
||||
const isEligibleDmmCoverCode = (value) => {
|
||||
const raw = String(value || '').trim().toUpperCase();
|
||||
if (!/^[A-Z0-9]{2,10}-\d{1,6}$/.test(raw)) return false;
|
||||
const code = raw.replace(/\s+/g, '-').toUpperCase();
|
||||
if (!/^[A-Z0-9]{2,10}-\d{1,6}$/.test(code) || /^FC2-/.test(code)) return false;
|
||||
if (/^\d{6}[-_]\d{2,3}$/.test(code)) return false;
|
||||
if (/^(DUGA|MYWIFE|HEYZO|PACO|10MU|1PONDO|CARIBBEAN|CARIB|TOKYO|GACHI|REAL|JUKU|AKA|NTR)-/.test(code)) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
export {
|
||||
__defProp,
|
||||
__typeError,
|
||||
@@ -1052,6 +1089,7 @@ export {
|
||||
isJavDb,
|
||||
isJavBus,
|
||||
isSearchPage,
|
||||
isEligibleDmmCoverCode,
|
||||
Status_RUNNING,
|
||||
Status_SUCCESS,
|
||||
Status_FAIL,
|
||||
|
||||
+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) {
|
||||
|
||||
+26
-24
@@ -29,7 +29,7 @@ import { TOP250Plugin } from './plugins/top250.js';
|
||||
import { NavBarPlugin } from './plugins/nav-bar.js';
|
||||
import { BusDetailPagePlugin } from './plugins/bus-detail-page.js';
|
||||
import { BusPreviewVideoPlugin } from './plugins/bus-preview-video.js';
|
||||
import {JavxyPreviewVideoPlugin} from './plugins/javxy-preview-video.js';
|
||||
import {JavopenPreviewVideoPlugin} from './plugins/javopen-preview-video.js';
|
||||
import { ImageRecognitionPlugin } from './plugins/image-recognition.js';
|
||||
import { BusNavBarPlugin } from './plugins/bus-nav-bar.js';
|
||||
import { RelatedPlugin } from './plugins/related.js';
|
||||
@@ -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");
|
||||
|
||||
@@ -877,7 +879,7 @@ const pluginManager = function() {
|
||||
pluginManager2.register(DetailPageButtonPlugin);
|
||||
pluginManager2.register(HighlightMagnetPlugin);
|
||||
pluginManager2.register(PreviewVideoPlugin);
|
||||
pluginManager2.register(JavxyPreviewVideoPlugin);
|
||||
pluginManager2.register(JavopenPreviewVideoPlugin);
|
||||
pluginManager2.register(FilterTitleKeywordPlugin);
|
||||
pluginManager2.register(ActressInfoPlugin);
|
||||
pluginManager2.register(OtherSitePlugin);
|
||||
@@ -908,7 +910,7 @@ const pluginManager = function() {
|
||||
pluginManager2.register(FilterTitleKeywordPlugin);
|
||||
pluginManager2.register(HighlightMagnetPlugin);
|
||||
pluginManager2.register(BusPreviewVideoPlugin);
|
||||
pluginManager2.register(JavxyPreviewVideoPlugin);
|
||||
pluginManager2.register(JavopenPreviewVideoPlugin);
|
||||
pluginManager2.register(MagnetHubPlugin);
|
||||
pluginManager2.register(ScreenShotPlugin);
|
||||
pluginManager2.register(OtherSitePlugin);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,19 @@
|
||||
import { BasePlugin } from '../core/base-plugin.js';
|
||||
import { __publicField, isJavBus, isJavDb, Status_FILTER, Status_FAVORITE, Status_HAS_DOWN, Status_HAS_WATCH, NO, YES } from '../core/constants.js';
|
||||
import {
|
||||
__publicField,
|
||||
isJavBus,
|
||||
isJavDb,
|
||||
isEligibleDmmCoverCode,
|
||||
Status_FILTER,
|
||||
Status_FAVORITE,
|
||||
Status_HAS_DOWN,
|
||||
Status_HAS_WATCH,
|
||||
NO,
|
||||
YES
|
||||
} from '../core/constants.js';
|
||||
import { HotkeyManager } from './preview-video.js';
|
||||
import {GM_xmlhttpRequest} from 'vite-plugin-monkey/dist/client';
|
||||
import {getCover} from '../api/dmmrsc.js';
|
||||
|
||||
class DetailPageButtonPlugin extends BasePlugin {
|
||||
getName() {
|
||||
@@ -23,7 +36,7 @@ class DetailPageButtonPlugin extends BasePlugin {
|
||||
}
|
||||
async createMenuBtn() {
|
||||
const pageInfo = this.getPageInfo(), carNum2 = pageInfo.carNum,
|
||||
buttonsHtml = '\n <div style="margin: 10px auto; display: flex; justify-content: space-between; align-items: center; flex-wrap:wrap;gap: 20px;">\n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="filterBtn" class="menu-btn" style="width: 120px; background-color:#de3333; color: white; text-align: center; padding: 8px 0;">\n <span>🚫 屏蔽</span>\n </a>\n <a id="favoriteBtn" class="menu-btn" style="width: 120px; background-color:#25b1dc; color: white; text-align: center; padding: 8px 0;">\n <span>⭐ 收藏</span>\n </a>\n <a id="hasDownBtn" class="menu-btn" style="width: 120px; background-color:#7bc73b; color: white; text-align: center; padding: 8px 0;">\n <span>📥️ 已下载</span>\n </a>\n <a id="hasWatchBtn" class="menu-btn" style="width: 120px; background-color:#d7a80c; color: white; text-align: center; padding: 8px 0;">\n <span>🔍 已观看</span>\n </a>\n </div>\n \n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="enable-magnets-filter" class="menu-btn" style="width: 140px; background-color: #c2bd4c; color: white; text-align: center; padding: 8px 0;">\n <span id="magnets-span">排序与过滤</span>\n </a>\n <a id="magnetSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to right, rgb(245,140,1), rgb(84,161,29)); color: white; text-align: center; padding: 8px 0;">\n <span>磁力搜索</span>\n </a>\n <a id="subtitleSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕搜索</span>\n </a>\n <!--\n <a id="xunLeiSubtitleBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (迅雷)</span>\n </a>\n <a id="search-subtitle-btn" class="menu-btn" style="width: 160px; background: linear-gradient(to bottom, #8d5656, rgb(196,159,91)); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (SubTitleCat)</span>\n </a>\n -->\n </div>\n </div>\n ';
|
||||
buttonsHtml = '\n <div style="margin: 10px auto; display: flex; justify-content: space-between; align-items: center; flex-wrap:wrap;gap: 20px;">\n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="filterBtn" class="menu-btn" style="width: 120px; background-color:#de3333; color: white; text-align: center; padding: 8px 0;">\n <span>🚫 屏蔽</span>\n </a>\n <a id="favoriteBtn" class="menu-btn" style="width: 120px; background-color:#25b1dc; color: white; text-align: center; padding: 8px 0;">\n <span>⭐ 收藏</span>\n </a>\n <a id="hasDownBtn" class="menu-btn" style="width: 120px; background-color:#7bc73b; color: white; text-align: center; padding: 8px 0;">\n <span>📥️ 已下载</span>\n </a>\n <a id="hasWatchBtn" class="menu-btn" style="width: 120px; background-color:#d7a80c; color: white; text-align: center; padding: 8px 0;">\n <span>🔍 已观看</span>\n </a>\n </div>\n \n <div style="display: flex; gap: 10px; flex-wrap:wrap;">\n <a id="downloadCoverBtn" class="menu-btn" style="width: 140px; background-color: #9b59b6; color: white; text-align: center; padding: 8px 0;">\n <span>下载高清封面</span>\n </a>\n <a id="enable-magnets-filter" class="menu-btn" style="width: 140px; background-color: #c2bd4c; color: white; text-align: center; padding: 8px 0;">\n <span id="magnets-span">排序与过滤</span>\n </a>\n <a id="magnetSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to right, rgb(245,140,1), rgb(84,161,29)); color: white; text-align: center; padding: 8px 0;">\n <span>磁力搜索</span>\n </a>\n <a id="subtitleSearchBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕搜索</span>\n </a>\n <!--\n <a id="xunLeiSubtitleBtn" class="menu-btn" style="width: 120px; background: linear-gradient(to left, #375f7c, #2196F3); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (迅雷)</span>\n </a>\n <a id="search-subtitle-btn" class="menu-btn" style="width: 160px; background: linear-gradient(to bottom, #8d5656, rgb(196,159,91)); color: white; text-align: center; padding: 8px 0;">\n <span>字幕 (SubTitleCat)</span>\n </a>\n -->\n </div>\n </div>\n ';
|
||||
isJavDb && $(".tabs").after(buttonsHtml);
|
||||
isJavBus && $("#mag-submit-show").before(buttonsHtml);
|
||||
$("#favoriteBtn").on("click", (() => this.favoriteOne()));
|
||||
@@ -87,6 +100,50 @@ class DetailPageButtonPlugin extends BasePlugin {
|
||||
$("#subtitleSearchBtn").on("click", (() => {
|
||||
window.JavPackSubtitle.openSearchModal({details: {code: carNum2}});
|
||||
}));
|
||||
$("#downloadCoverBtn").on("click", (async () => {
|
||||
if (!isEligibleDmmCoverCode(carNum2)) {
|
||||
show.error("当前番号不支持下载高清封面");
|
||||
return;
|
||||
}
|
||||
const $btn = $("#downloadCoverBtn");
|
||||
$btn.addClass("is-loading").prop("disabled", true);
|
||||
try {
|
||||
const coverUrl = await getCover(carNum2);
|
||||
if (!coverUrl) {
|
||||
show.error("未找到高清封面");
|
||||
return;
|
||||
}
|
||||
const fileName = `${carNum2}-cover.jpg`;
|
||||
GM_xmlhttpRequest({
|
||||
method: 'GET',
|
||||
url: coverUrl,
|
||||
responseType: 'blob',
|
||||
onload: (response) => {
|
||||
if (response.status === 200) {
|
||||
const blobUrl = URL.createObjectURL(response.response);
|
||||
const a = document.createElement('a');
|
||||
a.href = blobUrl;
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(blobUrl);
|
||||
}, 100);
|
||||
show.ok("封面下载已开始");
|
||||
} else {
|
||||
show.error(`封面下载失败: ${response.status}`);
|
||||
}
|
||||
},
|
||||
onerror: () => show.error("封面下载网络错误"),
|
||||
ontimeout: () => show.error("封面下载超时"),
|
||||
});
|
||||
} catch (e) {
|
||||
show.error("获取封面信息失败");
|
||||
} finally {
|
||||
$btn.removeClass("is-loading").prop("disabled", false);
|
||||
}
|
||||
}));
|
||||
window.JavPackSubtitle.preload115Matches(carNum2);
|
||||
// $("#search-subtitle-btn").on("click", (event => utils.openPage(`https://subtitlecat.com/index.php?search=${carNum2}`, carNum2, !1, event)));
|
||||
// $("#xunLeiSubtitleBtn").on("click", (() => this.searchXunLeiSubtitle(carNum2)));
|
||||
|
||||
@@ -162,10 +162,17 @@ class ImageRecognitionPlugin extends BasePlugin {
|
||||
type: mimeType
|
||||
}), formData = new FormData;
|
||||
formData.append("image", blob);
|
||||
const idKey = "jhs_imgurClientId";
|
||||
let clientId = localStorage.getItem(idKey);
|
||||
if (!clientId) {
|
||||
clientId = window.prompt("请输入您自己的Imgur Client-ID(可前往 https://api.imgur.com/oauth2/addclient 免费申请)用于图片上传搜索:");
|
||||
if (!clientId) throw new Error("未提供Imgur Client-ID,无法上传图片");
|
||||
localStorage.setItem(idKey, clientId);
|
||||
}
|
||||
const response = await fetch("https://api.imgur.com/3/image", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: "Client-ID d70305e7c3ac5c6"
|
||||
Authorization: `Client-ID ${clientId}`
|
||||
},
|
||||
body: formData
|
||||
}), data = await response.json();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {BasePlugin} from '../core/base-plugin.js';
|
||||
import {__publicField, isJavBus, isJavDb, qualityOptions} from '../core/constants.js';
|
||||
import {isM3U8Url, attachHlsToVideo, destroyHls, loadHlsLibrary, getHlsClass} from '../lib/hls-runtime.js';
|
||||
import {getJavxyVideoUrls} from '../api/javxy.js';
|
||||
import {getTrailer} from '../api/dmmrsc.js';
|
||||
|
||||
class JavxyPreviewVideoPlugin extends BasePlugin {
|
||||
class JavopenPreviewVideoPlugin extends BasePlugin {
|
||||
getName() {
|
||||
return "JavxyPreviewVideoPlugin";
|
||||
return "JavopenPreviewVideoPlugin";
|
||||
}
|
||||
|
||||
_attachVideoSrc(videoEl, src) {
|
||||
@@ -17,20 +17,13 @@ class JavxyPreviewVideoPlugin extends BasePlugin {
|
||||
if (HlsClass) {
|
||||
attachHlsToVideo(videoEl, src).catch((err) => {
|
||||
clog.error('视频预览 HLS 加载失败:', err);
|
||||
videoEl.src = src;
|
||||
videoEl.load && videoEl.load();
|
||||
});
|
||||
} else {
|
||||
loadHlsLibrary().then((HlsClass) => {
|
||||
if (videoEl && videoEl.isConnected && HlsClass) {
|
||||
attachHlsToVideo(videoEl, src).catch((err) => {
|
||||
clog.error('视频预览 HLS 延迟加载失败:', err);
|
||||
videoEl.src = src;
|
||||
videoEl.load && videoEl.load();
|
||||
});
|
||||
} else if (videoEl && videoEl.isConnected) {
|
||||
videoEl.src = src;
|
||||
videoEl.load && videoEl.load();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -51,38 +44,38 @@ class JavxyPreviewVideoPlugin extends BasePlugin {
|
||||
|
||||
async initCss() {
|
||||
return `
|
||||
.jhs-javxy-video-modal {
|
||||
.jhs-javopen-video-modal {
|
||||
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: rgba(0,0,0,0.95); z-index: 12345700;
|
||||
display: flex; justify-content: center; align-items: center;
|
||||
opacity: 0; visibility: hidden; transition: opacity 0.2s;
|
||||
}
|
||||
.jhs-javxy-video-modal.is-open { opacity: 1; visibility: visible; }
|
||||
.jhs-javxy-video-modal-inner {
|
||||
.jhs-javopen-video-modal.is-open { opacity: 1; visibility: visible; }
|
||||
.jhs-javopen-video-modal-inner {
|
||||
display: flex; flex-direction: column; align-items: center;
|
||||
gap: 12px; max-width: 90vw; max-height: 90vh;
|
||||
}
|
||||
.jhs-javxy-video-wrapper {
|
||||
.jhs-javopen-video-wrapper {
|
||||
width: 80vw; max-height: 80vh; aspect-ratio: 16/9;
|
||||
background: #000; position: relative;
|
||||
}
|
||||
.jhs-javxy-video-wrapper video {
|
||||
.jhs-javopen-video-wrapper video {
|
||||
position: absolute; top: 0; left: 0; width: 100%; height: 100%;
|
||||
}
|
||||
.jhs-javxy-quality-bar {
|
||||
.jhs-javopen-quality-bar {
|
||||
display: flex; gap: 8px; flex-wrap: wrap; justify-content: center;
|
||||
}
|
||||
.jhs-javxy-quality-btn {
|
||||
.jhs-javopen-quality-btn {
|
||||
min-width: 60px; padding: 5px 10px; font-size: 13px;
|
||||
background: rgba(255,255,255,0.2); color: #fff;
|
||||
border: 1px solid rgba(255,255,255,0.5); border-radius: 4px;
|
||||
cursor: pointer; transition: background 0.2s;
|
||||
}
|
||||
.jhs-javxy-quality-btn:hover { background: rgba(255,255,255,0.4); }
|
||||
.jhs-javxy-quality-btn.active {
|
||||
.jhs-javopen-quality-btn:hover { background: rgba(255,255,255,0.4); }
|
||||
.jhs-javopen-quality-btn.active {
|
||||
background: #1890ff; border-color: #096dd9; font-weight: bold;
|
||||
}
|
||||
.jhs-javxy-loading {
|
||||
.jhs-javopen-loading {
|
||||
color: #fff; font-size: 16px; text-align: center;
|
||||
}
|
||||
`;
|
||||
@@ -94,10 +87,10 @@ class JavxyPreviewVideoPlugin extends BasePlugin {
|
||||
const carNum = this.getPageInfo().carNum;
|
||||
if (!carNum) return;
|
||||
|
||||
this._addJavxyButton(carNum);
|
||||
this._addJavopenButton(carNum);
|
||||
}
|
||||
|
||||
_addJavxyButton(carNum) {
|
||||
_addJavopenButton(carNum) {
|
||||
let $target = null;
|
||||
|
||||
if (isJavDb) {
|
||||
@@ -128,7 +121,7 @@ class JavxyPreviewVideoPlugin extends BasePlugin {
|
||||
e.stopPropagation();
|
||||
$btn.css("pointer-events", "none").find("span").text("...");
|
||||
try {
|
||||
await this._openJavxyPlayer(carNum);
|
||||
await this._openJavopenPlayer(carNum);
|
||||
} finally {
|
||||
$btn.css("pointer-events", "").find("span").text("视频预览");
|
||||
}
|
||||
@@ -136,23 +129,23 @@ class JavxyPreviewVideoPlugin extends BasePlugin {
|
||||
$target.after($btn);
|
||||
}
|
||||
|
||||
async _openJavxyPlayer(carNum) {
|
||||
if ($("#jhs-javxy-video-modal").length === 0) {
|
||||
async _openJavopenPlayer(carNum) {
|
||||
if ($("#jhs-javopen-video-modal").length === 0) {
|
||||
$("body").append(`
|
||||
<div id="jhs-javxy-video-modal" class="jhs-javxy-video-modal">
|
||||
<div class="jhs-javxy-video-modal-inner">
|
||||
<div class="jhs-javxy-video-wrapper">
|
||||
<video id="jhs-javxy-video" controls playsinline></video>
|
||||
<div id="jhs-javopen-video-modal" class="jhs-javopen-video-modal">
|
||||
<div class="jhs-javopen-video-modal-inner">
|
||||
<div class="jhs-javopen-video-wrapper">
|
||||
<video id="jhs-javopen-video" controls playsinline></video>
|
||||
</div>
|
||||
<div class="jhs-javxy-quality-bar"></div>
|
||||
<div class="jhs-javopen-quality-bar"></div>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
const $modal = $("#jhs-javxy-video-modal");
|
||||
const $modal = $("#jhs-javopen-video-modal");
|
||||
$modal.on("click", (e) => {
|
||||
if (e.target.id === "jhs-javxy-video-modal") {
|
||||
if (e.target.id === "jhs-javopen-video-modal") {
|
||||
$modal.removeClass("is-open");
|
||||
const videoEl = document.getElementById("jhs-javxy-video");
|
||||
const videoEl = document.getElementById("jhs-javopen-video");
|
||||
if (videoEl) {
|
||||
destroyHls(videoEl);
|
||||
videoEl.pause();
|
||||
@@ -162,7 +155,7 @@ class JavxyPreviewVideoPlugin extends BasePlugin {
|
||||
$(document).on("keydown", (e) => {
|
||||
if (e.key === "Escape" && $modal.hasClass("is-open")) {
|
||||
$modal.removeClass("is-open");
|
||||
const videoEl = document.getElementById("jhs-javxy-video");
|
||||
const videoEl = document.getElementById("jhs-javopen-video");
|
||||
if (videoEl) {
|
||||
destroyHls(videoEl);
|
||||
videoEl.pause();
|
||||
@@ -171,23 +164,23 @@ class JavxyPreviewVideoPlugin extends BasePlugin {
|
||||
});
|
||||
}
|
||||
|
||||
const $modal = $("#jhs-javxy-video-modal");
|
||||
const $wrapper = $modal.find(".jhs-javxy-video-wrapper");
|
||||
const $qualityBar = $modal.find(".jhs-javxy-quality-bar");
|
||||
$wrapper.html('<div class="jhs-javxy-loading">视频预览加载中...</div>');
|
||||
const $modal = $("#jhs-javopen-video-modal");
|
||||
const $wrapper = $modal.find(".jhs-javopen-video-wrapper");
|
||||
const $qualityBar = $modal.find(".jhs-javopen-quality-bar");
|
||||
$wrapper.html('<div class="jhs-javopen-loading">视频预览加载中...</div>');
|
||||
$qualityBar.empty();
|
||||
$modal.addClass("is-open");
|
||||
|
||||
let videoMap = null;
|
||||
try {
|
||||
videoMap = await getJavxyVideoUrls(carNum);
|
||||
videoMap = await getTrailer(carNum);
|
||||
} catch (err) {
|
||||
$wrapper.html(`<div class="jhs-javxy-loading">视频预览请求失败: ${err.message}</div>`);
|
||||
$wrapper.html(`<div class="jhs-javopen-loading">视频预览请求失败: ${err.message}</div>`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!videoMap || Object.keys(videoMap).length === 0) {
|
||||
$wrapper.html('<div class="jhs-javxy-loading">未找到可用的视频源</div>');
|
||||
$wrapper.html('<div class="jhs-javopen-loading">未找到可用的视频源</div>');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -195,10 +188,10 @@ class JavxyPreviewVideoPlugin extends BasePlugin {
|
||||
const defaultQuality = this._selectDefaultQuality(qualityList, '720p');
|
||||
const defaultUrl = videoMap[defaultQuality];
|
||||
|
||||
$wrapper.html(`<video id="jhs-javxy-video" controls playsinline>
|
||||
$wrapper.html(`<video id="jhs-javopen-video" controls playsinline>
|
||||
<source src="${defaultUrl}" />
|
||||
</video>`);
|
||||
const videoEl = document.getElementById("jhs-javxy-video");
|
||||
const videoEl = document.getElementById("jhs-javopen-video");
|
||||
if (!videoEl) return;
|
||||
|
||||
if (isM3U8Url(defaultUrl)) {
|
||||
@@ -209,13 +202,13 @@ class JavxyPreviewVideoPlugin extends BasePlugin {
|
||||
qualityOptions.forEach((option) => {
|
||||
const url = videoMap[option.quality];
|
||||
if (url) {
|
||||
buttonsHtml += `<button class="jhs-javxy-quality-btn${option.quality === defaultQuality ? ' active' : ''}"
|
||||
buttonsHtml += `<button class="jhs-javopen-quality-btn${option.quality === defaultQuality ? ' active' : ''}"
|
||||
data-quality="${option.quality}" data-video-src="${url}">${option.text}</button>`;
|
||||
}
|
||||
});
|
||||
$qualityBar.html(buttonsHtml);
|
||||
|
||||
$qualityBar.off("click").on("click", ".jhs-javxy-quality-btn", (e) => {
|
||||
$qualityBar.off("click").on("click", ".jhs-javopen-quality-btn", (e) => {
|
||||
const $btn = $(e.currentTarget);
|
||||
if ($btn.hasClass("active")) return;
|
||||
const src = $btn.attr("data-video-src");
|
||||
@@ -228,7 +221,7 @@ class JavxyPreviewVideoPlugin extends BasePlugin {
|
||||
}
|
||||
videoEl.play().catch(() => {
|
||||
});
|
||||
$qualityBar.find(".jhs-javxy-quality-btn").removeClass("active");
|
||||
$qualityBar.find(".jhs-javopen-quality-btn").removeClass("active");
|
||||
$btn.addClass("active");
|
||||
});
|
||||
|
||||
@@ -236,4 +229,4 @@ class JavxyPreviewVideoPlugin extends BasePlugin {
|
||||
}
|
||||
}
|
||||
|
||||
export {JavxyPreviewVideoPlugin};
|
||||
export {JavopenPreviewVideoPlugin};
|
||||
+1066
-142
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);
|
||||
};
|
||||
|
||||
@@ -17,30 +18,49 @@ class WangPan115TaskPlugin extends BasePlugin {
|
||||
}
|
||||
async handle() {
|
||||
$(".buttons button[data-clipboard-text*='magnet:']").each(((i, el) => {
|
||||
$(el).parent().append($("<button>").text("115离线下载").addClass("button is-info is-small").click((async event => {
|
||||
const $copyBtn = $(el);
|
||||
const magnet = $copyBtn.attr("data-clipboard-text");
|
||||
$copyBtn.css("margin-right", (parseInt($copyBtn.css("margin-right")) - 4) + "px");
|
||||
const $downloadBtn = $copyBtn.siblings("a.button, button").first();
|
||||
if ($downloadBtn.length) {
|
||||
$downloadBtn.css("margin-right", (parseInt($downloadBtn.css("margin-right")) + 3) + "px");
|
||||
}
|
||||
$("<button>").text("验车").addClass("button is-small").attr("style", "background-color:#2b6cb0 !important;color:#fff !important;border:none !important;cursor:pointer;border-radius:4px;font-size:12px;padding:2px 8px;").click((event => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.getBean("MagnetHubPlugin").checkWhatslink(magnet);
|
||||
})).appendTo($copyBtn.parent());
|
||||
$("<button>").text("115离线下载").addClass("button is-small").attr("style", "background-color:#4a90e2 !important;color:#fff !important;border:none !important;cursor:pointer;border-radius:4px;font-size:12px;padding:2px 8px;").click((async event => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
let loadObj = loading();
|
||||
try {
|
||||
await this.handleAddTask($(el).attr("data-clipboard-text"));
|
||||
await this.handleAddTask(magnet);
|
||||
} catch (e) {
|
||||
show.error("发生错误:" + e);
|
||||
console.error(e);
|
||||
} finally {
|
||||
loadObj.close();
|
||||
}
|
||||
})));
|
||||
})).appendTo($copyBtn.parent());
|
||||
}));
|
||||
isJavBus && window.isDetailPage && utils.loopDetector((() => $("#magnet-table td a").length > 0), (() => {
|
||||
this.bus115Down();
|
||||
}));
|
||||
}
|
||||
async bus115Down() {
|
||||
const $headerRow = $("#magnet-table tr").first();
|
||||
$("#magnet-table tr").each(((i, row) => {
|
||||
if (row === $headerRow[0]) return;
|
||||
const magnetLink = $(row).find("td:nth-child(1) a").attr("href");
|
||||
if (magnetLink && magnetLink.includes("magnet:")) {
|
||||
const actionCell = $("<td>").addClass("action-cell");
|
||||
$("<button>").text("115离线下载").addClass("button is-info is-small").click((async event => {
|
||||
$("<button>").text("验车").addClass("button is-small").attr("style", "background-color:#2b6cb0 !important;color:#fff !important;border:none !important;margin-right:4px;cursor:pointer;border-radius:4px;font-size:12px;padding:2px 8px;").click((event => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.getBean("MagnetHubPlugin").checkWhatslink(magnetLink);
|
||||
})).appendTo(actionCell);
|
||||
$("<button>").text("115离线下载").addClass("button is-small").attr("style", "background-color:#4a90e2 !important;color:#fff !important;border:none !important;cursor:pointer;border-radius:4px;font-size:12px;padding:2px 8px;").click((async event => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
let loadObj = loading();
|
||||
@@ -56,7 +76,9 @@ class WangPan115TaskPlugin extends BasePlugin {
|
||||
$(row).append(actionCell);
|
||||
}
|
||||
}));
|
||||
$("#magnet-table tbody").length > 0 && $("#magnet-table tbody tr").append($("<td>").text("操作"));
|
||||
if ($headerRow.find("td, th").last().text().trim() !== "操作") {
|
||||
$headerRow.append($("<td>").text("操作"));
|
||||
}
|
||||
}
|
||||
async getSavePathId(nyName) {
|
||||
let savePath115 = await storageManager.getSetting("savePath115", "云下载");
|
||||
|
||||
+185
-60
@@ -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,54 +412,39 @@ 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, "无安全可删除目录");
|
||||
continue;
|
||||
}
|
||||
let itemDeleted = 0;
|
||||
const uniqueDirIds = [...new Set(safeMatchList.map((m) => m.dirId))];
|
||||
for (let d = 0; d < uniqueDirIds.length; d++) {
|
||||
if (d > 0) {
|
||||
const groupedByDir = {};
|
||||
for (const m of safeMatchList) {
|
||||
if (!groupedByDir[m.dirId]) groupedByDir[m.dirId] = [];
|
||||
groupedByDir[m.dirId].push(m);
|
||||
}
|
||||
const entries = Object.entries(groupedByDir);
|
||||
for (let g = 0; g < entries.length; g++) {
|
||||
if (g > 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) {
|
||||
const [dirId, items] = entries[g];
|
||||
if (items.length === 1) {
|
||||
const result = await this._deleteOne(dirId, false, i, selectedItems, item, onProgress);
|
||||
if (result) {
|
||||
itemDeleted++;
|
||||
deletedCount++;
|
||||
}
|
||||
} else {
|
||||
for (let f = 0; f < items.length; f++) {
|
||||
if (f > 0) await randomDelay();
|
||||
const fileDeleted = await this._deleteOne(items[f].folderId, true, i, selectedItems, item, onProgress);
|
||||
if (fileDeleted) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -482,9 +473,48 @@ const _WangPan115MatchPlugin = class _WangPan115MatchPlugin extends BasePlugin {
|
||||
return false;
|
||||
}
|
||||
|
||||
async _deleteOne(deleteId, isFile, batchIndex, selectedItems, item, onProgress) {
|
||||
let retryCount = 0;
|
||||
const maxRetries = 3;
|
||||
const label = isFile ? "文件" : "目录";
|
||||
while (retryCount <= maxRetries) {
|
||||
try {
|
||||
const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", {
|
||||
"fid[0]": deleteId,
|
||||
ignore_warn: "1"
|
||||
});
|
||||
if (result && result.state) {
|
||||
return 1;
|
||||
} else {
|
||||
const errMsg = (result && result.error) || "未知错误";
|
||||
if (retryCount < maxRetries && this.isRateLimited(result)) {
|
||||
retryCount++;
|
||||
const backoff = (retryCount + 1) * 2000 + Math.random() * 1000;
|
||||
onProgress && onProgress(batchIndex + 1, selectedItems.length, item, `风控触发,${backoff / 1000}秒后重试(${retryCount}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, backoff));
|
||||
continue;
|
||||
}
|
||||
console.error(`删除${label}失败: ${deleteId}`, errMsg);
|
||||
return 0;
|
||||
}
|
||||
} catch (e) {
|
||||
if (retryCount < maxRetries && this.isRateLimited(e)) {
|
||||
retryCount++;
|
||||
const backoff = (retryCount + 1) * 2000 + Math.random() * 1000;
|
||||
onProgress && onProgress(batchIndex + 1, selectedItems.length, item, `风控触发,${backoff / 1000}秒后重试(${retryCount}/${maxRetries})`);
|
||||
await new Promise(resolve => setTimeout(resolve, backoff));
|
||||
continue;
|
||||
}
|
||||
console.error(`删除${label}失败: ${deleteId}`, e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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中删除。`);
|
||||
@@ -537,13 +567,32 @@ const _WangPan115MatchPlugin = class _WangPan115MatchPlugin extends BasePlugin {
|
||||
let loadObj = loading();
|
||||
let deletedCount = 0;
|
||||
try {
|
||||
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", {
|
||||
"fid[0]": dirId,
|
||||
ignore_warn: "1"
|
||||
});
|
||||
result && result.state ? deletedCount++ : console.error(`删除目录失败: ${dirId}`, result);
|
||||
const selectedItems = selected.map((i) => safeMatchList[i]);
|
||||
const totalByDir = {};
|
||||
for (const m of safeMatchList) {
|
||||
totalByDir[m.dirId] = (totalByDir[m.dirId] || 0) + 1;
|
||||
}
|
||||
const groupedByDir = {};
|
||||
for (const item of selectedItems) {
|
||||
if (!groupedByDir[item.dirId]) groupedByDir[item.dirId] = [];
|
||||
groupedByDir[item.dirId].push(item);
|
||||
}
|
||||
for (const [dirId, items] of Object.entries(groupedByDir)) {
|
||||
if (items.length === totalByDir[dirId]) {
|
||||
const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", {
|
||||
"fid[0]": dirId,
|
||||
ignore_warn: "1"
|
||||
});
|
||||
result && result.state ? deletedCount++ : console.error(`删除目录失败: ${dirId}`, result);
|
||||
} else {
|
||||
for (const item of items) {
|
||||
const result = await gmHttp.postForm("https://webapi.115.com/rb/delete", {
|
||||
"fid[0]": item.folderId,
|
||||
ignore_warn: "1"
|
||||
});
|
||||
result && result.state ? deletedCount++ : console.error(`删除文件失败: ${item.folderId}`, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
loadObj.close();
|
||||
show.ok(`作品 "${carNum2}" 已删除 ${deletedCount} 个文件夹`);
|
||||
@@ -573,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>`);
|
||||
@@ -651,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 };
|
||||
+5
-6
@@ -6,10 +6,10 @@ export default defineConfig({
|
||||
monkey({
|
||||
entry: 'src/main.js',
|
||||
userscript: {
|
||||
name: 'JHS-enhance',
|
||||
namespace: 'JHS-enhance',
|
||||
version: '3.3.7',
|
||||
author: 'xie bro,fireinrain',
|
||||
name: 'JHS-enhance',
|
||||
namespace: 'JHS-enhance',
|
||||
version: '3.3.15',
|
||||
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