baiduwangpan-cli 1.0.2 → 1.1.1
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 +47 -14
- package/bin/bdp.js +119 -11
- package/lib/browser-login.js +5 -10
- package/lib/config.js +64 -16
- package/lib/group.js +389 -64
- package/package.json +2 -2
- package/scripts/postinstall.js +110 -65
package/README.md
CHANGED
|
@@ -42,7 +42,9 @@
|
|
|
42
42
|
| **列出群组** | `bdp groups` | 所有群聊 + gid |
|
|
43
43
|
| **群内分享库** | `bdp gshares <gid>` | 顶层分享目录 |
|
|
44
44
|
| **浏览群文件** | `bdp gls <gid> <fs_id>` | 分页 + 递归 |
|
|
45
|
-
| **搜索群文件** | `bdp gsearch <gid> "关键词"` |
|
|
45
|
+
| **搜索群文件** | `bdp gsearch <gid> "关键词"` | 按文件名搜索(缓存命中秒回) |
|
|
46
|
+
| 群目录树 | `bdp gtree <gid>` | BFS 构建目录树 |
|
|
47
|
+
| 群缓存管理 | `bdp cache [clear]` | 查看 / 清空会话缓存 |
|
|
46
48
|
|
|
47
49
|
## 🚀 快速开始
|
|
48
50
|
|
|
@@ -52,7 +54,8 @@
|
|
|
52
54
|
npm install -g baiduwangpan-cli
|
|
53
55
|
```
|
|
54
56
|
|
|
55
|
-
安装时会**自动下载** BaiduPCS-Go
|
|
57
|
+
安装时会**自动下载** BaiduPCS-Go 引擎(镜像优先加速,国内也能装;全部下载源失败时打印手动下载地址,不阻断安装)。
|
|
58
|
+
CLI 会自动发现引擎(包根固定路径或 `BaiduPCS-Go-v4.0.1-windows-x64/` 版本子目录),升级不会丢 `pcsPath` 配置。也支持手动克隆:
|
|
56
59
|
|
|
57
60
|
```bash
|
|
58
61
|
git clone https://github.com/NkAntony777/baiduwangpan-cli.git
|
|
@@ -135,21 +138,43 @@ bdp rm <path> 删除文件/目录
|
|
|
135
138
|
|
|
136
139
|
```
|
|
137
140
|
bdp groups 列出所有群组
|
|
138
|
-
bdp gshares <gid>
|
|
139
|
-
bdp gls <gid> <fs_id> [--page N]
|
|
140
|
-
|
|
141
|
+
bdp gshares <gid> [--no-cache] 列出群内分享库 (自动游标翻页)
|
|
142
|
+
bdp gls <gid> <fs_id> [--page N] [--page-size N] [--from-uk X] [--msg-id Y]
|
|
143
|
+
[--parent-fs-id Z] [--no-cache] 浏览分享库内容
|
|
144
|
+
bdp gtree <gid> [--depth N] [--concurrency N] [--max-nodes N]
|
|
145
|
+
[--max-pages N] [--max-requests N] [--no-cache] 构建群目录树
|
|
146
|
+
bdp gsearch <gid> <keyword> 搜索群文件名 (全量遍历目录)
|
|
147
|
+
[--page N] [--limit N] [--depth N] [--concurrency N]
|
|
148
|
+
[--max-pages N] [--max-requests N] [--no-unique]
|
|
149
|
+
[--all|--all-results] [--timeout N] [--no-cache]
|
|
150
|
+
bdp cache [clear] 查看 / 清空会话缓存 (~/.bdp/cache/)
|
|
141
151
|
```
|
|
142
152
|
|
|
143
153
|
### 通用选项
|
|
144
154
|
|
|
145
155
|
```
|
|
146
156
|
--json JSON 结构化输出 (Agent 友好)
|
|
157
|
+
--json-file <path> JSON 写入文件 (UTF-8, 绕开控制台编码)
|
|
158
|
+
--verbose 进度/日志只写 stderr
|
|
147
159
|
-n <N> 行数 (head/tail, 默认 20)
|
|
148
160
|
-p <path> 搜索路径 (search, 默认 /)
|
|
149
161
|
-i 忽略大小写 (grep)
|
|
150
162
|
-N 显示行号 (cat/grep)
|
|
151
163
|
-o <dir> 输出目录 (get)
|
|
152
|
-
--page <N> 页码 (gls)
|
|
164
|
+
--page <N> 页码 (gls/gsearch, 默认 1)
|
|
165
|
+
--page-size <N> 每页条数 (gls, 默认 50, 最大 100)
|
|
166
|
+
--limit <N> 每页条数 (gsearch, 默认 50)
|
|
167
|
+
--depth <N> 递归深度 (gsearch 默认 1 / gtree 默认 2)
|
|
168
|
+
--concurrency <N> 并发扫描数 (gsearch/gtree, 默认 4, 1-8)
|
|
169
|
+
--max-nodes <N> 树节点上限 (gtree, 默认 2000)
|
|
170
|
+
--max-pages <N> 单目录最大页数 (gsearch/gtree, 默认 50, 100 条/页)
|
|
171
|
+
--max-requests <N> 单命令请求预算 (gsearch/gtree, 默认 400, 防限流)
|
|
172
|
+
--from-uk <X> / --msg-id <Y> 分享来源参数 (gls 子目录)
|
|
173
|
+
--parent-fs-id <Z> 父目录 fsId (gls, errno=-3 时回退用)
|
|
174
|
+
--no-unique 保留不同 msgId 的重复项 (gsearch)
|
|
175
|
+
--all, --all-results 忽略分页获取全部结果 (gsearch; 不限时, 慢但完整)
|
|
176
|
+
--timeout <N> 超时秒数,到点返回已扫到的部分结果 (gsearch; 0=不限时, 默认 0)
|
|
177
|
+
--no-cache 禁用会话缓存 (gsearch/gtree/gshares/gls)
|
|
153
178
|
```
|
|
154
179
|
|
|
155
180
|
## 🤖 Agent 集成示例
|
|
@@ -229,18 +254,25 @@ bdp groups bdp gshares <gid>
|
|
|
229
254
|
│ │
|
|
230
255
|
├── /mbox/group/list ├── /mbox/group/listshare
|
|
231
256
|
│ → 获取全部群组 (gid) │ → 获取分享库 (msg_id + from_uk + fs_id)
|
|
232
|
-
│ │
|
|
257
|
+
│ │ └ has_more=1 时按 last_msg_time 游标翻页
|
|
233
258
|
└───────────────────────────────────┘
|
|
234
259
|
↓
|
|
235
|
-
bdp gls <gid> <fs_id>
|
|
236
|
-
│
|
|
237
|
-
├── /mbox/msg/shareinfo
|
|
238
|
-
│ → 浏览文件/子目录
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
文件列表 (名称/大小/类型/fs_id)
|
|
260
|
+
bdp gls <gid> <fs_id> bdp gsearch <gid> "关键词"
|
|
261
|
+
│ │
|
|
262
|
+
├── /mbox/msg/shareinfo ├── 全量遍历: shareinfo 自动翻页 (每页上限 100)
|
|
263
|
+
│ → 浏览文件/子目录 │ → 目录级并发扫描 + 磁盘缓存
|
|
264
|
+
↓ ↓
|
|
265
|
+
文件列表 (名称/大小/类型/fs_id) 匹配结果
|
|
242
266
|
```
|
|
243
267
|
|
|
268
|
+
**逆向实测结论(2026-08)**:
|
|
269
|
+
|
|
270
|
+
- 群聊文件**没有服务端搜索接口**(`listshare`/`shareinfo` 的 `key`/`keyword` 参数被忽略,`/api/search` 不支持群维度)——网页端搜索同样是客户端遍历,`gsearch` 走的是同一条路。
|
|
271
|
+
- `shareinfo` 每页**硬上限 100 条**(`num>100` 不返回更多),`page` 翻页可用 → `gsearch`/`gtree` 自动全页遍历,不再漏数据。
|
|
272
|
+
- 高频调用会触发限流,表现为 `errno=-3`、**self-echo**(`errno=0` 但返回目录自身)、或空列表;工具自动退避重试、连续限流停止扫描,重跑命令即续扫(`--max-requests` 可控制单命令请求预算,默认 400)。
|
|
273
|
+
- 群文件路径可能含**非法百分号编码**(裸 `%`),直接 `decodeURIComponent` 会抛 `URIError` 报废整个目录 → 已改为安全解码。
|
|
274
|
+
- `gsearch`/`gtree`/`gshares` 默认启用**磁盘会话缓存**(目录 30min / 分享 5min,`~/.bdp/cache/`,`--no-cache` 关闭、`bdp cache clear` 清理)——重复搜索秒回,深扫续扫收敛。
|
|
275
|
+
|
|
244
276
|
## 📁 项目结构
|
|
245
277
|
|
|
246
278
|
```
|
|
@@ -314,6 +346,7 @@ skills/baiduwangpan/
|
|
|
314
346
|
- **下载限速**:非 SVIP 用户下载受百度限速策略限制
|
|
315
347
|
- **cat 限制**:为防止误读超大文件,`cat` 默认限制 1MB,可通过 `BDP_MAX_CAT_BYTES` 环境变量调整
|
|
316
348
|
- **群聊文件**:群聊 API 为逆向获取,可能随百度服务端变更而失效
|
|
349
|
+
- **1.1.0 升级**:若引擎丢失(postinstall 网络失败),CLI 会自动发现 `BaiduPCS-Go-v*` 版本子目录中的二进制;config 里失效的绝对 `pcsPath` 也会自动回退到自动发现,无需手改配置
|
|
317
350
|
|
|
318
351
|
## 🤝 致谢
|
|
319
352
|
|
package/bin/bdp.js
CHANGED
|
@@ -82,8 +82,12 @@ function parseArgs(argv) {
|
|
|
82
82
|
opts.flags.unique = true;
|
|
83
83
|
} else if (arg === "--no-unique") {
|
|
84
84
|
opts.flags.unique = false;
|
|
85
|
-
} else if (arg === "--all") {
|
|
85
|
+
} else if (arg === "--all" || arg === "--all-results") {
|
|
86
86
|
opts.flags.all = true;
|
|
87
|
+
} else if (arg === "--timeout") {
|
|
88
|
+
opts.flags.timeout = parseInt(argv[++i], 10) || 0;
|
|
89
|
+
} else if (arg === "--timeout-ms") {
|
|
90
|
+
opts.flags.timeoutMs = parseInt(argv[++i], 10) || 0;
|
|
87
91
|
} else if (arg === "-n" || arg === "--lines") {
|
|
88
92
|
opts.flags.n = parseInt(argv[++i], 10) || 20;
|
|
89
93
|
} else if (arg === "-p" || arg === "--path") {
|
|
@@ -112,6 +116,12 @@ function parseArgs(argv) {
|
|
|
112
116
|
opts.flags.depth = parseInt(argv[++i], 10) || 2;
|
|
113
117
|
} else if (arg === "--max-nodes") {
|
|
114
118
|
opts.flags.maxNodes = parseInt(argv[++i], 10) || 2000;
|
|
119
|
+
} else if (arg === "--max-pages") {
|
|
120
|
+
opts.flags.maxPages = parseInt(argv[++i], 10) || 50;
|
|
121
|
+
} else if (arg === "--max-requests") {
|
|
122
|
+
opts.flags.maxRequests = parseInt(argv[++i], 10) || 400;
|
|
123
|
+
} else if (arg === "--no-cache") {
|
|
124
|
+
opts.flags.noCache = true;
|
|
115
125
|
} else if (arg === "--from-uk") {
|
|
116
126
|
opts.flags.fromUk = argv[++i];
|
|
117
127
|
} else if (arg === "--msg-id") {
|
|
@@ -379,7 +389,7 @@ cmds.groups = async (args, json) => {
|
|
|
379
389
|
cmds.gshares = async (args, json) => {
|
|
380
390
|
const gid = args._[0];
|
|
381
391
|
if (!gid) { console.error("Usage: bdp gshares <gid>"); process.exit(1); }
|
|
382
|
-
const shares = await group.listShares(gid);
|
|
392
|
+
const shares = await group.listShares(gid, { cache: args.flags.noCache !== true });
|
|
383
393
|
output(shares, json, (list) => {
|
|
384
394
|
console.log(`${list.length} shares in group ${gid}:\n`);
|
|
385
395
|
list.forEach((s, i) => {
|
|
@@ -397,6 +407,7 @@ cmds.gls = async (args, json) => {
|
|
|
397
407
|
const opts = {
|
|
398
408
|
page: args.flags.page || 1,
|
|
399
409
|
pageSize: args.flags.pageSize || 50,
|
|
410
|
+
cache: args.flags.noCache !== true,
|
|
400
411
|
};
|
|
401
412
|
if (args.flags.fromUk) opts.fromUk = args.flags.fromUk;
|
|
402
413
|
if (args.flags.msgId) opts.msgId = args.flags.msgId;
|
|
@@ -410,6 +421,10 @@ cmds.gls = async (args, json) => {
|
|
|
410
421
|
return;
|
|
411
422
|
}
|
|
412
423
|
|
|
424
|
+
if (result.autoResolved) {
|
|
425
|
+
console.error(`[auto] 传入的 fromUk/msgId 与群 ${gid} 不匹配(API errno=2131),已自动改用 gshares 解析的参数 (fromUk=${result.fromUk}, msgId=${result.msgId})`);
|
|
426
|
+
}
|
|
427
|
+
|
|
413
428
|
if (result.fallback) {
|
|
414
429
|
verboseLog(args, `fallback: ${result.fallback.reason} -> ${result.fallback.resolvedFsId} (${result.fallback.level})`);
|
|
415
430
|
}
|
|
@@ -436,8 +451,11 @@ cmds.gtree = async (args, json) => {
|
|
|
436
451
|
depth: args.flags.depth || 2,
|
|
437
452
|
concurrency: args.flags.concurrency || 4,
|
|
438
453
|
maxNodes: args.flags.maxNodes || 2000,
|
|
454
|
+
maxPages: args.flags.maxPages || 50,
|
|
455
|
+
maxRequests: args.flags.maxRequests || 400,
|
|
456
|
+
cache: args.flags.noCache !== true,
|
|
439
457
|
};
|
|
440
|
-
verboseLog(args, `gtree gid=${gid} depth=${opts.depth} concurrency=${opts.concurrency} maxNodes=${opts.maxNodes}`);
|
|
458
|
+
verboseLog(args, `gtree gid=${gid} depth=${opts.depth} concurrency=${opts.concurrency} maxNodes=${opts.maxNodes} maxPages=${opts.maxPages} maxRequests=${opts.maxRequests} cache=${opts.cache}`);
|
|
441
459
|
const result = await group.treeFiles(gid, opts);
|
|
442
460
|
|
|
443
461
|
if (args.flags.jsonFile || json) {
|
|
@@ -462,14 +480,64 @@ cmds.gsearch = async (args, json) => {
|
|
|
462
480
|
concurrency: args.flags.concurrency || 4,
|
|
463
481
|
unique: args.flags.unique !== false,
|
|
464
482
|
all: args.flags.all === true,
|
|
483
|
+
timeoutMs: args.flags.timeoutMs || (args.flags.timeout ? args.flags.timeout * 1000 : 0),
|
|
465
484
|
depth: args.flags.depth || 1,
|
|
485
|
+
maxPages: args.flags.maxPages || 50,
|
|
486
|
+
maxRequests: args.flags.maxRequests || 400,
|
|
487
|
+
cache: args.flags.noCache !== true,
|
|
466
488
|
};
|
|
467
489
|
|
|
468
|
-
|
|
490
|
+
// --json-file:扫描过程中持续写部分结果,即使被外部超时/杀掉,部分结果也已落盘
|
|
491
|
+
const jsonFile = args.flags.jsonFile;
|
|
492
|
+
const fsMod = require("fs");
|
|
493
|
+
let lastProgressWrite = 0;
|
|
494
|
+
if (jsonFile) {
|
|
495
|
+
fsMod.writeFileSync(jsonFile, JSON.stringify({ running: true, results: [], complete: false, partial: true }, null, 2), "utf-8");
|
|
496
|
+
}
|
|
497
|
+
opts.onProgress = (snap) => {
|
|
498
|
+
if (!jsonFile) return;
|
|
499
|
+
const now = Date.now();
|
|
500
|
+
if (snap.running && now - lastProgressWrite < 400) return; // 节流,避免频繁写盘
|
|
501
|
+
lastProgressWrite = now;
|
|
502
|
+
fsMod.writeFileSync(
|
|
503
|
+
jsonFile,
|
|
504
|
+
JSON.stringify(
|
|
505
|
+
{
|
|
506
|
+
running: snap.running,
|
|
507
|
+
results: snap.results,
|
|
508
|
+
page: snap.page,
|
|
509
|
+
pageSize: snap.pageSize,
|
|
510
|
+
complete: snap.complete,
|
|
511
|
+
partial: snap.partial,
|
|
512
|
+
timedOut: snap.timedOut,
|
|
513
|
+
stoppedReason: snap.stoppedReason,
|
|
514
|
+
scannedShares: snap.scannedShares,
|
|
515
|
+
totalShares: snap.totalShares,
|
|
516
|
+
failedShares: snap.failedShares,
|
|
517
|
+
throttledShares: snap.throttledShares,
|
|
518
|
+
cachedDirs: snap.cachedDirs,
|
|
519
|
+
budgetUsed: snap.budgetUsed,
|
|
520
|
+
depth: snap.depth,
|
|
521
|
+
maxPages: snap.maxPages,
|
|
522
|
+
},
|
|
523
|
+
null,
|
|
524
|
+
2
|
|
525
|
+
),
|
|
526
|
+
"utf-8"
|
|
527
|
+
);
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
verboseLog(args, `gsearch gid=${gid} keyword="${keyword}" page=${opts.page} limit=${opts.limit} concurrency=${opts.concurrency} unique=${opts.unique} all=${opts.all} timeoutMs=${opts.timeoutMs} depth=${opts.depth} maxPages=${opts.maxPages} maxRequests=${opts.maxRequests} cache=${opts.cache}`);
|
|
469
531
|
const result = await group.searchFiles(gid, keyword, opts);
|
|
470
532
|
|
|
471
533
|
if (result.partial) {
|
|
472
534
|
verboseLog(args, `partial: ${result.failedShares}/${result.totalShares} shares failed`);
|
|
535
|
+
if (result.failedDirs && result.failedDirs.length > 0) {
|
|
536
|
+
verboseLog(args, `failed dirs: ${result.failedDirs.map((f) => `${f.name || f.fsId}(${f.errno})`).slice(0, 10).join(", ")}`);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
if (result.throttled) {
|
|
540
|
+
verboseLog(args, `throttled: API 限流中断,重跑命令可续扫(磁盘缓存只补缺失目录)`);
|
|
473
541
|
}
|
|
474
542
|
|
|
475
543
|
if (args.flags.legacyJson) {
|
|
@@ -485,19 +553,52 @@ cmds.gsearch = async (args, json) => {
|
|
|
485
553
|
const dir = r.isDir ? "[DIR] " : " ";
|
|
486
554
|
console.log(` ${dir}${r.path || r.name} ${formatSize(r.size)} fs_id:${r.fsId}`);
|
|
487
555
|
});
|
|
488
|
-
if (result.
|
|
556
|
+
if (result.timedOut) {
|
|
557
|
+
console.error(`⚠️ 搜索超时(${Math.round(opts.timeoutMs / 1000)}s),已返回部分结果(complete:false, partial:true)。重跑或增大 --timeout / 用 --all-results 不限时`);
|
|
558
|
+
} else if (result.throttled) {
|
|
559
|
+
console.error(`⚠️ API 限流中断(已扫描 ${result.scannedShares}/${result.totalShares} 分享,失败 ${result.failedShares})。重跑命令可续扫(磁盘缓存加速)`);
|
|
560
|
+
} else if (result.partial) {
|
|
489
561
|
console.error(`[verbose] ${result.failedShares} shares failed (partial result)`);
|
|
490
562
|
}
|
|
491
563
|
}
|
|
492
564
|
};
|
|
493
565
|
|
|
566
|
+
cmds.cache = async (args, json) => {
|
|
567
|
+
const action = args._[0] || "info";
|
|
568
|
+
const { clearGroupCache } = group;
|
|
569
|
+
if (action === "clear") {
|
|
570
|
+
clearGroupCache();
|
|
571
|
+
output({ cleared: true }, json, () => console.log("✅ 群聊会话缓存已清除"));
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
// info: 显示缓存目录位置
|
|
575
|
+
const path = require("path");
|
|
576
|
+
const os = require("os");
|
|
577
|
+
const dir = process.env.BDP_CACHE_DIR || path.join(os.homedir(), ".bdp", "cache");
|
|
578
|
+
output({ cacheDir: dir, ttl: { dir: "30min", shares: "5min" } }, json, () => {
|
|
579
|
+
console.log(`Cache dir: ${dir}`);
|
|
580
|
+
console.log(`Dir TTL: 30 min (gsearch/gtree 目录列表)`);
|
|
581
|
+
console.log(`Shares TTL: 5 min (gshares 分享列表)`);
|
|
582
|
+
console.log(`Clear with: bdp cache clear`);
|
|
583
|
+
});
|
|
584
|
+
};
|
|
585
|
+
|
|
494
586
|
cmds.error = async (args, json) => {
|
|
495
587
|
const code = args._[0] || "-3";
|
|
496
588
|
const ERRORS = {
|
|
497
|
-
"-3": "群分享 API 拒绝请求。已观察到大目录、错误的 msgId/fromUk
|
|
498
|
-
"处理:减小 --page-size(自动重试 50→20→10
|
|
589
|
+
"-3": "群分享 API 拒绝请求。已观察到大目录、错误的 msgId/fromUk、过期分享、请求过于频繁(限流)都可能触发。\n" +
|
|
590
|
+
"处理:减小 --page-size(自动重试 50→20→10)或 --concurrency;\n" +
|
|
591
|
+
" 限流表现为 errno=0 但只返回目录自身(self-echo),工具会自动退避重试(1s/2s);\n" +
|
|
592
|
+
" 大目录已支持自动全页遍历(每页上限 100,--max-pages 控制页数);\n" +
|
|
499
593
|
" 若回退到父目录(fallback level=parent),Agent 应从父目录换路径继续遍历。",
|
|
500
594
|
"-6": "接口鉴权失败。通常为 access_token/cookie 失效,请重新执行 bdp login。",
|
|
595
|
+
"2131": "群分享 msg_id 不属于该群(shareinfo 以 msg_id 为查找键,from_uk 不参与校验)。\n" +
|
|
596
|
+
"常见原因:gid 与 --from-uk/--msg-id/--parent-fs-id 来自不同群/分享库(参数错配)。\n" +
|
|
597
|
+
"处理:用 bdp gshares <gid> 查看本群分享(或 bdp gsearch <gid> <关键词>),\n" +
|
|
598
|
+
" 取其中一行的 fsId/fromUk/msgId 重试,保证 gid/fsId/fromUk/msgId 同源;\n" +
|
|
599
|
+
" fsId 为子目录时保持其父分享行的 fromUk/msgId 不变;\n" +
|
|
600
|
+
" 或去掉 --from-uk/--msg-id 让 CLI 自动解析(仅顶层分享)。\n" +
|
|
601
|
+
" 工具已支持:显式参数错配时自动按 fsId 纠正(结果含 autoResolved:true)。",
|
|
501
602
|
};
|
|
502
603
|
const msg = ERRORS[code] || `未知错误码 ${code},请提交 Issue: https://github.com/NkAntony777/baiduwangpan-cli/issues`;
|
|
503
604
|
if (args.flags.jsonFile || json) {
|
|
@@ -534,8 +635,11 @@ GROUP CHAT OPERATIONS (群聊)
|
|
|
534
635
|
[--depth N] [--concurrency N] [--max-nodes N]
|
|
535
636
|
gls <gid> <fs_id> Browse files in a share
|
|
536
637
|
[--page N] [--page-size N] [--from-uk X] [--msg-id Y] [--parent-fs-id Z]
|
|
537
|
-
gsearch <gid> <keyword> Search group file names
|
|
538
|
-
[--page N] [--limit N] [--depth N] [--concurrency N]
|
|
638
|
+
gsearch <gid> <keyword> Search group file names (全量遍历目录, 缓存命中秒回)
|
|
639
|
+
[--page N] [--limit N] [--depth N] [--concurrency N]
|
|
640
|
+
[--max-pages N] [--max-requests N] [--no-unique]
|
|
641
|
+
[--all|--all-results] [--timeout N] [--no-cache]
|
|
642
|
+
cache [clear] Show cache info or clear session cache
|
|
539
643
|
error <code> Explain an error code (e.g. -3)
|
|
540
644
|
|
|
541
645
|
CONFIGURATION
|
|
@@ -559,9 +663,13 @@ OPTIONS
|
|
|
559
663
|
--limit <N> Page size for gsearch (default 50)
|
|
560
664
|
--depth <N> Recursive search/tree depth (gsearch default 1, gtree default 2)
|
|
561
665
|
--max-nodes <N> Tree node cap (gtree default 2000)
|
|
666
|
+
--max-pages <N> Max pages per directory scan (gsearch/gtree default 50, 100 items/page)
|
|
667
|
+
--max-requests <N> Request budget per command (gsearch/gtree default 400, prevents throttle)
|
|
562
668
|
--concurrency <N> Parallel share scans for gsearch/gtree (default 4)
|
|
669
|
+
--no-cache Disable in-process session cache (gsearch/gtree/gshares/gls)
|
|
563
670
|
--no-unique Keep duplicates from different msgId (gsearch)
|
|
564
|
-
--all
|
|
671
|
+
--all, --all-results Fetch all results, no paging (gsearch; slow but never truncates)
|
|
672
|
+
--timeout <N> Abort after N seconds and return partial results (gsearch; 0=unlimited)
|
|
565
673
|
|
|
566
674
|
EXAMPLES
|
|
567
675
|
bdp login --bduss abc123 --stoken def456
|
|
@@ -594,7 +702,7 @@ async function main() {
|
|
|
594
702
|
await handler(args, json);
|
|
595
703
|
} catch (e) {
|
|
596
704
|
if (json) {
|
|
597
|
-
console.log(JSON.stringify({ error: e.message }));
|
|
705
|
+
console.log(JSON.stringify({ error: e.message, ...(e.errno !== undefined ? { errno: e.errno } : {}) }));
|
|
598
706
|
} else {
|
|
599
707
|
console.error(`[ERROR] ${e.message}`);
|
|
600
708
|
}
|
package/lib/browser-login.js
CHANGED
|
@@ -281,15 +281,10 @@ async function getBrowserSession(settings = {}) {
|
|
|
281
281
|
|
|
282
282
|
async function browserJson(url, options = {}, settings = {}) {
|
|
283
283
|
const session = await getBrowserSession(settings);
|
|
284
|
-
|
|
285
|
-
try {
|
|
286
|
-
response = await evaluateFetch(session.cdp, url, options);
|
|
287
|
-
} catch (error) {
|
|
288
|
-
session.cdp.close();
|
|
289
|
-
activeSession = null;
|
|
290
|
-
throw error;
|
|
291
|
-
}
|
|
284
|
+
const response = await evaluateFetch(session.cdp, url, options);
|
|
292
285
|
|
|
286
|
+
// 注意:fetch 错误(网络/API 断连)不关闭共享会话——并发批处理中一个请求失败会
|
|
287
|
+
// 连带杀死同批其他在途请求(ws 关闭)。会话死活由下次调用前的 ping 自动检测重连。
|
|
293
288
|
try {
|
|
294
289
|
return JSON.parse(response.text.replace(/^\s+/, ""));
|
|
295
290
|
} catch {
|
|
@@ -384,8 +379,8 @@ async function browserLogin({
|
|
|
384
379
|
|
|
385
380
|
function scheduleDisconnect() {
|
|
386
381
|
if (disconnectTimer) clearTimeout(disconnectTimer);
|
|
387
|
-
// 空闲
|
|
388
|
-
disconnectTimer = setTimeout(disconnectBrowserSession,
|
|
382
|
+
// 空闲 90 秒后断开浏览器会话(并发批处理扫描中请求间隔可能较长,短超时会中途断连)
|
|
383
|
+
disconnectTimer = setTimeout(disconnectBrowserSession, 90000);
|
|
389
384
|
disconnectTimer.unref();
|
|
390
385
|
}
|
|
391
386
|
|
package/lib/config.js
CHANGED
|
@@ -5,6 +5,8 @@ const os = require("os");
|
|
|
5
5
|
const CONFIG_DIR = process.env.BDP_CONFIG_DIR || path.join(os.homedir(), ".bdp");
|
|
6
6
|
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
7
7
|
|
|
8
|
+
const PCS_BINARY_NAMES = ["BaiduPCS-Go.exe", "BaiduPCS-Go"];
|
|
9
|
+
|
|
8
10
|
function load() {
|
|
9
11
|
try {
|
|
10
12
|
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
@@ -19,6 +21,49 @@ function save(cfg) {
|
|
|
19
21
|
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), "utf-8");
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
// 在目录内查找 BaiduPCS-Go 二进制:
|
|
25
|
+
// 1) 固定路径(postinstall 目标,如 node_modules/baiduwangpan-cli/BaiduPCS-Go.exe)
|
|
26
|
+
// 2) 版本子目录(如 BaiduPCS-Go-v4.0.1-windows-x64/BaiduPCS-Go.exe,
|
|
27
|
+
// postinstall 失败后手动解压的常见位置)
|
|
28
|
+
function findPCSInDir(dir) {
|
|
29
|
+
if (!dir) return null;
|
|
30
|
+
for (const name of PCS_BINARY_NAMES) {
|
|
31
|
+
const p = path.join(dir, name);
|
|
32
|
+
try {
|
|
33
|
+
if (fs.existsSync(p) && fs.statSync(p).isFile()) return p;
|
|
34
|
+
} catch {}
|
|
35
|
+
}
|
|
36
|
+
let entries;
|
|
37
|
+
try {
|
|
38
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (!entry.isDirectory() || !entry.name.includes("BaiduPCS-Go")) continue;
|
|
44
|
+
for (const name of PCS_BINARY_NAMES) {
|
|
45
|
+
const p = path.join(dir, entry.name, name);
|
|
46
|
+
try {
|
|
47
|
+
if (fs.existsSync(p) && fs.statSync(p).isFile()) return p;
|
|
48
|
+
} catch {}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function findPCS() {
|
|
55
|
+
// 1. 包根目录(postinstall 目标):固定路径 + 版本子目录
|
|
56
|
+
const pkgRoot = path.join(__dirname, "..");
|
|
57
|
+
const found = findPCSInDir(pkgRoot);
|
|
58
|
+
if (found) return found;
|
|
59
|
+
// 2. 全局 node_modules 目录(含版本子目录)
|
|
60
|
+
const nmGlobal = path.join(__dirname, "..", "..");
|
|
61
|
+
const foundGlobal = findPCSInDir(nmGlobal);
|
|
62
|
+
if (foundGlobal) return foundGlobal;
|
|
63
|
+
// 3. Fallback to PATH
|
|
64
|
+
return "BaiduPCS-Go";
|
|
65
|
+
}
|
|
66
|
+
|
|
22
67
|
function get() {
|
|
23
68
|
const fileCfg = load();
|
|
24
69
|
return {
|
|
@@ -28,13 +73,29 @@ function get() {
|
|
|
28
73
|
webTransport: fileCfg.webTransport || "curl",
|
|
29
74
|
browserProfile: fileCfg.browserProfile || "",
|
|
30
75
|
browserPort: parseInt(fileCfg.browserPort || "0", 10) || 0,
|
|
31
|
-
pcsPath: fileCfg
|
|
76
|
+
pcsPath: resolvePcsPath(fileCfg),
|
|
32
77
|
ua: fileCfg.ua || process.env.NETDISK_UA ||
|
|
33
78
|
"netdisk;P2SP;3.0.0.8;netdisk;11.12.3;ANG-AN00;android-android;10.0;JSbridge4.4.0;jointBridge;1.1.0;",
|
|
34
79
|
maxBytes: parseInt(fileCfg.maxBytes || process.env.BDP_MAX_CAT_BYTES || "1048576", 10),
|
|
35
80
|
};
|
|
36
81
|
}
|
|
37
82
|
|
|
83
|
+
// pcsPath 解析顺序:config 文件 → 环境变量 BAIDUPCS_CMD → 自动发现。
|
|
84
|
+
// 兼容 1.1.0 升级丢路径问题:若 config 里存的绝对路径已失效(升级后旧包被替换),
|
|
85
|
+
// 自动回退到自动发现(findPCS 会搜版本子目录),避免 search/cat/get/grep 全部报
|
|
86
|
+
// "BaiduPCS-Go not found at: BaiduPCS-Go"。
|
|
87
|
+
function resolvePcsPath(fileCfg) {
|
|
88
|
+
const stored = fileCfg.pcsPath || process.env.BAIDUPCS_CMD || "";
|
|
89
|
+
const auto = findPCS();
|
|
90
|
+
if (stored && path.isAbsolute(stored)) {
|
|
91
|
+
try {
|
|
92
|
+
if (fs.existsSync(stored)) return stored;
|
|
93
|
+
} catch {}
|
|
94
|
+
return auto; // 绝对路径已失效 → 自动发现
|
|
95
|
+
}
|
|
96
|
+
return stored || auto;
|
|
97
|
+
}
|
|
98
|
+
|
|
38
99
|
function set(key, value) {
|
|
39
100
|
const cfg = load();
|
|
40
101
|
cfg[key] = value;
|
|
@@ -50,26 +111,13 @@ function setAuth(values) {
|
|
|
50
111
|
for (const key of ["bduss", "stoken", "cookie", "webTransport", "browserProfile", "browserPort"]) {
|
|
51
112
|
delete cfg[key];
|
|
52
113
|
}
|
|
114
|
+
// setAuth 只替换认证相关字段,保留 pcsPath/ua/maxBytes 等自定义配置(升级不丢 pcsPath)
|
|
53
115
|
save({ ...cfg, ...values });
|
|
54
116
|
}
|
|
55
117
|
|
|
56
|
-
function findPCS() {
|
|
57
|
-
// 1. Look in package root (postinstall target)
|
|
58
|
-
const pkgRoot = path.join(__dirname, "..");
|
|
59
|
-
for (const name of ["BaiduPCS-Go.exe", "BaiduPCS-Go"]) {
|
|
60
|
-
const p = path.join(pkgRoot, name);
|
|
61
|
-
if (fs.existsSync(p)) return p;
|
|
62
|
-
}
|
|
63
|
-
// 2. Look in global node_modules
|
|
64
|
-
const nmGlobal = path.join(__dirname, "..", "..", "BaiduPCS-Go.exe");
|
|
65
|
-
if (fs.existsSync(nmGlobal)) return nmGlobal;
|
|
66
|
-
// 3. Fallback to PATH
|
|
67
|
-
return "BaiduPCS-Go";
|
|
68
|
-
}
|
|
69
|
-
|
|
70
118
|
function isLoggedIn() {
|
|
71
119
|
const cfg = get();
|
|
72
120
|
return !!(cfg.bduss && cfg.stoken);
|
|
73
121
|
}
|
|
74
122
|
|
|
75
|
-
module.exports = { get, set, update, setAuth, load, save, isLoggedIn, CONFIG_DIR, CONFIG_FILE };
|
|
123
|
+
module.exports = { get, set, update, setAuth, load, save, isLoggedIn, findPCS, findPCSInDir, resolvePcsPath, CONFIG_DIR, CONFIG_FILE };
|