@zaofan/dsh-qqbot 1.10.9 → 1.10.10

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.
Files changed (44) hide show
  1. package/README.md +3 -2
  2. package/client/qqbot-settings.js +6204 -6199
  3. package/dist/features/botplay.d.ts.map +1 -1
  4. package/dist/features/botplay.js +6 -2
  5. package/dist/features/botplay.js.map +1 -1
  6. package/dist/features/ext-capabilities.d.ts +2 -0
  7. package/dist/features/ext-capabilities.d.ts.map +1 -1
  8. package/dist/features/ext-capabilities.js +6 -0
  9. package/dist/features/ext-capabilities.js.map +1 -1
  10. package/dist/features/ext-outbound.d.ts +238 -0
  11. package/dist/features/ext-outbound.d.ts.map +1 -0
  12. package/dist/features/ext-outbound.js +518 -0
  13. package/dist/features/ext-outbound.js.map +1 -0
  14. package/dist/features/ext-reload.d.ts +19 -0
  15. package/dist/features/ext-reload.d.ts.map +1 -1
  16. package/dist/features/ext-reload.js +40 -0
  17. package/dist/features/ext-reload.js.map +1 -1
  18. package/dist/features/qq-user-questions.d.ts +6 -0
  19. package/dist/features/qq-user-questions.d.ts.map +1 -1
  20. package/dist/features/qq-user-questions.js +17 -3
  21. package/dist/features/qq-user-questions.js.map +1 -1
  22. package/dist/gateway/middleware-setup.d.ts.map +1 -1
  23. package/dist/gateway/middleware-setup.js +19 -0
  24. package/dist/gateway/middleware-setup.js.map +1 -1
  25. package/dist/session/session-manager.d.ts.map +1 -1
  26. package/dist/session/session-manager.js +5 -2
  27. package/dist/session/session-manager.js.map +1 -1
  28. package/dist/transport/inbound.js +3 -3
  29. package/dist/transport/inbound.js.map +1 -1
  30. package/dist/transport/outbound-buffer.d.ts +31 -1
  31. package/dist/transport/outbound-buffer.d.ts.map +1 -1
  32. package/dist/transport/outbound-buffer.js +49 -1
  33. package/dist/transport/outbound-buffer.js.map +1 -1
  34. package/dist/transport/outbound.d.ts.map +1 -1
  35. package/dist/transport/outbound.js +20 -0
  36. package/dist/transport/outbound.js.map +1 -1
  37. package/examples/ext-commands//344/274/232/350/257/235/345/210/227/350/241/250.mjs +756 -742
  38. package/examples/ext-commands//345/224/244/351/206/222.mjs +71 -0
  39. package/examples/ext-commands//345/255/220/344/274/232/350/257/235.mjs +16 -5
  40. package/examples/ext-outbound/README.md +139 -0
  41. package/examples/ext-outbound/sticker-semantic.mjs +524 -0
  42. package/examples/ext-tools/sticker_auto.mjs +260 -0
  43. package/examples/ext-tools/vote.mjs +534 -323
  44. package/package.json +144 -144
@@ -0,0 +1,260 @@
1
+ /**
2
+ * sticker_auto.mjs — 【扩展工具】「自动配表情包」开关 + **隔离设置**(2026-10-16 建,2026-10-17 加隔离)
3
+ *
4
+ * 配套:出站扩展 `outbound/sticker-semantic.mjs`(它读这里的两个文件决定要不要自动配图)。
5
+ * 安装:两个文件分别放到
6
+ * `{数据根}/.qqbot-extensions/tools/sticker_auto.mjs`
7
+ * `{数据根}/.qqbot-extensions/outbound/sticker-semantic.mjs`
8
+ * 然后 `tools_reload`(工具立即生效;出站扩展也一起热刷,见 /tools-reload 回执里的"出站扩展: N 个")。
9
+ *
10
+ * ── 用法(AI 侧)──────────────────────────────────────────────────────────
11
+ * sticker_auto {action:'status'} // 看总开关 + 隔离名单
12
+ * sticker_auto {action:'on'} / {action:'off'} / {action:'toggle'} // 总开关(全局)
13
+ * sticker_auto {action:'scope-add', target:'group:<群openid>', label:'群名'}
14
+ * sticker_auto {action:'scope-remove', target:'group:<群openid>'}
15
+ * sticker_auto {action:'scope-list'}
16
+ * sticker_auto {action:'scope-mode', mode:'whitelist'|'all'}
17
+ * sticker_auto {action:'scope-clear'}
18
+ *
19
+ * ── 两个状态文件(出站扩展读的就是它们)──────────────────────────────────
20
+ * ① 总开关:`{数据根}/.qqbot-extensions/data/auto-sticker.json`
21
+ * → `{ "enabled": true|false, "updatedAt": 毫秒 }`;不存在 = 默认开(按示范扩展的 DEFAULT_ENABLED)。
22
+ * ② 隔离名单:`{数据根}/.qqbot-extensions/data/sticker-semantic-scope.json`
23
+ * → `{ "mode": "whitelist"|"all", "targets": [{key,label,addedAt}] }`
24
+ * ★ **不存在 = whitelist + 空名单 ⇒ 谁都不配图** —— 主人 2026-10-17 定:
25
+ * 「自动发图应该只对单个目标生效(像智能恢复那样,指定一个群或私聊有效,隔离设置)」。
26
+ * 要恢复"所有目标都生效"就 `scope-mode {mode:'all'}`。
27
+ */
28
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
29
+ import { dirname, join } from 'node:path';
30
+
31
+ const TOOL = 'sticker_auto';
32
+
33
+ /** 总开关文件(与 outbound/sticker-semantic.mjs 约定的那份) */
34
+ function stateFile(dataRoot) {
35
+ return join(String(dataRoot || ''), '.qqbot-extensions', 'data', 'auto-sticker.json');
36
+ }
37
+
38
+ /** 隔离名单文件 */
39
+ function scopeFile(dataRoot) {
40
+ return join(String(dataRoot || ''), '.qqbot-extensions', 'data', 'sticker-semantic-scope.json');
41
+ }
42
+
43
+ function readJson(f) {
44
+ try {
45
+ if (!existsSync(f)) return null;
46
+ return JSON.parse(readFileSync(f, 'utf8'));
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function writeJson(f, obj) {
53
+ mkdirSync(dirname(f), { recursive: true });
54
+ writeFileSync(f, JSON.stringify(obj, null, 2), 'utf8');
55
+ return f;
56
+ }
57
+
58
+ function readState(dataRoot) {
59
+ const j = readJson(stateFile(dataRoot));
60
+ if (!j) return { exists: false, enabled: undefined };
61
+ return { exists: true, enabled: typeof j?.enabled === 'boolean' ? j.enabled : undefined };
62
+ }
63
+
64
+ function writeState(dataRoot, enabled) {
65
+ return writeJson(stateFile(dataRoot), { enabled: !!enabled, updatedAt: Date.now() });
66
+ }
67
+
68
+ /**
69
+ * 归一化目标写法 → `group:<openid>` / `c2c:<openid>`(也允许裸 openid,扩展那边两种都认)。
70
+ * 认的写法:`group:xxx` / `c2c:xxx` / `群:xxx` / `私聊:xxx` / `群 xxx` / `私聊 xxx` / 裸 id
71
+ */
72
+ function normalizeTarget(raw) {
73
+ let s = String(raw ?? '').trim();
74
+ if (!s) return '';
75
+ s = s.replace(/^(群聊|群|私聊|好友)\s*[::]?\s*/, (m) => (/私聊|好友/.test(m) ? 'c2c:' : 'group:'));
76
+ const m = /^(group|c2c)\s*[::]\s*(.+)$/i.exec(s);
77
+ if (m) return `${m[1].toLowerCase()}:${m[2].trim()}`;
78
+ return s; // 裸 openid:原样存,扩展匹配时会同时试 `scope:id` 和裸 id
79
+ }
80
+
81
+ /** 读隔离名单;文件不存在 ⇒ 空白名单(=谁都不发) */
82
+ function readScope(dataRoot) {
83
+ const j = readJson(scopeFile(dataRoot));
84
+ const exists = !!j;
85
+ const mode = j?.mode === 'all' ? 'all' : 'whitelist';
86
+ const list = (Array.isArray(j?.targets) ? j.targets : [])
87
+ .map((t) => (typeof t === 'string' ? { key: t } : t))
88
+ .map((t) => ({ key: String(t?.key || '').trim(), label: String(t?.label || '').trim(), addedAt: t?.addedAt }))
89
+ .filter((t) => t.key);
90
+ return { exists, mode, list };
91
+ }
92
+
93
+ function writeScope(dataRoot, mode, list) {
94
+ return writeJson(scopeFile(dataRoot), {
95
+ mode: mode === 'all' ? 'all' : 'whitelist',
96
+ targets: list.map((t) => ({ key: t.key, label: t.label || '', addedAt: t.addedAt || Date.now() })),
97
+ updatedAt: Date.now(),
98
+ });
99
+ }
100
+
101
+ /** 名单渲染成几行文本 */
102
+ function listLines(scope) {
103
+ if (!scope.list.length) return '(名单为空)';
104
+ return scope.list.map((t, i) => `${i + 1}. ${t.key}${t.label ? ` ${t.label}` : ''}`).join('\n');
105
+ }
106
+
107
+ function scopeSummary(dataRoot, scope) {
108
+ return (
109
+ `隔离模式:${scope.mode === 'all' ? 'all(所有群/私聊都配图)' : 'whitelist(只对下面名单配图)'}` +
110
+ (scope.exists ? '' : ' ← 还没有隔离文件,用的是默认「whitelist + 空名单」= 谁都不配图') +
111
+ `\n生效名单(${scope.list.length} 个):\n${listLines(scope)}` +
112
+ `\n隔离文件:${scopeFile(dataRoot)}`
113
+ );
114
+ }
115
+
116
+ export default {
117
+ name: TOOL,
118
+ description:
119
+ '开关「自动配表情包」并管理生效范围(隔离设置):on=开启 / off=关闭 / toggle=反转 / status=状态;' +
120
+ 'scope-add=把某个群或私聊加进生效名单 / scope-remove=移出 / scope-list=看名单 / ' +
121
+ 'scope-mode=切换 whitelist(只对名单生效,默认) 或 all(所有目标都生效) / scope-clear=清空名单。' +
122
+ '默认是隔离的:没加进名单的群/私聊不会自动配图。状态存在数据根下两个扩展共用。',
123
+ inputSchema: {
124
+ action: {
125
+ type: 'string',
126
+ required: true,
127
+ description:
128
+ 'on / off / toggle / status / scope-add / scope-remove / scope-list / scope-mode / scope-clear',
129
+ },
130
+ target: {
131
+ type: 'string',
132
+ description:
133
+ 'scope-add、scope-remove 用:目标写成 group:<群openid> 或 c2c:<用户openid>(也可只写裸 openid)',
134
+ },
135
+ label: { type: 'string', description: '可选备注(群名 / 昵称),只写进配置文件方便人看' },
136
+ mode: { type: 'string', description: 'scope-mode 用:whitelist(只对名单生效,默认)/ all(所有目标)' },
137
+ },
138
+
139
+ async run(args, env) {
140
+ const action = String(args?.action ?? 'status').trim().toLowerCase();
141
+ const dataRoot = String(env?.dataRoot ?? '');
142
+ if (!dataRoot) {
143
+ return { ok: false, msg: '拿不到数据根(env.dataRoot 为空),无法读写开关文件' };
144
+ }
145
+
146
+ const cur = readState(dataRoot);
147
+ const enabled = cur.exists ? !!cur.enabled : true; // 文件不存在 ⇒ 与示范扩展的默认值一致(默认开)
148
+ const scope = readScope(dataRoot);
149
+
150
+ // ── 只读:状态 ────────────────────────────────────────────────────────
151
+ if (action === 'status') {
152
+ return {
153
+ ok: true,
154
+ msg:
155
+ `自动配表情包总开关:${enabled ? '已开启' : '已关闭'}` +
156
+ (cur.exists ? '' : '(还没有状态文件,用的是默认值「开」)') +
157
+ `\n${scopeSummary(dataRoot, scope)}`,
158
+ };
159
+ }
160
+
161
+ // ── 隔离名单管理 ──────────────────────────────────────────────────────
162
+ if (action === 'scope-list') {
163
+ return { ok: true, msg: `自动配表情包 —— ${scopeSummary(dataRoot, scope)}` };
164
+ }
165
+
166
+ if (action === 'scope-mode') {
167
+ const mode = String(args?.mode ?? '').trim().toLowerCase();
168
+ if (mode !== 'whitelist' && mode !== 'all') {
169
+ return { ok: false, msg: `scope-mode 只认 whitelist / all,收到:${mode || '(空)'}` };
170
+ }
171
+ try {
172
+ writeScope(dataRoot, mode, scope.list);
173
+ } catch (err) {
174
+ return { ok: false, msg: `写隔离文件失败:${err instanceof Error ? err.message : String(err)}` };
175
+ }
176
+ return {
177
+ ok: true,
178
+ msg:
179
+ mode === 'all'
180
+ ? '已切到 all:之后所有群/私聊都会自动配图(名单保留但不再用于判定)'
181
+ : `已切回 whitelist:只对名单里的 ${scope.list.length} 个目标配图`,
182
+ };
183
+ }
184
+
185
+ if (action === 'scope-clear') {
186
+ try {
187
+ writeScope(dataRoot, scope.mode, []);
188
+ } catch (err) {
189
+ return { ok: false, msg: `写隔离文件失败:${err instanceof Error ? err.message : String(err)}` };
190
+ }
191
+ return { ok: true, msg: '隔离名单已清空(whitelist 模式下 = 谁都不再自动配图)' };
192
+ }
193
+
194
+ if (action === 'scope-add' || action === 'scope-remove') {
195
+ const key = normalizeTarget(args?.target);
196
+ if (!key) {
197
+ return {
198
+ ok: false,
199
+ msg: "要指定 target,写成 group:<群openid> 或 c2c:<用户openid>(例:sticker_auto {action:'scope-add', target:'group:86976CE63D8FA0DA762429F01DC41545'})",
200
+ };
201
+ }
202
+ const hitIdx = scope.list.findIndex((t) => t.key === key);
203
+ if (action === 'scope-add') {
204
+ if (hitIdx >= 0) {
205
+ if (args?.label) scope.list[hitIdx].label = String(args.label).trim();
206
+ } else {
207
+ scope.list.push({ key, label: String(args?.label || '').trim(), addedAt: Date.now() });
208
+ }
209
+ } else {
210
+ if (hitIdx < 0) {
211
+ return {
212
+ ok: false,
213
+ msg: `名单里没有 ${key},没东西可删。\n当前名单(${scope.list.length} 个):\n${listLines(scope)}`,
214
+ };
215
+ }
216
+ scope.list.splice(hitIdx, 1);
217
+ }
218
+ try {
219
+ writeScope(dataRoot, scope.mode, scope.list);
220
+ } catch (err) {
221
+ return { ok: false, msg: `写隔离文件失败:${err instanceof Error ? err.message : String(err)}` };
222
+ }
223
+ return {
224
+ ok: true,
225
+ msg:
226
+ (action === 'scope-add'
227
+ ? `已加入生效名单:${key}` + (args?.label ? `(${String(args.label).trim()})` : '')
228
+ : `已移出生效名单:${key}`) +
229
+ `\n模式:${scope.mode === 'all' ? 'all(名单不参与判定)' : 'whitelist'}` +
230
+ `\n当前名单(${scope.list.length} 个):\n${listLines(scope)}` +
231
+ (enabled ? '' : '\n注意:总开关是「已关闭」状态,光加名单不会配图,需要再 action=on。'),
232
+ };
233
+ }
234
+
235
+ // ── 总开关 ────────────────────────────────────────────────────────────
236
+ let next;
237
+ if (action === 'on') next = true;
238
+ else if (action === 'off') next = false;
239
+ else if (action === 'toggle') next = !enabled;
240
+ else {
241
+ return {
242
+ ok: false,
243
+ msg: `不认识的 action:${action}(用 on / off / toggle / status / scope-add / scope-remove / scope-list / scope-mode / scope-clear)`,
244
+ };
245
+ }
246
+
247
+ try {
248
+ writeState(dataRoot, next);
249
+ } catch (err) {
250
+ return { ok: false, msg: `写开关失败:${err instanceof Error ? err.message : String(err)}` };
251
+ }
252
+ return {
253
+ ok: true,
254
+ msg:
255
+ `自动配表情包总开关已${next ? '开启' : '关闭'}` +
256
+ (next ? '(下一条回复起生效)' : '(她不再自动配图)') +
257
+ (next ? `\n注意:只有名单里的目标才会配图(当前 ${scope.list.length} 个,模式 ${scope.mode})。` : ''),
258
+ };
259
+ },
260
+ };