baiduwangpan-cli 1.6.0 → 1.7.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.
@@ -0,0 +1,109 @@
1
+ // lib/file-type.js — 二进制文件类型识别(魔数 + 扩展名兜底)
2
+ //
3
+ // 目标不是完整 MIME 表,而是回答两个问题:
4
+ // 1) 这是不是可以当文本读的文件?(否则 bdp cat 会把二进制乱码灌进 Agent 上下文)
5
+ // 2) 大概是什么类型?(让 bdp peek 报出 pdf/zip/image 这类可用的标签)
6
+
7
+ const MAGIC = [
8
+ { type: "pdf", bytes: [0x25, 0x50, 0x44, 0x46] }, // %PDF
9
+ { type: "zip", bytes: [0x50, 0x4b, 0x03, 0x04] }, // PK\x03\x04
10
+ { type: "gzip", bytes: [0x1f, 0x8b] },
11
+ { type: "png", bytes: [0x89, 0x50, 0x4e, 0x47] },
12
+ { type: "jpg", bytes: [0xff, 0xd8, 0xff] },
13
+ { type: "gif", bytes: [0x47, 0x49, 0x46, 0x38] },
14
+ { type: "bmp", bytes: [0x42, 0x4d] },
15
+ { type: "webp", bytes: [0x52, 0x49, 0x46, 0x46] }, // RIFF
16
+ { type: "wav", bytes: [0x52, 0x49, 0x46, 0x46] }, // RIFF(容器,看扩展名细分)
17
+ { type: "mp3", bytes: [0x49, 0x44, 0x33] },
18
+ { type: "flac", bytes: [0x66, 0x4c, 0x61, 0x43] },
19
+ { type: "elf", bytes: [0x7f, 0x45, 0x4c, 0x46] },
20
+ { type: "exe", bytes: [0x4d, 0x5a] }, // MZ
21
+ { type: "rar", bytes: [0x52, 0x61, 0x72, 0x21] },
22
+ { type: "7z", bytes: [0x37, 0x7a, 0xbc, 0xaf] },
23
+ { type: "mkv", bytes: [0x1a, 0x45, 0xdf, 0xa3] },
24
+ { type: "mp4", bytes: [0x66, 0x74, 0x79, 0x70] }, // ftyp(偏移 4)
25
+ { type: "sqlite", bytes: [0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66] },
26
+ ];
27
+
28
+ const EXT_TYPES = {
29
+ pdf: "pdf", doc: "doc", docx: "docx", xls: "xls", xlsx: "xlsx", ppt: "ppt", pptx: "pptx",
30
+ txt: "text", md: "text", markdown: "text", csv: "text", json: "text", jsonl: "text",
31
+ xml: "text", html: "text", htm: "text", log: "text", ini: "text", cfg: "text",
32
+ js: "text", mjs: "text", ts: "text", jsx: "text", tsx: "text", py: "text", rb: "text",
33
+ c: "text", cpp: "text", h: "text", hpp: "text", go: "text", rs: "text", java: "text",
34
+ sh: "text", bat: "text", ps1: "text", sql: "text", yaml: "text", yml: "text", toml: "text",
35
+ epub: "epub", mobi: "book", azw3: "book", djvu: "book",
36
+ zip: "zip", rar: "zip", "7z": "zip", gz: "zip", tgz: "zip", tar: "zip", bz2: "zip", xz: "zip",
37
+ png: "image", jpg: "image", jpeg: "image", gif: "image", bmp: "image", webp: "image",
38
+ tif: "image", tiff: "image", heic: "image", svg: "image",
39
+ mp3: "audio", wav: "audio", flac: "audio", m4a: "audio", aac: "audio", ogg: "audio", wma: "audio",
40
+ mp4: "video", mkv: "video", mov: "video", avi: "video", wmv: "video", flv: "video", m4v: "video",
41
+ };
42
+
43
+ const ZIP_CONTAINER_TYPES = ["docx", "xlsx", "pptx", "epub"];
44
+ const BINARY_CATEGORIES = ["image", "audio", "video"];
45
+
46
+ function extOf(p) {
47
+ const base = String(p || "").split(/[\\/]/).pop() || "";
48
+ const i = base.lastIndexOf(".");
49
+ if (i <= 0) return "";
50
+ return base.slice(i + 1).toLowerCase();
51
+ }
52
+
53
+ // 魔数检测(支持偏移量,如 mp4 的 "ftyp" 在偏移 4)
54
+ function magicType(buf) {
55
+ if (!buf || buf.length < 4) return null;
56
+ for (const m of MAGIC) {
57
+ const off = m.type === "mp4" ? 4 : 0;
58
+ if (buf.length < off + m.bytes.length) continue;
59
+ let ok = true;
60
+ for (let i = 0; i < m.bytes.length; i++) {
61
+ if (buf[off + i] !== m.bytes[i]) { ok = false; break; }
62
+ }
63
+ if (ok) return m.type;
64
+ }
65
+ return null;
66
+ }
67
+
68
+ // NUL 字节是二进制最可靠的信号之一(纯文本文件里几乎不会出现)
69
+ function hasNul(buf, limit = 8192) {
70
+ const end = Math.min(buf.length, limit);
71
+ for (let i = 0; i < end; i++) if (buf[i] === 0) return true;
72
+ return false;
73
+ }
74
+
75
+ /**
76
+ * 综合判定文件类型。
77
+ * @param {Buffer} buf 文件头部字节
78
+ * @param {string} p 文件路径(用于扩展名兜底)
79
+ * @returns {{ type: string, binary: boolean, reason: string }}
80
+ */
81
+ function detectFileType(buf, p) {
82
+ const magic = magicType(buf);
83
+ const ext = extOf(p);
84
+ const extType = EXT_TYPES[ext] || null;
85
+
86
+ if (magic) {
87
+ if (magic === "zip") {
88
+ const t = extType && ZIP_CONTAINER_TYPES.includes(extType) ? extType : "zip";
89
+ return { type: t, binary: true, reason: "magic:zip" };
90
+ }
91
+ if (magic === "riff") {
92
+ const t = extType === "wav" ? "wav" : extType === "webp" ? "webp" : "binary";
93
+ return { type: t, binary: true, reason: "magic:riff" };
94
+ }
95
+ return { type: magic, binary: true, reason: `magic:${magic}` };
96
+ }
97
+ if (hasNul(buf)) {
98
+ return { type: extType || "binary", binary: true, reason: "nul-byte" };
99
+ }
100
+ if (extType === "book") {
101
+ return { type: extType, binary: true, reason: `ext:${ext}` };
102
+ }
103
+ if (extType && BINARY_CATEGORIES.includes(extType)) {
104
+ return { type: extType, binary: true, reason: `ext:${ext}` };
105
+ }
106
+ return { type: extType || "text", binary: false, reason: "text" };
107
+ }
108
+
109
+ module.exports = { detectFileType, magicType, hasNul, extOf, EXT_TYPES };