@tencent-connect/openclaw-qqbot 1.7.0 → 1.7.2
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 +216 -49
- package/README.zh.md +216 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/src/api.d.ts +6 -0
- package/dist/src/api.js +33 -4
- package/dist/src/approval-handler.d.ts +47 -0
- package/dist/src/approval-handler.js +372 -0
- package/dist/src/channel.js +72 -0
- package/dist/src/config.d.ts +5 -1
- package/dist/src/config.js +12 -2
- package/dist/src/gateway.js +175 -170
- package/dist/src/slash-commands.d.ts +7 -2
- package/dist/src/slash-commands.js +354 -3
- package/dist/src/tools/channel.js +1 -4
- package/dist/src/tools/remind.js +0 -1
- package/dist/src/transport/index.d.ts +10 -0
- package/dist/src/transport/index.js +9 -0
- package/dist/src/transport/webhook-transport.d.ts +67 -0
- package/dist/src/transport/webhook-transport.js +245 -0
- package/dist/src/transport/webhook-verify.d.ts +48 -0
- package/dist/src/transport/webhook-verify.js +98 -0
- package/dist/src/types.d.ts +85 -0
- package/dist/src/utils/audio-convert.js +37 -9
- package/index.ts +1 -0
- package/package.json +1 -1
- package/scripts/postinstall-link-sdk.js +44 -0
- package/scripts/upgrade-via-npm.sh +358 -62
- package/scripts/upgrade-via-source.sh +122 -85
- package/src/api.ts +50 -5
- package/src/approval-handler.ts +505 -0
- package/src/channel.ts +76 -0
- package/src/config.ts +15 -2
- package/src/gateway.ts +181 -169
- package/src/onboarding.ts +8 -0
- package/src/openclaw-plugin-sdk.d.ts +127 -2
- package/src/slash-commands.ts +390 -5
- package/src/tools/channel.ts +1 -7
- package/src/tools/remind.ts +0 -2
- package/src/transport/index.ts +11 -0
- package/src/transport/webhook-transport.ts +332 -0
- package/src/transport/webhook-verify.ts +119 -0
- package/src/types.ts +100 -1
- package/src/typings/openclaw-webhook-ingress.d.ts +66 -0
- package/src/utils/audio-convert.ts +37 -9
package/src/slash-commands.ts
CHANGED
|
@@ -22,6 +22,7 @@ import { saveCredentialBackup } from "./credential-backup.js";
|
|
|
22
22
|
import { fileURLToPath } from "node:url";
|
|
23
23
|
import { getPackageVersion } from "./utils/pkg-version.js";
|
|
24
24
|
import { getQQBotRuntime } from "./runtime.js";
|
|
25
|
+
import { isApprovalFeatureAvailable } from "./approval-handler.js";
|
|
25
26
|
const require = createRequire(import.meta.url);
|
|
26
27
|
|
|
27
28
|
let PLUGIN_VERSION = getPackageVersion(import.meta.url);
|
|
@@ -33,11 +34,16 @@ export function getFrameworkVersion(): string {
|
|
|
33
34
|
// Windows 上 npm 安装的 CLI 通常是 .cmd wrapper,execFileSync 需要 shell:true 才能执行
|
|
34
35
|
for (const cli of ["openclaw", "clawdbot", "moltbot"]) {
|
|
35
36
|
try {
|
|
36
|
-
const
|
|
37
|
+
const rawOut = execFileSync(cli, ["--version"], {
|
|
37
38
|
timeout: 3000, encoding: "utf8",
|
|
38
39
|
...(isWindows() ? { shell: true } : {}),
|
|
39
40
|
}).trim();
|
|
40
41
|
// 输出格式: "OpenClaw 2026.3.13 (61d171a)"
|
|
42
|
+
// CLI 启动时可能输出 proxy 等初始化日志到 stdout,需过滤出真正的版本行
|
|
43
|
+
const out = rawOut
|
|
44
|
+
.split("\n")
|
|
45
|
+
.find((line) => /^(OpenClaw|clawdbot|moltbot)\s/i.test(line))
|
|
46
|
+
?.trim() ?? rawOut;
|
|
41
47
|
if (out) {
|
|
42
48
|
return out;
|
|
43
49
|
}
|
|
@@ -216,8 +222,8 @@ export interface QueueSnapshot {
|
|
|
216
222
|
senderPending: number;
|
|
217
223
|
}
|
|
218
224
|
|
|
219
|
-
/**
|
|
220
|
-
export type SlashCommandResult = string | SlashCommandFileResult | null;
|
|
225
|
+
/** 斜杠指令返回值:文本、带文件的结果、委托给模型、或 null(不处理) */
|
|
226
|
+
export type SlashCommandResult = string | SlashCommandFileResult | SlashCommandDelegateResult | null;
|
|
221
227
|
|
|
222
228
|
/** 带文件的指令结果(先回复文本,再发送文件) */
|
|
223
229
|
export interface SlashCommandFileResult {
|
|
@@ -226,6 +232,12 @@ export interface SlashCommandFileResult {
|
|
|
226
232
|
filePath: string;
|
|
227
233
|
}
|
|
228
234
|
|
|
235
|
+
/** 委托给 AI 模型处理:用加工后的 prompt 替换原始消息入队 */
|
|
236
|
+
export interface SlashCommandDelegateResult {
|
|
237
|
+
/** 替换原始消息内容的 prompt,交给 AI 模型执行 */
|
|
238
|
+
delegatePrompt: string;
|
|
239
|
+
}
|
|
240
|
+
|
|
229
241
|
/** 斜杠指令定义 */
|
|
230
242
|
interface SlashCommand {
|
|
231
243
|
/** 指令名(不含 /) */
|
|
@@ -325,7 +337,7 @@ registerCommand({
|
|
|
325
337
|
].join("\n"),
|
|
326
338
|
handler: (ctx) => {
|
|
327
339
|
// 群聊场景排除仅限私聊的指令
|
|
328
|
-
const GROUP_EXCLUDED_COMMANDS = new Set(["bot-upgrade", "bot-clear-storage"]);
|
|
340
|
+
const GROUP_EXCLUDED_COMMANDS = new Set(["bot-upgrade", "bot-clear-storage", "bot-logs", "bot-approve", "bot-group-allways", "bot-streaming"]);
|
|
329
341
|
const isGroup = ctx.type === "group";
|
|
330
342
|
|
|
331
343
|
const lines = [`### QQBot插件内置调试指令`, ``];
|
|
@@ -1630,7 +1642,12 @@ registerCommand({
|
|
|
1630
1642
|
`导出最近的 OpenClaw 日志文件(最多 4 个)。`,
|
|
1631
1643
|
`每个文件最多保留最后 1000 行,以文件形式返回。`,
|
|
1632
1644
|
].join("\n"),
|
|
1633
|
-
handler: () => {
|
|
1645
|
+
handler: (ctx) => {
|
|
1646
|
+
// 日志导出仅在私聊中可用
|
|
1647
|
+
if (ctx.type !== "c2c") {
|
|
1648
|
+
return `💡 请在私聊中使用此指令`;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1634
1651
|
const logDirs = collectCandidateLogDirs();
|
|
1635
1652
|
const recentFiles = collectRecentLogFiles(logDirs).slice(0, 4);
|
|
1636
1653
|
|
|
@@ -2008,6 +2025,374 @@ registerCommand({
|
|
|
2008
2025
|
},
|
|
2009
2026
|
});
|
|
2010
2027
|
|
|
2028
|
+
// ============ /bot-approve 审批配置管理 ============
|
|
2029
|
+
|
|
2030
|
+
/**
|
|
2031
|
+
* /bot-approve — 管理命令执行审批配置
|
|
2032
|
+
*
|
|
2033
|
+
* 修改 openclaw.json 中 tools.exec.security / tools.exec.ask 字段。
|
|
2034
|
+
*
|
|
2035
|
+
* security: deny | allowlist | full
|
|
2036
|
+
* ask: off | on-miss | always
|
|
2037
|
+
*/
|
|
2038
|
+
registerCommand({
|
|
2039
|
+
name: "bot-approve",
|
|
2040
|
+
description: "管理命令执行审批配置",
|
|
2041
|
+
usage: [
|
|
2042
|
+
`/bot-approve 查看操作指引`,
|
|
2043
|
+
`/bot-approve on 开启审批(白名单模式,推荐)`,
|
|
2044
|
+
`/bot-approve off 关闭审批,命令直接执行`,
|
|
2045
|
+
`/bot-approve always 始终审批,每次执行都需审批`,
|
|
2046
|
+
`/bot-approve reset 恢复框架默认值`,
|
|
2047
|
+
`/bot-approve status 查看当前审批配置`,
|
|
2048
|
+
].join("\n"),
|
|
2049
|
+
handler: async (ctx) => {
|
|
2050
|
+
// 审批管理仅在私聊中可用
|
|
2051
|
+
if (ctx.type !== "c2c") {
|
|
2052
|
+
return `💡 请在私聊中使用此指令`;
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
const arg = ctx.args.trim().toLowerCase();
|
|
2056
|
+
|
|
2057
|
+
// 审批功能需要 openclaw >= 3.22(gateway-runtime 模块)
|
|
2058
|
+
if (!isApprovalFeatureAvailable()) {
|
|
2059
|
+
return `❌ 当前 OpenClaw 版本不支持审批功能,请升级至最新版本后重试。`;
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
let runtime;
|
|
2063
|
+
try {
|
|
2064
|
+
runtime = getQQBotRuntime();
|
|
2065
|
+
} catch {
|
|
2066
|
+
// runtime 不可用,构造 prompt 委托给 AI 模型通过 CLI 完成
|
|
2067
|
+
const presetMap: Record<string, { security: string; ask: string; desc: string }> = {
|
|
2068
|
+
on: { security: "allowlist", ask: "on-miss", desc: "开启审批(白名单模式)" },
|
|
2069
|
+
off: { security: "full", ask: "off", desc: "关闭审批" },
|
|
2070
|
+
always: { security: "allowlist", ask: "always", desc: "严格模式(每次都审批)" },
|
|
2071
|
+
strict: { security: "allowlist", ask: "always", desc: "严格模式(每次都审批)" },
|
|
2072
|
+
};
|
|
2073
|
+
const preset = presetMap[arg];
|
|
2074
|
+
if (preset) {
|
|
2075
|
+
return {
|
|
2076
|
+
delegatePrompt: [
|
|
2077
|
+
`请帮我${preset.desc}。执行以下单条命令(不要拆开):`,
|
|
2078
|
+
`openclaw config set tools.exec.security ${preset.security} && openclaw config set tools.exec.ask ${preset.ask}`,
|
|
2079
|
+
`执行完成后告诉用户审批配置已更新为 security=${preset.security}, ask=${preset.ask}。`,
|
|
2080
|
+
].join("\n"),
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
if (arg === "reset") {
|
|
2084
|
+
return {
|
|
2085
|
+
delegatePrompt: [
|
|
2086
|
+
`请帮我重置审批配置。执行以下单条命令(不要拆开):`,
|
|
2087
|
+
`openclaw config unset tools.exec.security && openclaw config unset tools.exec.ask`,
|
|
2088
|
+
`执行完成后告诉用户审批配置已重置为框架默认值。`,
|
|
2089
|
+
].join("\n"),
|
|
2090
|
+
};
|
|
2091
|
+
}
|
|
2092
|
+
if (arg === "status") {
|
|
2093
|
+
return {
|
|
2094
|
+
delegatePrompt: [
|
|
2095
|
+
`请帮我查看当前命令执行审批配置。执行以下单条命令(不要拆开):`,
|
|
2096
|
+
`echo "security=$(openclaw config get tools.exec.security)" && echo "ask=$(openclaw config get tools.exec.ask)"`,
|
|
2097
|
+
`然后告诉用户当前 security 和 ask 的值,以及可用的操作选项:`,
|
|
2098
|
+
`- /bot-approve on 开启审批(白名单模式)`,
|
|
2099
|
+
`- /bot-approve off 关闭审批`,
|
|
2100
|
+
`- /bot-approve always 严格模式`,
|
|
2101
|
+
`- /bot-approve reset 恢复默认`,
|
|
2102
|
+
].join("\n"),
|
|
2103
|
+
};
|
|
2104
|
+
}
|
|
2105
|
+
// 无参数或未知参数:直接返回操作指引
|
|
2106
|
+
return [
|
|
2107
|
+
`🔐 命令执行审批配置`,
|
|
2108
|
+
``,
|
|
2109
|
+
`<qqbot-cmd-input text="/bot-approve on" show="/bot-approve on"/> 开启审批(白名单模式)`,
|
|
2110
|
+
`<qqbot-cmd-input text="/bot-approve off" show="/bot-approve off"/> 关闭审批`,
|
|
2111
|
+
`<qqbot-cmd-input text="/bot-approve always" show="/bot-approve always"/> 严格模式`,
|
|
2112
|
+
`<qqbot-cmd-input text="/bot-approve reset" show="/bot-approve reset"/> 恢复默认`,
|
|
2113
|
+
`<qqbot-cmd-input text="/bot-approve status" show="/bot-approve status"/> 查看当前配置`,
|
|
2114
|
+
].join("\n");
|
|
2115
|
+
}
|
|
2116
|
+
const configApi = runtime.config as {
|
|
2117
|
+
loadConfig: () => Record<string, unknown>;
|
|
2118
|
+
writeConfigFile: (cfg: unknown) => Promise<void>;
|
|
2119
|
+
};
|
|
2120
|
+
|
|
2121
|
+
const loadExecConfig = () => {
|
|
2122
|
+
const cfg = configApi.loadConfig() as Record<string, unknown>;
|
|
2123
|
+
const tools = (cfg.tools ?? {}) as Record<string, unknown>;
|
|
2124
|
+
const exec = (tools.exec ?? {}) as Record<string, unknown>;
|
|
2125
|
+
return {
|
|
2126
|
+
security: String(exec.security ?? "deny"),
|
|
2127
|
+
ask: String(exec.ask ?? "on-miss"),
|
|
2128
|
+
};
|
|
2129
|
+
};
|
|
2130
|
+
|
|
2131
|
+
const writeExecConfig = async (security: string, ask: string) => {
|
|
2132
|
+
const cfg = structuredClone(configApi.loadConfig()) as Record<string, unknown>;
|
|
2133
|
+
const tools = ((cfg.tools ?? {}) as Record<string, unknown>);
|
|
2134
|
+
const exec = ((tools.exec ?? {}) as Record<string, unknown>);
|
|
2135
|
+
exec.security = security;
|
|
2136
|
+
exec.ask = ask;
|
|
2137
|
+
tools.exec = exec;
|
|
2138
|
+
cfg.tools = tools;
|
|
2139
|
+
await configApi.writeConfigFile(cfg);
|
|
2140
|
+
};
|
|
2141
|
+
|
|
2142
|
+
const formatStatus = (security: string, ask: string) => {
|
|
2143
|
+
const secIcon = security === "full" ? "🟢" : security === "allowlist" ? "🟡" : "🔴";
|
|
2144
|
+
const askIcon = ask === "off" ? "🟢" : ask === "always" ? "🔴" : "🟡";
|
|
2145
|
+
return [
|
|
2146
|
+
`🔐 当前审批配置`,
|
|
2147
|
+
``,
|
|
2148
|
+
`${secIcon} 安全模式 (security): **${security}**`,
|
|
2149
|
+
`${askIcon} 审批模式 (ask): **${ask}**`,
|
|
2150
|
+
``,
|
|
2151
|
+
security === "deny" ? `⚠️ 当前为 deny 模式,所有命令执行被拒绝` :
|
|
2152
|
+
security === "full" && ask === "off" ? `✅ 所有命令无需审批直接执行` :
|
|
2153
|
+
security === "allowlist" && ask === "on-miss" ? `🛡️ 白名单命令直接执行,其余需审批` :
|
|
2154
|
+
ask === "always" ? `🔒 每次命令执行都需要人工审批` :
|
|
2155
|
+
`ℹ️ security=${security}, ask=${ask}`,
|
|
2156
|
+
].join("\n");
|
|
2157
|
+
};
|
|
2158
|
+
|
|
2159
|
+
// 无参数:操作指引
|
|
2160
|
+
if (!arg) {
|
|
2161
|
+
return [
|
|
2162
|
+
`🔐 命令执行审批配置`,
|
|
2163
|
+
``,
|
|
2164
|
+
`<qqbot-cmd-input text="/bot-approve on" show="/bot-approve on"/> 开启审批(白名单模式)`,
|
|
2165
|
+
`<qqbot-cmd-input text="/bot-approve off" show="/bot-approve off"/> 关闭审批`,
|
|
2166
|
+
`<qqbot-cmd-input text="/bot-approve always" show="/bot-approve always"/> 严格模式`,
|
|
2167
|
+
`<qqbot-cmd-input text="/bot-approve reset" show="/bot-approve reset"/> 恢复默认`,
|
|
2168
|
+
`<qqbot-cmd-input text="/bot-approve status" show="/bot-approve status"/> 查看当前配置`,
|
|
2169
|
+
].join("\n");
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
// status: 查看当前配置
|
|
2173
|
+
if (arg === "status") {
|
|
2174
|
+
const { security, ask } = loadExecConfig();
|
|
2175
|
+
return [
|
|
2176
|
+
formatStatus(security, ask),
|
|
2177
|
+
``,
|
|
2178
|
+
`<qqbot-cmd-input text="/bot-approve on" show="/bot-approve on"/> 开启审批`,
|
|
2179
|
+
`<qqbot-cmd-input text="/bot-approve off" show="/bot-approve off"/> 关闭审批`,
|
|
2180
|
+
`<qqbot-cmd-input text="/bot-approve always" show="/bot-approve always"/> 严格模式`,
|
|
2181
|
+
`<qqbot-cmd-input text="/bot-approve reset" show="/bot-approve reset"/> 恢复默认`,
|
|
2182
|
+
].join("\n");
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
// on: 开启审批(白名单 + 未命中审批)
|
|
2186
|
+
if (arg === "on") {
|
|
2187
|
+
try {
|
|
2188
|
+
await writeExecConfig("allowlist", "on-miss");
|
|
2189
|
+
return [
|
|
2190
|
+
`✅ 审批已开启`,
|
|
2191
|
+
``,
|
|
2192
|
+
`• security = allowlist(白名单模式)`,
|
|
2193
|
+
`• ask = on-miss(未命中白名单时需审批)`,
|
|
2194
|
+
``,
|
|
2195
|
+
`已批准的命令自动加入白名单,下次直接执行。`,
|
|
2196
|
+
].join("\n");
|
|
2197
|
+
} catch (err) {
|
|
2198
|
+
return `❌ 配置更新失败: ${err}`;
|
|
2199
|
+
}
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
// off: 关闭审批
|
|
2203
|
+
if (arg === "off") {
|
|
2204
|
+
try {
|
|
2205
|
+
await writeExecConfig("full", "off");
|
|
2206
|
+
return [
|
|
2207
|
+
`✅ 审批已关闭`,
|
|
2208
|
+
``,
|
|
2209
|
+
`• security = full(允许所有命令)`,
|
|
2210
|
+
`• ask = off(不需要审批)`,
|
|
2211
|
+
``,
|
|
2212
|
+
`⚠️ 所有命令将直接执行,不会弹出审批确认。`,
|
|
2213
|
+
].join("\n");
|
|
2214
|
+
} catch (err) {
|
|
2215
|
+
return `❌ 配置更新失败: ${err}`;
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
// always: 始终审批(每次都审批)
|
|
2220
|
+
if (arg === "always") {
|
|
2221
|
+
try {
|
|
2222
|
+
await writeExecConfig("allowlist", "always");
|
|
2223
|
+
return [
|
|
2224
|
+
`✅ 已切换为严格审批模式`,
|
|
2225
|
+
``,
|
|
2226
|
+
`• security = allowlist`,
|
|
2227
|
+
`• ask = always(每次执行都需审批)`,
|
|
2228
|
+
``,
|
|
2229
|
+
`每个命令都会弹出审批按钮,需手动确认。`,
|
|
2230
|
+
].join("\n");
|
|
2231
|
+
} catch (err) {
|
|
2232
|
+
return `❌ 配置更新失败: ${err}`;
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
// reset: 删除配置,恢复框架默认值
|
|
2237
|
+
if (arg === "reset") {
|
|
2238
|
+
try {
|
|
2239
|
+
const cfg = structuredClone(configApi.loadConfig()) as Record<string, unknown>;
|
|
2240
|
+
const tools = (cfg.tools ?? {}) as Record<string, unknown>;
|
|
2241
|
+
const exec = (tools.exec ?? {}) as Record<string, unknown>;
|
|
2242
|
+
delete exec.security;
|
|
2243
|
+
delete exec.ask;
|
|
2244
|
+
if (Object.keys(exec).length === 0) {
|
|
2245
|
+
delete tools.exec;
|
|
2246
|
+
} else {
|
|
2247
|
+
tools.exec = exec;
|
|
2248
|
+
}
|
|
2249
|
+
if (Object.keys(tools).length === 0) {
|
|
2250
|
+
delete cfg.tools;
|
|
2251
|
+
} else {
|
|
2252
|
+
cfg.tools = tools;
|
|
2253
|
+
}
|
|
2254
|
+
await configApi.writeConfigFile(cfg);
|
|
2255
|
+
return [
|
|
2256
|
+
`✅ 审批配置已重置`,
|
|
2257
|
+
``,
|
|
2258
|
+
`已移除 tools.exec.security 和 tools.exec.ask`,
|
|
2259
|
+
`框架将使用默认值(security=deny, ask=on-miss)`,
|
|
2260
|
+
``,
|
|
2261
|
+
`如需开启命令执行,请使用 /bot-approve on`,
|
|
2262
|
+
].join("\n");
|
|
2263
|
+
} catch (err) {
|
|
2264
|
+
return `❌ 配置更新失败: ${err}`;
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
|
|
2268
|
+
return [
|
|
2269
|
+
`❌ 未知参数: ${arg}`,
|
|
2270
|
+
``,
|
|
2271
|
+
`可用选项: on | off | always | reset`,
|
|
2272
|
+
`输入 /bot-approve ? 查看详细用法`,
|
|
2273
|
+
].join("\n");
|
|
2274
|
+
},
|
|
2275
|
+
});
|
|
2276
|
+
|
|
2277
|
+
// ============ /bot-group-allways ============
|
|
2278
|
+
|
|
2279
|
+
/**
|
|
2280
|
+
* /bot-group-allways on|off — 修改群消息默认响应模式
|
|
2281
|
+
*
|
|
2282
|
+
* 直接修改当前账户的 defaultRequireMention 配置项并持久化到 openclaw.json。
|
|
2283
|
+
* 修改后即时生效(下一条消息起按新配置处理)。
|
|
2284
|
+
*
|
|
2285
|
+
* on = AI 自主判断何时发言(无需 @),off = 仅被 @ 时回复
|
|
2286
|
+
*/
|
|
2287
|
+
registerCommand({
|
|
2288
|
+
name: "bot-group-allways",
|
|
2289
|
+
description: "修改群消息默认响应模式",
|
|
2290
|
+
usage: [
|
|
2291
|
+
`/bot-group-allways on AI 自主判断何时发言(无需 @)`,
|
|
2292
|
+
`/bot-group-allways off 仅在被 @ 时回复`,
|
|
2293
|
+
`/bot-group-allways 查看当前设置`,
|
|
2294
|
+
``,
|
|
2295
|
+
`设为 on 后,AI 会自主判断每条消息是否需要回复(无需 @)。`,
|
|
2296
|
+
`仍可通过 groups.{groupId}.requireMention 对单个群覆盖。`,
|
|
2297
|
+
``,
|
|
2298
|
+
`优先级:具体群配置 > 通配符 "*" > defaultRequireMention(本指令)> 默认 true`,
|
|
2299
|
+
].join("\n"),
|
|
2300
|
+
handler: async (ctx) => {
|
|
2301
|
+
// 群响应模式设置仅在私聊中可用
|
|
2302
|
+
if (ctx.type !== "c2c") {
|
|
2303
|
+
return `💡 请在私聊中使用此指令`;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
const arg = ctx.args.trim().toLowerCase();
|
|
2307
|
+
|
|
2308
|
+
// 读取当前 defaultRequireMention 状态
|
|
2309
|
+
const currentVal = ctx.accountConfig?.defaultRequireMention;
|
|
2310
|
+
const currentRequireMention = currentVal ?? true; // 未设置时硬编码默认为 true
|
|
2311
|
+
|
|
2312
|
+
// 无参数:查看当前状态
|
|
2313
|
+
if (!arg) {
|
|
2314
|
+
return [
|
|
2315
|
+
`🤖 群自主发言状态:${currentRequireMention ? "❌ 仅被 @ 时回复" : "✅ 自主判断何时发言"}`,
|
|
2316
|
+
`使用 <qqbot-cmd-input text="/bot-group-allways on" show="/bot-group-allways on"/> 设为自主发言`,
|
|
2317
|
+
`使用 <qqbot-cmd-input text="/bot-group-allways off" show="/bot-group-allways off"/> 设为仅被 @ 时回复`,
|
|
2318
|
+
].join("\n");
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
if (arg !== "on" && arg !== "off") {
|
|
2322
|
+
return `❌ 参数错误,请使用 on 或 off\n\n示例:/bot-group-allways on`;
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
const newRequireMention = arg === "off"; // on=自主发言(requireMention=false), off=仅被@时回复(requireMention=true)
|
|
2326
|
+
|
|
2327
|
+
// 如果状态没变,直接返回
|
|
2328
|
+
if (newRequireMention === currentRequireMention) {
|
|
2329
|
+
return `🤖 群自主发言已经是"${arg}"状态,无需操作`;
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
// 更新配置
|
|
2333
|
+
try {
|
|
2334
|
+
const runtime = getQQBotRuntime();
|
|
2335
|
+
const configApi = runtime.config as {
|
|
2336
|
+
loadConfig: () => Record<string, unknown>;
|
|
2337
|
+
writeConfigFile: (cfg: unknown) => Promise<void>;
|
|
2338
|
+
};
|
|
2339
|
+
|
|
2340
|
+
const currentCfg = structuredClone(configApi.loadConfig()) as Record<string, unknown>;
|
|
2341
|
+
const qqbot = ((currentCfg.channels ?? {}) as Record<string, unknown>).qqbot as Record<string, unknown> | undefined;
|
|
2342
|
+
|
|
2343
|
+
if (!qqbot) {
|
|
2344
|
+
return `❌ 配置文件中未找到 qqbot 通道配置`;
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
const accountId = ctx.accountId;
|
|
2348
|
+
const isNamedAccount = accountId !== "default" && (qqbot.accounts as Record<string, Record<string, unknown>> | undefined)?.[accountId];
|
|
2349
|
+
|
|
2350
|
+
if (isNamedAccount) {
|
|
2351
|
+
// 命名账户:更新 accounts.{accountId}.defaultRequireMention
|
|
2352
|
+
const accounts = qqbot.accounts as Record<string, Record<string, unknown>>;
|
|
2353
|
+
const acct = accounts[accountId] ?? {};
|
|
2354
|
+
acct.defaultRequireMention = newRequireMention;
|
|
2355
|
+
accounts[accountId] = acct;
|
|
2356
|
+
qqbot.accounts = accounts;
|
|
2357
|
+
} else {
|
|
2358
|
+
// 默认账户:更新 qqbot.defaultRequireMention
|
|
2359
|
+
qqbot.defaultRequireMention = newRequireMention;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
await configApi.writeConfigFile(currentCfg);
|
|
2363
|
+
|
|
2364
|
+
return [
|
|
2365
|
+
`✅ 群自主发言已设置为 ${newRequireMention ? "**off**(仅被 @ 时回复)" : "**on**(AI 自主判断何时发言)"}`,
|
|
2366
|
+
``,
|
|
2367
|
+
newRequireMention
|
|
2368
|
+
? `仅在被 @ 机器人才会回复。`
|
|
2369
|
+
: `AI 将自主判断群消息是否需要回复,无需被 @ 即可发言。`,
|
|
2370
|
+
``,
|
|
2371
|
+
].join("\n");
|
|
2372
|
+
} catch (err) {
|
|
2373
|
+
const fwVer = getFrameworkVersion();
|
|
2374
|
+
return [
|
|
2375
|
+
`❌ 当前版本不支持该指令`,
|
|
2376
|
+
``,
|
|
2377
|
+
`🦞框架版本:${fwVer}`,
|
|
2378
|
+
`🤖QQBot 插件版本:v${PLUGIN_VERSION}`,
|
|
2379
|
+
``,
|
|
2380
|
+
`可通过以下命令手动设置:`,
|
|
2381
|
+
``,
|
|
2382
|
+
`\`\`\`shell`,
|
|
2383
|
+
`# 设为 AI 自主判断何时发言(defaultRequireMention=false)`,
|
|
2384
|
+
`openclaw config set channels.qqbot.defaultRequireMention false`,
|
|
2385
|
+
`# 或设为仅被 @ 时回复(defaultRequireMention=true)`,
|
|
2386
|
+
`openclaw config set channels.qqbot.defaultRequireMention true`,
|
|
2387
|
+
``,
|
|
2388
|
+
`# 重启网关使配置生效`,
|
|
2389
|
+
`openclaw gateway restart`,
|
|
2390
|
+
`\`\`\``,
|
|
2391
|
+
].join("\n");
|
|
2392
|
+
}
|
|
2393
|
+
},
|
|
2394
|
+
});
|
|
2395
|
+
|
|
2011
2396
|
// ============ 匹配入口 ============
|
|
2012
2397
|
|
|
2013
2398
|
/**
|
package/src/tools/channel.ts
CHANGED
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
2
|
import { resolveQQBotAccount } from "../config.js";
|
|
3
3
|
import { listQQBotAccountIds } from "../config.js";
|
|
4
|
-
import { getAccessToken } from "../api.js";
|
|
5
|
-
|
|
6
|
-
// ========== 常量 ==========
|
|
7
|
-
|
|
8
|
-
const API_BASE = "https://api.sgroup.qq.com";
|
|
4
|
+
import { getAccessToken, API_BASE } from "../api.js";
|
|
9
5
|
|
|
10
6
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
11
7
|
|
|
@@ -276,6 +272,4 @@ export function registerChannelTool(api: OpenClawPluginApi): void {
|
|
|
276
272
|
},
|
|
277
273
|
{ name: "qqbot_channel_api" },
|
|
278
274
|
);
|
|
279
|
-
|
|
280
|
-
console.log("[qqbot-channel-api] Registered QQ channel API proxy tool");
|
|
281
275
|
}
|
package/src/tools/remind.ts
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transport module — QQ Bot event receiving mechanisms.
|
|
3
|
+
*
|
|
4
|
+
* Supports two transport modes:
|
|
5
|
+
* - **WebSocket** (default): long-lived WS connection with heartbeat, RESUME, etc.
|
|
6
|
+
* - **Webhook** (HTTP callback): QQ platform POSTs events to registered path.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type { WebhookInboundEvent, WebhookTransportOptions, QQBotWebhookTarget } from "./webhook-transport.js";
|
|
10
|
+
export { startWebhookTransport, resolveWebhookPath } from "./webhook-transport.js";
|
|
11
|
+
export { verifyWebhookSignature, signValidationResponse, ed25519Sign } from "./webhook-verify.js";
|