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.
- package/LICENSE +21 -21
- package/README.md +504 -471
- package/bin/bdp.js +1647 -1452
- package/lib/browser-login.js +427 -427
- package/lib/config.js +213 -213
- package/lib/console.js +99 -99
- package/lib/file-type.js +109 -0
- package/lib/group.js +929 -925
- package/lib/http.js +130 -130
- package/lib/index.js +7 -7
- package/lib/mcp.js +287 -0
- package/lib/name-match.js +67 -67
- package/lib/pan.js +1034 -767
- package/lib/partial.js +53 -53
- package/lib/pcs-extra.js +327 -318
- package/lib/table.js +173 -0
- package/package.json +48 -48
- package/scripts/postinstall.js +340 -340
package/lib/config.js
CHANGED
|
@@ -1,213 +1,213 @@
|
|
|
1
|
-
const fs = require("fs");
|
|
2
|
-
const path = require("path");
|
|
3
|
-
const os = require("os");
|
|
4
|
-
|
|
5
|
-
const CONFIG_DIR = process.env.BDP_CONFIG_DIR || path.join(os.homedir(), ".bdp");
|
|
6
|
-
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
7
|
-
|
|
8
|
-
const PCS_BINARY_NAMES = ["BaiduPCS-Go.exe", "BaiduPCS-Go"];
|
|
9
|
-
|
|
10
|
-
// 认证相关字段:profile 只覆盖这些字段,pcsPath/ua/maxBytes 保持全局
|
|
11
|
-
const AUTH_KEYS = ["bduss", "stoken", "cookie", "webTransport", "browserProfile", "browserPort"];
|
|
12
|
-
|
|
13
|
-
function load() {
|
|
14
|
-
try {
|
|
15
|
-
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
16
|
-
return JSON.parse(raw);
|
|
17
|
-
} catch {
|
|
18
|
-
return {};
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function save(cfg) {
|
|
23
|
-
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
24
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), "utf-8");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// 在目录内查找 BaiduPCS-Go 二进制:
|
|
28
|
-
// 1) 固定路径(postinstall 目标,如 node_modules/baiduwangpan-cli/BaiduPCS-Go.exe)
|
|
29
|
-
// 2) 版本子目录(如 BaiduPCS-Go-v4.0.1-windows-x64/BaiduPCS-Go.exe,
|
|
30
|
-
// postinstall 失败后手动解压的常见位置)
|
|
31
|
-
function findPCSInDir(dir) {
|
|
32
|
-
if (!dir) return null;
|
|
33
|
-
for (const name of PCS_BINARY_NAMES) {
|
|
34
|
-
const p = path.join(dir, name);
|
|
35
|
-
try {
|
|
36
|
-
if (fs.existsSync(p) && fs.statSync(p).isFile()) return p;
|
|
37
|
-
} catch {}
|
|
38
|
-
}
|
|
39
|
-
let entries;
|
|
40
|
-
try {
|
|
41
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
42
|
-
} catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
for (const entry of entries) {
|
|
46
|
-
if (!entry.isDirectory() || !entry.name.includes("BaiduPCS-Go")) continue;
|
|
47
|
-
for (const name of PCS_BINARY_NAMES) {
|
|
48
|
-
const p = path.join(dir, entry.name, name);
|
|
49
|
-
try {
|
|
50
|
-
if (fs.existsSync(p) && fs.statSync(p).isFile()) return p;
|
|
51
|
-
} catch {}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function findPCS() {
|
|
58
|
-
// 1. 包根目录(postinstall 目标):固定路径 + 版本子目录
|
|
59
|
-
const pkgRoot = path.join(__dirname, "..");
|
|
60
|
-
const found = findPCSInDir(pkgRoot);
|
|
61
|
-
if (found) return found;
|
|
62
|
-
// 2. 全局 node_modules 目录(含版本子目录)
|
|
63
|
-
const nmGlobal = path.join(__dirname, "..", "..");
|
|
64
|
-
const foundGlobal = findPCSInDir(nmGlobal);
|
|
65
|
-
if (foundGlobal) return foundGlobal;
|
|
66
|
-
// 3. Fallback to PATH
|
|
67
|
-
return "BaiduPCS-Go";
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function get() {
|
|
71
|
-
const fileCfg = load();
|
|
72
|
-
const profile = profileOf(fileCfg);
|
|
73
|
-
const src = { ...fileCfg };
|
|
74
|
-
if (profile) {
|
|
75
|
-
// profile 只覆盖认证相关字段;pcsPath/ua/maxBytes 保持全局
|
|
76
|
-
for (const key of AUTH_KEYS) {
|
|
77
|
-
if (key in profile) src[key] = profile[key];
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return {
|
|
81
|
-
bduss: src.bduss || process.env.BDP_BDUSS || "",
|
|
82
|
-
stoken: src.stoken || process.env.BDP_STOKEN || "",
|
|
83
|
-
cookie: src.cookie || process.env.BDP_COOKIE || "",
|
|
84
|
-
webTransport: src.webTransport || "curl",
|
|
85
|
-
browserProfile: src.browserProfile || "",
|
|
86
|
-
browserPort: parseInt(src.browserPort || "0", 10) || 0,
|
|
87
|
-
pcsPath: resolvePcsPath(src),
|
|
88
|
-
ua: src.ua || process.env.NETDISK_UA ||
|
|
89
|
-
"netdisk;P2SP;3.0.0.8;netdisk;11.12.3;ANG-AN00;android-android;10.0;JSbridge4.4.0;jointBridge;1.1.0;",
|
|
90
|
-
maxBytes: parseInt(src.maxBytes || process.env.BDP_MAX_CAT_BYTES || "1048576", 10),
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// pcsPath 解析顺序:config 文件 → 环境变量 BAIDUPCS_CMD → 自动发现。
|
|
95
|
-
// 兼容 1.1.0 升级丢路径问题:若 config 里存的绝对路径已失效(升级后旧包被替换),
|
|
96
|
-
// 自动回退到自动发现(findPCS 会搜版本子目录),避免 search/cat/get/grep 全部报
|
|
97
|
-
// "BaiduPCS-Go not found at: BaiduPCS-Go"。
|
|
98
|
-
function resolvePcsPath(fileCfg) {
|
|
99
|
-
const stored = fileCfg.pcsPath || process.env.BAIDUPCS_CMD || "";
|
|
100
|
-
const auto = findPCS();
|
|
101
|
-
if (stored && path.isAbsolute(stored)) {
|
|
102
|
-
try {
|
|
103
|
-
if (fs.existsSync(stored)) return stored;
|
|
104
|
-
} catch {}
|
|
105
|
-
return auto; // 绝对路径已失效 → 自动发现
|
|
106
|
-
}
|
|
107
|
-
return stored || auto;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function set(key, value) {
|
|
111
|
-
const cfg = load();
|
|
112
|
-
cfg[key] = value;
|
|
113
|
-
save(cfg);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function update(values) {
|
|
117
|
-
save({ ...load(), ...values });
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function setAuth(values) {
|
|
121
|
-
const cfg = load();
|
|
122
|
-
const name = activeProfileName(cfg);
|
|
123
|
-
const prof = profileOf(cfg);
|
|
124
|
-
if (name && prof) {
|
|
125
|
-
// 激活 profile:只重写该 profile 的认证字段,全局字段与其他 profile 不受影响
|
|
126
|
-
const merged = { ...prof };
|
|
127
|
-
for (const key of AUTH_KEYS) delete merged[key];
|
|
128
|
-
cfg.profiles[name] = { ...merged, ...values };
|
|
129
|
-
save(cfg);
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
// 无激活 profile:保持原语义,认证字段写在全局
|
|
133
|
-
for (const key of AUTH_KEYS) {
|
|
134
|
-
delete cfg[key];
|
|
135
|
-
}
|
|
136
|
-
// setAuth 只替换认证相关字段,保留 pcsPath/ua/maxBytes 等自定义配置(升级不丢 pcsPath)
|
|
137
|
-
save({ ...cfg, ...values });
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function isLoggedIn() {
|
|
141
|
-
const cfg = get();
|
|
142
|
-
return !!(cfg.bduss && cfg.stoken);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// ---------- profile(多账号)----------
|
|
146
|
-
// 存储:config.json 内 "profiles": { "<name>": { 认证字段覆盖 } } + "activeProfile": "<name>"。
|
|
147
|
-
// 无 activeProfile 时一切行为与单账号完全一致(向后兼容)。
|
|
148
|
-
|
|
149
|
-
function activeProfileName(fileCfg = null) {
|
|
150
|
-
const cfg = fileCfg || load();
|
|
151
|
-
const name = cfg.activeProfile;
|
|
152
|
-
return typeof name === "string" && name !== "" ? name : null;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// 返回激活 profile 的对象;activeProfile 未设置或对应条目缺失/畸形时返回 null
|
|
156
|
-
function profileOf(fileCfg) {
|
|
157
|
-
const name = activeProfileName(fileCfg);
|
|
158
|
-
if (!name) return null;
|
|
159
|
-
const profiles = fileCfg.profiles;
|
|
160
|
-
if (!profiles || typeof profiles !== "object" || Array.isArray(profiles)) return null;
|
|
161
|
-
const p = profiles[name];
|
|
162
|
-
if (!p || typeof p !== "object" || Array.isArray(p)) return null;
|
|
163
|
-
return p;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// 切换当前 profile 并持久化;name 为空串/null/undefined 表示切回全局(等价 unsetProfile())
|
|
167
|
-
function useProfile(name) {
|
|
168
|
-
if (name === undefined || name === null) return unsetProfile();
|
|
169
|
-
const n = String(name).trim();
|
|
170
|
-
if (n === "") return unsetProfile();
|
|
171
|
-
const cfg = load();
|
|
172
|
-
if (!cfg.profiles || typeof cfg.profiles !== "object" || Array.isArray(cfg.profiles)) {
|
|
173
|
-
cfg.profiles = {};
|
|
174
|
-
}
|
|
175
|
-
const existing = cfg.profiles[n];
|
|
176
|
-
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
177
|
-
cfg.profiles[n] = {}; // name 不存在时建空 profile
|
|
178
|
-
}
|
|
179
|
-
cfg.activeProfile = n;
|
|
180
|
-
save(cfg);
|
|
181
|
-
return n;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// 切回全局:删除 activeProfile;未激活时不做任何写操作
|
|
185
|
-
function unsetProfile() {
|
|
186
|
-
const cfg = load();
|
|
187
|
-
if (!("activeProfile" in cfg)) return null;
|
|
188
|
-
delete cfg.activeProfile;
|
|
189
|
-
save(cfg);
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// 当前 profile 名(无 → null)
|
|
194
|
-
function getProfile() {
|
|
195
|
-
return activeProfileName();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// [{ name, bduss? }],按配置文件中的存储顺序
|
|
199
|
-
function listProfiles() {
|
|
200
|
-
const cfg = load();
|
|
201
|
-
const profiles = cfg.profiles;
|
|
202
|
-
if (!profiles || typeof profiles !== "object" || Array.isArray(profiles)) return [];
|
|
203
|
-
const out = [];
|
|
204
|
-
for (const [name, p] of Object.entries(profiles)) {
|
|
205
|
-
if (!p || typeof p !== "object" || Array.isArray(p)) continue;
|
|
206
|
-
const entry = { name };
|
|
207
|
-
if (p.bduss) entry.bduss = p.bduss;
|
|
208
|
-
out.push(entry);
|
|
209
|
-
}
|
|
210
|
-
return out;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
module.exports = { get, set, update, setAuth, load, save, isLoggedIn, useProfile, unsetProfile, getProfile, listProfiles, findPCS, findPCSInDir, resolvePcsPath, CONFIG_DIR, CONFIG_FILE };
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
|
|
5
|
+
const CONFIG_DIR = process.env.BDP_CONFIG_DIR || path.join(os.homedir(), ".bdp");
|
|
6
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
7
|
+
|
|
8
|
+
const PCS_BINARY_NAMES = ["BaiduPCS-Go.exe", "BaiduPCS-Go"];
|
|
9
|
+
|
|
10
|
+
// 认证相关字段:profile 只覆盖这些字段,pcsPath/ua/maxBytes 保持全局
|
|
11
|
+
const AUTH_KEYS = ["bduss", "stoken", "cookie", "webTransport", "browserProfile", "browserPort"];
|
|
12
|
+
|
|
13
|
+
function load() {
|
|
14
|
+
try {
|
|
15
|
+
const raw = fs.readFileSync(CONFIG_FILE, "utf-8");
|
|
16
|
+
return JSON.parse(raw);
|
|
17
|
+
} catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function save(cfg) {
|
|
23
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
24
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(cfg, null, 2), "utf-8");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 在目录内查找 BaiduPCS-Go 二进制:
|
|
28
|
+
// 1) 固定路径(postinstall 目标,如 node_modules/baiduwangpan-cli/BaiduPCS-Go.exe)
|
|
29
|
+
// 2) 版本子目录(如 BaiduPCS-Go-v4.0.1-windows-x64/BaiduPCS-Go.exe,
|
|
30
|
+
// postinstall 失败后手动解压的常见位置)
|
|
31
|
+
function findPCSInDir(dir) {
|
|
32
|
+
if (!dir) return null;
|
|
33
|
+
for (const name of PCS_BINARY_NAMES) {
|
|
34
|
+
const p = path.join(dir, name);
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(p) && fs.statSync(p).isFile()) return p;
|
|
37
|
+
} catch {}
|
|
38
|
+
}
|
|
39
|
+
let entries;
|
|
40
|
+
try {
|
|
41
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
if (!entry.isDirectory() || !entry.name.includes("BaiduPCS-Go")) continue;
|
|
47
|
+
for (const name of PCS_BINARY_NAMES) {
|
|
48
|
+
const p = path.join(dir, entry.name, name);
|
|
49
|
+
try {
|
|
50
|
+
if (fs.existsSync(p) && fs.statSync(p).isFile()) return p;
|
|
51
|
+
} catch {}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function findPCS() {
|
|
58
|
+
// 1. 包根目录(postinstall 目标):固定路径 + 版本子目录
|
|
59
|
+
const pkgRoot = path.join(__dirname, "..");
|
|
60
|
+
const found = findPCSInDir(pkgRoot);
|
|
61
|
+
if (found) return found;
|
|
62
|
+
// 2. 全局 node_modules 目录(含版本子目录)
|
|
63
|
+
const nmGlobal = path.join(__dirname, "..", "..");
|
|
64
|
+
const foundGlobal = findPCSInDir(nmGlobal);
|
|
65
|
+
if (foundGlobal) return foundGlobal;
|
|
66
|
+
// 3. Fallback to PATH
|
|
67
|
+
return "BaiduPCS-Go";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function get() {
|
|
71
|
+
const fileCfg = load();
|
|
72
|
+
const profile = profileOf(fileCfg);
|
|
73
|
+
const src = { ...fileCfg };
|
|
74
|
+
if (profile) {
|
|
75
|
+
// profile 只覆盖认证相关字段;pcsPath/ua/maxBytes 保持全局
|
|
76
|
+
for (const key of AUTH_KEYS) {
|
|
77
|
+
if (key in profile) src[key] = profile[key];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
bduss: src.bduss || process.env.BDP_BDUSS || "",
|
|
82
|
+
stoken: src.stoken || process.env.BDP_STOKEN || "",
|
|
83
|
+
cookie: src.cookie || process.env.BDP_COOKIE || "",
|
|
84
|
+
webTransport: src.webTransport || "curl",
|
|
85
|
+
browserProfile: src.browserProfile || "",
|
|
86
|
+
browserPort: parseInt(src.browserPort || "0", 10) || 0,
|
|
87
|
+
pcsPath: resolvePcsPath(src),
|
|
88
|
+
ua: src.ua || process.env.NETDISK_UA ||
|
|
89
|
+
"netdisk;P2SP;3.0.0.8;netdisk;11.12.3;ANG-AN00;android-android;10.0;JSbridge4.4.0;jointBridge;1.1.0;",
|
|
90
|
+
maxBytes: parseInt(src.maxBytes || process.env.BDP_MAX_CAT_BYTES || "1048576", 10),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// pcsPath 解析顺序:config 文件 → 环境变量 BAIDUPCS_CMD → 自动发现。
|
|
95
|
+
// 兼容 1.1.0 升级丢路径问题:若 config 里存的绝对路径已失效(升级后旧包被替换),
|
|
96
|
+
// 自动回退到自动发现(findPCS 会搜版本子目录),避免 search/cat/get/grep 全部报
|
|
97
|
+
// "BaiduPCS-Go not found at: BaiduPCS-Go"。
|
|
98
|
+
function resolvePcsPath(fileCfg) {
|
|
99
|
+
const stored = fileCfg.pcsPath || process.env.BAIDUPCS_CMD || "";
|
|
100
|
+
const auto = findPCS();
|
|
101
|
+
if (stored && path.isAbsolute(stored)) {
|
|
102
|
+
try {
|
|
103
|
+
if (fs.existsSync(stored)) return stored;
|
|
104
|
+
} catch {}
|
|
105
|
+
return auto; // 绝对路径已失效 → 自动发现
|
|
106
|
+
}
|
|
107
|
+
return stored || auto;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function set(key, value) {
|
|
111
|
+
const cfg = load();
|
|
112
|
+
cfg[key] = value;
|
|
113
|
+
save(cfg);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function update(values) {
|
|
117
|
+
save({ ...load(), ...values });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function setAuth(values) {
|
|
121
|
+
const cfg = load();
|
|
122
|
+
const name = activeProfileName(cfg);
|
|
123
|
+
const prof = profileOf(cfg);
|
|
124
|
+
if (name && prof) {
|
|
125
|
+
// 激活 profile:只重写该 profile 的认证字段,全局字段与其他 profile 不受影响
|
|
126
|
+
const merged = { ...prof };
|
|
127
|
+
for (const key of AUTH_KEYS) delete merged[key];
|
|
128
|
+
cfg.profiles[name] = { ...merged, ...values };
|
|
129
|
+
save(cfg);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// 无激活 profile:保持原语义,认证字段写在全局
|
|
133
|
+
for (const key of AUTH_KEYS) {
|
|
134
|
+
delete cfg[key];
|
|
135
|
+
}
|
|
136
|
+
// setAuth 只替换认证相关字段,保留 pcsPath/ua/maxBytes 等自定义配置(升级不丢 pcsPath)
|
|
137
|
+
save({ ...cfg, ...values });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isLoggedIn() {
|
|
141
|
+
const cfg = get();
|
|
142
|
+
return !!(cfg.bduss && cfg.stoken);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---------- profile(多账号)----------
|
|
146
|
+
// 存储:config.json 内 "profiles": { "<name>": { 认证字段覆盖 } } + "activeProfile": "<name>"。
|
|
147
|
+
// 无 activeProfile 时一切行为与单账号完全一致(向后兼容)。
|
|
148
|
+
|
|
149
|
+
function activeProfileName(fileCfg = null) {
|
|
150
|
+
const cfg = fileCfg || load();
|
|
151
|
+
const name = cfg.activeProfile;
|
|
152
|
+
return typeof name === "string" && name !== "" ? name : null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 返回激活 profile 的对象;activeProfile 未设置或对应条目缺失/畸形时返回 null
|
|
156
|
+
function profileOf(fileCfg) {
|
|
157
|
+
const name = activeProfileName(fileCfg);
|
|
158
|
+
if (!name) return null;
|
|
159
|
+
const profiles = fileCfg.profiles;
|
|
160
|
+
if (!profiles || typeof profiles !== "object" || Array.isArray(profiles)) return null;
|
|
161
|
+
const p = profiles[name];
|
|
162
|
+
if (!p || typeof p !== "object" || Array.isArray(p)) return null;
|
|
163
|
+
return p;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 切换当前 profile 并持久化;name 为空串/null/undefined 表示切回全局(等价 unsetProfile())
|
|
167
|
+
function useProfile(name) {
|
|
168
|
+
if (name === undefined || name === null) return unsetProfile();
|
|
169
|
+
const n = String(name).trim();
|
|
170
|
+
if (n === "") return unsetProfile();
|
|
171
|
+
const cfg = load();
|
|
172
|
+
if (!cfg.profiles || typeof cfg.profiles !== "object" || Array.isArray(cfg.profiles)) {
|
|
173
|
+
cfg.profiles = {};
|
|
174
|
+
}
|
|
175
|
+
const existing = cfg.profiles[n];
|
|
176
|
+
if (!existing || typeof existing !== "object" || Array.isArray(existing)) {
|
|
177
|
+
cfg.profiles[n] = {}; // name 不存在时建空 profile
|
|
178
|
+
}
|
|
179
|
+
cfg.activeProfile = n;
|
|
180
|
+
save(cfg);
|
|
181
|
+
return n;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 切回全局:删除 activeProfile;未激活时不做任何写操作
|
|
185
|
+
function unsetProfile() {
|
|
186
|
+
const cfg = load();
|
|
187
|
+
if (!("activeProfile" in cfg)) return null;
|
|
188
|
+
delete cfg.activeProfile;
|
|
189
|
+
save(cfg);
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 当前 profile 名(无 → null)
|
|
194
|
+
function getProfile() {
|
|
195
|
+
return activeProfileName();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// [{ name, bduss? }],按配置文件中的存储顺序
|
|
199
|
+
function listProfiles() {
|
|
200
|
+
const cfg = load();
|
|
201
|
+
const profiles = cfg.profiles;
|
|
202
|
+
if (!profiles || typeof profiles !== "object" || Array.isArray(profiles)) return [];
|
|
203
|
+
const out = [];
|
|
204
|
+
for (const [name, p] of Object.entries(profiles)) {
|
|
205
|
+
if (!p || typeof p !== "object" || Array.isArray(p)) continue;
|
|
206
|
+
const entry = { name };
|
|
207
|
+
if (p.bduss) entry.bduss = p.bduss;
|
|
208
|
+
out.push(entry);
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
module.exports = { get, set, update, setAuth, load, save, isLoggedIn, useProfile, unsetProfile, getProfile, listProfiles, findPCS, findPCSInDir, resolvePcsPath, CONFIG_DIR, CONFIG_FILE };
|
package/lib/console.js
CHANGED
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* 控制台编码工具
|
|
5
|
-
*
|
|
6
|
-
* 背景: 在中文 Windows (zh-CN) 上, 控制台默认代码页为 cp936 (GBK),
|
|
7
|
-
* 而 Node 的 stdout 始终输出 UTF-8 字节, 导致 `bdp` 的中文输出在
|
|
8
|
-
* PowerShell / cmd 中显示乱码 (如 "测试中文输出" 显示为 "娴嬭瘯涓枃杈撳嚭")。
|
|
9
|
-
*
|
|
10
|
-
* 修复思路: 检测当前代码页, 若不是 65001 (UTF-8) 且标准输出是终端,
|
|
11
|
-
* 则通过 `cmd /c chcp 65001` 切换代码页, 使控制台按 UTF-8 解释输出。
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const { spawnSync, execSync } = require("node:child_process");
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* 解析 `cmd /c chcp` 的输出, 提取代码页数字。
|
|
18
|
-
*
|
|
19
|
-
* 注意: chcp 输出的语言随当前代码页变化 ——
|
|
20
|
-
* 代码页为 936 时: "活动代码页: 936" (GBK 编码, 中文标签)
|
|
21
|
-
* 代码页为 65001 时: "Active code page: 65001" (英文标签)
|
|
22
|
-
* 因此同时兼容中英文两种格式。
|
|
23
|
-
*
|
|
24
|
-
* @param {string} output `cmd /c chcp` 的原始输出
|
|
25
|
-
* @returns {number|null} 代码页数字; 空串或无法解析时返回 null
|
|
26
|
-
*/
|
|
27
|
-
function parseChcp(output) {
|
|
28
|
-
if (typeof output !== "string" || output.trim() === "") return null;
|
|
29
|
-
// 兼容 "Active code page: 936" 与 "活动代码页: 936" 两种格式
|
|
30
|
-
const m = output.match(/(?:code\s*page|代码页)\s*[::]?\s*(\d+)/i);
|
|
31
|
-
if (!m) return null;
|
|
32
|
-
const n = Number(m[1]);
|
|
33
|
-
return Number.isInteger(n) && n > 0 ? n : null;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 从 `cmd /c chcp` 的原始输出 Buffer 中解析代码页数字。
|
|
38
|
-
* 无论输出是 UTF-8 还是 GBK 编码, 数字本身都是 ASCII, 因此兼容两种场景:
|
|
39
|
-
* 1) 按 UTF-8 解码解析;
|
|
40
|
-
* 2) 失败则按 GBK 解码 (TextDecoder, 依赖 ICU; 无 ICU 时静默跳过);
|
|
41
|
-
* 3) 兜底: 直接从原始字节中提取数字 (任何编码下数字均为 ASCII)。
|
|
42
|
-
*
|
|
43
|
-
* @param {Buffer} buf chcp 输出的原始字节
|
|
44
|
-
* @returns {number|null} 代码页数字; 解析失败返回 null
|
|
45
|
-
*/
|
|
46
|
-
function resolveChcpNumber(buf) {
|
|
47
|
-
let cp = parseChcp(buf.toString("utf8"));
|
|
48
|
-
if (cp !== null) return cp;
|
|
49
|
-
try {
|
|
50
|
-
cp = parseChcp(new TextDecoder("gbk").decode(buf));
|
|
51
|
-
} catch {
|
|
52
|
-
/* 构建无 GBK 解码器时跳过 */
|
|
53
|
-
}
|
|
54
|
-
if (cp !== null) return cp;
|
|
55
|
-
const m = buf.toString("latin1").match(/(\d{3,5})/);
|
|
56
|
-
return m ? Number(m[1]) : null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* 确保 Windows 控制台使用 UTF-8 代码页, 避免中文乱码。
|
|
61
|
-
*
|
|
62
|
-
* 仅在同时满足以下条件时才尝试切换:
|
|
63
|
-
* - process.platform === "win32" (其他平台无此问题)
|
|
64
|
-
* - process.stdout.isTTY (标准输出是终端; 重定向/管道场景不处理)
|
|
65
|
-
* - 当前代码页不是 65001 (已是 UTF-8 则跳过)
|
|
66
|
-
*
|
|
67
|
-
* 切换失败时静默忽略, 不抛异常; 幂等, 可安全重复调用。
|
|
68
|
-
*/
|
|
69
|
-
function ensureUtf8Console() {
|
|
70
|
-
if (process.platform !== "win32" || !process.stdout.isTTY) return;
|
|
71
|
-
|
|
72
|
-
// 1. 查询当前代码页 (spawnSync 返回原始 Buffer, 不经过控制台解码)
|
|
73
|
-
let current = null;
|
|
74
|
-
try {
|
|
75
|
-
const r = spawnSync("cmd", ["/c", "chcp"], {
|
|
76
|
-
encoding: "buffer",
|
|
77
|
-
windowsHide: true,
|
|
78
|
-
});
|
|
79
|
-
if (r.status !== 0 || !r.stdout || r.stdout.length === 0) return;
|
|
80
|
-
current = resolveChcpNumber(r.stdout);
|
|
81
|
-
} catch {
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// 2. 已是最新代码页或无法解析, 无需处理
|
|
86
|
-
if (current === null || current === 65001) return;
|
|
87
|
-
|
|
88
|
-
// 3. 切换代码页。
|
|
89
|
-
// 注意: 不能用 windowsHide —— 隐藏窗口的子进程会被分配独立的隐藏控制台,
|
|
90
|
-
// 其中的 chcp 只改那个隐藏控制台的代码页, 对用户所在控制台无效 (已实测)。
|
|
91
|
-
// 失败静默 (例如控制台被其它程序独占)。
|
|
92
|
-
try {
|
|
93
|
-
execSync("cmd /c chcp 65001 >nul", { stdio: "ignore" });
|
|
94
|
-
} catch {
|
|
95
|
-
/* 静默失败 */
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
module.exports = { parseChcp, ensureUtf8Console };
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 控制台编码工具
|
|
5
|
+
*
|
|
6
|
+
* 背景: 在中文 Windows (zh-CN) 上, 控制台默认代码页为 cp936 (GBK),
|
|
7
|
+
* 而 Node 的 stdout 始终输出 UTF-8 字节, 导致 `bdp` 的中文输出在
|
|
8
|
+
* PowerShell / cmd 中显示乱码 (如 "测试中文输出" 显示为 "娴嬭瘯涓枃杈撳嚭")。
|
|
9
|
+
*
|
|
10
|
+
* 修复思路: 检测当前代码页, 若不是 65001 (UTF-8) 且标准输出是终端,
|
|
11
|
+
* 则通过 `cmd /c chcp 65001` 切换代码页, 使控制台按 UTF-8 解释输出。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const { spawnSync, execSync } = require("node:child_process");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 解析 `cmd /c chcp` 的输出, 提取代码页数字。
|
|
18
|
+
*
|
|
19
|
+
* 注意: chcp 输出的语言随当前代码页变化 ——
|
|
20
|
+
* 代码页为 936 时: "活动代码页: 936" (GBK 编码, 中文标签)
|
|
21
|
+
* 代码页为 65001 时: "Active code page: 65001" (英文标签)
|
|
22
|
+
* 因此同时兼容中英文两种格式。
|
|
23
|
+
*
|
|
24
|
+
* @param {string} output `cmd /c chcp` 的原始输出
|
|
25
|
+
* @returns {number|null} 代码页数字; 空串或无法解析时返回 null
|
|
26
|
+
*/
|
|
27
|
+
function parseChcp(output) {
|
|
28
|
+
if (typeof output !== "string" || output.trim() === "") return null;
|
|
29
|
+
// 兼容 "Active code page: 936" 与 "活动代码页: 936" 两种格式
|
|
30
|
+
const m = output.match(/(?:code\s*page|代码页)\s*[::]?\s*(\d+)/i);
|
|
31
|
+
if (!m) return null;
|
|
32
|
+
const n = Number(m[1]);
|
|
33
|
+
return Number.isInteger(n) && n > 0 ? n : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 从 `cmd /c chcp` 的原始输出 Buffer 中解析代码页数字。
|
|
38
|
+
* 无论输出是 UTF-8 还是 GBK 编码, 数字本身都是 ASCII, 因此兼容两种场景:
|
|
39
|
+
* 1) 按 UTF-8 解码解析;
|
|
40
|
+
* 2) 失败则按 GBK 解码 (TextDecoder, 依赖 ICU; 无 ICU 时静默跳过);
|
|
41
|
+
* 3) 兜底: 直接从原始字节中提取数字 (任何编码下数字均为 ASCII)。
|
|
42
|
+
*
|
|
43
|
+
* @param {Buffer} buf chcp 输出的原始字节
|
|
44
|
+
* @returns {number|null} 代码页数字; 解析失败返回 null
|
|
45
|
+
*/
|
|
46
|
+
function resolveChcpNumber(buf) {
|
|
47
|
+
let cp = parseChcp(buf.toString("utf8"));
|
|
48
|
+
if (cp !== null) return cp;
|
|
49
|
+
try {
|
|
50
|
+
cp = parseChcp(new TextDecoder("gbk").decode(buf));
|
|
51
|
+
} catch {
|
|
52
|
+
/* 构建无 GBK 解码器时跳过 */
|
|
53
|
+
}
|
|
54
|
+
if (cp !== null) return cp;
|
|
55
|
+
const m = buf.toString("latin1").match(/(\d{3,5})/);
|
|
56
|
+
return m ? Number(m[1]) : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 确保 Windows 控制台使用 UTF-8 代码页, 避免中文乱码。
|
|
61
|
+
*
|
|
62
|
+
* 仅在同时满足以下条件时才尝试切换:
|
|
63
|
+
* - process.platform === "win32" (其他平台无此问题)
|
|
64
|
+
* - process.stdout.isTTY (标准输出是终端; 重定向/管道场景不处理)
|
|
65
|
+
* - 当前代码页不是 65001 (已是 UTF-8 则跳过)
|
|
66
|
+
*
|
|
67
|
+
* 切换失败时静默忽略, 不抛异常; 幂等, 可安全重复调用。
|
|
68
|
+
*/
|
|
69
|
+
function ensureUtf8Console() {
|
|
70
|
+
if (process.platform !== "win32" || !process.stdout.isTTY) return;
|
|
71
|
+
|
|
72
|
+
// 1. 查询当前代码页 (spawnSync 返回原始 Buffer, 不经过控制台解码)
|
|
73
|
+
let current = null;
|
|
74
|
+
try {
|
|
75
|
+
const r = spawnSync("cmd", ["/c", "chcp"], {
|
|
76
|
+
encoding: "buffer",
|
|
77
|
+
windowsHide: true,
|
|
78
|
+
});
|
|
79
|
+
if (r.status !== 0 || !r.stdout || r.stdout.length === 0) return;
|
|
80
|
+
current = resolveChcpNumber(r.stdout);
|
|
81
|
+
} catch {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 2. 已是最新代码页或无法解析, 无需处理
|
|
86
|
+
if (current === null || current === 65001) return;
|
|
87
|
+
|
|
88
|
+
// 3. 切换代码页。
|
|
89
|
+
// 注意: 不能用 windowsHide —— 隐藏窗口的子进程会被分配独立的隐藏控制台,
|
|
90
|
+
// 其中的 chcp 只改那个隐藏控制台的代码页, 对用户所在控制台无效 (已实测)。
|
|
91
|
+
// 失败静默 (例如控制台被其它程序独占)。
|
|
92
|
+
try {
|
|
93
|
+
execSync("cmd /c chcp 65001 >nul", { stdio: "ignore" });
|
|
94
|
+
} catch {
|
|
95
|
+
/* 静默失败 */
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { parseChcp, ensureUtf8Console };
|