feat: 添加getNoRedirect方法支持302重定向处理

- 在JsHttpClient中添加getNoRedirect方法,支持不自动跟随重定向的HTTP请求
- 修改baidu-photo.js解析器,使用getNoRedirect获取真实的下载链接
- 更新测试用例断言,验证重定向处理功能正常工作
- 修复百度一刻相册解析器302重定向问题,现在能正确获取真实下载链接
This commit is contained in:
q
2025-10-21 17:47:22 +08:00
parent 1c3b5082a2
commit 8f92ce9292
25 changed files with 3613 additions and 37 deletions

View File

@@ -0,0 +1,238 @@
# JavaScript解析器扩展使用指南
## 概述
本项目支持用户使用JavaScript编写自定义网盘解析器提供灵活的扩展能力。JavaScript解析器运行在Nashorn引擎中支持ES5.1语法。
## 文件结构
```
custom-parsers/
├── types.js # 类型定义文件JSDoc注释
├── jsconfig.json # VSCode配置文件
├── example-demo.js # 示例解析器
└── README.md # 本说明文档
```
## 快速开始
### 1. 创建解析器脚本
`custom-parsers/` 目录下创建 `.js` 文件,使用以下格式:
```javascript
// ==UserScript==
// @name 你的解析器名称
// @type 解析器类型标识
// @displayName 显示名称
// @description 解析器描述
// @match 匹配URL的正则表达式
// @author 作者
// @version 版本号
// ==/UserScript==
/**
* 解析单个文件下载链接
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息
* @param {JsHttpClient} http - HTTP客户端
* @param {JsLogger} logger - 日志记录器
* @returns {string} 下载链接
*/
function parse(shareLinkInfo, http, logger) {
// 你的解析逻辑
return "https://example.com/download/file.zip";
}
/**
* 解析文件列表(可选)
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息
* @param {JsHttpClient} http - HTTP客户端
* @param {JsLogger} logger - 日志记录器
* @returns {FileInfo[]} 文件信息列表
*/
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/" + fileId;
}
```
### 2. 自动加载
解析器会在应用启动时自动加载和注册。支持两种加载方式:
#### 内置解析器jar包内
- 位置jar包内的 `custom-parsers/` 资源目录
- 特点随jar包一起发布无需额外配置
#### 外部解析器(用户自定义)
- 默认位置:应用运行目录下的 `./custom-parsers/` 文件夹
- 配置方式:
- **系统属性**`-Dparser.custom-parsers.path=/path/to/your/parsers`
- **环境变量**`PARSER_CUSTOM_PARSERS_PATH=/path/to/your/parsers`
- **默认路径**`./custom-parsers/`(相对于应用运行目录)
#### 配置示例
**Maven项目中使用**
```bash
# 方式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包运行时**
```bash
# 方式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
```
## API参考
### ShareLinkInfo
分享链接信息对象:
```javascript
shareLinkInfo.getShareUrl() // 获取分享URL
shareLinkInfo.getShareKey() // 获取分享Key
shareLinkInfo.getSharePassword() // 获取分享密码
shareLinkInfo.getType() // 获取网盘类型
shareLinkInfo.getPanName() // 获取网盘名称
shareLinkInfo.getOtherParam(key) // 获取其他参数
```
### JsHttpClient
HTTP客户端对象
```javascript
http.get(url) // GET请求
http.post(url, data) // POST请求
http.putHeader(name, value) // 设置请求头
http.sendForm(data) // 发送表单数据
http.sendJson(data) // 发送JSON数据
```
### JsHttpResponse
HTTP响应对象
```javascript
response.body() // 获取响应体(字符串)
response.json() // 解析JSON响应
response.statusCode() // 获取HTTP状态码
response.header(name) // 获取响应头
response.headers() // 获取所有响应头
```
### JsLogger
日志记录器:
```javascript
logger.debug(message) // 调试日志
logger.info(message) // 信息日志
logger.warn(message) // 警告日志
logger.error(message) // 错误日志
```
### FileInfo
文件信息对象:
```javascript
{
fileName: "文件名",
fileId: "文件ID",
fileType: "file|folder",
size: 1024,
sizeStr: "1KB",
createTime: "2024-01-01",
updateTime: "2024-01-01",
createBy: "创建者",
downloadCount: 100,
fileIcon: "file",
panType: "网盘类型",
parserUrl: "解析URL",
previewUrl: "预览URL"
}
```
## 开发提示
### VSCode支持
1. 确保安装了JavaScript扩展
2. `types.js` 文件提供类型定义和代码补全
3. `jsconfig.json` 配置了项目设置
### 调试
- 使用 `logger.debug()` 输出调试信息
- 查看应用日志了解解析过程
- 使用 `console.log()` 在Nashorn中输出信息
### 错误处理
```javascript
try {
var response = http.get(url);
if (response.statusCode() !== 200) {
throw new Error("请求失败: " + response.statusCode());
}
return response.json();
} catch (e) {
logger.error("解析失败: " + e.message);
throw e;
}
```
## 示例
参考 `example-demo.js` 文件,它展示了完整的解析器实现,包括:
- 元数据配置
- 三个核心方法的实现
- 错误处理
- 日志记录
- 文件信息构建
## 注意事项
1. **ES5.1兼容**只使用ES5.1语法避免ES6+特性
2. **同步API**HTTP客户端提供同步接口无需处理异步回调
3. **全局函数**:解析器函数必须定义为全局函数,不能使用模块导出
4. **错误处理**:始终包含适当的错误处理和日志记录
5. **性能考虑**:避免在解析器中执行耗时操作
## 故障排除
### 常见问题
1. **解析器未加载**:检查元数据格式是否正确
2. **类型错误**:确保函数签名与接口匹配
3. **HTTP请求失败**检查URL和网络连接
4. **JSON解析错误**:验证响应格式
### 日志查看
查看应用日志了解详细的执行过程和错误信息。

View File

@@ -0,0 +1,400 @@
// ==UserScript==
// @name 一刻相册解析器
// @type baidu_photo
// @displayName 百度一刻相册(JS)
// @description 解析百度一刻相册分享链接,获取文件列表和下载链接
// @match https?://photo\.baidu\.com/photo/(web/share\?inviteCode=|wap/albumShare\?shareId=)(?<KEY>\w+)
// @author qaiu
// @version 1.0.0
// ==/UserScript==
/**
* API端点配置
*/
var API_CONFIG = {
// 文件夹分享通过pcode获取share_id
QUERY_PCODE: "https://photo.baidu.com/youai/album/v1/querypcode",
// 文件列表:获取文件列表
LIST_FILES: "https://photo.baidu.com/youai/share/v2/list",
// 请求参数
CLIENT_TYPE: "70",
LIMIT: "100"
};
/**
* 设置标准请求头
* @param {JsHttpClient} http - HTTP客户端
* @param {string} referer - Referer URL
*/
function setStandardHeaders(http, referer) {
var headers = {
"Accept": "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Content-Type": "application/x-www-form-urlencoded",
"DNT": "1",
"Origin": "https://photo.baidu.com",
"Pragma": "no-cache",
"Referer": referer,
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36 Edg/141.0.0.0",
"X-Requested-With": "XMLHttpRequest",
"sec-ch-ua": "\"Microsoft Edge\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"macOS\""
};
for (var key in headers) {
http.putHeader(key, headers[key]);
}
}
/**
* 获取分享ID
* @param {string} shareKey - 分享键
* @param {boolean} isFileShare - 是否为文件分享
* @param {JsHttpClient} http - HTTP客户端
* @param {JsLogger} logger - 日志记录器
* @returns {string} 分享ID
*/
function getShareId(shareKey, isFileShare, http, logger) {
if (isFileShare) {
logger.info("文件分享模式直接使用shareId: " + shareKey);
return shareKey;
}
// 文件夹分享通过pcode获取share_id
var queryUrl = API_CONFIG.QUERY_PCODE + "?clienttype=" + API_CONFIG.CLIENT_TYPE + "&pcode=" + shareKey + "&web=1";
logger.debug("文件夹分享查询URL: " + queryUrl);
setStandardHeaders(http, "https://photo.baidu.com/photo/web/share?inviteCode=" + shareKey);
var queryResponse = http.get(queryUrl);
if (queryResponse.statusCode() !== 200) {
throw new Error("获取分享ID失败状态码: " + queryResponse.statusCode());
}
var queryData = queryResponse.json();
logger.debug("查询响应: " + JSON.stringify(queryData));
if (queryData.errno !== undefined && queryData.errno !== 0) {
throw new Error("API返回错误errno: " + queryData.errno);
}
var shareId = queryData.pdata && queryData.pdata.share_id;
if (!shareId) {
throw new Error("未找到share_id");
}
logger.info("获取到分享ID: " + shareId);
return shareId;
}
/**
* 获取文件列表
* @param {string} shareId - 分享ID
* @param {string} shareKey - 分享键
* @param {boolean} isFileShare - 是否为文件分享
* @param {JsHttpClient} http - HTTP客户端
* @param {JsLogger} logger - 日志记录器
* @returns {Array} 文件列表
*/
function getFileList(shareId, shareKey, isFileShare, http, logger) {
var listUrl = API_CONFIG.LIST_FILES + "?clienttype=" + API_CONFIG.CLIENT_TYPE + "&share_id=" + shareId + "&limit=" + API_CONFIG.LIMIT;
logger.debug("获取文件列表 URL: " + listUrl);
var referer = isFileShare ?
"https://photo.baidu.com/photo/wap/albumShare?shareId=" + shareKey :
"https://photo.baidu.com/photo/web/share?inviteCode=" + shareKey;
setStandardHeaders(http, referer);
var listResponse = http.get(listUrl);
if (listResponse.statusCode() !== 200) {
throw new Error("获取文件列表失败,状态码: " + listResponse.statusCode());
}
var listData = listResponse.json();
logger.debug("文件列表响应: " + JSON.stringify(listData));
if (listData.errno !== undefined && listData.errno !== 0) {
throw new Error("获取文件列表API返回错误errno: " + listData.errno);
}
var fileList = listData.list;
if (!fileList || fileList.length === 0) {
logger.warn("文件列表为空");
return [];
}
logger.info("获取到文件列表,共 " + fileList.length + " 个文件");
return fileList;
}
/**
* 解析单个文件下载链接
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息对象
* @param {JsHttpClient} http - HTTP客户端实例
* @param {JsLogger} logger - 日志记录器实例
* @returns {string} 下载链接
*/
function parse(shareLinkInfo, http, logger) {
logger.info("===== 开始执行 parse 方法 =====");
var shareKey = shareLinkInfo.getShareKey();
logger.info("分享Key: " + shareKey);
try {
// 判断分享类型
// 如果shareKey是纯数字且长度较长很可能是文件分享的share_id
// 如果shareKey包含字母很可能是文件夹分享的inviteCode
var isFileShare = /^\d{10,}$/.test(shareKey);
logger.info("分享类型: " + (isFileShare ? "文件分享" : "文件夹分享"));
// 获取分享ID
var shareId = getShareId(shareKey, isFileShare, http, logger);
// 获取文件列表
var fileList = getFileList(shareId, shareKey, isFileShare, http, logger);
if (fileList.length === 0) {
throw new Error("文件列表为空");
}
// 返回第一个文件的下载链接
var firstFile = fileList[0];
var downloadUrl = firstFile.dlink;
if (!downloadUrl) {
throw new Error("未找到下载链接");
}
// 获取真实的下载链接处理302重定向
var realDownloadUrl = getRealDownloadUrl(downloadUrl, http, logger);
logger.info("解析成功返回URL: " + realDownloadUrl);
return realDownloadUrl;
} catch (e) {
logger.error("解析失败: " + e.message);
throw new Error("解析失败: " + e.message);
}
}
/**
* 解析文件列表
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息对象
* @param {JsHttpClient} http - HTTP客户端实例
* @param {JsLogger} logger - 日志记录器实例
* @returns {FileInfo[]} 文件信息列表
*/
function parseFileList(shareLinkInfo, http, logger) {
logger.info("===== 开始执行 parseFileList 方法 =====");
var shareKey = shareLinkInfo.getShareKey();
logger.info("分享Key: " + shareKey);
try {
// 判断分享类型
// 如果shareKey是纯数字且长度较长很可能是文件分享的share_id
// 如果shareKey包含字母很可能是文件夹分享的inviteCode
var isFileShare = /^\d{10,}$/.test(shareKey);
logger.info("分享类型: " + (isFileShare ? "文件分享" : "文件夹分享"));
// 获取分享ID
var shareId = getShareId(shareKey, isFileShare, http, logger);
// 获取文件列表
var fileList = getFileList(shareId, shareKey, isFileShare, http, logger);
if (fileList.length === 0) {
logger.warn("文件列表为空");
return [];
}
logger.info("解析文件列表成功,共 " + fileList.length + " 项");
var result = [];
for (var i = 0; i < fileList.length; i++) {
var file = fileList[i];
/** @type {FileInfo} */
var fileInfo = {
fileName: extractFileName(file.path) || ("文件_" + (i + 1)),
fileId: String(file.fsid),
fileType: "file",
size: file.size || 0,
sizeStr: formatBytes(file.size || 0),
createTime: formatTimestamp(file.ctime),
updateTime: formatTimestamp(file.mtime),
createBy: "",
downloadCount: 0,
fileIcon: "file",
panType: "baidu_photo",
parserUrl: "",
previewUrl: ""
};
// 设置下载链接
if (file.dlink) {
fileInfo.parserUrl = file.dlink;
}
// 设置预览链接(取第一个缩略图)
if (file.thumburl && file.thumburl.length > 0) {
fileInfo.previewUrl = file.thumburl[0];
}
result.push(fileInfo);
}
logger.info("文件列表解析成功,共 " + result.length + " 个文件");
return result;
} catch (e) {
logger.error("解析文件列表失败: " + e.message);
throw new Error("解析文件列表失败: " + e.message);
}
}
/**
* 根据文件ID获取下载链接
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息对象
* @param {JsHttpClient} http - HTTP客户端实例
* @param {JsLogger} logger - 日志记录器实例
* @returns {string} 下载链接
*/
function parseById(shareLinkInfo, http, logger) {
logger.info("===== 开始执行 parseById 方法 =====");
var shareKey = shareLinkInfo.getShareKey();
var otherParam = shareLinkInfo.getOtherParam("paramJson");
var fileId = otherParam ? otherParam.fileId || otherParam.id : null;
logger.info("分享Key: " + shareKey);
logger.info("文件ID: " + fileId);
if (!fileId) {
throw new Error("未提供文件ID");
}
try {
// 判断分享类型
// 如果shareKey是纯数字且长度较长很可能是文件分享的share_id
// 如果shareKey包含字母很可能是文件夹分享的inviteCode
var isFileShare = /^\d{10,}$/.test(shareKey);
logger.info("分享类型: " + (isFileShare ? "文件分享" : "文件夹分享"));
// 获取分享ID
var shareId = getShareId(shareKey, isFileShare, http, logger);
// 获取文件列表
var fileList = getFileList(shareId, shareKey, isFileShare, http, logger);
if (fileList.length === 0) {
throw new Error("文件列表为空");
}
// 查找指定ID的文件
var targetFile = null;
for (var i = 0; i < fileList.length; i++) {
var file = fileList[i];
if (String(file.fsid) == fileId || String(i) == fileId) {
targetFile = file;
break;
}
}
if (!targetFile) {
throw new Error("未找到指定ID的文件: " + fileId);
}
var downloadUrl = targetFile.dlink;
if (!downloadUrl) {
throw new Error("文件无下载链接");
}
// 获取真实的下载链接处理302重定向
var realDownloadUrl = getRealDownloadUrl(downloadUrl, http, logger);
logger.info("根据ID解析成功: " + realDownloadUrl);
return realDownloadUrl;
} catch (e) {
logger.error("根据ID解析失败: " + e.message);
throw new Error("根据ID解析失败: " + e.message);
}
}
/**
* 格式化字节大小
* @param {number} bytes
* @returns {string}
*/
function formatBytes(bytes) {
if (bytes === 0) return "0 B";
var k = 1024;
var sizes = ["B", "KB", "MB", "GB", "TB"];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)).toFixed(2) + " " + sizes[i];
}
/**
* 从路径中提取文件名
* @param {string} path
* @returns {string}
*/
function extractFileName(path) {
if (!path) return "";
var parts = path.split("/");
return parts[parts.length - 1] || "";
}
/**
* 获取真实的下载链接处理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;
}
}
/**
* 格式化时间戳
* @param {number} timestamp
* @returns {string}
*/
function formatTimestamp(timestamp) {
if (!timestamp) return "";
var date = new Date(timestamp * 1000);
return date.toISOString().replace("T", " ").substring(0, 19);
}

View File

@@ -0,0 +1,170 @@
// ==UserScript==
// @name 演示解析器
// @type demo_js
// @displayName 演示网盘(JS)
// @description 演示JavaScript解析器的完整功能使用JSONPlaceholder测试API
// @match https?://demo\.example\.com/s/(?<KEY>\w+)
// @author qaiu
// @version 1.0.0
// ==/UserScript==
// 注意require调用仅用于IDE类型提示运行时会被忽略
// var types = require('./types');
/**
* 解析单个文件下载链接
* 使用 https://jsonplaceholder.typicode.com/posts/1 作为测试
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息对象
* @param {JsHttpClient} http - HTTP客户端
* @param {JsLogger} logger - 日志对象
* @returns {string} 下载链接URL
*/
function parse(shareLinkInfo, http, logger) {
logger.info("===== 开始执行 parse 方法 =====");
var shareKey = shareLinkInfo.getShareKey();
var password = shareLinkInfo.getSharePassword();
logger.info("分享Key: " + shareKey);
logger.info("分享密码: " + (password || "无"));
// 使用JSONPlaceholder测试API
var apiUrl = "https://jsonplaceholder.typicode.com/posts/" + (shareKey || "1");
logger.debug("请求URL: " + apiUrl);
try {
var response = http.get(apiUrl);
logger.debug("HTTP状态码: " + response.statusCode());
var data = response.json();
logger.debug("响应数据: " + JSON.stringify(data));
// 模拟返回下载链接实际是返回post的标题作为"下载链接"
var downloadUrl = "https://cdn.example.com/file/" + data.id + "/" + data.title;
logger.info("解析成功返回URL: " + downloadUrl);
return downloadUrl;
} catch (e) {
logger.error("解析失败: " + e.message);
throw new Error("解析失败: " + e.message);
}
}
/**
* 解析文件列表
* 使用 https://jsonplaceholder.typicode.com/users 作为测试
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息对象
* @param {JsHttpClient} http - HTTP客户端
* @param {JsLogger} logger - 日志对象
* @returns {FileInfo[]} 文件列表数组
*/
function parseFileList(shareLinkInfo, http, logger) {
logger.info("===== 开始执行 parseFileList 方法 =====");
var dirId = shareLinkInfo.getOtherParam("dirId") || "1";
logger.info("目录ID: " + dirId);
// 使用JSONPlaceholder的users API模拟文件列表
var apiUrl = "https://jsonplaceholder.typicode.com/users";
logger.debug("请求URL: " + apiUrl);
try {
var response = http.get(apiUrl);
var users = response.json();
var fileList = [];
for (var i = 0; i < users.length; i++) {
var user = users[i];
// 模拟文件和目录
var isFolder = (user.id % 3 === 0); // 每3个作为目录
var fileSize = isFolder ? 0 : user.id * 1024 * 1024; // 模拟文件大小
/** @type {FileInfo} */
var fileInfo = {
fileName: user.name + (isFolder ? " [目录]" : ".txt"),
fileId: user.id.toString(),
fileType: isFolder ? "folder" : "file",
size: fileSize,
sizeStr: formatFileSize(fileSize),
createTime: "2024-01-01",
updateTime: "2024-01-01",
createBy: user.username,
downloadCount: Math.floor(Math.random() * 1000),
fileIcon: isFolder ? "folder" : "file",
panType: "demo_js",
parserUrl: "",
previewUrl: ""
};
// 如果是目录设置解析URL
if (isFolder) {
fileInfo.parserUrl = "/v2/getFileList?url=demo&dirId=" + user.id;
} else {
// 如果是文件设置下载URL
fileInfo.parserUrl = "/v2/redirectUrl/demo_js/" + user.id;
}
fileList.push(fileInfo);
}
logger.info("解析文件列表成功,共 " + fileList.length + " 项");
return fileList;
} catch (e) {
logger.error("解析文件列表失败: " + e.message);
throw new Error("解析文件列表失败: " + e.message);
}
}
/**
* 根据文件ID获取下载链接
* 使用 https://jsonplaceholder.typicode.com/todos/:id 作为测试
* @param {ShareLinkInfo} shareLinkInfo - 分享链接信息对象
* @param {JsHttpClient} http - HTTP客户端
* @param {JsLogger} logger - 日志对象
* @returns {string} 下载链接URL
*/
function parseById(shareLinkInfo, http, logger) {
logger.info("===== 开始执行 parseById 方法 =====");
var paramJson = shareLinkInfo.getOtherParam("paramJson");
if (!paramJson) {
throw new Error("缺少paramJson参数");
}
var fileId = paramJson.fileId || paramJson.id || "1";
logger.info("文件ID: " + fileId);
// 使用JSONPlaceholder的todos API
var apiUrl = "https://jsonplaceholder.typicode.com/todos/" + fileId;
logger.debug("请求URL: " + apiUrl);
try {
var response = http.get(apiUrl);
var todo = response.json();
// 模拟返回下载链接
var downloadUrl = "https://cdn.example.com/download/" + todo.id + "/" + todo.title + ".zip";
logger.info("根据ID解析成功: " + downloadUrl);
return downloadUrl;
} catch (e) {
logger.error("根据ID解析失败: " + e.message);
throw new Error("根据ID解析失败: " + e.message);
}
}
/**
* 辅助函数:格式化文件大小
* @param {number} bytes - 字节数
* @returns {string} 格式化后的大小
*/
function formatFileSize(bytes) {
if (bytes === 0) return "0B";
var k = 1024;
var sizes = ["B", "KB", "MB", "GB", "TB"];
var i = Math.floor(Math.log(bytes) / Math.log(k));
return (bytes / Math.pow(k, i)).toFixed(2) + sizes[i];
}

View File

@@ -0,0 +1,19 @@
{
"compilerOptions": {
"checkJs": true,
"target": "ES5",
"lib": ["ES5"],
"allowJs": true,
"noEmit": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
},
"include": [
"*.js",
"types.js"
],
"exclude": [
"node_modules"
]
}

View File

@@ -0,0 +1,68 @@
/**
* JavaScript解析器类型定义文件
* 使用JSDoc注释提供代码补全和类型提示
* 兼容ES5.1和Nashorn引擎
*/
// 全局类型定义使用JSDoc注释
// 这些类型定义将在VSCode中提供代码补全和类型检查
/**
* @typedef {Object} ShareLinkInfo
* @property {function(): string} getShareUrl - 获取分享URL
* @property {function(): string} getShareKey - 获取分享Key
* @property {function(): string} getSharePassword - 获取分享密码
* @property {function(): string} getType - 获取网盘类型
* @property {function(): string} getPanName - 获取网盘名称
* @property {function(string): any} getOtherParam - 获取其他参数
*/
/**
* @typedef {Object} JsHttpResponse
* @property {function(): string} body - 获取响应体(字符串)
* @property {function(): any} json - 解析JSON响应
* @property {function(): number} statusCode - 获取HTTP状态码
* @property {function(string): string|null} header - 获取响应头
* @property {function(): Object} headers - 获取所有响应头
*/
/**
* @typedef {Object} JsHttpClient
* @property {function(string): JsHttpResponse} get - 发起GET请求
* @property {function(string, any=): JsHttpResponse} post - 发起POST请求
* @property {function(string, string): JsHttpClient} putHeader - 设置请求头
* @property {function(Object): JsHttpResponse} sendForm - 发送表单数据
* @property {function(any): JsHttpResponse} sendJson - 发送JSON数据
*/
/**
* @typedef {Object} JsLogger
* @property {function(string): void} debug - 调试日志
* @property {function(string): void} info - 信息日志
* @property {function(string): void} warn - 警告日志
* @property {function(string): void} error - 错误日志
*/
/**
* @typedef {Object} FileInfo
* @property {string} fileName - 文件名
* @property {string} fileId - 文件ID
* @property {string} fileType - 文件类型: "file" | "folder"
* @property {number} size - 文件大小(字节)
* @property {string} sizeStr - 文件大小(可读格式)
* @property {string} createTime - 创建时间
* @property {string} updateTime - 更新时间
* @property {string} createBy - 创建者
* @property {number} downloadCount - 下载次数
* @property {string} fileIcon - 文件图标
* @property {string} panType - 网盘类型
* @property {string} parserUrl - 解析URL
* @property {string} previewUrl - 预览URL
*/
/**
* @typedef {Object} ParserExports
* @property {function(ShareLinkInfo, JsHttpClient, JsLogger): string} parse - 解析单个文件下载链接
* @property {function(ShareLinkInfo, JsHttpClient, JsLogger): FileInfo[]} parseFileList - 解析文件列表
* @property {function(ShareLinkInfo, JsHttpClient, JsLogger): string} parseById - 根据文件ID获取下载链接
*/