koishi-plugin-baidu-search 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/lib/index.d.ts +12 -0
- package/lib/index.js +1020 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# koishi-plugin-baidu-search
|
|
2
|
+
|
|
3
|
+
电影电视剧百度网盘资源搜索插件。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 🔍 **自定义搜索API**:已配置两个api
|
|
8
|
+
- 🔄 **备用API自动切换**:主API失败时自动使用备用API
|
|
9
|
+
- ✅ **链接有效性验证**:自动过滤失效的分享链接
|
|
10
|
+
- 📄 **分页浏览**:支持上一页/下一页浏览搜索结果
|
|
11
|
+
- 🔁 **自动重试**:网络异常时自动重试,提高成功率
|
|
12
|
+
- 📊 **详细日志**:记录所有操作,方便调试
|
|
13
|
+
|
|
14
|
+
## 安装
|
|
15
|
+
|
|
16
|
+
### 方法一:本地插件(推荐)
|
|
17
|
+
|
|
18
|
+
1. 将 `baidu-search` 文件夹放到 Koishi 的 `external` 目录
|
|
19
|
+
2. 在 Koishi 根目录执行 `yarn install`
|
|
20
|
+
3. 在控制台启用插件
|
|
21
|
+
|
|
22
|
+
### 方法二:npm 安装
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
yarn add koishi-plugin-baidu-search
|
|
26
|
+
# 或
|
|
27
|
+
npm install koishi-plugin-baidu-search
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 配置说明
|
|
31
|
+
|
|
32
|
+
都已在代码里配置好,安装即用
|
|
33
|
+
|
|
34
|
+
| 配置项 | 默认值 | 说明 |
|
|
35
|
+
|--------|--------|------|
|
|
36
|
+
| watchGroups | `[]` | 监控的群号列表,留空监控所有群 |
|
|
37
|
+
| limit | `10` | 每次搜索返回的最大结果数 |
|
|
38
|
+
| sessionTimeout | `120` | 搜索会话超时时间(秒) |
|
|
39
|
+
| saveTimeout | `180` | 转存超时时间(秒) |
|
|
40
|
+
|
|
41
|
+
## 使用方法
|
|
42
|
+
|
|
43
|
+
### 基本搜索
|
|
44
|
+
|
|
45
|
+
在群聊中发送:
|
|
46
|
+
```
|
|
47
|
+
百度 流浪地球
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
插件会返回搜索结果列表,回复数字编号即可转存。
|
|
51
|
+
|
|
52
|
+
### 翻页浏览
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
下一页
|
|
56
|
+
上一页
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## 工作流程
|
|
60
|
+
|
|
61
|
+
1. 用户发送搜索指令
|
|
62
|
+
2. 调用主搜索API(失败则尝试备用API)
|
|
63
|
+
3. 验证链接有效性,过滤失效链接
|
|
64
|
+
4. 返回有效结果列表
|
|
65
|
+
5. 用户选择编号
|
|
66
|
+
6. 转存到百度网盘 `/机器人` 目录
|
|
67
|
+
7. 创建新分享链接(提取码:6666)
|
|
68
|
+
8. 发送链接给用户
|
|
69
|
+
|
|
70
|
+
## 日志说明
|
|
71
|
+
|
|
72
|
+
插件会记录详细的操作日志:
|
|
73
|
+
|
|
74
|
+
- `[INFO]` 正常操作信息
|
|
75
|
+
- `[WARN]` 警告信息(如重试)
|
|
76
|
+
- `[ERROR]` 错误信息
|
|
77
|
+
- `[DEBUG]` 调试信息(需开启调试模式)
|
|
78
|
+
|
|
79
|
+
查看日志可以了解:
|
|
80
|
+
- 搜索到的资源及链接
|
|
81
|
+
- 链接验证结果
|
|
82
|
+
- 转存过程
|
|
83
|
+
|
|
84
|
+
## 开发
|
|
85
|
+
|
|
86
|
+
### 编译
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
yarn build
|
|
90
|
+
# 或
|
|
91
|
+
npm run build
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 打包
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm pack
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 调试
|
|
101
|
+
|
|
102
|
+
在 Koishi 配置中启用调试模式,可以看到更详细的日志。
|
|
103
|
+
|
|
104
|
+
## 许可证
|
|
105
|
+
|
|
106
|
+
MIT
|
|
107
|
+
|
|
108
|
+
## 更新日志
|
|
109
|
+
|
|
110
|
+
### v1.0.0
|
|
111
|
+
- 初始版本
|
|
112
|
+
- 支持百度网盘资源搜索和转存
|
|
113
|
+
- 自动链接验证
|
|
114
|
+
- 分页浏览功能
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const name = "baidu-search";
|
|
3
|
+
export interface Config {
|
|
4
|
+
watchGroups: string[];
|
|
5
|
+
limit: number;
|
|
6
|
+
sessionTimeout: number;
|
|
7
|
+
saveTimeout: number;
|
|
8
|
+
adminIds?: string[];
|
|
9
|
+
enableGroupDirectTransfer?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const Config: Schema<Config>;
|
|
12
|
+
export declare function apply(ctx: Context, config: Config): void;
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,1020 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Config = exports.name = void 0;
|
|
4
|
+
exports.apply = apply;
|
|
5
|
+
const koishi_1 = require("koishi");
|
|
6
|
+
exports.name = 'baidu-search';
|
|
7
|
+
// ==================== 内置配置(管理员可在代码中修改) ====================
|
|
8
|
+
const INTERNAL_CONFIG = {
|
|
9
|
+
// 搜索 API 配置(与 quark-search 保持一致,仅 cloud_types 改为 baidu)
|
|
10
|
+
searchApiUrl: 'https://wzapi.com/api/jhsj',
|
|
11
|
+
searchApiMethod: 'GET',
|
|
12
|
+
searchApiParams: '{"kw": "{keyword}", "cloud_types": "baidu"}',
|
|
13
|
+
searchApiHeaders: '{"User-Agent": "Mozilla/5.0"}',
|
|
14
|
+
searchResultPath: 'data.merged_by_type.baidu',
|
|
15
|
+
searchTitleField: 'note',
|
|
16
|
+
searchUrlField: 'url',
|
|
17
|
+
enableBackupApi: true,
|
|
18
|
+
backupApiUrl: 'https://api.iyuns.com/api/wpysso',
|
|
19
|
+
backupApiMethod: 'GET',
|
|
20
|
+
backupApiParams: '{"kw": "{keyword}"}',
|
|
21
|
+
backupApiHeaders: '{"User-Agent": "Mozilla/5.0"}',
|
|
22
|
+
backupResultPath: 'data',
|
|
23
|
+
backupTitleField: 'title',
|
|
24
|
+
backupUrlField: 'url',
|
|
25
|
+
// 百度网盘 Cookie(请在代码中填写实际可用的 Cookie)
|
|
26
|
+
baiduCookie: 'XFI=0c01b813-9929-2ffc-be09-0456acbd7d17; XFCS=E35A08D13CA78E5FE220BA4DA4A7F8937DEAB76C07A1068833F8AAD0A308B2EC; XFT=JCMrhiIgMG0hTJJLPoJ/Je22g4I9fW9vb2jwhsFd9Rs=; BIDUPSID=9D6EC93AA00E3F987F35C1935FA7EFD4; PSTM=1756714097; BAIDUID=9D6EC93AA00E3F987F35C1935FA7EFD4:SL=0:NR=10:FG=1; BAIDUID_BFESS=9D6EC93AA00E3F987F35C1935FA7EFD4:SL=0:NR=10:FG=1; BAIDU_WISE_UID=wapp_1760323830542_510; __bid_n=199f75c28e824964265563; MCITY=-270%3A268%3A; ZFY=FZt2:BUU06DE7Z5E:Ap0i3XaBTKxxPzyUTJZp6ADN7MnQ:C; H_WISE_SIDS_BFESS=63140_64989_65227_65243_65428_65457_65608_65643_65601_65645_65662_65678_65672_65688_65729_65759_65784_65791_65862_65914_65361; H_PS_PSSID=63140_66118_66209_66243_66274_66253_66393_66516_66529_66547_66588_66593_65792_66601_66615_66642_66651_66678_66668_66693_66686_66696_66621_66774_66783_66793_66803_66802_66599; H_WISE_SIDS=63140_66118_66209_66243_66274_66253_66393_66516_66529_66547_66588_66593_65792_66601_66615_66642_66651_66678_66668_66693_66686_66696_66621_66774_66783_66793_66803_66802_66599; csrfToken=-Ec8H-Esv3y2M3f2XlTJMNW3; Hm_lvt_0ba7bcf57b5e55fbfbab9a2750acdf3e=1765796144; HMACCOUNT=03C9A035C494C31D; Hm_lvt_182d6d59474cf78db37e0b2248640ea5=1765796144; Hm_lvt_e961721efc295d08f83faba5dbe3a12b=1765796144; newlogin=1; ploganondeg=1; ppfuid=FOCoIC3q5fKa8fgJnwzbE67EJ49BGJeplOzf+4l4EOvDuu2RXBRv6R3A1AZMa49I27C0gDDLrJyxcIIeAeEhD8JYsoLTpBiaCXhLqvzbzmvy3SeAW17tKgNq/Xx+RgOdb8TWCFe62MVrDTY6lMf2GrfqL8c87KLF2qFER3obJGnsqkZri/4OJbm7r4CyJIowGEimjy3MrXEpSuItnI4KD1ziJfZNxo8yAiRbKjVuLfyDFMkrGDsyg5dNhHl7/LyWn+snuJUZLcdRLoqHtKxcMxJsVwXkGdF24AsEQ3K5XBbh9EHAWDOg2T1ejpq0s2eFy9ar/j566XqWDobGoNNfmfpaEhZpob9le2b5QIEdiQedMiSfc+v/KNjQKaTOaSekXhLZ126CPFCIEuj/nWa+RCvURLVm4bpFOvBv8e58/dDOXSxFWocn8LvXoXRLp3fo5/fbkvdNVYtfeSgcfqcJz7tVNIB9KqyLqpAKG/6PN5nHZFO3SaB4GS7zlBrG2cLm8lTRl19JYcYcqvy3P/50mxpWDwUUC4pvKOF9e+pwNq7l6HzKEZyCMUDd+W6AiaksYiu+4AAz72OnMQfgAyNUbW3IyzL5c+UBht87WUigOY9alcIuR+n1gwn+Dmf3unATYGtv0zKmAog3Ny9wFYiQ/gdKSrR9D25HSwrLQyIe5QKTkKSlY6nVev8MhaT3AUPwNqYIvWCQZXWkhuuU0ZXLMYAKJSeHY7mTrwwSSKC3ZaKm5QkRfoQ+kuqh9uWM3a8OdgtUXRFXk2kUXsUEfTKTd0PbbN8QsYZ2ERU6hCb6Obp91rSPWb2eeCt263/A+EJVR/A8+3BQ92SIDoXabq8Wb8ZGN9BAsC9g5OdjE6lhwzTadptHqT7mZN901gDzA4lMYEG/kekC+0J5/N5yVy+ei6oHzwX7uar2Q/mbW6M2KoPvEbUDKu/Iht/UpGjxFN30IqUKPz9wP4MiFigf6zfu9H7MoBk2+fk1Zu8uXMwS+/rrQ6U1O7Zv2wiyJOnrYyq/5Tv2IOghUDulefRvlX9eT7gQwEiclvXWS2pMTilyx6wORXYWMC8Ewe1rUuQprEZZNDywMI17CupLBOAx9qwTTBhEMNzi6OXbElHkA3erw56I0vmkH9G20tmAiqCABGBI1qeHlbtIIUXAPQK2AKm25kN9e++uG7KATaiQSHPJR405LDjC+5v0mQclI0YcJp8DvGLdRUpGcbUX7V27dvoxZNlkNAKwTxTOnYZkLWOYVTD5EoNlrqqJb8Op38LjSNcK; BDUSS=HdyY05xTmhXVGhUclNFZzhCVGVRbDg1MGV3bXBFbGVQWDZ2QUh4TmtGQndkbWRwSVFBQUFBJCQAAAAAAAAAAAEAAAABN~vNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHDpP2lw6T9pM; BDUSS_BFESS=HdyY05xTmhXVGhUclNFZzhCVGVRbDg1MGV3bXBFbGVQWDZ2QUh4TmtGQndkbWRwSVFBQUFBJCQAAAAAAAAAAAEAAAABN~vNAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHDpP2lw6T9pM; STOKEN=e1e62c50b5d03828f628895ce1a79bd5369ec515c962ef3e7dee4248e274f897; PANPSC=1523649500083366391%3Au9Rut0jYI4pl%2FLDfonqUYVcS2d9ns3O5C61tf8CKQkhfQsayMBXIeQXQ225L75l2z81ttRoL0tCck0T9AttkQXMXQEwNWhh0amDgJ1NFn0oWXujC1eV6KOKEQjOY60ydECuWaePJJP%2B4A0ipQ2gQX0SbgxEKExKM0oUakcVUn8vvFIVZmIcELSHq5mg%2FcPBD1h8mCCD3Fkl50CABoQspXaL521JCTvbkImGEceQsDbSzeW2p0vjigguGXLVSDT5bkrFWWqsgPEySsVZaqyA8TA%3D%3D; Hm_lpvt_0ba7bcf57b5e55fbfbab9a2750acdf3e=1765796217; Hm_lpvt_e961721efc295d08f83faba5dbe3a12b=1765796219; ndut_fmt=719B5C6037C2EB647DA066E7E817CB9B0A31EBFD3E30B521FE17FFC45AAA2D72; ab_sr=101._Yjg2NmNlOWY4MTg0ZTMzMGFlNTUzODgzZWZhNmM5ZDU4MWQzY2IxMzRhM2E0NTYzNjI0YjQ0YjI5OWQyNTI5NDQ2YzIyYjc1MmEwNGUzMjA5Y2Q3NDRkOTEwZTM3NzM2MzFmNGUxMzYzZmYwYmQ1MDcxNDZhYjBjMGE2MGViMjVlZWQ1ZWQ0YjBlYjk2OGNlZmM3ZDAwMWZlZmZjOTU5NTJkNDdlNTc2MjA5ZDZlYThlZjI4ZTMyNWNmMjE5YjM5; Hm_lpvt_182d6d59474cf78db37e0b2248640ea5=1765796223',
|
|
27
|
+
// 固定转存目录
|
|
28
|
+
saveFolder: '/机器人',
|
|
29
|
+
// 分享过期类型(0:永久,1/7/30:天数,具体以百度接口为准)
|
|
30
|
+
expiredType: 0,
|
|
31
|
+
// 分享提取码
|
|
32
|
+
sharePassword: '6666',
|
|
33
|
+
// 管理员用户ID(仅这些用户的私聊会触发直接转存)
|
|
34
|
+
adminIds: [],
|
|
35
|
+
// 是否允许在群聊内直接链接转存(默认关闭)
|
|
36
|
+
enableGroupDirectTransfer: false,
|
|
37
|
+
};
|
|
38
|
+
exports.Config = koishi_1.Schema.object({
|
|
39
|
+
watchGroups: koishi_1.Schema.array(String)
|
|
40
|
+
.default([])
|
|
41
|
+
.description('监控的群号列表,留空则监控所有群'),
|
|
42
|
+
limit: koishi_1.Schema.number()
|
|
43
|
+
.default(10)
|
|
44
|
+
.description('每次搜索返回的最大结果数'),
|
|
45
|
+
sessionTimeout: koishi_1.Schema.number()
|
|
46
|
+
.default(120)
|
|
47
|
+
.description('搜索会话超时时间(秒)'),
|
|
48
|
+
saveTimeout: koishi_1.Schema.number()
|
|
49
|
+
.default(180)
|
|
50
|
+
.description('转存超时时间(秒)'),
|
|
51
|
+
adminIds: koishi_1.Schema.array(String)
|
|
52
|
+
.default([])
|
|
53
|
+
.description('管理员用户ID列表,仅这些用户的私聊可触发直接转存'),
|
|
54
|
+
enableGroupDirectTransfer: koishi_1.Schema.boolean()
|
|
55
|
+
.default(false)
|
|
56
|
+
.description('是否允许在群聊内直接链接触发转存(默认关闭)'),
|
|
57
|
+
});
|
|
58
|
+
// 每页显示数量
|
|
59
|
+
const PAGE_SIZE = 10;
|
|
60
|
+
// 会话缓存
|
|
61
|
+
const searchSessions = new Map();
|
|
62
|
+
// 错误码映射(参考 PHP 版 BaiduWork)
|
|
63
|
+
const BAIDU_ERROR_CODES = {
|
|
64
|
+
[-1]: '链接错误,链接失效或缺少提取码或访问频繁风控',
|
|
65
|
+
[-4]: '无效登录。请退出账号在其他地方的登录',
|
|
66
|
+
[-6]: '请用浏览器无痕模式获取 Cookie 后再试',
|
|
67
|
+
[-7]: '转存失败,转存文件夹名有非法字符',
|
|
68
|
+
[-8]: '转存失败,目录中已有同名文件或文件夹存在',
|
|
69
|
+
[-9]: '链接不存在或提取码错误',
|
|
70
|
+
[-10]: '转存失败,容量不足',
|
|
71
|
+
[-12]: '链接错误,提取码错误',
|
|
72
|
+
[-62]: '链接访问次数过多,请稍后再试',
|
|
73
|
+
[0]: '转存成功',
|
|
74
|
+
[2]: '转存失败,目标目录不存在',
|
|
75
|
+
[4]: '转存失败,目录中存在同名文件',
|
|
76
|
+
[12]: '转存失败,转存文件数超过限制',
|
|
77
|
+
[20]: '转存失败,容量不足',
|
|
78
|
+
[105]: '链接错误,所访问的页面不存在',
|
|
79
|
+
[115]: '该文件禁止分享',
|
|
80
|
+
};
|
|
81
|
+
function getErrorMessage(code) {
|
|
82
|
+
return BAIDU_ERROR_CODES[code] ?? `未知错误(错误码:${code})`;
|
|
83
|
+
}
|
|
84
|
+
// 百度请求通用请求头
|
|
85
|
+
function getBaiduHeaders(cookie) {
|
|
86
|
+
return {
|
|
87
|
+
'Host': 'pan.baidu.com',
|
|
88
|
+
'Connection': 'keep-alive',
|
|
89
|
+
'Upgrade-Insecure-Requests': '1',
|
|
90
|
+
'Sec-Fetch-Dest': 'document',
|
|
91
|
+
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
|
|
92
|
+
'Sec-Fetch-Site': 'same-site',
|
|
93
|
+
'Sec-Fetch-Mode': 'navigate',
|
|
94
|
+
'Referer': 'https://pan.baidu.com',
|
|
95
|
+
'Accept-Encoding': 'gzip, deflate, br',
|
|
96
|
+
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-US;q=0.7,en-GB;q=0.6,ru;q=0.5',
|
|
97
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
|
|
98
|
+
'Cookie': cookie,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function sleep(ms) {
|
|
102
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
103
|
+
}
|
|
104
|
+
// 从文本中尝试提取提取码
|
|
105
|
+
function parsePasscode(text) {
|
|
106
|
+
if (!text)
|
|
107
|
+
return undefined;
|
|
108
|
+
const patterns = [
|
|
109
|
+
/提取码[::]\s*([a-zA-Z0-9]{4})/i,
|
|
110
|
+
/密码[::]\s*([a-zA-Z0-9]{4})/i,
|
|
111
|
+
/提取码为[::]?\s*([a-zA-Z0-9]{4})/i,
|
|
112
|
+
/密码为[::]?\s*([a-zA-Z0-9]{4})/i,
|
|
113
|
+
];
|
|
114
|
+
for (const p of patterns) {
|
|
115
|
+
const m = text.match(p);
|
|
116
|
+
if (m?.[1])
|
|
117
|
+
return m[1];
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
// 进一步:从 URL 查询参数中提取提取码(pwd/password/code/passcode)
|
|
122
|
+
function parsePasscodeFromUrl(u) {
|
|
123
|
+
if (!u)
|
|
124
|
+
return undefined;
|
|
125
|
+
// 清理首尾可能存在的反引号或引号以及多余空格
|
|
126
|
+
const cleaned = String(u).trim().replace(/^`+|`+$/g, '').replace(/^"+|"+$/g, '');
|
|
127
|
+
const m = cleaned.match(/[?&](?:pwd|password|code|passcode)=([a-zA-Z0-9]{4})/i);
|
|
128
|
+
return m?.[1];
|
|
129
|
+
}
|
|
130
|
+
// 统一清理分享链接(去除反引号/引号/多余空格,并只保留 https://pan.baidu.com/s/<id> 前缀部分)
|
|
131
|
+
function sanitizeShareUrl(u) {
|
|
132
|
+
const cleaned = String(u || '').trim().replace(/^`+|`+$/g, '').replace(/^"+|"+$/g, '').replace(/^'+|'+$/g, '');
|
|
133
|
+
const m = cleaned.match(/https:\/\/pan\.baidu\.com\/s\/[A-Za-z0-9_-]+/);
|
|
134
|
+
return m ? m[0] : cleaned;
|
|
135
|
+
}
|
|
136
|
+
// 在分享链接上拼接提取码参数(?pwd=xxxx),同时下方仍显示提取码
|
|
137
|
+
function attachPasscodeToUrl(u, passcode) {
|
|
138
|
+
const base = sanitizeShareUrl(u);
|
|
139
|
+
const code = (passcode && /^[a-zA-Z0-9]{4}$/.test(passcode)) ? passcode : (parsePasscodeFromUrl(u) || undefined);
|
|
140
|
+
if (!code)
|
|
141
|
+
return base;
|
|
142
|
+
return base.includes('?') ? `${base}&pwd=${code}` : `${base}?pwd=${code}`;
|
|
143
|
+
}
|
|
144
|
+
// 进一步:从对象字段中提取提取码(优先 URL 里的 ?pwd=)
|
|
145
|
+
function detectPasscode(item, u, ...texts) {
|
|
146
|
+
// 1) 先从 URL 中获取(用户明确要求)
|
|
147
|
+
const byUrl = parsePasscodeFromUrl(u);
|
|
148
|
+
if (byUrl)
|
|
149
|
+
return byUrl;
|
|
150
|
+
// 2) 再尝试对象字段
|
|
151
|
+
const fields = ['pwd', 'password', 'passcode', 'code'];
|
|
152
|
+
for (const f of fields) {
|
|
153
|
+
const v = item?.[f];
|
|
154
|
+
if (typeof v === 'string' && /^[a-zA-Z0-9]{4}$/.test(v))
|
|
155
|
+
return v;
|
|
156
|
+
}
|
|
157
|
+
// 3) 最后尝试从文本(标题/描述)提取
|
|
158
|
+
for (const t of texts) {
|
|
159
|
+
const v = parsePasscode(t || '');
|
|
160
|
+
if (v)
|
|
161
|
+
return v;
|
|
162
|
+
}
|
|
163
|
+
return undefined;
|
|
164
|
+
}
|
|
165
|
+
// 检测百度链接有效性(不保证 100% 准确,仅快速检查)
|
|
166
|
+
async function checkBaiduLinkValid(ctx, url, logger) {
|
|
167
|
+
// 已按用户要求移除有效性检查逻辑,统一视为可访问,由转存阶段处理实际错误
|
|
168
|
+
return { valid: true, message: '跳过有效性检查' };
|
|
169
|
+
}
|
|
170
|
+
// 批量检测链接有效性
|
|
171
|
+
async function filterValidLinks(ctx, config, items, logger) {
|
|
172
|
+
const out = [];
|
|
173
|
+
const concurrency = 10;
|
|
174
|
+
for (let i = 0; i < items.length; i += concurrency) {
|
|
175
|
+
const batch = items.slice(i, i + concurrency);
|
|
176
|
+
const results = await Promise.all(batch.map(async (item) => {
|
|
177
|
+
try {
|
|
178
|
+
const shareUrl = sanitizeShareUrl(item.url);
|
|
179
|
+
let cookie = config.baiduCookie;
|
|
180
|
+
const codeCandidate = (item.passcode && item.passcode.length === 4) ? item.passcode : (parsePasscodeFromUrl(shareUrl) || undefined);
|
|
181
|
+
// 先尝试解析分享页参数
|
|
182
|
+
let paramsList = await getTransferParams(ctx, cookie, shareUrl, logger);
|
|
183
|
+
if (typeof paramsList === 'number') {
|
|
184
|
+
// 需要提取码时尝试验证
|
|
185
|
+
if (codeCandidate) {
|
|
186
|
+
const bdstoken = await getBdstoken(ctx, cookie);
|
|
187
|
+
if (typeof bdstoken === 'string') {
|
|
188
|
+
const randsk = await verifyPassCode(ctx, cookie, bdstoken, shareUrl, codeCandidate, logger);
|
|
189
|
+
if (typeof randsk === 'string') {
|
|
190
|
+
cookie = updateCookieBdclnd(cookie, randsk);
|
|
191
|
+
paramsList = await getTransferParams(ctx, cookie, shareUrl, logger);
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
logger.info(`有效性校验:提取码验证失败 errno=${randsk}`);
|
|
195
|
+
item.valid = false;
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
item.valid = false;
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (typeof paramsList !== 'number') {
|
|
206
|
+
item.valid = true;
|
|
207
|
+
if (!item.passcode && codeCandidate)
|
|
208
|
+
item.passcode = codeCandidate;
|
|
209
|
+
return item;
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
item.valid = false;
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
logger.warn('有效性校验异常: ' + (e?.message || String(e)));
|
|
218
|
+
item.valid = false;
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}));
|
|
222
|
+
for (const r of results) {
|
|
223
|
+
if (r)
|
|
224
|
+
out.push(r);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
function apply(ctx, config) {
|
|
230
|
+
const logger = ctx.logger('baidu-search');
|
|
231
|
+
const fullConfig = { ...INTERNAL_CONFIG, ...config };
|
|
232
|
+
// 会话清理
|
|
233
|
+
const cleanupInterval = setInterval(() => {
|
|
234
|
+
const now = Date.now();
|
|
235
|
+
for (const [key, s] of searchSessions) {
|
|
236
|
+
if (now - s.timestamp > fullConfig.sessionTimeout * 1000) {
|
|
237
|
+
searchSessions.delete(key);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}, 30000);
|
|
241
|
+
ctx.on('dispose', () => {
|
|
242
|
+
clearInterval(cleanupInterval);
|
|
243
|
+
searchSessions.clear();
|
|
244
|
+
});
|
|
245
|
+
// 监听群消息
|
|
246
|
+
ctx.guild().on('message', async (session) => {
|
|
247
|
+
const groupId = session.guildId || session.channelId;
|
|
248
|
+
if (fullConfig.watchGroups.length > 0 && !fullConfig.watchGroups.includes(groupId))
|
|
249
|
+
return;
|
|
250
|
+
const content = session.content?.trim() || '';
|
|
251
|
+
const sessionKey = `${groupId}:${session.userId}`;
|
|
252
|
+
const bot = session.bot;
|
|
253
|
+
const internal = bot.internal;
|
|
254
|
+
// 数字选择
|
|
255
|
+
if (/^\d+$/.test(content)) {
|
|
256
|
+
const userSession = searchSessions.get(sessionKey);
|
|
257
|
+
if (userSession && Date.now() - userSession.timestamp < fullConfig.sessionTimeout * 1000) {
|
|
258
|
+
const index = parseInt(content, 10);
|
|
259
|
+
if (index >= 1 && index <= userSession.validResults.length) {
|
|
260
|
+
await handleSelection(ctx, fullConfig, internal, groupId, session.userId, userSession, index, logger);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
// 翻页
|
|
266
|
+
if (content === '下一页' || content === '上一页') {
|
|
267
|
+
const userSession = searchSessions.get(sessionKey);
|
|
268
|
+
if (userSession && Date.now() - userSession.timestamp < fullConfig.sessionTimeout * 1000) {
|
|
269
|
+
const totalPages = Math.ceil(userSession.validResults.length / PAGE_SIZE);
|
|
270
|
+
if (content === '下一页') {
|
|
271
|
+
if (userSession.currentPage < totalPages) {
|
|
272
|
+
userSession.currentPage++;
|
|
273
|
+
userSession.timestamp = Date.now();
|
|
274
|
+
await sendPageResults(ctx, internal, groupId, session.userId, userSession, fullConfig, logger);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
await internal.sendGroupMsg(groupId, [
|
|
278
|
+
{ type: 'at', data: { qq: session.userId } },
|
|
279
|
+
{ type: 'text', data: { text: ' 已经是最后一页了' } },
|
|
280
|
+
]);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
if (userSession.currentPage > 1) {
|
|
285
|
+
userSession.currentPage--;
|
|
286
|
+
userSession.timestamp = Date.now();
|
|
287
|
+
await sendPageResults(ctx, internal, groupId, session.userId, userSession, fullConfig, logger);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
await internal.sendGroupMsg(groupId, [
|
|
291
|
+
{ type: 'at', data: { qq: session.userId } },
|
|
292
|
+
{ type: 'text', data: { text: ' 已经是第一页了' } },
|
|
293
|
+
]);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// 直接链接转存(群聊):仅在开启开关时触发
|
|
300
|
+
if (fullConfig.enableGroupDirectTransfer && content.includes('pan.baidu.com')) {
|
|
301
|
+
const shareUrl = sanitizeShareUrl(content);
|
|
302
|
+
const passcode = parsePasscodeFromUrl(content) || parsePasscode(content);
|
|
303
|
+
logger.info(`收到群聊直接链接转存请求: ${shareUrl}${passcode ? `,提取码: ${passcode}` : ''}`);
|
|
304
|
+
try {
|
|
305
|
+
const result = await baiduTransferAndShare(ctx, fullConfig, shareUrl, '用户提供资源', passcode, logger);
|
|
306
|
+
if (result.success) {
|
|
307
|
+
const lines = [];
|
|
308
|
+
lines.push('✅ 转存成功!');
|
|
309
|
+
lines.push('');
|
|
310
|
+
lines.push(`📺 用户提供资源`)
|
|
311
|
+
- lines.push(`🔗 ${result.shareUrl}`)
|
|
312
|
+
+ lines.push(`🔗 ${attachPasscodeToUrl(result.shareUrl, fullConfig.sharePassword)}`);
|
|
313
|
+
lines.push(`🔑 提取码:${fullConfig.sharePassword}`);
|
|
314
|
+
await internal.sendGroupMsg(groupId, [
|
|
315
|
+
{ type: 'at', data: { qq: session.userId } },
|
|
316
|
+
{ type: 'text', data: { text: '\n' + lines.join('\n') } },
|
|
317
|
+
]);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
const lines = [];
|
|
321
|
+
lines.push(`⚠️ ${result.message || '转存失败'}`);
|
|
322
|
+
await internal.sendGroupMsg(groupId, [
|
|
323
|
+
{ type: 'at', data: { qq: session.userId } },
|
|
324
|
+
{ type: 'text', data: { text: '\n' + lines.join('\n') } },
|
|
325
|
+
]);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
logger.error('群聊直接链接转存异常:', error);
|
|
330
|
+
await internal.sendGroupMsg(groupId, [
|
|
331
|
+
{ type: 'at', data: { qq: session.userId } },
|
|
332
|
+
{ type: 'text', data: { text: ` 转存服务异常:${error?.message || '未知错误'}` } },
|
|
333
|
+
]);
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// 搜索
|
|
338
|
+
if (!content.startsWith('搜百度'))
|
|
339
|
+
return;
|
|
340
|
+
const keyword = content.slice('搜百度'.length).trim();
|
|
341
|
+
if (!keyword) {
|
|
342
|
+
await internal.sendGroupMsg(groupId, [
|
|
343
|
+
{ type: 'at', data: { qq: session.userId } },
|
|
344
|
+
{ type: 'text', data: { text: ' 请输入要搜索的内容,例如:搜百度 流浪地球' } },
|
|
345
|
+
]);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
logger.info(`收到搜索请求: ${keyword}, 群: ${groupId}, 用户: ${session.userId}`);
|
|
349
|
+
try {
|
|
350
|
+
let results = [];
|
|
351
|
+
let usedBackup = false;
|
|
352
|
+
// 优先尝试主 API
|
|
353
|
+
try {
|
|
354
|
+
results = await searchResources(ctx, fullConfig, keyword, logger, false);
|
|
355
|
+
}
|
|
356
|
+
catch (err) {
|
|
357
|
+
logger.warn(`主API搜索异常: ${err?.code || err?.cause?.code || err?.message}`);
|
|
358
|
+
}
|
|
359
|
+
// 主 API 无结果或异常时,回退到备用 API
|
|
360
|
+
if ((!results || !results.length) && fullConfig.enableBackupApi && fullConfig.backupApiUrl) {
|
|
361
|
+
logger.info('主API不可用或无结果,尝试备用API...');
|
|
362
|
+
try {
|
|
363
|
+
results = await searchResources(ctx, fullConfig, keyword, logger, true);
|
|
364
|
+
usedBackup = true;
|
|
365
|
+
}
|
|
366
|
+
catch (e) {
|
|
367
|
+
logger.error('备用API搜索失败:', e);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (!results || !results.length) {
|
|
371
|
+
await internal.sendGroupMsg(groupId, [
|
|
372
|
+
{ type: 'at', data: { qq: session.userId } },
|
|
373
|
+
{ type: 'text', data: { text: ` 未找到「${keyword}」相关资源,换个关键词试试吧~` } },
|
|
374
|
+
]);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const apiSource = usedBackup ? '(备用API)' : '';
|
|
378
|
+
logger.info(`搜索到 ${results.length} 个${apiSource},验证中...`);
|
|
379
|
+
results.forEach((item, i) => logger.debug(`[${i + 1}] ${item.title} - ${item.url}`));
|
|
380
|
+
const firstBatch = results.slice(0, PAGE_SIZE);
|
|
381
|
+
const validFirst = await filterValidLinks(ctx, fullConfig, firstBatch, logger);
|
|
382
|
+
if (!validFirst.length) {
|
|
383
|
+
const moreBatch = results.slice(PAGE_SIZE, PAGE_SIZE * 3);
|
|
384
|
+
const validMore = await filterValidLinks(ctx, fullConfig, moreBatch, logger);
|
|
385
|
+
if (!validMore.length) {
|
|
386
|
+
await internal.sendGroupMsg(groupId, [
|
|
387
|
+
{ type: 'at', data: { qq: session.userId } },
|
|
388
|
+
{ type: 'text', data: { text: ` 「${keyword}」的资源链接均已失效,换个关键词试试吧~` } },
|
|
389
|
+
]);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
validFirst.push(...validMore);
|
|
393
|
+
}
|
|
394
|
+
const userSession = {
|
|
395
|
+
results,
|
|
396
|
+
validResults: validFirst,
|
|
397
|
+
timestamp: Date.now(),
|
|
398
|
+
keyword,
|
|
399
|
+
currentPage: 1,
|
|
400
|
+
validating: false,
|
|
401
|
+
};
|
|
402
|
+
searchSessions.set(sessionKey, userSession);
|
|
403
|
+
await sendPageResults(ctx, internal, groupId, session.userId, userSession, fullConfig, logger);
|
|
404
|
+
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
logger.error('搜索失败:', error);
|
|
407
|
+
let errorMsg = '搜索服务暂时不可用,请稍后再试';
|
|
408
|
+
if (error?.code === 'ECONNRESET' || error?.cause?.code === 'ECONNRESET')
|
|
409
|
+
errorMsg = '搜索服务连接中断,请稍后再试';
|
|
410
|
+
else if (error?.code === 'ETIMEDOUT' || error?.message?.includes('timeout'))
|
|
411
|
+
errorMsg = '搜索请求超时,请稍后再试';
|
|
412
|
+
else if (error?.code === 'ENOTFOUND')
|
|
413
|
+
errorMsg = '无法连接到搜索服务,请检查网络';
|
|
414
|
+
await internal.sendGroupMsg(groupId, [
|
|
415
|
+
{ type: 'at', data: { qq: session.userId } },
|
|
416
|
+
{ type: 'text', data: { text: ` ${errorMsg}` } },
|
|
417
|
+
]);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
// 监听管理员私聊消息:仅允许 adminIds 私聊触发直接链接转存
|
|
421
|
+
ctx.private().on('message', async (session) => {
|
|
422
|
+
const userId = session.userId;
|
|
423
|
+
const content = session.content?.trim() || '';
|
|
424
|
+
const bot = session.bot;
|
|
425
|
+
const internal = bot.internal;
|
|
426
|
+
if (!fullConfig.adminIds?.length || !fullConfig.adminIds.includes(userId))
|
|
427
|
+
return;
|
|
428
|
+
if (content.includes('pan.baidu.com')) {
|
|
429
|
+
const shareUrl = sanitizeShareUrl(content);
|
|
430
|
+
const passcode = parsePasscodeFromUrl(content) || parsePasscode(content);
|
|
431
|
+
logger.info(`收到管理员私聊转存请求: ${shareUrl}${passcode ? `,提取码: ${passcode}` : ''}`);
|
|
432
|
+
try {
|
|
433
|
+
const result = await baiduTransferAndShare(ctx, fullConfig, shareUrl, '管理员提供资源', passcode, logger);
|
|
434
|
+
if (result.success) {
|
|
435
|
+
const lines = [];
|
|
436
|
+
lines.push('✅ 转存成功!');
|
|
437
|
+
lines.push('');
|
|
438
|
+
lines.push(`📺 管理员提供资源`)
|
|
439
|
+
- lines.push(`🔗 ${result.shareUrl}`)
|
|
440
|
+
+ lines.push(`🔗 ${attachPasscodeToUrl(result.shareUrl, fullConfig.sharePassword)}`);
|
|
441
|
+
lines.push(`🔑 提取码:${fullConfig.sharePassword}`);
|
|
442
|
+
// 私聊回复
|
|
443
|
+
await internal.sendPrivateMsg(userId, lines.join('\n'));
|
|
444
|
+
}
|
|
445
|
+
else {
|
|
446
|
+
await internal.sendPrivateMsg(userId, `⚠️ ${result.message || '转存失败'}`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
logger.error('管理员私聊转存异常:', error);
|
|
451
|
+
await internal.sendPrivateMsg(userId, `转存服务异常:${error?.message || '未知错误'}`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
// 发送分页结果
|
|
457
|
+
async function sendPageResults(ctx, internal, groupId, userId, userSession, config, logger) {
|
|
458
|
+
const validResults = userSession.validResults;
|
|
459
|
+
const totalPages = Math.ceil(validResults.length / PAGE_SIZE);
|
|
460
|
+
const startIndex = (userSession.currentPage - 1) * PAGE_SIZE;
|
|
461
|
+
const endIndex = Math.min(startIndex + PAGE_SIZE, validResults.length);
|
|
462
|
+
const pageResults = validResults.slice(startIndex, endIndex);
|
|
463
|
+
const lines = [];
|
|
464
|
+
lines.push(`🔍「${userSession.keyword}」搜索结果(第${userSession.currentPage}/${totalPages}页,共${validResults.length}条有效资源):`);
|
|
465
|
+
lines.push('');
|
|
466
|
+
pageResults.forEach((item, idx) => {
|
|
467
|
+
const globalIndex = startIndex + idx + 1;
|
|
468
|
+
lines.push(`${globalIndex}. ${item.title}`);
|
|
469
|
+
});
|
|
470
|
+
lines.push('');
|
|
471
|
+
lines.push('📝 回复数字编号获取对应资源');
|
|
472
|
+
if (userSession.currentPage < totalPages)
|
|
473
|
+
lines.push('📄 回复"下一页"查看更多');
|
|
474
|
+
if (userSession.currentPage > 1)
|
|
475
|
+
lines.push('📄 回复"上一页"返回');
|
|
476
|
+
lines.push(`⏰ ${config.sessionTimeout}秒内有效`);
|
|
477
|
+
try {
|
|
478
|
+
await internal.sendGroupMsg(groupId, [
|
|
479
|
+
{ type: 'at', data: { qq: userId } },
|
|
480
|
+
{ type: 'text', data: { text: '\n' + lines.join('\n') } },
|
|
481
|
+
]);
|
|
482
|
+
}
|
|
483
|
+
catch (e) {
|
|
484
|
+
logger.error('发送结果失败:', e);
|
|
485
|
+
}
|
|
486
|
+
// 异步预验证下一批
|
|
487
|
+
const validatedCount = validResults.length;
|
|
488
|
+
const totalCount = userSession.results.length;
|
|
489
|
+
if (validatedCount < totalCount && !userSession.validating) {
|
|
490
|
+
userSession.validating = true;
|
|
491
|
+
const nextStart = validatedCount;
|
|
492
|
+
const nextEnd = Math.min(nextStart + PAGE_SIZE, totalCount);
|
|
493
|
+
const nextBatch = userSession.results.slice(nextStart, nextEnd);
|
|
494
|
+
const unvalidated = nextBatch.filter(i => i.valid === undefined);
|
|
495
|
+
if (unvalidated.length > 0) {
|
|
496
|
+
filterValidLinks(ctx, config, unvalidated, logger).then(items => {
|
|
497
|
+
userSession.validResults.push(...items);
|
|
498
|
+
userSession.validating = false;
|
|
499
|
+
}).catch(() => userSession.validating = false);
|
|
500
|
+
}
|
|
501
|
+
else {
|
|
502
|
+
userSession.validating = false;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// 搜索资源
|
|
507
|
+
async function searchResources(ctx, config, keyword, logger, useBackup = false) {
|
|
508
|
+
const maxRetries = 2;
|
|
509
|
+
let lastError = null;
|
|
510
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
511
|
+
try {
|
|
512
|
+
if (attempt > 0) {
|
|
513
|
+
logger.info(`第 ${attempt + 1} 次尝试搜索...`);
|
|
514
|
+
await sleep(1000 * attempt);
|
|
515
|
+
}
|
|
516
|
+
return await performSearch(ctx, config, keyword, logger, useBackup);
|
|
517
|
+
}
|
|
518
|
+
catch (error) {
|
|
519
|
+
lastError = error;
|
|
520
|
+
const code = error?.code || error?.cause?.code;
|
|
521
|
+
logger.warn(`搜索尝试 ${attempt + 1} 失败: ${code || error?.message}`);
|
|
522
|
+
if (code === 'ECONNRESET' || code === 'ETIMEDOUT' || error?.message?.includes('timeout')) {
|
|
523
|
+
if (attempt < maxRetries)
|
|
524
|
+
continue;
|
|
525
|
+
}
|
|
526
|
+
throw error;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
throw lastError;
|
|
530
|
+
}
|
|
531
|
+
// 实际搜索
|
|
532
|
+
async function performSearch(ctx, config, keyword, logger, useBackup = false) {
|
|
533
|
+
// 选择 API
|
|
534
|
+
const apiUrl = useBackup ? config.backupApiUrl : config.searchApiUrl;
|
|
535
|
+
const apiMethod = useBackup ? config.backupApiMethod : config.searchApiMethod;
|
|
536
|
+
const apiParamsStr = useBackup ? config.backupApiParams : config.searchApiParams;
|
|
537
|
+
const apiHeadersStr = useBackup ? config.backupApiHeaders : config.searchApiHeaders;
|
|
538
|
+
const resultPath = useBackup ? config.backupResultPath : config.searchResultPath;
|
|
539
|
+
const titleField = useBackup ? config.backupTitleField : config.searchTitleField;
|
|
540
|
+
const urlField = useBackup ? config.backupUrlField : config.searchUrlField;
|
|
541
|
+
// 解析参数
|
|
542
|
+
let params = {};
|
|
543
|
+
try {
|
|
544
|
+
params = JSON.parse(apiParamsStr);
|
|
545
|
+
for (const k in params)
|
|
546
|
+
if (typeof params[k] === 'string')
|
|
547
|
+
params[k] = params[k].replace('{keyword}', keyword);
|
|
548
|
+
}
|
|
549
|
+
catch (e) {
|
|
550
|
+
logger.error('解析搜索参数失败:', e);
|
|
551
|
+
}
|
|
552
|
+
// 解析请求头
|
|
553
|
+
let headers = {};
|
|
554
|
+
try {
|
|
555
|
+
headers = JSON.parse(apiHeadersStr);
|
|
556
|
+
}
|
|
557
|
+
catch (e) {
|
|
558
|
+
logger.error('解析请求头失败:', e);
|
|
559
|
+
}
|
|
560
|
+
// 请求文本响应
|
|
561
|
+
let responseText;
|
|
562
|
+
const qs = new URLSearchParams(params).toString();
|
|
563
|
+
const url = apiUrl + (qs ? '?' + qs : '');
|
|
564
|
+
if (apiMethod === 'GET') {
|
|
565
|
+
responseText = await ctx.http.get(url, { headers, timeout: 30000, responseType: 'text' });
|
|
566
|
+
}
|
|
567
|
+
else {
|
|
568
|
+
responseText = await ctx.http.post(apiUrl, params, { headers: { ...headers, 'Content-Type': 'application/json' }, timeout: 30000, responseType: 'text' });
|
|
569
|
+
}
|
|
570
|
+
const responseStr = String(responseText);
|
|
571
|
+
const results = [];
|
|
572
|
+
const baiduPattern = /https:\/\/pan\.baidu\.com\/s\/[A-Za-z0-9_-]+/g;
|
|
573
|
+
const isValidTitle = (title) => {
|
|
574
|
+
if (!title)
|
|
575
|
+
return false;
|
|
576
|
+
if (title.length > 200)
|
|
577
|
+
return false;
|
|
578
|
+
return true;
|
|
579
|
+
};
|
|
580
|
+
// SSE
|
|
581
|
+
if (responseStr.includes('data:') && responseStr.includes('\n')) {
|
|
582
|
+
const lines = responseStr.split('\n');
|
|
583
|
+
let sseFound = false;
|
|
584
|
+
for (const line of lines) {
|
|
585
|
+
const t = line.trim();
|
|
586
|
+
if (t.startsWith('data:') && !t.includes('[DONE]')) {
|
|
587
|
+
sseFound = true;
|
|
588
|
+
try {
|
|
589
|
+
const jsonStr = t.substring(5).trim();
|
|
590
|
+
if (jsonStr) {
|
|
591
|
+
const item = JSON.parse(jsonStr);
|
|
592
|
+
const title = item.title || item.note || '';
|
|
593
|
+
let rawUrl = item.url || '';
|
|
594
|
+
let itemUrl = rawUrl;
|
|
595
|
+
const m = itemUrl.match(/https:\/\/pan\.baidu\.com\/s\/[A-Za-z0-9_-]+/);
|
|
596
|
+
if (m)
|
|
597
|
+
itemUrl = m[0];
|
|
598
|
+
const passcode = detectPasscode(item, rawUrl, title, item.desc || '');
|
|
599
|
+
if (isValidTitle(title) && itemUrl && itemUrl.includes('pan.baidu.com')) {
|
|
600
|
+
results.push({ title, url: itemUrl, passcode });
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
catch { }
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (sseFound && results.length > 0)
|
|
608
|
+
return results;
|
|
609
|
+
}
|
|
610
|
+
// JSON
|
|
611
|
+
let jsonData;
|
|
612
|
+
try {
|
|
613
|
+
jsonData = JSON.parse(responseStr);
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
// 纯文本提取链接
|
|
617
|
+
const all = responseStr.matchAll(baiduPattern);
|
|
618
|
+
let index = 0;
|
|
619
|
+
for (const m of all) {
|
|
620
|
+
if (!results.find(r => r.url === m[0]))
|
|
621
|
+
results.push({ title: `资源 ${++index}`, url: m[0] });
|
|
622
|
+
}
|
|
623
|
+
return results;
|
|
624
|
+
}
|
|
625
|
+
// 路径提取
|
|
626
|
+
let data = jsonData;
|
|
627
|
+
if (resultPath) {
|
|
628
|
+
const paths = resultPath.split('.');
|
|
629
|
+
for (const p of paths) {
|
|
630
|
+
if (data && typeof data === 'object' && p in data)
|
|
631
|
+
data = data[p];
|
|
632
|
+
else {
|
|
633
|
+
data = null;
|
|
634
|
+
break;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (Array.isArray(data)) {
|
|
639
|
+
for (const item of data) {
|
|
640
|
+
const title = item[titleField] || item.note || item.title || '';
|
|
641
|
+
let rawUrl = item[urlField] || item.url || '';
|
|
642
|
+
let itemUrl = rawUrl;
|
|
643
|
+
const m = itemUrl.match(/https:\/\/pan\.baidu\.com\/s\/[A-Za-z0-9_-]+/);
|
|
644
|
+
if (m)
|
|
645
|
+
itemUrl = m[0];
|
|
646
|
+
const passcode = detectPasscode(item, rawUrl, title, item.desc || '');
|
|
647
|
+
if (isValidTitle(title) && itemUrl && itemUrl.includes('pan.baidu.com')) {
|
|
648
|
+
results.push({ title, url: itemUrl, passcode });
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
if (results.length === 0) {
|
|
653
|
+
const itemPattern = /"(?:note|title)"\s*:\s*"([^"]+)"[^}]*"url"\s*:\s*"([^"]+pan\.baidu\.com[^"]+)"/g;
|
|
654
|
+
const itemPattern2 = /"url"\s*:\s*"([^"]+pan\.baidu\.com[^"]+)"[^}]*"(?:note|title)"\s*:\s*"([^"]+)"/g;
|
|
655
|
+
let m;
|
|
656
|
+
while ((m = itemPattern.exec(responseStr)) !== null) {
|
|
657
|
+
const title = m[1];
|
|
658
|
+
let itemUrl = m[2].replace(/\\\//g, '/');
|
|
659
|
+
const urlMatch = itemUrl.match(/https:\/\/pan\.baidu\.com\/s\/[A-Za-z0-9_-]+/);
|
|
660
|
+
if (isValidTitle(title) && urlMatch && !results.find(r => r.url === urlMatch[0])) {
|
|
661
|
+
results.push({ title, url: urlMatch[0], passcode: parsePasscodeFromUrl(itemUrl) || parsePasscode(title) });
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
while ((m = itemPattern2.exec(responseStr)) !== null) {
|
|
665
|
+
let itemUrl = m[1].replace(/\\\//g, '/');
|
|
666
|
+
const title = m[2];
|
|
667
|
+
const urlMatch = itemUrl.match(/https:\/\/pan\.baidu\.com\/s\/[A-Za-z0-9_-]+/);
|
|
668
|
+
if (isValidTitle(title) && urlMatch && !results.find(r => r.url === urlMatch[0])) {
|
|
669
|
+
results.push({ title, url: urlMatch[0], passcode: parsePasscodeFromUrl(itemUrl) || parsePasscode(title) });
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
if (results.length === 0) {
|
|
673
|
+
const all = responseStr.matchAll(baiduPattern);
|
|
674
|
+
let idx = 0;
|
|
675
|
+
for (const mm of all) {
|
|
676
|
+
if (!results.find(r => r.url === mm[0]))
|
|
677
|
+
results.push({ title: `资源 ${++idx}`, url: mm[0] });
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
logger.info(`搜索到 ${results.length} 个资源`);
|
|
682
|
+
if (results.length) {
|
|
683
|
+
logger.debug('搜索结果详情:');
|
|
684
|
+
results.forEach((it, i) => logger.debug(` [${i + 1}] ${it.title} - ${it.url}${it.passcode ? ` (提取码:${it.passcode})` : ''}`));
|
|
685
|
+
}
|
|
686
|
+
return results;
|
|
687
|
+
}
|
|
688
|
+
// 处理用户选择
|
|
689
|
+
async function handleSelection(ctx, config, internal, groupId, userId, userSession, index, logger) {
|
|
690
|
+
const selected = userSession.validResults[index - 1];
|
|
691
|
+
logger.info(`用户选择: ${selected.title} - ${selected.url}`);
|
|
692
|
+
if (selected.passcode) {
|
|
693
|
+
logger.info(`已识别到提取码: ${selected.passcode}`);
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
logger.info('未识别到提取码,将在转存阶段自动尝试获取');
|
|
697
|
+
}
|
|
698
|
+
await internal.sendGroupMsg(groupId, [
|
|
699
|
+
{ type: 'at', data: { qq: userId } },
|
|
700
|
+
{ type: 'text', data: { text: ` 正在转存「${selected.title}」,请稍候...` } },
|
|
701
|
+
]);
|
|
702
|
+
try {
|
|
703
|
+
const result = await baiduTransferAndShare(ctx, config, selected.url, selected.title, selected.passcode, logger);
|
|
704
|
+
if (result.success) {
|
|
705
|
+
userSession.timestamp = Date.now();
|
|
706
|
+
const lines = [];
|
|
707
|
+
lines.push('✅ 转存成功!');
|
|
708
|
+
lines.push('');
|
|
709
|
+
lines.push(`📺 ${selected.title}`);
|
|
710
|
+
lines.push(`🔗 ${attachPasscodeToUrl(result.shareUrl, config.sharePassword)}`);
|
|
711
|
+
lines.push(`🔑 提取码:${config.sharePassword}`);
|
|
712
|
+
lines.push('');
|
|
713
|
+
lines.push('💡 可继续回复编号选择其他资源');
|
|
714
|
+
lines.push(`⏰ 会话剩余 ${config.sessionTimeout} 秒`);
|
|
715
|
+
await internal.sendGroupMsg(groupId, [
|
|
716
|
+
{ type: 'at', data: { qq: userId } },
|
|
717
|
+
{ type: 'text', data: { text: '\n' + lines.join('\n') } },
|
|
718
|
+
]);
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
userSession.timestamp = Date.now();
|
|
722
|
+
const lines = [];
|
|
723
|
+
lines.push(`⚠️ ${result.message}`);
|
|
724
|
+
lines.push('');
|
|
725
|
+
lines.push('💡 请重新选择其他资源编号');
|
|
726
|
+
lines.push(`⏰ 会话剩余 ${config.sessionTimeout} 秒`);
|
|
727
|
+
await internal.sendGroupMsg(groupId, [
|
|
728
|
+
{ type: 'at', data: { qq: userId } },
|
|
729
|
+
{ type: 'text', data: { text: '\n' + lines.join('\n') } },
|
|
730
|
+
]);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
catch (error) {
|
|
734
|
+
logger.error('转存异常:', error);
|
|
735
|
+
userSession.timestamp = Date.now();
|
|
736
|
+
const lines = [];
|
|
737
|
+
lines.push(`⚠️ ${error?.message || '转存服务异常'}`);
|
|
738
|
+
lines.push('');
|
|
739
|
+
lines.push('💡 请重新选择其他资源编号');
|
|
740
|
+
lines.push(`⏰ 会话剩余 ${config.sessionTimeout} 秒`);
|
|
741
|
+
await internal.sendGroupMsg(groupId, [
|
|
742
|
+
{ type: 'at', data: { qq: userId } },
|
|
743
|
+
{ type: 'text', data: { text: '\n' + lines.join('\n') } },
|
|
744
|
+
]);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
// ==================== 百度转存与分享 ====================
|
|
748
|
+
function extractSurl(shareUrl) {
|
|
749
|
+
const normalized = sanitizeShareUrl(shareUrl);
|
|
750
|
+
const m = normalized.match(/\/s\/([^?]+)/);
|
|
751
|
+
const raw = m?.[1] || null;
|
|
752
|
+
return raw && raw.startsWith('1') ? raw.slice(1) : raw;
|
|
753
|
+
}
|
|
754
|
+
function updateCookieBdclnd(cookie, bdclnd) {
|
|
755
|
+
const parts = cookie.split(';').map(s => s.trim()).filter(Boolean);
|
|
756
|
+
const dict = {};
|
|
757
|
+
for (const p of parts) {
|
|
758
|
+
const [k, v] = p.split('=', 2);
|
|
759
|
+
if (k && v !== undefined)
|
|
760
|
+
dict[k] = v;
|
|
761
|
+
}
|
|
762
|
+
dict['BDCLND'] = bdclnd;
|
|
763
|
+
return Object.entries(dict).map(([k, v]) => `${k}=${v}`).join('; ');
|
|
764
|
+
}
|
|
765
|
+
async function getBdstoken(ctx, cookie) {
|
|
766
|
+
const url = 'https://pan.baidu.com/api/gettemplatevariable';
|
|
767
|
+
const params = {
|
|
768
|
+
clienttype: '0',
|
|
769
|
+
app_id: '250528',
|
|
770
|
+
web: '1',
|
|
771
|
+
fields: '["bdstoken","token","uk","isdocuser","servertime"]',
|
|
772
|
+
};
|
|
773
|
+
const headers = getBaiduHeaders(cookie);
|
|
774
|
+
const res = await ctx.http.get(url, { headers, params, timeout: 20000 });
|
|
775
|
+
if (res?.errno !== 0)
|
|
776
|
+
return res?.errno ?? -1;
|
|
777
|
+
return res?.result?.bdstoken;
|
|
778
|
+
}
|
|
779
|
+
async function verifyPassCode(ctx, cookie, bdstoken, linkUrl, passCode, logger) {
|
|
780
|
+
const url = 'https://pan.baidu.com/share/verify';
|
|
781
|
+
const surlRaw = extractSurl(linkUrl);
|
|
782
|
+
const surl = surlRaw && surlRaw.startsWith('') ? surlRaw : (surlRaw || '');
|
|
783
|
+
const params = {
|
|
784
|
+
surl,
|
|
785
|
+
bdstoken,
|
|
786
|
+
t: Date.now(),
|
|
787
|
+
channel: 'chunlei',
|
|
788
|
+
web: '1',
|
|
789
|
+
clienttype: '0',
|
|
790
|
+
app_id: '250528',
|
|
791
|
+
};
|
|
792
|
+
// 使用 application/x-www-form-urlencoded 以符合 Baidu 的 AJAX 接口要求
|
|
793
|
+
const form = new URLSearchParams({ pwd: passCode, vcode: '', vcode_str: '' }).toString();
|
|
794
|
+
const headers = getBaiduHeaders(cookie);
|
|
795
|
+
// 使用实际分享页作为 Referer,尽量保留 ?pwd= 以贴近浏览器行为
|
|
796
|
+
const baseRef = sanitizeShareUrl(linkUrl);
|
|
797
|
+
const pwdMatch = /[?&](?:pwd|password|code|passcode)=([a-zA-Z0-9]{4})/i.exec(String(linkUrl));
|
|
798
|
+
headers['Referer'] = pwdMatch ? `${baseRef}?pwd=${pwdMatch[1]}` : baseRef;
|
|
799
|
+
headers['Origin'] = 'https://pan.baidu.com';
|
|
800
|
+
headers['Accept'] = 'application/json, text/plain, */*';
|
|
801
|
+
headers['X-Requested-With'] = 'XMLHttpRequest';
|
|
802
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
|
|
803
|
+
headers['Sec-Fetch-Mode'] = 'cors';
|
|
804
|
+
headers['Sec-Fetch-Dest'] = 'empty';
|
|
805
|
+
headers['Sec-Fetch-Site'] = 'same-origin';
|
|
806
|
+
if (logger)
|
|
807
|
+
logger.info(`share/verify 请求参数: surl=${params.surl}, pwd=${passCode}`);
|
|
808
|
+
const res = await ctx.http.post(url, form, { headers, params, timeout: 20000 });
|
|
809
|
+
if (logger)
|
|
810
|
+
logger.info('share/verify 原始响应: ' + JSON.stringify(res));
|
|
811
|
+
if (res?.errno !== 0)
|
|
812
|
+
return res?.errno ?? -1;
|
|
813
|
+
return res?.randsk;
|
|
814
|
+
}
|
|
815
|
+
async function getTransferParams(ctx, cookie, linkUrl, logger) {
|
|
816
|
+
const headers = getBaiduHeaders(cookie);
|
|
817
|
+
headers['Referer'] = sanitizeShareUrl(linkUrl);
|
|
818
|
+
headers['Origin'] = 'https://pan.baidu.com';
|
|
819
|
+
const html = await ctx.http.get(sanitizeShareUrl(linkUrl), { headers, timeout: 20000, responseType: 'text' });
|
|
820
|
+
if (logger)
|
|
821
|
+
logger.info('分享页原始 HTML 长度: ' + String((html || '').length));
|
|
822
|
+
const response = String(html || '');
|
|
823
|
+
const patterns = {
|
|
824
|
+
shareid: /"shareid":(\d+?),"/g,
|
|
825
|
+
user_id: /"share_uk":"(\d+?)","/g,
|
|
826
|
+
fs_id: /"fs_id":(\d+?),"/g,
|
|
827
|
+
server_filename: /"server_filename":"(.+?)","/g,
|
|
828
|
+
isdir: /"isdir":(\d+?),"/g,
|
|
829
|
+
};
|
|
830
|
+
const results = { shareid: [], user_id: [], fs_id: [], server_filename: [], isdir: [] };
|
|
831
|
+
for (const k of Object.keys(patterns)) {
|
|
832
|
+
let m;
|
|
833
|
+
while ((m = patterns[k].exec(response)) !== null) {
|
|
834
|
+
results[k].push(m[1]);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
if (logger)
|
|
838
|
+
logger.info('解析到的分享参数: ' + JSON.stringify(results));
|
|
839
|
+
if (!results.shareid.length || !results.user_id.length || !results.fs_id.length || !results.server_filename.length || !results.isdir.length) {
|
|
840
|
+
return -1;
|
|
841
|
+
}
|
|
842
|
+
return [
|
|
843
|
+
results.shareid[0],
|
|
844
|
+
results.user_id[0],
|
|
845
|
+
results.fs_id.map(x => Number(x)),
|
|
846
|
+
Array.from(new Set(results.server_filename)),
|
|
847
|
+
results.isdir.map(x => Number(x)),
|
|
848
|
+
];
|
|
849
|
+
}
|
|
850
|
+
async function getDirList(ctx, cookie, bdstoken, folder) {
|
|
851
|
+
const url = 'https://pan.baidu.com/api/list';
|
|
852
|
+
const params = {
|
|
853
|
+
order: 'time',
|
|
854
|
+
desc: '1',
|
|
855
|
+
showempty: '0',
|
|
856
|
+
web: '1',
|
|
857
|
+
page: '1',
|
|
858
|
+
num: '1000',
|
|
859
|
+
dir: folder,
|
|
860
|
+
bdstoken,
|
|
861
|
+
};
|
|
862
|
+
const headers = getBaiduHeaders(cookie);
|
|
863
|
+
const res = await ctx.http.get(url, { headers, params, timeout: 20000 });
|
|
864
|
+
if (res?.errno !== 0)
|
|
865
|
+
return res?.errno ?? -1;
|
|
866
|
+
return res?.list || [];
|
|
867
|
+
}
|
|
868
|
+
async function createDir(ctx, cookie, bdstoken, folder) {
|
|
869
|
+
const url = 'https://pan.baidu.com/api/create';
|
|
870
|
+
const params = { a: 'commit', bdstoken };
|
|
871
|
+
const headers = getBaiduHeaders(cookie);
|
|
872
|
+
headers['Accept'] = 'application/json, text/plain, */*';
|
|
873
|
+
headers['X-Requested-With'] = 'XMLHttpRequest';
|
|
874
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
|
|
875
|
+
headers['Origin'] = 'https://pan.baidu.com';
|
|
876
|
+
headers['Sec-Fetch-Mode'] = 'cors';
|
|
877
|
+
headers['Sec-Fetch-Dest'] = 'empty';
|
|
878
|
+
headers['Sec-Fetch-Site'] = 'same-origin';
|
|
879
|
+
const form = new URLSearchParams({ path: folder, isdir: '1', block_list: '[]' }).toString();
|
|
880
|
+
const res = await ctx.http.post(url, form, { headers, params, timeout: 20000 });
|
|
881
|
+
return res?.errno ?? -1;
|
|
882
|
+
}
|
|
883
|
+
async function transferFile(ctx, cookie, bdstoken, paramsList, folderName) {
|
|
884
|
+
const url = 'https://pan.baidu.com/share/transfer';
|
|
885
|
+
const params = {
|
|
886
|
+
shareid: paramsList[0],
|
|
887
|
+
from: paramsList[1],
|
|
888
|
+
bdstoken,
|
|
889
|
+
channel: 'chunlei',
|
|
890
|
+
web: '1',
|
|
891
|
+
clienttype: '0',
|
|
892
|
+
ondup: 'newcopy',
|
|
893
|
+
};
|
|
894
|
+
const folderPath = folderName.startsWith('/') ? folderName : '/' + folderName;
|
|
895
|
+
const headers = getBaiduHeaders(cookie);
|
|
896
|
+
headers['Accept'] = 'application/json, text/plain, */*';
|
|
897
|
+
headers['X-Requested-With'] = 'XMLHttpRequest';
|
|
898
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
|
|
899
|
+
headers['Origin'] = 'https://pan.baidu.com';
|
|
900
|
+
headers['Sec-Fetch-Mode'] = 'cors';
|
|
901
|
+
headers['Sec-Fetch-Dest'] = 'empty';
|
|
902
|
+
headers['Sec-Fetch-Site'] = 'same-origin';
|
|
903
|
+
const form = new URLSearchParams({
|
|
904
|
+
fsidlist: '[' + paramsList[2].join(',') + ']',
|
|
905
|
+
path: folderPath,
|
|
906
|
+
}).toString();
|
|
907
|
+
const res = await ctx.http.post(url, form, { headers, params, timeout: 30000 });
|
|
908
|
+
return res?.errno ?? -1;
|
|
909
|
+
}
|
|
910
|
+
async function createShare(ctx, cookie, bdstoken, fsId, expired, password) {
|
|
911
|
+
const url = 'https://pan.baidu.com/share/set';
|
|
912
|
+
const params = {
|
|
913
|
+
channel: 'chunlei',
|
|
914
|
+
bdstoken,
|
|
915
|
+
clienttype: '0',
|
|
916
|
+
app_id: '250528',
|
|
917
|
+
web: '1',
|
|
918
|
+
};
|
|
919
|
+
const headers = getBaiduHeaders(cookie);
|
|
920
|
+
headers['Accept'] = 'application/json, text/plain, */*';
|
|
921
|
+
headers['X-Requested-With'] = 'XMLHttpRequest';
|
|
922
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
|
|
923
|
+
headers['Origin'] = 'https://pan.baidu.com';
|
|
924
|
+
headers['Sec-Fetch-Mode'] = 'cors';
|
|
925
|
+
headers['Sec-Fetch-Dest'] = 'empty';
|
|
926
|
+
headers['Sec-Fetch-Site'] = 'same-origin';
|
|
927
|
+
const form = new URLSearchParams({
|
|
928
|
+
period: String(expired),
|
|
929
|
+
pwd: password,
|
|
930
|
+
eflag_disable: 'true',
|
|
931
|
+
channel_list: '[]',
|
|
932
|
+
schannel: '4',
|
|
933
|
+
fid_list: '[' + String(fsId) + ']',
|
|
934
|
+
}).toString();
|
|
935
|
+
const res = await ctx.http.post(url, form, { headers, params, timeout: 20000 });
|
|
936
|
+
if (res?.errno !== 0)
|
|
937
|
+
return { errno: res?.errno ?? -1 };
|
|
938
|
+
return { errno: 0, link: res?.link };
|
|
939
|
+
}
|
|
940
|
+
async function baiduTransferAndShare(ctx, config, shareUrl, title, passcode, logger) {
|
|
941
|
+
let cookie = config.baiduCookie;
|
|
942
|
+
if (!cookie || cookie === 'BAIDU_COOKIE_PLACEHOLDER') {
|
|
943
|
+
return { success: false, message: '请在代码中填写有效的百度 Cookie' };
|
|
944
|
+
}
|
|
945
|
+
try {
|
|
946
|
+
// 1) 获取 bdstoken
|
|
947
|
+
const bdstoken = await getBdstoken(ctx, cookie);
|
|
948
|
+
if (typeof bdstoken !== 'string') {
|
|
949
|
+
return { success: false, message: getErrorMessage(Number(bdstoken)) };
|
|
950
|
+
}
|
|
951
|
+
// 2) 验证提取码(如有)并更新 BDCLND
|
|
952
|
+
const codeCandidate = (passcode && passcode.length === 4) ? passcode : (parsePasscodeFromUrl(shareUrl) || parsePasscode(title));
|
|
953
|
+
if (codeCandidate) {
|
|
954
|
+
logger.info(`转存阶段尝试验证提取码: ${codeCandidate}`);
|
|
955
|
+
}
|
|
956
|
+
else {
|
|
957
|
+
logger.info('转存阶段未获取到提取码,将直接尝试解析分享页参数');
|
|
958
|
+
}
|
|
959
|
+
if (codeCandidate && codeCandidate.length === 4) {
|
|
960
|
+
const randsk = await verifyPassCode(ctx, cookie, bdstoken, shareUrl, codeCandidate, logger);
|
|
961
|
+
if (typeof randsk !== 'string') {
|
|
962
|
+
logger.warn(`提取码验证失败: pwd=${codeCandidate}, errno=${randsk}`);
|
|
963
|
+
return { success: false, message: getErrorMessage(Number(randsk)) };
|
|
964
|
+
}
|
|
965
|
+
cookie = updateCookieBdclnd(cookie, randsk);
|
|
966
|
+
logger.info('已更新 BDCLND Cookie(自动识别提取码)');
|
|
967
|
+
}
|
|
968
|
+
// 3) 获取转存参数
|
|
969
|
+
const paramsList = await getTransferParams(ctx, cookie, shareUrl, logger);
|
|
970
|
+
if (typeof paramsList === 'number') {
|
|
971
|
+
return { success: false, message: getErrorMessage(paramsList) };
|
|
972
|
+
}
|
|
973
|
+
const [shareId, userId, fsIds, fileNames] = paramsList;
|
|
974
|
+
logger.info(`解析分享成功: shareid=${shareId}, uk=${userId}, fsids=${fsIds.length}`);
|
|
975
|
+
// 4) 确保转存目录存在
|
|
976
|
+
const folder = config.saveFolder;
|
|
977
|
+
let list = await getDirList(ctx, cookie, bdstoken, folder);
|
|
978
|
+
if (typeof list === 'number') {
|
|
979
|
+
// 目录不存在则创建
|
|
980
|
+
const createCode = await createDir(ctx, cookie, bdstoken, folder);
|
|
981
|
+
if (createCode !== 0 && createCode !== -8) { // -8 可能是已存在
|
|
982
|
+
return { success: false, message: getErrorMessage(createCode) };
|
|
983
|
+
}
|
|
984
|
+
list = await getDirList(ctx, cookie, bdstoken, folder);
|
|
985
|
+
if (typeof list === 'number') {
|
|
986
|
+
return { success: false, message: getErrorMessage(list) };
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
// 5) 执行转存
|
|
990
|
+
const code = await transferFile(ctx, cookie, bdstoken, paramsList, folder);
|
|
991
|
+
if (code !== 0) {
|
|
992
|
+
return { success: false, message: getErrorMessage(code) };
|
|
993
|
+
}
|
|
994
|
+
// 6) 在目录中定位已转存的条目,获取 fs_id(自己的网盘内)
|
|
995
|
+
const dirListAfter = await getDirList(ctx, cookie, bdstoken, folder);
|
|
996
|
+
if (typeof dirListAfter === 'number') {
|
|
997
|
+
return { success: false, message: getErrorMessage(dirListAfter) };
|
|
998
|
+
}
|
|
999
|
+
const targetNames = new Set(fileNames);
|
|
1000
|
+
const candidates = dirListAfter.filter(f => targetNames.has(f.server_filename));
|
|
1001
|
+
if (!candidates.length) {
|
|
1002
|
+
// 回退:使用原 fsIds 的第一个
|
|
1003
|
+
const fallbackFsId = fsIds[0];
|
|
1004
|
+
const shareRes = await createShare(ctx, cookie, bdstoken, fallbackFsId, config.expiredType, config.sharePassword);
|
|
1005
|
+
if (shareRes.errno !== 0)
|
|
1006
|
+
return { success: false, message: getErrorMessage(shareRes.errno) };
|
|
1007
|
+
return { success: true, shareUrl: shareRes.link };
|
|
1008
|
+
}
|
|
1009
|
+
// 7) 创建分享(取首个命中的 fs_id)
|
|
1010
|
+
const fsId = candidates[0].fs_id;
|
|
1011
|
+
const shareRes = await createShare(ctx, cookie, bdstoken, fsId, config.expiredType, config.sharePassword);
|
|
1012
|
+
if (shareRes.errno !== 0)
|
|
1013
|
+
return { success: false, message: getErrorMessage(shareRes.errno) };
|
|
1014
|
+
return { success: true, shareUrl: shareRes.link };
|
|
1015
|
+
}
|
|
1016
|
+
catch (error) {
|
|
1017
|
+
logger.error('转存出错:', error);
|
|
1018
|
+
return { success: false, message: error?.message || '转存过程出错' };
|
|
1019
|
+
}
|
|
1020
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-baidu-search",
|
|
3
|
+
"description": "电影电视剧百度网盘资源搜索插件",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"typings": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib",
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"chatbot",
|
|
14
|
+
"koishi",
|
|
15
|
+
"plugin",
|
|
16
|
+
"baidu",
|
|
17
|
+
"网盘"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc"
|
|
21
|
+
},
|
|
22
|
+
"koishi": {
|
|
23
|
+
"description": {
|
|
24
|
+
"zh": "电影电视剧百度网盘资源搜索插件"
|
|
25
|
+
},
|
|
26
|
+
"service": {
|
|
27
|
+
"required": [
|
|
28
|
+
"http"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"koishi": "^4.18.9"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"typescript": "^5.9.3"
|
|
37
|
+
}
|
|
38
|
+
}
|