mirror of
https://github.com/qaiu/netdisk-fast-download.git
synced 2025-12-16 12:23:03 +00:00
feat: 添加getNoRedirect方法支持302重定向处理
- 在JsHttpClient中添加getNoRedirect方法,支持不自动跟随重定向的HTTP请求 - 修改baidu-photo.js解析器,使用getNoRedirect获取真实的下载链接 - 更新测试用例断言,验证重定向处理功能正常工作 - 修复百度一刻相册解析器302重定向问题,现在能正确获取真实下载链接
This commit is contained in:
@@ -233,11 +233,22 @@ public class Example {
|
||||
.setShareLinkInfoPwd("1234") // 设置密码(可选)
|
||||
.createTool(); // 创建工具实例
|
||||
|
||||
// 解析获取下载链接
|
||||
// 方式1: 使用同步方法解析(推荐)
|
||||
String downloadUrl = tool.parseSync();
|
||||
System.out.println("下载链接: " + downloadUrl);
|
||||
|
||||
// 方式2: 异步解析
|
||||
// 方式2: 使用同步方法解析文件列表
|
||||
List<FileInfo> files = tool.parseFileListSync();
|
||||
System.out.println("文件列表: " + files.size() + " 个文件");
|
||||
|
||||
// 方式3: 使用同步方法根据文件ID获取下载链接
|
||||
if (!files.isEmpty()) {
|
||||
String fileId = files.get(0).getFileId();
|
||||
String fileDownloadUrl = tool.parseByIdSync();
|
||||
System.out.println("文件下载链接: " + fileDownloadUrl);
|
||||
}
|
||||
|
||||
// 方式4: 异步解析(仍支持)
|
||||
tool.parse().onSuccess(url -> {
|
||||
System.out.println("异步获取下载链接: " + url);
|
||||
}).onFailure(err -> {
|
||||
@@ -247,6 +258,42 @@ public class Example {
|
||||
}
|
||||
```
|
||||
|
||||
## 同步方法支持
|
||||
|
||||
解析器现在支持三种同步方法,简化了使用方式:
|
||||
|
||||
### 1. parseSync()
|
||||
解析单个文件的下载链接:
|
||||
```java
|
||||
String downloadUrl = tool.parseSync();
|
||||
```
|
||||
|
||||
### 2. parseFileListSync()
|
||||
解析文件列表(目录):
|
||||
```java
|
||||
List<FileInfo> files = tool.parseFileListSync();
|
||||
for (FileInfo file : files) {
|
||||
System.out.println("文件: " + file.getFileName());
|
||||
}
|
||||
```
|
||||
|
||||
### 3. parseByIdSync()
|
||||
根据文件ID获取下载链接:
|
||||
```java
|
||||
String fileDownloadUrl = tool.parseByIdSync();
|
||||
```
|
||||
|
||||
### 同步方法优势
|
||||
- **简化使用**: 无需处理 Future 和回调
|
||||
- **异常处理**: 同步方法会抛出异常,便于错误处理
|
||||
- **代码简洁**: 减少异步代码的复杂性
|
||||
|
||||
### 异步方法仍可用
|
||||
原有的异步方法仍然支持:
|
||||
- `parse()`: 返回 `Future<String>`
|
||||
- `parseFileList()`: 返回 `Future<List<FileInfo>>`
|
||||
- `parseById()`: 返回 `Future<String>`
|
||||
|
||||
## 注意事项
|
||||
|
||||
### 1. 类型标识规范
|
||||
@@ -363,9 +410,21 @@ public class CompleteExample {
|
||||
|
||||
// 创建工具并解析
|
||||
IPanTool tool = parser.createTool();
|
||||
|
||||
// 使用同步方法解析
|
||||
String url = tool.parseSync();
|
||||
System.out.println("✓ 下载链接: " + url);
|
||||
|
||||
// 解析文件列表
|
||||
List<FileInfo> files = tool.parseFileListSync();
|
||||
System.out.println("✓ 文件列表: " + files.size() + " 个文件");
|
||||
|
||||
// 根据文件ID获取下载链接
|
||||
if (!files.isEmpty()) {
|
||||
String fileDownloadUrl = tool.parseByIdSync();
|
||||
System.out.println("✓ 文件下载链接: " + fileDownloadUrl);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("✗ 解析失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
465
parser/doc/JAVASCRIPT_PARSER_GUIDE.md
Normal file
465
parser/doc/JAVASCRIPT_PARSER_GUIDE.md
Normal file
@@ -0,0 +1,465 @@
|
||||
# JavaScript解析器扩展开发指南
|
||||
|
||||
## 概述
|
||||
|
||||
本指南介绍如何使用JavaScript编写自定义网盘解析器,支持通过JavaScript代码实现网盘解析逻辑,无需编写Java代码。
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 1. 创建JavaScript脚本
|
||||
|
||||
在 `./custom-parsers/` 目录下创建 `.js` 文件,使用以下模板:
|
||||
|
||||
```javascript
|
||||
// ==UserScript==
|
||||
// @name 我的解析器
|
||||
// @type my_parser
|
||||
// @displayName 我的网盘
|
||||
// @description 使用JavaScript实现的网盘解析器
|
||||
// @match https?://example\.com/s/(?<KEY>\w+)
|
||||
// @author yourname
|
||||
// @version 1.0.0
|
||||
// ==/UserScript==
|
||||
|
||||
// 使用require导入类型定义(仅用于IDE类型提示)
|
||||
var types = require('./types');
|
||||
/** @typedef {types.ShareLinkInfo} ShareLinkInfo */
|
||||
/** @typedef {types.JsHttpClient} JsHttpClient */
|
||||
/** @typedef {types.JsLogger} JsLogger */
|
||||
/** @typedef {types.FileInfo} FileInfo */
|
||||
|
||||
/**
|
||||
* 解析单个文件下载链接
|
||||
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息
|
||||
* @param {JsHttpClient} http - HTTP客户端
|
||||
* @param {JsLogger} logger - 日志对象
|
||||
* @returns {string} 下载链接
|
||||
*/
|
||||
function parse(shareLinkInfo, http, logger) {
|
||||
var url = shareLinkInfo.getShareUrl();
|
||||
var response = http.get(url);
|
||||
return response.body();
|
||||
}
|
||||
|
||||
// 导出函数
|
||||
exports.parse = parse;
|
||||
```
|
||||
|
||||
### 2. 重启应用
|
||||
|
||||
重启应用后,JavaScript解析器会自动加载并注册。
|
||||
|
||||
## 元数据格式
|
||||
|
||||
### 必填字段
|
||||
|
||||
- `@name`: 脚本名称
|
||||
- `@type`: 解析器类型标识(唯一)
|
||||
- `@displayName`: 显示名称
|
||||
- `@match`: URL匹配正则(必须包含 `(?<KEY>...)` 命名捕获组)
|
||||
|
||||
### 可选字段
|
||||
|
||||
- `@description`: 描述信息
|
||||
- `@author`: 作者
|
||||
- `@version`: 版本号
|
||||
|
||||
### 示例
|
||||
|
||||
```javascript
|
||||
// ==UserScript==
|
||||
// @name 蓝奏云解析器
|
||||
// @type lanzou_js
|
||||
// @displayName 蓝奏云(JS)
|
||||
// @description 使用JavaScript实现的蓝奏云解析器
|
||||
// @match https?://.*\.lanzou[a-z]\.com/(?<KEY>\w+)
|
||||
// @match https?://.*\.lanzoui\.com/(?<KEY>\w+)
|
||||
// @author qaiu
|
||||
// @version 1.0.0
|
||||
// ==/UserScript==
|
||||
```
|
||||
|
||||
## API参考
|
||||
|
||||
### ShareLinkInfo对象
|
||||
|
||||
提供分享链接信息的访问接口:
|
||||
|
||||
```javascript
|
||||
// 获取分享URL
|
||||
var shareUrl = shareLinkInfo.getShareUrl();
|
||||
|
||||
// 获取分享Key
|
||||
var shareKey = shareLinkInfo.getShareKey();
|
||||
|
||||
// 获取分享密码
|
||||
var password = shareLinkInfo.getSharePassword();
|
||||
|
||||
// 获取网盘类型
|
||||
var type = shareLinkInfo.getType();
|
||||
|
||||
// 获取网盘名称
|
||||
var panName = shareLinkInfo.getPanName();
|
||||
|
||||
// 获取其他参数
|
||||
var dirId = shareLinkInfo.getOtherParam("dirId");
|
||||
var paramJson = shareLinkInfo.getOtherParam("paramJson");
|
||||
|
||||
// 检查参数是否存在
|
||||
if (shareLinkInfo.hasOtherParam("customParam")) {
|
||||
var value = shareLinkInfo.getOtherParamAsString("customParam");
|
||||
}
|
||||
```
|
||||
|
||||
### JsHttpClient对象
|
||||
|
||||
提供HTTP请求功能:
|
||||
|
||||
```javascript
|
||||
// GET请求
|
||||
var response = http.get("https://api.example.com/data");
|
||||
|
||||
// POST请求
|
||||
var response = http.post("https://api.example.com/submit", {
|
||||
key: "value",
|
||||
data: "test"
|
||||
});
|
||||
|
||||
// 设置请求头
|
||||
http.putHeader("User-Agent", "MyBot/1.0")
|
||||
.putHeader("Authorization", "Bearer token");
|
||||
|
||||
// 发送表单数据
|
||||
var formResponse = http.sendForm({
|
||||
username: "user",
|
||||
password: "pass"
|
||||
});
|
||||
|
||||
// 发送JSON数据
|
||||
var jsonResponse = http.sendJson({
|
||||
name: "test",
|
||||
value: 123
|
||||
});
|
||||
```
|
||||
|
||||
### JsHttpResponse对象
|
||||
|
||||
处理HTTP响应:
|
||||
|
||||
```javascript
|
||||
var response = http.get("https://api.example.com/data");
|
||||
|
||||
// 获取响应体(字符串)
|
||||
var body = response.body();
|
||||
|
||||
// 解析JSON响应
|
||||
var data = response.json();
|
||||
|
||||
// 获取状态码
|
||||
var status = response.statusCode();
|
||||
|
||||
// 获取响应头
|
||||
var contentType = response.header("Content-Type");
|
||||
var allHeaders = response.headers();
|
||||
|
||||
// 检查请求是否成功
|
||||
if (response.isSuccess()) {
|
||||
logger.info("请求成功");
|
||||
} else {
|
||||
logger.error("请求失败: " + status);
|
||||
}
|
||||
```
|
||||
|
||||
### JsLogger对象
|
||||
|
||||
提供日志功能:
|
||||
|
||||
```javascript
|
||||
// 不同级别的日志
|
||||
logger.debug("调试信息");
|
||||
logger.info("一般信息");
|
||||
logger.warn("警告信息");
|
||||
logger.error("错误信息");
|
||||
|
||||
// 带参数的日志
|
||||
logger.info("用户 {} 访问了 {}", username, url);
|
||||
|
||||
// 检查日志级别
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("详细的调试信息");
|
||||
}
|
||||
```
|
||||
|
||||
## 实现方法
|
||||
|
||||
JavaScript解析器支持三种方法,对应Java接口的三种同步方法:
|
||||
|
||||
### parse方法(必填)
|
||||
|
||||
解析单个文件的下载链接,对应Java的 `parseSync()` 方法:
|
||||
|
||||
```javascript
|
||||
function parse(shareLinkInfo, http, logger) {
|
||||
var shareUrl = shareLinkInfo.getShareUrl();
|
||||
var password = shareLinkInfo.getSharePassword();
|
||||
|
||||
// 发起请求获取页面
|
||||
var response = http.get(shareUrl);
|
||||
var html = response.body();
|
||||
|
||||
// 解析HTML获取下载链接
|
||||
var regex = /downloadUrl["']:\s*["']([^"']+)["']/;
|
||||
var match = html.match(regex);
|
||||
|
||||
if (match) {
|
||||
return match[1]; // 返回下载链接
|
||||
} else {
|
||||
throw new Error("无法解析下载链接");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### parseFileList方法(可选)
|
||||
|
||||
解析文件列表(目录),对应Java的 `parseFileListSync()` 方法:
|
||||
|
||||
```javascript
|
||||
function parseFileList(shareLinkInfo, http, logger) {
|
||||
var dirId = shareLinkInfo.getOtherParam("dirId") || "0";
|
||||
|
||||
// 请求文件列表API
|
||||
var response = http.get("/api/list?dirId=" + dirId);
|
||||
var data = response.json();
|
||||
|
||||
var fileList = [];
|
||||
for (var i = 0; i < data.files.length; i++) {
|
||||
var file = data.files[i];
|
||||
|
||||
var fileInfo = {
|
||||
fileName: file.name,
|
||||
fileId: file.id,
|
||||
fileType: file.isDir ? "folder" : "file",
|
||||
size: file.size,
|
||||
sizeStr: formatSize(file.size),
|
||||
createTime: file.createTime,
|
||||
parserUrl: "/v2/redirectUrl/my_parser/" + file.id
|
||||
};
|
||||
|
||||
fileList.push(fileInfo);
|
||||
}
|
||||
|
||||
return fileList;
|
||||
}
|
||||
```
|
||||
|
||||
### parseById方法(可选)
|
||||
|
||||
根据文件ID获取下载链接,对应Java的 `parseByIdSync()` 方法:
|
||||
|
||||
```javascript
|
||||
function parseById(shareLinkInfo, http, logger) {
|
||||
var paramJson = shareLinkInfo.getOtherParam("paramJson");
|
||||
var fileId = paramJson.fileId;
|
||||
|
||||
// 请求下载API
|
||||
var response = http.get("/api/download?fileId=" + fileId);
|
||||
var data = response.json();
|
||||
|
||||
return data.downloadUrl;
|
||||
}
|
||||
```
|
||||
|
||||
## 同步方法支持
|
||||
|
||||
JavaScript解析器的方法都是同步执行的,对应Java接口的三种同步方法:
|
||||
|
||||
### 方法对应关系
|
||||
|
||||
| JavaScript方法 | Java同步方法 | 说明 |
|
||||
|----------------|-------------|------|
|
||||
| `parse()` | `parseSync()` | 解析单个文件下载链接 |
|
||||
| `parseFileList()` | `parseFileListSync()` | 解析文件列表 |
|
||||
| `parseById()` | `parseByIdSync()` | 根据文件ID获取下载链接 |
|
||||
|
||||
### 使用示例
|
||||
|
||||
```javascript
|
||||
// 在Java中调用JavaScript解析器
|
||||
IPanTool tool = ParserCreate.fromType("my_js_parser")
|
||||
.shareKey("abc123")
|
||||
.createTool();
|
||||
|
||||
// 使用同步方法调用JavaScript函数
|
||||
String downloadUrl = tool.parseSync(); // 调用 parse() 函数
|
||||
List<FileInfo> files = tool.parseFileListSync(); // 调用 parseFileList() 函数
|
||||
String fileUrl = tool.parseByIdSync(); // 调用 parseById() 函数
|
||||
```
|
||||
|
||||
### 注意事项
|
||||
|
||||
- JavaScript方法都是同步执行的,无需处理异步回调
|
||||
- 如果JavaScript方法抛出异常,Java同步方法会抛出相应的异常
|
||||
- 建议在JavaScript方法中添加适当的错误处理和日志记录
|
||||
|
||||
## 导出方式
|
||||
|
||||
支持三种导出方式:
|
||||
|
||||
### 方式1:分别导出(推荐)
|
||||
|
||||
```javascript
|
||||
function parse(shareLinkInfo, http, logger) { }
|
||||
function parseFileList(shareLinkInfo, http, logger) { }
|
||||
function parseById(shareLinkInfo, http, logger) { }
|
||||
|
||||
exports.parse = parse;
|
||||
exports.parseFileList = parseFileList;
|
||||
exports.parseById = parseById;
|
||||
```
|
||||
|
||||
### 方式2:直接挂载
|
||||
|
||||
```javascript
|
||||
exports.parse = function(shareLinkInfo, http, logger) { };
|
||||
exports.parseFileList = function(shareLinkInfo, http, logger) { };
|
||||
exports.parseById = function(shareLinkInfo, http, logger) { };
|
||||
```
|
||||
|
||||
### 方式3:module.exports
|
||||
|
||||
```javascript
|
||||
function parse(shareLinkInfo, http, logger) { }
|
||||
function parseFileList(shareLinkInfo, http, logger) { }
|
||||
function parseById(shareLinkInfo, http, logger) { }
|
||||
|
||||
module.exports = {
|
||||
parse: parse,
|
||||
parseFileList: parseFileList,
|
||||
parseById: parseById
|
||||
};
|
||||
```
|
||||
|
||||
## VSCode配置
|
||||
|
||||
### 1. 安装JavaScript扩展
|
||||
|
||||
安装 "JavaScript (ES6) code snippets" 扩展。
|
||||
|
||||
### 2. 配置jsconfig.json
|
||||
|
||||
在 `custom-parsers` 目录下创建 `jsconfig.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"checkJs": true,
|
||||
"target": "ES5",
|
||||
"lib": ["ES5"],
|
||||
"allowJs": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["*.js", "types.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 使用类型提示
|
||||
|
||||
```javascript
|
||||
// 引用类型定义
|
||||
var types = require('./types');
|
||||
/** @typedef {types.ShareLinkInfo} ShareLinkInfo */
|
||||
/** @typedef {types.JsHttpClient} JsHttpClient */
|
||||
|
||||
// 使用类型注解
|
||||
/**
|
||||
* @param {ShareLinkInfo} shareLinkInfo
|
||||
* @param {JsHttpClient} http
|
||||
* @returns {string}
|
||||
*/
|
||||
function parse(shareLinkInfo, http, logger) {
|
||||
// VSCode会提供代码补全和类型检查
|
||||
}
|
||||
```
|
||||
|
||||
## 调试技巧
|
||||
|
||||
### 1. 使用日志
|
||||
|
||||
```javascript
|
||||
function parse(shareLinkInfo, http, logger) {
|
||||
logger.info("开始解析: " + shareLinkInfo.getShareUrl());
|
||||
|
||||
var response = http.get(shareLinkInfo.getShareUrl());
|
||||
logger.debug("响应状态: " + response.statusCode());
|
||||
logger.debug("响应内容: " + response.body().substring(0, 100));
|
||||
|
||||
// 解析逻辑...
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 错误处理
|
||||
|
||||
```javascript
|
||||
function parse(shareLinkInfo, http, logger) {
|
||||
try {
|
||||
var response = http.get(shareLinkInfo.getShareUrl());
|
||||
|
||||
if (!response.isSuccess()) {
|
||||
throw new Error("HTTP请求失败: " + response.statusCode());
|
||||
}
|
||||
|
||||
var data = response.json();
|
||||
return data.downloadUrl;
|
||||
|
||||
} catch (e) {
|
||||
logger.error("解析失败: " + e.message);
|
||||
throw e; // 重新抛出异常
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 启用调试模式
|
||||
|
||||
设置系统属性启用详细日志:
|
||||
|
||||
```bash
|
||||
-Dnfd.js.debug=true
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q: 如何获取分享密码?
|
||||
|
||||
A: 使用 `shareLinkInfo.getSharePassword()` 方法。
|
||||
|
||||
### Q: 如何处理需要登录的网盘?
|
||||
|
||||
A: 使用 `http.putHeader()` 设置认证头,或使用 `http.sendForm()` 发送登录表单。
|
||||
|
||||
### Q: 如何解析复杂的HTML?
|
||||
|
||||
A: 使用正则表达式或字符串方法解析HTML内容。
|
||||
|
||||
### Q: 如何处理异步请求?
|
||||
|
||||
A: 当前版本使用同步API,所有HTTP请求都是同步的。
|
||||
|
||||
### Q: 如何调试JavaScript代码?
|
||||
|
||||
A: 使用 `logger.debug()` 输出调试信息,查看应用日志。
|
||||
|
||||
## 示例脚本
|
||||
|
||||
参考 `parser/src/main/resources/custom-parsers/example-demo.js` 文件,包含完整的示例实现。
|
||||
|
||||
## 限制说明
|
||||
|
||||
1. **JavaScript版本**: 仅支持ES5.1语法(Nashorn引擎限制)
|
||||
2. **同步执行**: 所有HTTP请求都是同步的
|
||||
3. **内存限制**: 长时间运行可能存在内存泄漏风险
|
||||
4. **安全限制**: 无法访问文件系统或执行系统命令
|
||||
|
||||
## 更新日志
|
||||
|
||||
- v1.0.0: 初始版本,支持基本的JavaScript解析器功能
|
||||
@@ -29,19 +29,25 @@ public class ParserQuickStart {
|
||||
// .setShareLinkInfoPwd("1234") // 如有提取码可设置
|
||||
.createTool();
|
||||
|
||||
// 3) 异步 -> 同步等待,获取文件列表
|
||||
List<FileInfo> files = tool.parseFileList()
|
||||
.toCompletionStage().toCompletableFuture().join();
|
||||
// 3) 使用同步方法获取文件列表(推荐)
|
||||
List<FileInfo> files = tool.parseFileListSync();
|
||||
for (FileInfo f : files) {
|
||||
System.out.printf("%s\t%s\t%s\n",
|
||||
f.getFileName(), f.getSizeStr(), f.getParserUrl());
|
||||
}
|
||||
|
||||
// 4) 原始解析输出(不同盘实现差异较大,仅供调试)
|
||||
// 4) 使用同步方法获取原始解析输出(不同盘实现差异较大,仅供调试)
|
||||
String raw = tool.parseSync();
|
||||
System.out.println("raw: " + (raw == null ? "null" : raw.substring(0, Math.min(raw.length(), 200)) + "..."));
|
||||
|
||||
// 5) 使用同步方法根据文件ID获取下载链接(可选)
|
||||
if (!files.isEmpty()) {
|
||||
String fileId = files.get(0).getFileId();
|
||||
String downloadUrl = tool.parseByIdSync();
|
||||
System.out.println("文件下载链接: " + downloadUrl);
|
||||
}
|
||||
|
||||
// 5) 生成 parser 短链 path(可用于上层路由聚合显示)
|
||||
// 6) 生成 parser 短链 path(可用于上层路由聚合显示)
|
||||
String path = ParserCreate.fromShareUrl(shareUrl).genPathSuffix();
|
||||
System.out.println("path suffix: /" + path);
|
||||
|
||||
@@ -56,13 +62,17 @@ IPanTool tool = ParserCreate.fromType("lz") // 对应 PanDomainTemplate.LZ
|
||||
.shareKey("abcd12") // 必填:分享 key
|
||||
.setShareLinkInfoPwd("1234") // 可选:提取码
|
||||
.createTool();
|
||||
// 获取文件列表
|
||||
List<FileInfo> files = tool.parseFileList().toCompletionStage().toCompletableFuture().join();
|
||||
// 获取文件列表(使用同步方法)
|
||||
List<FileInfo> files = tool.parseFileListSync();
|
||||
```
|
||||
|
||||
要点:
|
||||
- 必须先 WebClientVertxInit.init(Vertx);若未显式初始化,内部将懒加载 Vertx.vertx(),建议显式注入以统一生命周期。
|
||||
- parseFileList 返回 Future<List<FileInfo>>,可直接 join/await;parseSync 仅针对 parse() 的 String 结果。
|
||||
- 支持三种同步方法:
|
||||
- `parseSync()`: 解析单个文件下载链接
|
||||
- `parseFileListSync()`: 解析文件列表
|
||||
- `parseByIdSync()`: 根据文件ID获取下载链接
|
||||
- 异步方法仍可用:parse()、parseFileList()、parseById() 返回 Future 对象
|
||||
- 生成短链 path:ParserCreate.genPathSuffix()(用于页面/服务端聚合)。
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user