baiduwangpan-cli 1.1.2 → 1.1.5

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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # baiduwangpan-cli
4
4
 
5
- **百度网盘 CLI — 全盘读写 · 免下载读取 · 群聊文件浏览**
5
+ **百度网盘 CLI — 全盘读写 · 免下载读取 · 群聊文件浏览/下载**
6
6
 
7
7
  专为 AI Agent 设计的百度网盘命令行工具
8
8
 
@@ -21,7 +21,7 @@
21
21
 
22
22
  - **🗂️ 全盘访问** — 不受 `/apps/` 目录限制,读写网盘任意路径
23
23
  - **📄 免下载读取** — 通过 HTTP Range 直接读取文件内容到 stdout,无需下载整个文件
24
- - **👥 群聊文件浏览** — 逆向 mbox API,列出群组、浏览群内分享库、搜索群文件
24
+ - **👥 群聊文件浏览 + 下载** — 逆向 mbox API,列出群组、浏览群内分享库、搜索群文件,并支持**直接下载群文件到本地**(免转存)
25
25
  - **🤖 Agent 友好** — 所有命令支持 `--json` 结构化输出,零解析成本
26
26
  - **🔧 双模式** — 既是 CLI 工具,也可作为 Node.js 库 `require()` 使用
27
27
  - **🔐 凭证安全** — 配置存储在 `~/.bdp/config.json`,不写入代码或日志
@@ -43,6 +43,7 @@
43
43
  | **群内分享库** | `bdp gshares <gid>` | 顶层分享目录 |
44
44
  | **浏览群文件** | `bdp gls <gid> <fs_id>` | 分页 + 递归 |
45
45
  | **搜索群文件** | `bdp gsearch <gid> "关键词"` | 按文件名搜索(缓存命中秒回) |
46
+ | **下载群文件** | `bdp gdownload <gid> <fs_id>` | **直接下载到本地,免转存**(逆向 sharedownload API) |
46
47
  | 群目录树 | `bdp gtree <gid>` | BFS 构建目录树 |
47
48
  | 群缓存管理 | `bdp cache [clear]` | 查看 / 清空会话缓存 |
48
49
 
@@ -113,7 +114,8 @@ bdp cat /文档/config.json
113
114
  bdp groups
114
115
  bdp gshares 539478953581833690
115
116
  bdp gls 539478953581833690 742474845517885
116
- bdp gsearch 539478953581833690 "倪海厦"
117
+ # 下载群文件到本地 (免转存)
118
+ bdp gdownload 539478953581833690 954615608563687 -o ./downloads
117
119
  ```
118
120
 
119
121
  ## 📖 完整命令参考
@@ -146,10 +148,20 @@ bdp gtree <gid> [--depth N] [--concurrency N] [--max-nodes N]
146
148
  bdp gsearch <gid> <keyword> 搜索群文件名 (全量遍历目录)
147
149
  [--page N] [--limit N] [--depth N] [--concurrency N]
148
150
  [--max-pages N] [--max-requests N] [--no-unique]
149
- [--all|--all-results] [--timeout N] [--save-partial] [--no-cache]
151
+ [--all|--all-results] [--timeout N] [--save-partial]
152
+ [--any-word] [--exact] [--no-cache]
153
+ bdp gdownload <gid> <fs_id>... 直接下载群文件到本地 (逆向 /api/sharedownload, 免转存)
154
+ 单文件直下;多个 fs_id(同消息)自动 zip 打包下载
155
+ [-o dir] [--filename NAME] [--from-uk X] [--msg-id Y] [--no-cache]
150
156
  bdp cache [clear] 查看 / 清空会话缓存 (~/.bdp/cache/)
151
157
  ```
152
158
 
159
+ > **群文件下载限制说明**(实测,2026-08):
160
+ > - 单文件 ≤ ~20MB:直接下载 ✅
161
+ > - 同消息多文件:zip 打包下载 ✅(总包 ≤ ~100MB)
162
+ > - 大文件(> ~100MB):网页端受百度限制(单文件加密 / zip 报 `31090 package is too large`),需用官方客户端;转存接口已被百度下线(`errno=-10`)
163
+ > - 详见 `docs/group-download-final-verdict.md`
164
+
153
165
  ### 通用选项
154
166
 
155
167
  ```
@@ -175,6 +187,8 @@ bdp cache [clear] 查看 / 清空会话缓存 (~/.bdp/cache/)
175
187
  --all, --all-results 忽略分页获取全部结果 (gsearch; 不限时, 慢但完整)
176
188
  --timeout <N> 超时秒数,到点返回已扫到的部分结果 (gsearch; 0=不限时, 默认 0)
177
189
  --save-partial 未完整时自动把已搜到的结果存为 JSON 并打印路径 (gsearch; 配 --json-file 时写该文件)
190
+ --any-word 空格分隔关键词命中任意一个即可 (gsearch; 默认=全部命中 AND)
191
+ --exact 按文件名精确匹配,忽略路径前缀 (gsearch; 大小写不敏感)
178
192
  --no-cache 禁用会话缓存 (gsearch/gtree/gshares/gls)
179
193
  ```
180
194
 
@@ -221,12 +235,13 @@ const results = await group.searchFiles('539478953581833690', '倪海厦');
221
235
  │ (统一命令解析 + 路由) │
222
236
  ├──────────────────┬──────────────────────────────────┤
223
237
  │ lib/pan.js │ lib/group.js │
224
- │ 全盘文件操作 │ 群聊文件浏览 │
238
+ │ 全盘文件操作 │ 群聊文件浏览 + 下载 │
225
239
  ├──────────────────┼──────────────────────────────────┤
226
240
  │ BaiduPCS-Go │ lib/http.js (curl / browser) │
227
241
  │ (BDUSS 认证) │ mbox API (逆向) │
228
242
  │ locate → dlink │ /mbox/group/listshare │
229
243
  │ upload/download │ /mbox/msg/shareinfo │
244
+ │ │ /api/sharedownload → dlink │
230
245
  ├──────────────────┴──────────────────────────────────┤
231
246
  │ 百度网盘 CDN / API │
232
247
  └─────────────────────────────────────────────────────┘
package/bin/bdp.js CHANGED
@@ -21,6 +21,7 @@
21
21
  * bdp gshares <gid> 列出群内分享库
22
22
  * bdp gls <gid> <fs_id> 浏览分享库内容
23
23
  * bdp gsearch <gid> <keyword> 搜索群文件
24
+ * bdp gdownload <gid> <fs_id> 直接下载群文件到本地(免转存)
24
25
  *
25
26
  * 配置:
26
27
  * bdp login --bduss X --stoken Y 设置认证
@@ -91,6 +92,10 @@ function parseArgs(argv) {
91
92
  opts.flags.timeoutMs = parseInt(argv[++i], 10) || 0;
92
93
  } else if (arg === "--save-partial") {
93
94
  opts.flags.savePartial = true;
95
+ } else if (arg === "--any-word") {
96
+ opts.flags.anyWord = true;
97
+ } else if (arg === "--exact") {
98
+ opts.flags.exact = true;
94
99
  } else if (arg === "-n" || arg === "--lines") {
95
100
  opts.flags.n = parseInt(argv[++i], 10) || 20;
96
101
  } else if (arg === "-p" || arg === "--path") {
@@ -483,6 +488,8 @@ cmds.gsearch = async (args, json) => {
483
488
  concurrency: args.flags.concurrency || 4,
484
489
  unique: args.flags.unique !== false,
485
490
  all: args.flags.all === true,
491
+ exact: args.flags.exact === true,
492
+ anyWord: args.flags.anyWord === true,
486
493
  timeoutMs: args.flags.timeoutMs || (args.flags.timeout ? args.flags.timeout * 1000 : 0),
487
494
  depth: args.flags.depth || 1,
488
495
  maxPages: args.flags.maxPages || 50,
@@ -536,7 +543,7 @@ cmds.gsearch = async (args, json) => {
536
543
  );
537
544
  };
538
545
 
539
- 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} savePartial=${savePartial} depth=${opts.depth} maxPages=${opts.maxPages} maxRequests=${opts.maxRequests} cache=${opts.cache}`);
546
+ verboseLog(args, `gsearch gid=${gid} keyword="${keyword}" page=${opts.page} limit=${opts.limit} concurrency=${opts.concurrency} unique=${opts.unique} all=${opts.all} exact=${opts.exact} anyWord=${opts.anyWord} timeoutMs=${opts.timeoutMs} savePartial=${savePartial} depth=${opts.depth} maxPages=${opts.maxPages} maxRequests=${opts.maxRequests} cache=${opts.cache}`);
540
547
  const result = await group.searchFiles(gid, keyword, opts);
541
548
 
542
549
  if (result.partial) {
@@ -584,6 +591,33 @@ cmds.gsearch = async (args, json) => {
584
591
  }
585
592
  };
586
593
 
594
+ cmds.gdownload = async (args, json) => {
595
+ const gid = args._[0];
596
+ const fsIds = args._.slice(1);
597
+ if (!gid || fsIds.length === 0) { console.error("Usage: bdp gdownload <gid> <fs_id> [fs_id2 ...] [-o dir] [--from-uk X] [--msg-id Y] [--filename NAME]"); process.exit(1); }
598
+
599
+ const opts = {
600
+ cache: args.flags.noCache !== true,
601
+ };
602
+ if (args.flags.fromUk) opts.fromUk = args.flags.fromUk;
603
+ if (args.flags.msgId) opts.msgId = args.flags.msgId;
604
+ if (args.flags.o) opts.outDir = args.flags.o;
605
+ if (args.flags.filename) opts.filename = args.flags.filename;
606
+
607
+ verboseLog(args, `gdownload gid=${gid} fsIds=[${fsIds.join(",")}] outDir=${opts.outDir || process.cwd()} filename=${opts.filename || "auto"} fromUk=${opts.fromUk || "auto"} msgId=${opts.msgId || "auto"}`);
608
+
609
+ // 单文件 → 直接下载;多 fs_id(同一分享消息)→ zip 打包下载(逆向 type=batch)
610
+ const result = fsIds.length === 1
611
+ ? await group.downloadFile(gid, fsIds[0], opts)
612
+ : await group.downloadFiles(gid, fsIds, opts);
613
+
614
+ output(
615
+ { path: result.path, name: result.name, size: result.size, fsId: fsIds.length === 1 ? fsIds[0] : undefined, count: result.count, fromUk: result.fromUk, msgId: result.msgId },
616
+ json,
617
+ () => console.log(`✅ 已下载 ${result.size} 字节 → ${result.path}${result.count ? `(${result.count} 个文件 zip 打包)` : ""}`)
618
+ );
619
+ };
620
+
587
621
  cmds.cache = async (args, json) => {
588
622
  const action = args._[0] || "info";
589
623
  const { clearGroupCache } = group;
@@ -659,7 +693,11 @@ GROUP CHAT OPERATIONS (群聊)
659
693
  gsearch <gid> <keyword> Search group file names (全量遍历目录, 缓存命中秒回)
660
694
  [--page N] [--limit N] [--depth N] [--concurrency N]
661
695
  [--max-pages N] [--max-requests N] [--no-unique]
662
- [--all|--all-results] [--timeout N] [--save-partial] [--no-cache]
696
+ [--all|--all-results] [--timeout N] [--save-partial]
697
+ [--any-word] [--exact] [--no-cache]
698
+ gdownload <gid> <fs_id>... Direct download group files (免转存, 逆向 sharedownload API)
699
+ single fs_id → direct file; multiple fs_ids → zip 打包 (type=batch)
700
+ [--from-uk X] [--msg-id Y] [-o dir] [--filename NAME]
663
701
  cache [clear] Show cache info or clear session cache
664
702
  error <code> Explain an error code (e.g. -3)
665
703
 
@@ -692,6 +730,8 @@ OPTIONS
692
730
  --all, --all-results Fetch all results, no paging (gsearch; slow but never truncates)
693
731
  --timeout <N> Abort after N seconds and return partial results (gsearch; 0=unlimited)
694
732
  --save-partial Auto-save partial results to JSON when scan is incomplete (gsearch)
733
+ --any-word Match ANY of the space-separated keywords (OR) (gsearch; default=ALL)
734
+ --exact Match exact file name (case-insensitive) instead of substring (gsearch)
695
735
 
696
736
  EXAMPLES
697
737
  bdp login --bduss abc123 --stoken def456
@@ -704,6 +744,8 @@ EXAMPLES
704
744
  bdp gls 539478953581833690 292608024165826 --from-uk 2642611875 --msg-id 5069931974377329661 --json
705
745
  bdp gsearch 539478953581833690 "倪海厦" --limit 20 --json
706
746
  bdp gsearch 539478953581833690 "古籍" --page 2 --limit 20 --json-file result.json
747
+ bdp gdownload 539478953581833690 954615608563687 -o ./downloads --from-uk 1101635869133 --msg-id 713945176566573051
748
+ bdp gdownload 539478953581833690 527948256537856 986718405708023 -o ./zips # 多文件 zip 打包下载
707
749
  bdp error -3`;
708
750
 
709
751
  async function main() {
package/lib/group.js CHANGED
@@ -443,6 +443,186 @@ async function listFiles(gid, fsId, options = {}) {
443
443
  );
444
444
  }
445
445
 
446
+ // ── 群文件下载 (gdownload) ─────────────────────────────
447
+ //
448
+ // 逆向结论(2026-08-12):
449
+ // - /mbox/msg/transfer 转存已被百度拒绝(非空目录一律 errno=-10,UI 已移除保存按钮)
450
+ // - 新版 IM 前端的下载链路是 POST /api/sharedownload?sign=&timestamp=
451
+ // (sign/timestamp 留空即可),product=mbox 参数族,登录态直接返回可下载的 dlink
452
+ // - dlink 有 8 小时有效期;只支持单文件(目录 fs_id 返回的 dlink 为空)
453
+
454
+ // 解析 fs_id 的来源(fromUk/msgId):显式优先;顶层分享自动解析;否则报错提示
455
+ // 复用逻辑与 listFiles 一致,保证 gdownload 与 gls/gsearch 参数同源
456
+ async function resolveShareSource(gidS, fsIdS, options = {}, { cache }) {
457
+ let fromUkS = toStr(options.fromUk || "");
458
+ let msgIdS = toStr(options.msgId || "");
459
+ if (!fromUkS || !msgIdS) {
460
+ const shares = await listShares(gidS, { cache });
461
+ const match = shares.find((s) => s.fsId === fsIdS);
462
+ if (!match) {
463
+ throw new Error(
464
+ `Cannot determine fromUk/msgId for fsId ${fsIdS} (not a top-level share). ` +
465
+ `Pass --from-uk and --msg-id explicitly (use bdp gsearch/gsearch --json to get them).`
466
+ );
467
+ }
468
+ fromUkS = match.fromUk;
469
+ msgIdS = match.msgId;
470
+ }
471
+ return { fromUk: fromUkS, msgId: msgIdS };
472
+ }
473
+
474
+ // 获取群文件直链。返回 { dlink, name, size, isDir, md5, path, fsId, fromUk, msgId }
475
+ async function getShareDlink(gid, fsId, options = {}) {
476
+ const gidS = toStr(gid);
477
+ const fsIdS = toStr(fsId);
478
+ const cache = options.cache === true;
479
+
480
+ const { fromUk, msgId } = await resolveShareSource(gidS, fsIdS, options, { cache });
481
+ const bdstoken = await getBdstoken();
482
+ const url = `${http.PAN_BASE}/api/sharedownload?sign=&timestamp=&clienttype=0&app_id=${http.APP_ID}&web=1&bdstoken=${bdstoken}`;
483
+ const body = new URLSearchParams({
484
+ uk: fromUk,
485
+ product: "mbox",
486
+ encrypt: "0",
487
+ primaryid: msgId,
488
+ fid_list: JSON.stringify([fsIdS]),
489
+ extra: JSON.stringify({ type: "group", gid: gidS }),
490
+ }).toString();
491
+
492
+ const data = await http.webJson(url, { method: "POST", body });
493
+ if (data.errno !== 0) {
494
+ throw new Error(`API error: errno=${data.errno} (msg_id ${msgId} 不属于群 ${gidS} 或分享已失效)`);
495
+ }
496
+ const item = (data.list || [])[0];
497
+ if (!item) throw new Error(`No file info returned for fsId ${fsIdS}`);
498
+
499
+ const result = {
500
+ dlink: item.dlink || "",
501
+ name: item.server_filename || "",
502
+ size: parseInt(item.size || "0", 10),
503
+ isDir: item.isdir === 1 || item.isdir === "1",
504
+ md5: item.md5 || "",
505
+ path: safeDecode(item.path || ""),
506
+ fsId: toStr(item.fs_id),
507
+ fromUk,
508
+ msgId,
509
+ };
510
+ // 目录的 dlink 为无效串(实测 "&clienttype=0"),先于 dlink 判空检查
511
+ if (result.isDir) {
512
+ throw new Error(
513
+ `fsId ${fsIdS} 是目录(${result.name})。sharedownload 只支持单文件下载。` +
514
+ `请用 bdp gls <gid> <fs_id> 或 bdp gsearch <gid> <关键词> 找到具体文件后下载。`
515
+ );
516
+ }
517
+ if (!result.dlink) {
518
+ throw new Error(`No dlink returned for fsId ${fsIdS}(分享可能已失效或禁止下载)`);
519
+ }
520
+ return result;
521
+ }
522
+
523
+ // 下载群文件到本地。options: { outDir, filename, fromUk, msgId, ua }
524
+ // 返回 { path, name, size, dlink, fromUk, msgId }
525
+ async function downloadFile(gid, fsId, options = {}) {
526
+ const info = await getShareDlink(gid, fsId, options);
527
+ const { spawnSync } = require("child_process");
528
+ const fs = require("fs");
529
+ const path = require("path");
530
+
531
+ const outDir = options.outDir || process.cwd();
532
+ const filename = options.filename || info.name || `fs_${info.fsId}`;
533
+ const outPath = path.join(outDir, filename);
534
+
535
+ fs.mkdirSync(outDir, { recursive: true });
536
+ // dlink 校验 UA:netdisk 客户端 UA 会返回 118 字节错误页,浏览器 UA 正常(实测)
537
+ const args = [
538
+ "-s", "-L",
539
+ "-H", `User-Agent: ${options.ua || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}`,
540
+ "-o", outPath,
541
+ "--max-time", String(options.timeoutSec || 3600),
542
+ info.dlink,
543
+ ];
544
+ const result = spawnSync("curl", args, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024, timeout: (options.timeoutSec || 3600) * 1000 });
545
+ if (result.error) throw new Error(`Download failed: ${result.error.message}`);
546
+
547
+ let size = 0;
548
+ try { size = fs.statSync(outPath).size; } catch {}
549
+ if (info.size && size !== info.size) {
550
+ throw new Error(
551
+ `Download size mismatch: got ${size} bytes, expected ${info.size} bytes. ` +
552
+ `dlink 可能已过期(有效期 8h),请重试。`
553
+ );
554
+ }
555
+ return { path: outPath, name: filename, size, dlink: info.dlink, fromUk: info.fromUk, msgId: info.msgId };
556
+ }
557
+
558
+ // ── 群文件批量打包下载 (gdownload 多 fs_id → zip) ───────
559
+ //
560
+ // 逆向结论(2026-08-12 实测):
561
+ // - sharedownload 单文件 >~20MB 时返回加密 list(无法解密,key 未破解)
562
+ // - type=batch 时返回明文 zip 打包链接(method=batchdownload&zipcontent=...),
563
+ // 小文件打包实测可用(10MB zip 完整下载);超大文件报 error_code=31090 "package is too large"
564
+ // - zip 打包按 msg_id 归组:所有 fs_id 必须属于同一条分享消息
565
+
566
+ // 获取多文件 zip 打包直链。options: { fromUk, msgId, cache }
567
+ async function getShareZipDlink(gid, fsIds, options = {}) {
568
+ const gidS = toStr(gid);
569
+ const fsIdList = fsIds.map(toStr);
570
+ if (fsIdList.length < 2) throw new Error(`getShareZipDlink needs >= 2 fs_ids`);
571
+
572
+ // 来源以第一个 fs_id 为准(其余视为同一分享消息内的文件)
573
+ const { fromUk, msgId } = await resolveShareSource(gidS, fsIdList[0], options, { cache: options.cache === true });
574
+ const bdstoken = await getBdstoken();
575
+ const url = `${http.PAN_BASE}/api/sharedownload?sign=&timestamp=&clienttype=0&app_id=${http.APP_ID}&web=1&bdstoken=${bdstoken}`;
576
+ const body = new URLSearchParams({
577
+ uk: fromUk,
578
+ product: "mbox",
579
+ encrypt: "0",
580
+ type: "batch",
581
+ primaryid: msgId,
582
+ fid_list: JSON.stringify(fsIdList),
583
+ extra: JSON.stringify({ type: "group", gid: gidS }),
584
+ }).toString();
585
+
586
+ const data = await http.webJson(url, { method: "POST", body });
587
+ if (data.errno !== 0) {
588
+ throw new Error(`API error: errno=${data.errno} (zip batch)`);
589
+ }
590
+ if (!data.dlink) {
591
+ throw new Error(`No zip dlink returned for ${fsIdList.length} files`);
592
+ }
593
+ return { dlink: data.dlink, fromUk, msgId, count: fsIdList.length };
594
+ }
595
+
596
+ // 批量打包下载。options: { outDir, filename, fromUk, msgId, ua }
597
+ async function downloadFiles(gid, fsIds, options = {}) {
598
+ const { dlink, fromUk, msgId, count } = await getShareZipDlink(gid, fsIds, options);
599
+ const { spawnSync } = require("child_process");
600
+ const fs = require("fs");
601
+ const path = require("path");
602
+
603
+ const outDir = options.outDir || process.cwd();
604
+ const filename = options.filename || `bdp-group-${count}files.zip`;
605
+ const outPath = path.join(outDir, filename);
606
+
607
+ fs.mkdirSync(outDir, { recursive: true });
608
+ const args = [
609
+ "-s", "-L",
610
+ "-H", `User-Agent: ${options.ua || "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}`,
611
+ "-o", outPath,
612
+ "--max-time", String(options.timeoutSec || 7200),
613
+ dlink,
614
+ ];
615
+ const result = spawnSync("curl", args, { encoding: "utf-8", maxBuffer: 10 * 1024 * 1024, timeout: (options.timeoutSec || 7200) * 1000 });
616
+ if (result.error) throw new Error(`Download failed: ${result.error.message}`);
617
+
618
+ let size = 0;
619
+ try { size = fs.statSync(outPath).size; } catch {}
620
+ if (size === 0) {
621
+ throw new Error(`Zip download failed (0 bytes). dlink 可能已过期或包太大(error_code 31090)。`);
622
+ }
623
+ return { path: outPath, name: filename, size, dlink, fromUk, msgId, count };
624
+ }
625
+
446
626
  // ── 目录树 (gtree) ────────────────────────────────────
447
627
 
448
628
  async function treeFiles(gid, options = {}) {
@@ -514,7 +694,25 @@ async function treeFiles(gid, options = {}) {
514
694
 
515
695
  async function searchFiles(gid, keyword, options = {}) {
516
696
  const gidS = toStr(gid);
517
- const kw = keyword.toLowerCase();
697
+
698
+ // 关键词解析:空格分隔多关键词。
699
+ // - 默认(all):每个词都要出现(AND,可换序)→ "玄空 飞星" 只命中同时含两者的名字(精确收窄)
700
+ // - --any-word:任一词出现即可(OR,更宽)→ "玄空 飞星" 命中含"玄空"或"飞星"的名字
701
+ // - --exact:整名精确相等(最严格);单关键词默认仍是子串包含(兼容旧行为)
702
+ const rawKw = String(keyword).toLowerCase();
703
+ const words = rawKw.split(/\s+/).filter(Boolean);
704
+ const matchMode = options.exact ? "exact" : (options.anyWord ? "any" : "all");
705
+ const matchesName = (name) => {
706
+ const n = String(name || "").toLowerCase();
707
+ if (matchMode === "exact") {
708
+ // 精确匹配按文件名(去掉路径前缀),顶层分享 name 形如 "/xxx",子目录文件 name 无前缀
709
+ const base = n.includes("/") ? n.slice(n.lastIndexOf("/") + 1) : n;
710
+ return base === rawKw;
711
+ }
712
+ if (words.length <= 1) return n.includes(rawKw);
713
+ if (matchMode === "any") return words.some((w) => n.includes(w));
714
+ return words.every((w) => n.includes(w));
715
+ };
518
716
 
519
717
  const limit = options.limit || 50;
520
718
  const page = options.page || 1;
@@ -570,7 +768,7 @@ async function searchFiles(gid, keyword, options = {}) {
570
768
  for (const share of shares) {
571
769
  if (finished) break;
572
770
  if (!share.isDir) scannedShares++;
573
- if (share.name.toLowerCase().includes(kw)) {
771
+ if (matchesName(share.name)) {
574
772
  pushIfMatch({
575
773
  name: share.name,
576
774
  path: share.name,
@@ -617,7 +815,7 @@ async function searchFiles(gid, keyword, options = {}) {
617
815
  maxPages, cache, budget, throttleState,
618
816
  });
619
817
  if (cached) cachedDirs++;
620
- const matches = files.filter((f) => f.name.toLowerCase().includes(kw));
818
+ const matches = files.filter((f) => matchesName(f.name));
621
819
  const subDirs = files.filter((f) => f.isDir);
622
820
  return { matches, subDirs };
623
821
  } catch (e) {
@@ -733,5 +931,6 @@ async function searchFiles(gid, keyword, options = {}) {
733
931
 
734
932
  module.exports = {
735
933
  listGroups, listShares, listFiles, treeFiles, searchFiles,
934
+ getShareDlink, downloadFile, getShareZipDlink, downloadFiles,
736
935
  ShareApiError, describeErrno2131, clearGroupCache,
737
936
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baiduwangpan-cli",
3
- "version": "1.1.2",
3
+ "version": "1.1.5",
4
4
  "description": "百度网盘 CLI — 全盘读写 · 免下载读取文件内容 · 群聊文件浏览 (Agent 友好)",
5
5
  "main": "lib/index.js",
6
6
  "bin": {