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 +5 -1
- package/bin/bdp.js +42 -11
- package/lib/group.js +21 -3
- package/lib/partial.js +54 -0
- package/package.json +1 -1
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] [--
|
|
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
|
-
// --
|
|
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 (
|
|
495
|
-
fsMod.writeFileSync(
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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] [--
|
|
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
|
-
|
|
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
|
|
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
|
|
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 };
|