20 KiB
JavaScript解析器扩展开发指南
概述
本指南介绍如何使用JavaScript编写自定义网盘解析器,支持通过JavaScript代码实现网盘解析逻辑,无需编写Java代码。
目录
快速开始
1. 创建JavaScript脚本
在 ./custom-parsers/ 目录下创建 .js 文件,使用以下模板:
// ==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();
}
2. 解析器加载路径
JavaScript解析器支持两种加载方式:
内置解析器(jar包内)
- 位置:jar包内的
custom-parsers/资源目录 - 特点:随jar包一起发布,无需额外配置
- 路径:
parser/src/main/resources/custom-parsers/
外部解析器(用户自定义)
- 默认位置:应用运行目录下的
./custom-parsers/文件夹 - 配置方式(优先级从高到低):
- 系统属性:
-Dparser.custom-parsers.path=/path/to/your/parsers - 环境变量:
PARSER_CUSTOM_PARSERS_PATH=/path/to/your/parsers - 默认路径:
./custom-parsers/(相对于应用运行目录)
- 系统属性:
配置示例
Maven项目中使用:
# 方式1:系统属性
mvn exec:java -Dexec.mainClass="your.MainClass" -Dparser.custom-parsers.path=./src/main/resources/custom-parsers
# 方式2:环境变量
export PARSER_CUSTOM_PARSERS_PATH=./src/main/resources/custom-parsers
mvn exec:java -Dexec.mainClass="your.MainClass"
jar包运行时:
# 方式1:系统属性
java -Dparser.custom-parsers.path=/path/to/your/parsers -jar your-app.jar
# 方式2:环境变量
export PARSER_CUSTOM_PARSERS_PATH=/path/to/your/parsers
java -jar your-app.jar
Docker部署:
# 挂载外部解析器目录
docker run -d -v /path/to/your/parsers:/app/custom-parsers your-image
# 或使用环境变量
docker run -d -e PARSER_CUSTOM_PARSERS_PATH=/app/custom-parsers your-image
3. 重启应用
重启应用后,JavaScript解析器会自动加载并注册。查看应用日志确认解析器是否成功加载。
元数据格式
必填字段
@name: 脚本名称@type: 解析器类型标识(唯一)@displayName: 显示名称@match: URL匹配正则(必须包含(?<KEY>...)命名捕获组)
可选字段
@description: 描述信息@author: 作者@version: 版本号
示例
// ==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对象
提供分享链接信息的访问接口:
// 获取分享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请求功能:
// GET请求
var response = http.get("https://api.example.com/data");
// GET请求并跟随重定向
var redirectResponse = http.getWithRedirect("https://api.example.com/redirect");
// GET请求但不跟随重定向(用于获取Location头)
var noRedirectResponse = http.getNoRedirect("https://api.example.com/redirect");
if (noRedirectResponse.statusCode() >= 300 && noRedirectResponse.statusCode() < 400) {
var location = noRedirectResponse.header("Location");
console.log("重定向到: " + location);
}
// 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");
// 批量设置请求头
http.putHeaders({
"User-Agent": "MyBot/1.0",
"Authorization": "Bearer token",
"Accept": "application/json"
});
// 删除指定请求头
http.removeHeader("Authorization");
// 清空所有请求头(保留默认头)
http.clearHeaders();
// 获取所有请求头
var allHeaders = http.getHeaders();
logger.debug("当前请求头: " + JSON.stringify(allHeaders));
// 设置请求超时时间(秒)
http.setTimeout(60); // 设置为60秒
// PUT请求
var putResponse = http.put("https://api.example.com/resource", {
key: "value"
});
// DELETE请求
var deleteResponse = http.delete("https://api.example.com/resource/123");
// PATCH请求
var patchResponse = http.patch("https://api.example.com/resource/123", {
key: "newValue"
});
// URL编码/解码(静态方法)
var encoded = JsHttpClient.urlEncode("hello world"); // "hello%20world"
var decoded = JsHttpClient.urlDecode("hello%20world"); // "hello world"
// 发送简单表单数据
var formResponse = http.sendForm({
username: "user",
password: "pass"
});
// 发送JSON数据
var jsonResponse = http.sendJson({
name: "test",
value: 123
});
JsHttpResponse对象
处理HTTP响应:
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);
}
// 获取响应体字节数组
var bytes = response.bodyBytes();
// 获取响应体大小
var size = response.bodySize();
logger.info("响应体大小: " + size + " 字节");
JsLogger对象
提供日志功能:
// 不同级别的日志
logger.debug("调试信息");
logger.info("一般信息");
logger.warn("警告信息");
logger.error("错误信息");
// 带参数的日志
logger.info("用户 {} 访问了 {}", username, url);
// 检查日志级别
if (logger.isDebugEnabled()) {
logger.debug("详细的调试信息");
}
重定向处理
当网盘服务返回302重定向时,可以使用getNoRedirect方法获取真实的下载链接:
/**
* 获取真实的下载链接(处理302重定向)
* @param {string} downloadUrl - 原始下载链接
* @param {JsHttpClient} http - HTTP客户端
* @param {JsLogger} logger - 日志记录器
* @returns {string} 真实的下载链接
*/
function getRealDownloadUrl(downloadUrl, http, logger) {
try {
logger.info("获取真实下载链接: " + downloadUrl);
// 使用不跟随重定向的方法获取Location头
var headResponse = http.getNoRedirect(downloadUrl);
if (headResponse.statusCode() >= 300 && headResponse.statusCode() < 400) {
// 处理重定向
var location = headResponse.header("Location");
if (location) {
logger.info("获取到重定向链接: " + location);
return location;
}
}
// 如果没有重定向或无法获取Location,返回原链接
logger.debug("下载链接无需重定向或无法获取重定向信息");
return downloadUrl;
} catch (e) {
logger.error("获取真实下载链接失败: " + e.message);
// 如果获取失败,返回原链接
return downloadUrl;
}
}
// 在parse方法中使用
function parse(shareLinkInfo, http, logger) {
// ... 获取原始下载链接的代码 ...
var originalUrl = "https://example.com/download?id=123";
// 获取真实的下载链接
var realUrl = getRealDownloadUrl(originalUrl, http, logger);
return realUrl;
}
代理支持
JavaScript解析器支持HTTP代理配置,代理信息通过ShareLinkInfo的otherParam传递:
function parse(shareLinkInfo, http, logger) {
// 检查是否有代理配置
var proxyConfig = shareLinkInfo.getOtherParam("proxy");
if (proxyConfig) {
logger.info("使用代理: " + proxyConfig.host + ":" + proxyConfig.port);
}
// HTTP客户端会自动使用代理配置
var response = http.get("https://api.example.com/data");
return response.body();
}
代理配置格式:
{
"type": "HTTP", // 代理类型: HTTP, SOCKS4, SOCKS5
"host": "proxy.example.com",
"port": 8080,
"username": "user", // 可选,代理认证用户名
"password": "pass" // 可选,代理认证密码
}
实现方法
JavaScript解析器支持三种方法,对应Java接口的三种同步方法:
parse方法(必填)
解析单个文件的下载链接,对应Java的 parseSync() 方法:
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() 方法:
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() 方法:
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获取下载链接 |
使用示例
// 在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方法中添加适当的错误处理和日志记录
函数定义方式
JavaScript解析器使用全局函数定义,不需要exports对象:
/**
* 解析单个文件下载链接(必填)
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息
* @param {JsHttpClient} http - HTTP客户端
* @param {JsLogger} logger - 日志对象
* @returns {string} 下载链接
*/
function parse(shareLinkInfo, http, logger) {
// 实现解析逻辑
return "https://example.com/download";
}
/**
* 解析文件列表(可选)
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息
* @param {JsHttpClient} http - HTTP客户端
* @param {JsLogger} logger - 日志对象
* @returns {Array} 文件信息数组
*/
function parseFileList(shareLinkInfo, http, logger) {
// 实现文件列表解析逻辑
return [];
}
/**
* 根据文件ID获取下载链接(可选)
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息
* @param {JsHttpClient} http - HTTP客户端
* @param {JsLogger} logger - 日志对象
* @returns {string} 下载链接
*/
function parseById(shareLinkInfo, http, logger) {
// 实现按ID解析逻辑
return "https://example.com/download";
}
注意:JavaScript解析器通过engine.eval()执行,函数必须定义为全局函数,不需要使用exports或module.exports。
VSCode配置
1. 安装JavaScript扩展
安装 "JavaScript (ES6) code snippets" 扩展。
2. 配置jsconfig.json
在 custom-parsers 目录下创建 jsconfig.json:
{
"compilerOptions": {
"checkJs": true,
"target": "ES5",
"lib": ["ES5"],
"allowJs": true,
"noEmit": true
},
"include": ["*.js", "types.d.ts"],
"exclude": ["node_modules"]
}
3. 使用类型提示
// 引用类型定义
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. 使用日志
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. 错误处理
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. 启用调试模式
设置系统属性启用详细日志:
-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() 输出调试信息,查看应用日志。
Q: 如何批量设置请求头?
A: 使用 http.putHeaders() 方法批量设置多个请求头:
// 批量设置请求头
http.putHeaders({
"User-Agent": "Mozilla/5.0...",
"Accept": "application/json",
"Authorization": "Bearer token",
"Referer": "https://example.com"
});
Q: 如何清空所有请求头?
A: 使用 http.clearHeaders() 方法清空所有请求头(会保留默认头):
// 清空所有请求头,保留默认头(Accept-Encoding、User-Agent、Accept-Language)
http.clearHeaders();
Q: 如何设置请求超时时间?
A: 使用 http.setTimeout() 方法设置超时时间(秒):
// 设置超时时间为60秒
http.setTimeout(60);
var response = http.get("https://api.example.com/data");
示例脚本
参考以下示例文件,包含完整的解析器实现:
parser/src/main/resources/custom-parsers/example-demo.js- 完整的演示解析器,展示所有功能parser/src/main/resources/custom-parsers/baidu-photo.js- 百度相册解析器示例parser/src/main/resources/custom-parsers/migu-music.js- 咪咕音乐解析器示例parser/src/main/resources/custom-parsers/qishui-music.js- 汽水音乐解析器示例
这些示例展示了:
- 元数据配置
- 三个核心方法的实现(parse、parseFileList、parseById)
- 错误处理和日志记录
- 文件信息构建
- 重定向处理
- 代理支持
- Header管理(批量设置、清空等)
限制说明
- JavaScript版本: 仅支持ES5.1语法(Nashorn引擎限制)
- 同步执行: 所有HTTP请求都是同步的
- 内存限制: 长时间运行可能存在内存泄漏风险
- 安全限制: 无法访问文件系统或执行系统命令
相关文档
- 自定义解析器扩展指南 - Java自定义解析器扩展
- 自定义解析器快速开始 - 快速上手指南
- 解析器开发文档 - 解析器开发约定和规范
更新日志
- v1.0.0: 初始版本,支持基本的JavaScript解析器功能
- 支持外部解析器路径配置(系统属性、环境变量)
- 支持重定向处理(getNoRedirect、getWithRedirect)
- 支持代理配置(HTTP/SOCKS4/SOCKS5)
- v1.1.0: 增强HTTP客户端功能
- 新增header管理方法:clearHeaders、removeHeader、putHeaders、getHeaders
- 新增HTTP请求方法:PUT、DELETE、PATCH
- 新增工具方法:URL编码/解码(urlEncode、urlDecode)
- 新增超时时间设置:setTimeout
- 响应对象增强:bodyBytes、bodySize