baiduwangpan-cli 1.1.1 → 1.1.3

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
@@ -146,7 +146,8 @@ bdp gtree <gid> [--depth N] [--concurrency N] [--max-nodes N]
146
146
  bdp gsearch <gid> <keyword> 搜索群文件名 (全量遍历目录)
147
147
  [--page N] [--limit N] [--depth N] [--concurrency N]
148
148
  [--max-pages N] [--max-requests N] [--no-unique]
149
- [--all|--all-results] [--timeout N] [--no-cache]
149
+ [--all|--all-results] [--timeout N] [--save-partial]
150
+ [--any-word] [--exact] [--no-cache]
150
151
  bdp cache [clear] 查看 / 清空会话缓存 (~/.bdp/cache/)
151
152
  ```
152
153
 
@@ -174,6 +175,9 @@ bdp cache [clear] 查看 / 清空会话缓存 (~/.bdp/cache/)
174
175
  --no-unique 保留不同 msgId 的重复项 (gsearch)
175
176
  --all, --all-results 忽略分页获取全部结果 (gsearch; 不限时, 慢但完整)
176
177
  --timeout <N> 超时秒数,到点返回已扫到的部分结果 (gsearch; 0=不限时, 默认 0)
178
+ --save-partial 未完整时自动把已搜到的结果存为 JSON 并打印路径 (gsearch; 配 --json-file 时写该文件)
179
+ --any-word 空格分隔关键词命中任意一个即可 (gsearch; 默认=全部命中 AND)
180
+ --exact 按文件名精确匹配,忽略路径前缀 (gsearch; 大小写不敏感)
177
181
  --no-cache 禁用会话缓存 (gsearch/gtree/gshares/gls)
178
182
  ```
179
183
 
package/bin/bdp.js CHANGED
@@ -34,6 +34,7 @@
34
34
  const { config } = require("../lib");
35
35
  const pan = require("../lib/pan");
36
36
  const group = require("../lib/group");
37
+ const partial = require("../lib/partial");
37
38
 
38
39
  // ── Utils ──────────────────────────────────────────────
39
40
 
@@ -88,6 +89,12 @@ function parseArgs(argv) {
88
89
  opts.flags.timeout = parseInt(argv[++i], 10) || 0;
89
90
  } else if (arg === "--timeout-ms") {
90
91
  opts.flags.timeoutMs = parseInt(argv[++i], 10) || 0;
92
+ } else if (arg === "--save-partial") {
93
+ opts.flags.savePartial = true;
94
+ } else if (arg === "--any-word") {
95
+ opts.flags.anyWord = true;
96
+ } else if (arg === "--exact") {
97
+ opts.flags.exact = true;
91
98
  } else if (arg === "-n" || arg === "--lines") {
92
99
  opts.flags.n = parseInt(argv[++i], 10) || 20;
93
100
  } else if (arg === "-p" || arg === "--path") {
@@ -480,6 +487,8 @@ cmds.gsearch = async (args, json) => {
480
487
  concurrency: args.flags.concurrency || 4,
481
488
  unique: args.flags.unique !== false,
482
489
  all: args.flags.all === true,
490
+ exact: args.flags.exact === true,
491
+ anyWord: args.flags.anyWord === true,
483
492
  timeoutMs: args.flags.timeoutMs || (args.flags.timeout ? args.flags.timeout * 1000 : 0),
484
493
  depth: args.flags.depth || 1,
485
494
  maxPages: args.flags.maxPages || 50,
@@ -487,20 +496,26 @@ cmds.gsearch = async (args, json) => {
487
496
  cache: args.flags.noCache !== true,
488
497
  };
489
498
 
490
- // --json-file:扫描过程中持续写部分结果,即使被外部超时/杀掉,部分结果也已落盘
499
+ // --save-partial:未完整(超时/限流/预算/失败)时自动把已搜到的结果落盘;
500
+ // 未指定 --json-file 时自动生成路径(bdp-gsearch-<gid>-<kw>-partial-<ts>.json)
501
+ const savePartial = args.flags.savePartial === true;
491
502
  const jsonFile = args.flags.jsonFile;
503
+ const partialFile = jsonFile || (savePartial ? partial.defaultPartialFile(gid, keyword) : null);
492
504
  const fsMod = require("fs");
505
+
506
+ // 扫描过程中持续写部分结果(--json-file 或 --save-partial 自动文件),
507
+ // 即使被外部超时/杀掉,已搜到的结果也已落盘
493
508
  let lastProgressWrite = 0;
494
- if (jsonFile) {
495
- fsMod.writeFileSync(jsonFile, JSON.stringify({ running: true, results: [], complete: false, partial: true }, null, 2), "utf-8");
509
+ if (partialFile) {
510
+ fsMod.writeFileSync(partialFile, JSON.stringify({ running: true, results: [], complete: false, partial: true }, null, 2), "utf-8");
496
511
  }
497
512
  opts.onProgress = (snap) => {
498
- if (!jsonFile) return;
513
+ if (!partialFile) return;
499
514
  const now = Date.now();
500
515
  if (snap.running && now - lastProgressWrite < 400) return; // 节流,避免频繁写盘
501
516
  lastProgressWrite = now;
502
517
  fsMod.writeFileSync(
503
- jsonFile,
518
+ partialFile,
504
519
  JSON.stringify(
505
520
  {
506
521
  running: snap.running,
@@ -527,7 +542,7 @@ cmds.gsearch = async (args, json) => {
527
542
  );
528
543
  };
529
544
 
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}`);
545
+ 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}`);
531
546
  const result = await group.searchFiles(gid, keyword, opts);
532
547
 
533
548
  if (result.partial) {
@@ -542,10 +557,7 @@ cmds.gsearch = async (args, json) => {
542
557
 
543
558
  if (args.flags.legacyJson) {
544
559
  emitJson(result.results, args);
545
- return;
546
- }
547
-
548
- if (args.flags.jsonFile || json) {
560
+ } else if (args.flags.jsonFile || json) {
549
561
  emitJson(result, args);
550
562
  } else {
551
563
  console.log(`${result.returned} matches for "${keyword}"${result.hasMore ? " (has more — use --page " + (result.nextPage || "?") + ")" : ""}:\n`);
@@ -561,6 +573,21 @@ cmds.gsearch = async (args, json) => {
561
573
  console.error(`[verbose] ${result.failedShares} shares failed (partial result)`);
562
574
  }
563
575
  }
576
+
577
+ // --save-partial:未完整时把已搜到的结果写盘并告知路径(stderr,不污染 JSON stdout)
578
+ if (savePartial && partialFile && result.partial) {
579
+ if (!jsonFile) {
580
+ fsMod.writeFileSync(
581
+ partialFile,
582
+ JSON.stringify(partial.buildPartialPayload(result, { gid, keyword }), null, 2),
583
+ "utf-8"
584
+ );
585
+ }
586
+ console.error(`💾 部分结果已保存: ${partialFile}`);
587
+ } else if (savePartial && partialFile && !result.partial && !jsonFile) {
588
+ // 自动生成的文件且搜索完整 → 清理占位文件,避免留下垃圾
589
+ try { fsMod.unlinkSync(partialFile); } catch {}
590
+ }
564
591
  };
565
592
 
566
593
  cmds.cache = async (args, json) => {
@@ -638,7 +665,8 @@ GROUP CHAT OPERATIONS (群聊)
638
665
  gsearch <gid> <keyword> Search group file names (全量遍历目录, 缓存命中秒回)
639
666
  [--page N] [--limit N] [--depth N] [--concurrency N]
640
667
  [--max-pages N] [--max-requests N] [--no-unique]
641
- [--all|--all-results] [--timeout N] [--no-cache]
668
+ [--all|--all-results] [--timeout N] [--save-partial]
669
+ [--any-word] [--exact] [--no-cache]
642
670
  cache [clear] Show cache info or clear session cache
643
671
  error <code> Explain an error code (e.g. -3)
644
672
 
@@ -670,6 +698,9 @@ OPTIONS
670
698
  --no-unique Keep duplicates from different msgId (gsearch)
671
699
  --all, --all-results Fetch all results, no paging (gsearch; slow but never truncates)
672
700
  --timeout <N> Abort after N seconds and return partial results (gsearch; 0=unlimited)
701
+ --save-partial Auto-save partial results to JSON when scan is incomplete (gsearch)
702
+ --any-word Match ANY of the space-separated keywords (OR) (gsearch; default=ALL)
703
+ --exact Match exact file name (case-insensitive) instead of substring (gsearch)
673
704
 
674
705
  EXAMPLES
675
706
  bdp login --bduss abc123 --stoken def456
package/lib/group.js CHANGED
@@ -514,7 +514,25 @@ async function treeFiles(gid, options = {}) {
514
514
 
515
515
  async function searchFiles(gid, keyword, options = {}) {
516
516
  const gidS = toStr(gid);
517
- const kw = keyword.toLowerCase();
517
+
518
+ // 关键词解析:空格分隔多关键词。
519
+ // - 默认(all):每个词都要出现(AND,可换序)→ "玄空 飞星" 只命中同时含两者的名字(精确收窄)
520
+ // - --any-word:任一词出现即可(OR,更宽)→ "玄空 飞星" 命中含"玄空"或"飞星"的名字
521
+ // - --exact:整名精确相等(最严格);单关键词默认仍是子串包含(兼容旧行为)
522
+ const rawKw = String(keyword).toLowerCase();
523
+ const words = rawKw.split(/\s+/).filter(Boolean);
524
+ const matchMode = options.exact ? "exact" : (options.anyWord ? "any" : "all");
525
+ const matchesName = (name) => {
526
+ const n = String(name || "").toLowerCase();
527
+ if (matchMode === "exact") {
528
+ // 精确匹配按文件名(去掉路径前缀),顶层分享 name 形如 "/xxx",子目录文件 name 无前缀
529
+ const base = n.includes("/") ? n.slice(n.lastIndexOf("/") + 1) : n;
530
+ return base === rawKw;
531
+ }
532
+ if (words.length <= 1) return n.includes(rawKw);
533
+ if (matchMode === "any") return words.some((w) => n.includes(w));
534
+ return words.every((w) => n.includes(w));
535
+ };
518
536
 
519
537
  const limit = options.limit || 50;
520
538
  const page = options.page || 1;
@@ -570,7 +588,7 @@ async function searchFiles(gid, keyword, options = {}) {
570
588
  for (const share of shares) {
571
589
  if (finished) break;
572
590
  if (!share.isDir) scannedShares++;
573
- if (share.name.toLowerCase().includes(kw)) {
591
+ if (matchesName(share.name)) {
574
592
  pushIfMatch({
575
593
  name: share.name,
576
594
  path: share.name,
@@ -617,7 +635,7 @@ async function searchFiles(gid, keyword, options = {}) {
617
635
  maxPages, cache, budget, throttleState,
618
636
  });
619
637
  if (cached) cachedDirs++;
620
- const matches = files.filter((f) => f.name.toLowerCase().includes(kw));
638
+ const matches = files.filter((f) => matchesName(f.name));
621
639
  const subDirs = files.filter((f) => f.isDir);
622
640
  return { matches, subDirs };
623
641
  } catch (e) {
package/lib/partial.js ADDED
@@ -0,0 +1,54 @@
1
+ /**
2
+ * lib/partial.js — gsearch --save-partial 的纯工具函数
3
+ *
4
+ * 用途:搜索未完整(超时/限流/预算耗尽/失败)时,自动把已搜到的部分结果
5
+ * 落盘到 JSON 文件,避免"跑完了才发现只有 partial:true、结果却没保存"。
6
+ */
7
+
8
+ const path = require("path");
9
+
10
+ // 非法文件名字符(Windows/Linux 通用)
11
+ const ILLEGAL = /[\\/:*?"<>|]/g;
12
+
13
+ /**
14
+ * --save-partial 未指定 --json-file 时的默认保存路径:
15
+ * <cwd>/bdp-gsearch-<gid>-<keyword>-partial-<timestamp>.json
16
+ */
17
+ function defaultPartialFile(gid, keyword, options = {}) {
18
+ const cwd = options.cwd || process.cwd();
19
+ const date = options.date || new Date();
20
+ const safeKw = String(keyword == null ? "" : keyword).replace(ILLEGAL, "_").slice(0, 40) || "search";
21
+ const ts = date.toISOString().replace(/[:.]/g, "-");
22
+ return path.join(cwd, `bdp-gsearch-${String(gid)}-${safeKw}-partial-${ts}.json`);
23
+ }
24
+
25
+ /**
26
+ * 部分结果落盘对象:results 为已搜到的全部匹配(不翻页切片),
27
+ * 附带扫描诊断(scannedShares/totalShares/failedShares/stoppedReason 等),
28
+ * 供 Agent / 用户超时后直接读取。
29
+ */
30
+ function buildPartialPayload(result, meta = {}) {
31
+ return {
32
+ saved: "partial",
33
+ partial: true,
34
+ complete: false,
35
+ timedOut: !!result.timedOut,
36
+ stoppedReason: result.stoppedReason || null,
37
+ gid: meta.gid,
38
+ keyword: meta.keyword,
39
+ page: result.page,
40
+ pageSize: result.pageSize,
41
+ total: result.results.length,
42
+ results: result.results,
43
+ scannedShares: result.scannedShares,
44
+ totalShares: result.totalShares,
45
+ failedShares: result.failedShares,
46
+ throttledShares: result.throttledShares,
47
+ cachedDirs: result.cachedDirs,
48
+ budgetUsed: result.budgetUsed,
49
+ depth: result.depth,
50
+ maxPages: result.maxPages,
51
+ };
52
+ }
53
+
54
+ module.exports = { defaultPartialFile, buildPartialPayload };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baiduwangpan-cli",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "百度网盘 CLI — 全盘读写 · 免下载读取文件内容 · 群聊文件浏览 (Agent 友好)",
5
5
  "main": "lib/index.js",
6
6
  "bin": {