@zaofan/dsh-qqbot 1.10.35 → 1.10.36
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/package.json +1 -1
- package/settings-host.js +61 -37
package/package.json
CHANGED
package/settings-host.js
CHANGED
|
@@ -243,6 +243,9 @@ function viewOf(settings, ns) {
|
|
|
243
243
|
* 位置:{DSH_HOME}/qqbot-settings/<instanceNs>.json
|
|
244
244
|
*/
|
|
245
245
|
const OWN_SETTINGS_DIR = join(DSH_HOME_DIR, 'qqbot-settings');
|
|
246
|
+
/** appSecret 的回显掩码:前端看到它 / 空值 = 保留原值(不覆盖)。
|
|
247
|
+
* 2026-10-25 提到模块级:/read 与 /accounts 都要用。 */
|
|
248
|
+
const SECRET_MASK = '********';
|
|
246
249
|
|
|
247
250
|
/**
|
|
248
251
|
* 路径按 profile 隔离(2026-10-11;规则必须与 `src/features/ns-guard.ts` 的 `nsPathsOf()` 保持一致,改一处同步另一处):
|
|
@@ -258,36 +261,36 @@ const OWN_SETTINGS_DIR = join(DSH_HOME_DIR, 'qqbot-settings');
|
|
|
258
261
|
* 现在与 `src/shared/utils.ts` 的 `resolveProfileName()` **同规则**:环境变量 → `PROFILE_ROOT` 目录名
|
|
259
262
|
* (要求其父目录名为 `profiles`,挡住源码 checkout 目录)→ 落 `_default`。改一处务必同步另一处。
|
|
260
263
|
*/
|
|
261
|
-
function resolveProfileName() {
|
|
262
|
-
// ── 按维护手册 §「profile 定位的正解:回写环境变量,别改推断逻辑」 ──
|
|
263
|
-
// 宿主的 profileContext 是权威答案,src/index.ts 的 apply() 已经把它回写到:
|
|
264
|
-
// process.env.DSH_PROFILE / DSH_PROFILE_DIR
|
|
265
|
-
// globalThis.__dshProfileName / __dshProfileContext
|
|
266
|
-
// 这里只要**读**它,别自己猜路径。
|
|
267
|
-
|
|
268
|
-
// ① 环境变量(apply 阶段回写,最直接)
|
|
269
|
-
const env = String(process.env.DSH_PROFILE || '').trim();
|
|
270
|
-
if (env) return env;
|
|
271
|
-
|
|
272
|
-
// ② apply() 落下的全局变量 —— **这是关键补的一层**:
|
|
273
|
-
// settings-host.js 有可能在 apply() 之前就被加载(模块顶层求值),
|
|
274
|
-
// 那时环境变量还没回写;但真正用到 profile 名是在请求处理阶段(惰性化后),
|
|
275
|
-
// 那时 __dshProfileName 早就有了。
|
|
276
|
-
try {
|
|
277
|
-
const g = globalThis.__dshProfileName;
|
|
278
|
-
if (typeof g === 'string' && g.trim()) return g.trim();
|
|
279
|
-
} catch { /* 下一层 */ }
|
|
280
|
-
|
|
281
|
-
// ③ 老宿主没有 profileContext 服务 → 只能从 PROFILE_ROOT 目录名反推
|
|
282
|
-
try {
|
|
283
|
-
if (basename(dirname(PROFILE_ROOT)) === 'profiles') {
|
|
284
|
-
const n = basename(PROFILE_ROOT);
|
|
285
|
-
if (n && n !== 'profiles') return n;
|
|
286
|
-
}
|
|
287
|
-
} catch { /* 放弃 */ }
|
|
288
|
-
|
|
289
|
-
return '';
|
|
290
|
-
}
|
|
264
|
+
function resolveProfileName() {
|
|
265
|
+
// ── 按维护手册 §「profile 定位的正解:回写环境变量,别改推断逻辑」 ──
|
|
266
|
+
// 宿主的 profileContext 是权威答案,src/index.ts 的 apply() 已经把它回写到:
|
|
267
|
+
// process.env.DSH_PROFILE / DSH_PROFILE_DIR
|
|
268
|
+
// globalThis.__dshProfileName / __dshProfileContext
|
|
269
|
+
// 这里只要**读**它,别自己猜路径。
|
|
270
|
+
|
|
271
|
+
// ① 环境变量(apply 阶段回写,最直接)
|
|
272
|
+
const env = String(process.env.DSH_PROFILE || '').trim();
|
|
273
|
+
if (env) return env;
|
|
274
|
+
|
|
275
|
+
// ② apply() 落下的全局变量 —— **这是关键补的一层**:
|
|
276
|
+
// settings-host.js 有可能在 apply() 之前就被加载(模块顶层求值),
|
|
277
|
+
// 那时环境变量还没回写;但真正用到 profile 名是在请求处理阶段(惰性化后),
|
|
278
|
+
// 那时 __dshProfileName 早就有了。
|
|
279
|
+
try {
|
|
280
|
+
const g = globalThis.__dshProfileName;
|
|
281
|
+
if (typeof g === 'string' && g.trim()) return g.trim();
|
|
282
|
+
} catch { /* 下一层 */ }
|
|
283
|
+
|
|
284
|
+
// ③ 老宿主没有 profileContext 服务 → 只能从 PROFILE_ROOT 目录名反推
|
|
285
|
+
try {
|
|
286
|
+
if (basename(dirname(PROFILE_ROOT)) === 'profiles') {
|
|
287
|
+
const n = basename(PROFILE_ROOT);
|
|
288
|
+
if (n && n !== 'profiles') return n;
|
|
289
|
+
}
|
|
290
|
+
} catch { /* 放弃 */ }
|
|
291
|
+
|
|
292
|
+
return '';
|
|
293
|
+
}
|
|
291
294
|
/**
|
|
292
295
|
* 当前 profile 对应的存档子目录名。
|
|
293
296
|
*
|
|
@@ -297,12 +300,12 @@ function resolveProfileName() {
|
|
|
297
300
|
* (真实存档在 qqbot-settings/desktop/xxx.json)⇒ 凭据读不到 ⇒ nsBot() null。
|
|
298
301
|
* 改成每次调用时现算(开销是几次字符串/目录读,API 请求级,可忽略)。
|
|
299
302
|
*/
|
|
300
|
-
function nsProfileDir() {
|
|
301
|
-
// 惰性求值(issue #20 的核心修复):原来这是个模块顶层 const,
|
|
302
|
-
// 算的时候 apply() 还没回写 DSH_PROFILE → 永远落 _default。
|
|
303
|
-
const n = String(resolveProfileName()).trim().replace(/[^A-Za-z0-9_-]/g, '_');
|
|
304
|
-
if (n) return n;
|
|
305
|
-
return '_default';
|
|
303
|
+
function nsProfileDir() {
|
|
304
|
+
// 惰性求值(issue #20 的核心修复):原来这是个模块顶层 const,
|
|
305
|
+
// 算的时候 apply() 还没回写 DSH_PROFILE → 永远落 _default。
|
|
306
|
+
const n = String(resolveProfileName()).trim().replace(/[^A-Za-z0-9_-]/g, '_');
|
|
307
|
+
if (n) return n;
|
|
308
|
+
return '_default';
|
|
306
309
|
}
|
|
307
310
|
function safeNsOf(ns) {
|
|
308
311
|
return String(ns || NS).replace(/[^A-Za-z0-9_-]/g, '_') || 'im-qqbot';
|
|
@@ -579,6 +582,15 @@ export function apply(ctx) {
|
|
|
579
582
|
const own = readOwnSettings(ns);
|
|
580
583
|
// 自有存储优先:它就是用户在设置页保存的那份
|
|
581
584
|
const value = Object.assign({}, base.value || {}, own);
|
|
585
|
+
// ── 2026-10-25 修「保存一次 appSecret 就变成旧值」──────────────────────
|
|
586
|
+
// 原来把 appSecret 明文一起回给前端。value = Object.assign(base.value, own),
|
|
587
|
+
// 而 base 来自 cordis.patch.yml(**历史旧值**,2026-09-24 起就不再写它了)。
|
|
588
|
+
// 一旦 own 里缺 appSecret,patch 的旧值就露出来;前端把它当"当前值",
|
|
589
|
+
// 下次保存原样提交回来 → 盖掉用户存的那份 → 每次保存都被顶回去。
|
|
590
|
+
// 修法:这个接口永远只回掩码,前端已有的「掩码/空 = 保留原值」逻辑会正确处理。
|
|
591
|
+
if (value && typeof value === 'object' && value.appSecret) {
|
|
592
|
+
value.appSecret = SECRET_MASK;
|
|
593
|
+
}
|
|
582
594
|
// 2026-10-11: 顺手把"这个槽是谁在写"告诉前端 —— 被**别的 profile**占用时设置页能一眼看到(判据同 writeOwnSettings)
|
|
583
595
|
const w = readNsWriter(ns);
|
|
584
596
|
const conflict = nsConflictOf(ns);
|
|
@@ -1246,7 +1258,7 @@ export function apply(ctx) {
|
|
|
1246
1258
|
// 账号列表(多账号: 每条带 settings ns 与数据目录, 供二级 UI 按账号读写)
|
|
1247
1259
|
// 借鉴 zhengjy01/dsh-qqbot-panel(2026-09-06): appSecret 只回显掩码(不泄露明文),
|
|
1248
1260
|
// 并带 hasSecret 供前端区分"已保存"与"真空"; 保存时空值/掩码 = 保留原 secret。
|
|
1249
|
-
|
|
1261
|
+
|
|
1250
1262
|
route(ctx, 'GET', '/api/qqbot-settings/accounts', async (_req, res) => {
|
|
1251
1263
|
try {
|
|
1252
1264
|
const { bots, hasFile } = parsePatch();
|
|
@@ -2188,6 +2200,18 @@ export function apply(ctx) {
|
|
|
2188
2200
|
function mergeOwnIntoCfg(cfg, nsLike) {
|
|
2189
2201
|
let own = {};
|
|
2190
2202
|
try { own = readOwnSettings(String(nsLike)) || {}; } catch { own = {}; }
|
|
2203
|
+
// ── 2026-10-25 修「保存一次密钥就变」的另一半 ──────────────────────
|
|
2204
|
+
// patch 里的 appSecret 是**历史旧值**(2026-09-24 起已不再写 patch)。
|
|
2205
|
+
// 原来自有存储缺 appSecret 时就一直回退到 patch ⇒ patch 一旦被写脏,
|
|
2206
|
+
// 用户怎么保存都会被它顶回去。改成:**只在自有存储没有时继承一次并落盘**,
|
|
2207
|
+
// 之后永远以自有存储为准。
|
|
2208
|
+
try {
|
|
2209
|
+
if ((!own || !own.appSecret) && cfg && cfg.appSecret) {
|
|
2210
|
+
const seeded = Object.assign({}, own, { appSecret: String(cfg.appSecret) });
|
|
2211
|
+
writeOwnSettings(String(nsLike), seeded);
|
|
2212
|
+
own = seeded;
|
|
2213
|
+
}
|
|
2214
|
+
} catch { /* 继承失败不影响本次读取 */ }
|
|
2191
2215
|
const pick = (k) => {
|
|
2192
2216
|
const o = own[k];
|
|
2193
2217
|
if (o !== undefined && o !== '') return o; // ← 自有存储优先(用户最新保存的)
|