@zaofan/dsh-qqbot 1.10.34 → 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/README.md +13 -8
- package/package.json +1 -1
- package/settings-host.js +61 -37
package/README.md
CHANGED
|
@@ -107,9 +107,9 @@ DSH 前端会把**回复正文里的 markdown 图片**渲染成图;本插件
|
|
|
107
107
|
配合表情包库最顺手:`list_stickers` 搜到图 → 把本地路径写进正文 → 图组直接出来,比连发 `send_media` 更像翻漫画。
|
|
108
108
|
|
|
109
109
|
失败策略:图传不上去时会**原样留在文字里**(不阻断本条,不会吞掉正文)。
|
|
110
|
-
|
|
111
|
-
---
|
|
112
|
-
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
113
|
## 📦 扩展包与插件市场:把你的扩展分享给别人
|
|
114
114
|
|
|
115
115
|
单个 `.mjs` 是「扩展文件」,**一个目录就是一个「扩展包」** —— 可以带工具、斜杠命令、封面、版本号,装进别人的面板里就是一张卡片。
|
|
@@ -173,9 +173,14 @@ export default {
|
|
|
173
173
|
run: async (args) => ({ ok: true, msg: '选中:' + args.options[0] }),
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
//
|
|
176
|
+
// ✅ 也认 parameters 这个别名
|
|
177
|
+
// (源码 src/features/extension-store.ts:225 是 inputSchema ?? parameters ?? {})
|
|
178
|
+
// ⚠️ 但值同样必须是扁平字段表,不能包 type/properties
|
|
179
|
+
|
|
180
|
+
// ❌ 错:包成标准 JSON Schema
|
|
177
181
|
// inputSchema: { type: 'object', properties: { options: {...} } }
|
|
178
|
-
// →
|
|
182
|
+
// → dsh 会拿这个对象的顶层键当参数名,于是模型看到的参数是 type / properties,
|
|
183
|
+
// 真正的 options 藏在 properties 里 → 看不见、调不动
|
|
179
184
|
```
|
|
180
185
|
|
|
181
186
|
命令的契约也不一样:工具用 **`run`**,命令用 **`handler`**(返回字符串)。
|
|
@@ -273,15 +278,15 @@ env.user / env.owners / env.peer · env.log() · env.kernel(全权限遥控器
|
|
|
273
278
|
|
|
274
279
|
---
|
|
275
280
|
|
|
276
|
-
## 🛒 插件市场与扩展包面板
|
|
277
|
-
|
|
281
|
+
## 🛒 插件市场与扩展包面板
|
|
282
|
+
|
|
278
283
|
**插件市场**——两列卡片、封面图、版本号、安装/更新/卸载一键搞定(数据来自扩展索引仓库,支持填代理解决墙内访问);
|
|
279
284
|
**扩展包面板**——每个包下面直接列出它带的工具和 `/斜杠命令`,点「设置」还能配自定义参数。
|
|
280
285
|
|
|
281
286
|

|
|
282
287
|
|
|
283
288
|

|
|
284
|
-
|
|
289
|
+
|
|
285
290
|
## 🆚 官方 Bot 方案 vs onbot / NapCat 等第三方协议端
|
|
286
291
|
|
|
287
292
|
本插件走的是**腾讯官方 QQ 机器人开放平台**(AppID + AppSecret + 官方 SDK),
|
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; // ← 自有存储优先(用户最新保存的)
|