@tencent-connect/openclaw-qqbot 1.5.6 → 1.5.7
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 +46 -146
- package/README.zh.md +46 -146
- package/bin/qqbot-cli.js +6 -6
- package/dist/AI/345/210/233/346/226/260/345/272/224/347/224/250/345/245/226_/347/224/263/346/212/245/344/271/246.md +211 -0
- package/dist/src/gateway.js +109 -92
- package/dist/src/slash-commands.d.ts +48 -0
- package/dist/src/slash-commands.js +212 -0
- package/dist/src/utils/audio-convert.d.ts +0 -6
- package/dist/src/utils/audio-convert.js +0 -89
- package/package.json +1 -1
- package/scripts/{upgrade.sh → cleanup-legacy-plugins.sh} +3 -3
- package/scripts/set-markdown.sh +20 -20
- package/scripts/upgrade-via-npm.sh +204 -0
- package/scripts/{upgrade-and-run.sh → upgrade-via-source.sh} +60 -44
- package/src/api.ts +104 -24
- package/src/channel.ts +2 -1
- package/src/gateway.ts +229 -33
- package/src/image-server.ts +5 -2
- package/src/outbound.ts +32 -26
- package/src/ref-index-store.ts +358 -0
- package/src/types.ts +6 -0
- package/src/utils/platform.ts +16 -2
- package/scripts/draw_arch.py +0 -174
- package/scripts/npm-upgrade.sh +0 -120
- package/scripts/pull-latest.sh +0 -316
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QQ Bot 斜杠指令处理模块
|
|
3
|
+
*
|
|
4
|
+
* 支持的指令:
|
|
5
|
+
* - /echo <message> 直接回复消息(不经过 AI)
|
|
6
|
+
* - /debug 切换 debug 模式(开启后附带链路耗时统计)
|
|
7
|
+
* - /upgrade 自动执行插件更新
|
|
8
|
+
*/
|
|
9
|
+
import { exec } from "node:child_process";
|
|
10
|
+
import { promisify } from "node:util";
|
|
11
|
+
import { getAccessToken, sendC2CMessage, sendGroupMessage, sendChannelMessage, clearTokenCache, } from "./api.js";
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
// ============ Debug 模式管理 ============
|
|
14
|
+
/** 每个会话(peerId)独立的 debug 开关 */
|
|
15
|
+
const debugSessions = new Map();
|
|
16
|
+
export function isDebugEnabled(peerId) {
|
|
17
|
+
return debugSessions.get(peerId) === true;
|
|
18
|
+
}
|
|
19
|
+
export function setDebugEnabled(peerId, enabled) {
|
|
20
|
+
if (enabled) {
|
|
21
|
+
debugSessions.set(peerId, true);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
debugSessions.delete(peerId);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** 格式化耗时统计为可读文本 */
|
|
28
|
+
export function formatTimingTrace(trace) {
|
|
29
|
+
const lines = ["📊 链路耗时统计"];
|
|
30
|
+
const t0 = trace.messageReceivedAt;
|
|
31
|
+
const eventTs = trace.eventTimestamp ? new Date(trace.eventTimestamp).getTime() : 0;
|
|
32
|
+
const platformDelay = eventTs > 0 ? `${t0 - eventTs}ms` : "N/A";
|
|
33
|
+
lines.push(`├ 平台→QQBot插件: ${platformDelay}`);
|
|
34
|
+
if (trace.dispatchToOpenClawAt) {
|
|
35
|
+
lines.push(`├ QQBot插件耗时: ${trace.dispatchToOpenClawAt - t0}ms`);
|
|
36
|
+
}
|
|
37
|
+
if (trace.sendCompleteAt && trace.dispatchToOpenClawAt) {
|
|
38
|
+
lines.push(`└ OpenClaw耗时: ${trace.sendCompleteAt - trace.dispatchToOpenClawAt}ms`);
|
|
39
|
+
}
|
|
40
|
+
return lines.join("\n");
|
|
41
|
+
}
|
|
42
|
+
/** 发送带 token 重试的消息 */
|
|
43
|
+
async function sendReply(ctx, text) {
|
|
44
|
+
const { type, senderId, messageId, channelId, groupOpenid, account } = ctx;
|
|
45
|
+
const send = async (token) => {
|
|
46
|
+
if (type === "c2c") {
|
|
47
|
+
await sendC2CMessage(token, senderId, text, messageId);
|
|
48
|
+
}
|
|
49
|
+
else if (type === "group" && groupOpenid) {
|
|
50
|
+
await sendGroupMessage(token, groupOpenid, text, messageId);
|
|
51
|
+
}
|
|
52
|
+
else if (channelId) {
|
|
53
|
+
await sendChannelMessage(token, channelId, text, messageId);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
try {
|
|
57
|
+
const token = await getAccessToken(account.appId, account.clientSecret);
|
|
58
|
+
await send(token);
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
const errMsg = String(err);
|
|
62
|
+
if (errMsg.includes("401") || errMsg.includes("token") || errMsg.includes("access_token")) {
|
|
63
|
+
clearTokenCache(account.appId);
|
|
64
|
+
const newToken = await getAccessToken(account.appId, account.clientSecret);
|
|
65
|
+
await send(newToken);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// ============ 指令处理 ============
|
|
73
|
+
/** 处理 /echo 指令 */
|
|
74
|
+
async function handleEcho(ctx, args, receivedAt, eventTimestamp) {
|
|
75
|
+
const message = args.trim();
|
|
76
|
+
if (message) {
|
|
77
|
+
await sendReply(ctx, message);
|
|
78
|
+
}
|
|
79
|
+
// 计算事件时间戳 → 插件收到消息的延迟(QQ 平台 → WebSocket 传输耗时)
|
|
80
|
+
const eventTs = eventTimestamp ? new Date(eventTimestamp).getTime() : 0;
|
|
81
|
+
const platformDelay = eventTs > 0 ? `${receivedAt - eventTs}ms` : "N/A";
|
|
82
|
+
const timing = [
|
|
83
|
+
"⏱ 通道耗时",
|
|
84
|
+
`├ 事件时间: ${eventTs > 0 ? new Date(eventTs).toISOString() : "N/A"}`,
|
|
85
|
+
`├ 平台→插件: ${platformDelay}`,
|
|
86
|
+
`└ 插件处理: ${Date.now() - receivedAt}ms`,
|
|
87
|
+
].join("\n");
|
|
88
|
+
await sendReply(ctx, timing);
|
|
89
|
+
}
|
|
90
|
+
/** 处理 /debug 指令 */
|
|
91
|
+
async function handleDebug(ctx) {
|
|
92
|
+
const current = isDebugEnabled(ctx.peerId);
|
|
93
|
+
const next = !current;
|
|
94
|
+
setDebugEnabled(ctx.peerId, next);
|
|
95
|
+
const status = next
|
|
96
|
+
? "🔍 Debug 模式已开启\n后续消息回复将附带链路耗时统计"
|
|
97
|
+
: "🔕 Debug 模式已关闭";
|
|
98
|
+
await sendReply(ctx, status);
|
|
99
|
+
}
|
|
100
|
+
/** 处理 /upgrade 指令 */
|
|
101
|
+
async function handleUpgrade(ctx) {
|
|
102
|
+
await sendReply(ctx, "⏳ 正在执行插件更新,请稍候...");
|
|
103
|
+
// 检测 CLI 名称
|
|
104
|
+
let cmdName = "";
|
|
105
|
+
for (const name of ["openclaw", "clawdbot", "moltbot"]) {
|
|
106
|
+
try {
|
|
107
|
+
await execAsync(`command -v ${name}`);
|
|
108
|
+
cmdName = name;
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// not found, try next
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!cmdName) {
|
|
116
|
+
await sendReply(ctx, "❌ 更新失败: 未找到 openclaw / clawdbot / moltbot CLI");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const PKG_NAME = "@tencent-connect/openclaw-qqbot";
|
|
120
|
+
const steps = [];
|
|
121
|
+
let hasError = false;
|
|
122
|
+
try {
|
|
123
|
+
// [1/2] 直接安装最新版本(覆盖安装,不先 uninstall 避免触发框架自动重启)
|
|
124
|
+
steps.push("[1/2] 安装最新版本...");
|
|
125
|
+
try {
|
|
126
|
+
const { stdout, stderr } = await execAsync(`${cmdName} plugins install "${PKG_NAME}@latest"`, { timeout: 120000 });
|
|
127
|
+
const output = (stdout + "\n" + stderr).trim();
|
|
128
|
+
if (output) {
|
|
129
|
+
const lines = output.split("\n").filter(l => l.trim());
|
|
130
|
+
steps.push(...lines.slice(0, 10).map(l => ` ${l}`));
|
|
131
|
+
if (lines.length > 10) {
|
|
132
|
+
steps.push(` ... (${lines.length - 10} more lines)`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
steps.push(" ✅ 安装成功");
|
|
136
|
+
}
|
|
137
|
+
catch (installErr) {
|
|
138
|
+
const errOutput = installErr instanceof Error ? installErr.stderr || installErr.message : String(installErr);
|
|
139
|
+
steps.push(` ❌ 安装失败: ${String(errOutput).slice(0, 200)}`);
|
|
140
|
+
hasError = true;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
steps.push(`❌ 更新过程出错: ${String(err).slice(0, 200)}`);
|
|
145
|
+
hasError = true;
|
|
146
|
+
}
|
|
147
|
+
// 先发送结果汇总(必须在重启之前发送,否则进程被杀后无法发送)
|
|
148
|
+
if (!hasError) {
|
|
149
|
+
steps.push("[2/2] 即将重启网关...");
|
|
150
|
+
}
|
|
151
|
+
const header = hasError ? "❌ 插件更新完成(有错误)" : "✅ 插件更新完成";
|
|
152
|
+
const result = `${header}\n${"=".repeat(30)}\n${steps.join("\n")}`;
|
|
153
|
+
try {
|
|
154
|
+
await sendReply(ctx, result);
|
|
155
|
+
}
|
|
156
|
+
catch (sendErr) {
|
|
157
|
+
ctx.log?.error(`[qqbot] Failed to send upgrade result: ${sendErr}`);
|
|
158
|
+
}
|
|
159
|
+
// 最后再重启网关(重启会杀掉当前进程,之后的代码不会执行)
|
|
160
|
+
if (!hasError) {
|
|
161
|
+
try {
|
|
162
|
+
await execAsync(`${cmdName} gateway restart`, { timeout: 30000 });
|
|
163
|
+
}
|
|
164
|
+
catch (restartErr) {
|
|
165
|
+
const errOutput = restartErr instanceof Error ? restartErr.stderr || restartErr.message : String(restartErr);
|
|
166
|
+
ctx.log?.error(`[qqbot] Gateway restart failed: ${errOutput}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// ============ 指令分发 ============
|
|
171
|
+
/**
|
|
172
|
+
* 尝试处理斜杠指令
|
|
173
|
+
*
|
|
174
|
+
* @returns handled=true 表示该消息已作为指令处理,不需要继续走 AI 管道
|
|
175
|
+
*/
|
|
176
|
+
export async function handleSlashCommand(content, ctx, receivedAt, eventTimestamp) {
|
|
177
|
+
const trimmed = content.trim();
|
|
178
|
+
if (!trimmed.startsWith("/")) {
|
|
179
|
+
return { handled: false };
|
|
180
|
+
}
|
|
181
|
+
// 解析指令名和参数
|
|
182
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
183
|
+
const command = spaceIdx === -1 ? trimmed.toLowerCase() : trimmed.slice(0, spaceIdx).toLowerCase();
|
|
184
|
+
const args = spaceIdx === -1 ? "" : trimmed.slice(spaceIdx + 1);
|
|
185
|
+
ctx.log?.info(`[qqbot:${ctx.account.accountId}] Slash command: ${command}, args: ${args.slice(0, 50)}`);
|
|
186
|
+
try {
|
|
187
|
+
switch (command) {
|
|
188
|
+
case "/echo":
|
|
189
|
+
await handleEcho(ctx, args, receivedAt ?? Date.now(), eventTimestamp);
|
|
190
|
+
return { handled: true };
|
|
191
|
+
case "/debug":
|
|
192
|
+
await handleDebug(ctx);
|
|
193
|
+
return { handled: true };
|
|
194
|
+
case "/upgrade":
|
|
195
|
+
await handleUpgrade(ctx);
|
|
196
|
+
return { handled: true };
|
|
197
|
+
default:
|
|
198
|
+
// 不是已知指令,交给 AI 处理
|
|
199
|
+
return { handled: false };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
ctx.log?.error(`[qqbot:${ctx.account.accountId}] Slash command error: ${err}`);
|
|
204
|
+
try {
|
|
205
|
+
await sendReply(ctx, `❌ 指令执行失败: ${String(err).slice(0, 200)}`);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// 发送错误消息也失败了,只能记日志
|
|
209
|
+
}
|
|
210
|
+
return { handled: true };
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -22,8 +22,6 @@ export declare function isVoiceAttachment(att: {
|
|
|
22
22
|
export declare function formatDuration(durationMs: number): string;
|
|
23
23
|
export declare function isAudioFile(filePath: string): boolean;
|
|
24
24
|
export interface TTSConfig {
|
|
25
|
-
/** TTS 引擎类型:openai 兼容 API 或 Edge TTS */
|
|
26
|
-
provider: "openai" | "edge";
|
|
27
25
|
baseUrl: string;
|
|
28
26
|
apiKey: string;
|
|
29
27
|
model: string;
|
|
@@ -34,10 +32,6 @@ export interface TTSConfig {
|
|
|
34
32
|
queryParams?: Record<string, string>;
|
|
35
33
|
/** 自定义速度(默认不传) */
|
|
36
34
|
speed?: number;
|
|
37
|
-
/** Edge TTS 专用:语速调整,如 "+10%"、"-20%" */
|
|
38
|
-
rate?: string;
|
|
39
|
-
/** Edge TTS 专用:音调调整,如 "+10%"、"-10%" */
|
|
40
|
-
pitch?: string;
|
|
41
35
|
}
|
|
42
36
|
export declare function resolveTTSConfig(cfg: Record<string, unknown>): TTSConfig | null;
|
|
43
37
|
export declare function textToSpeechPCM(text: string, ttsCfg: TTSConfig): Promise<{
|
|
@@ -132,7 +132,6 @@ function resolveTTSFromBlock(block, providerCfg) {
|
|
|
132
132
|
const queryParams = { ...(providerCfg?.queryParams ?? {}), ...(block?.queryParams ?? {}) };
|
|
133
133
|
const speed = block?.speed;
|
|
134
134
|
return {
|
|
135
|
-
provider: "openai",
|
|
136
135
|
baseUrl: baseUrl.replace(/\/+$/, ""),
|
|
137
136
|
apiKey,
|
|
138
137
|
model,
|
|
@@ -142,27 +141,12 @@ function resolveTTSFromBlock(block, providerCfg) {
|
|
|
142
141
|
...(speed !== undefined ? { speed } : {}),
|
|
143
142
|
};
|
|
144
143
|
}
|
|
145
|
-
function resolveEdgeTTSFromBlock(block) {
|
|
146
|
-
const voice = block?.voice || "zh-CN-XiaoxiaoNeural";
|
|
147
|
-
return {
|
|
148
|
-
provider: "edge",
|
|
149
|
-
baseUrl: "",
|
|
150
|
-
apiKey: "",
|
|
151
|
-
model: "edge-tts",
|
|
152
|
-
voice,
|
|
153
|
-
...(block?.rate ? { rate: block.rate } : {}),
|
|
154
|
-
...(block?.pitch ? { pitch: block.pitch } : {}),
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
144
|
export function resolveTTSConfig(cfg) {
|
|
158
145
|
const c = cfg;
|
|
159
146
|
// 优先使用 channels.qqbot.tts(插件专属配置)
|
|
160
147
|
const channelTts = c?.channels?.qqbot?.tts;
|
|
161
148
|
if (channelTts && channelTts.enabled !== false) {
|
|
162
149
|
const providerId = channelTts?.provider || "openai";
|
|
163
|
-
if (providerId === "edge") {
|
|
164
|
-
return resolveEdgeTTSFromBlock(channelTts);
|
|
165
|
-
}
|
|
166
150
|
const providerCfg = c?.models?.providers?.[providerId];
|
|
167
151
|
const result = resolveTTSFromBlock(channelTts, providerCfg);
|
|
168
152
|
if (result)
|
|
@@ -172,9 +156,6 @@ export function resolveTTSConfig(cfg) {
|
|
|
172
156
|
const msgTts = c?.messages?.tts;
|
|
173
157
|
if (msgTts && msgTts.auto !== "disabled") {
|
|
174
158
|
const providerId = msgTts?.provider || "openai";
|
|
175
|
-
if (providerId === "edge") {
|
|
176
|
-
return resolveEdgeTTSFromBlock(msgTts);
|
|
177
|
-
}
|
|
178
159
|
const providerBlock = msgTts?.[providerId];
|
|
179
160
|
const providerCfg = c?.models?.providers?.[providerId];
|
|
180
161
|
const result = resolveTTSFromBlock(providerBlock ?? {}, providerCfg);
|
|
@@ -205,76 +186,6 @@ function buildTTSRequest(ttsCfg) {
|
|
|
205
186
|
return { url, headers };
|
|
206
187
|
}
|
|
207
188
|
export async function textToSpeechPCM(text, ttsCfg) {
|
|
208
|
-
if (ttsCfg.provider === "edge") {
|
|
209
|
-
return edgeTTSToPCM(text, ttsCfg);
|
|
210
|
-
}
|
|
211
|
-
return openaiTTSToPCM(text, ttsCfg);
|
|
212
|
-
}
|
|
213
|
-
async function edgeTTSToPCM(text, ttsCfg) {
|
|
214
|
-
const sampleRate = 24000;
|
|
215
|
-
const startTime = Date.now();
|
|
216
|
-
console.log(`[tts:edge] Request: voice=${ttsCfg.voice}, rate=${ttsCfg.rate ?? "default"}, pitch=${ttsCfg.pitch ?? "default"}`);
|
|
217
|
-
console.log(`[tts:edge] Input text (${text.length} chars): "${text.slice(0, 80)}${text.length > 80 ? "..." : ""}"`);
|
|
218
|
-
const { EdgeTTS } = await import("node-edge-tts");
|
|
219
|
-
const tts = new EdgeTTS({
|
|
220
|
-
voice: ttsCfg.voice,
|
|
221
|
-
outputFormat: `raw-${sampleRate}hz-16bit-mono-pcm`,
|
|
222
|
-
...(ttsCfg.rate ? { rate: ttsCfg.rate } : {}),
|
|
223
|
-
...(ttsCfg.pitch ? { pitch: ttsCfg.pitch } : {}),
|
|
224
|
-
timeout: 60000,
|
|
225
|
-
});
|
|
226
|
-
const tmpDir = fs.mkdtempSync(path.join(require("node:os").tmpdir(), "edge-tts-"));
|
|
227
|
-
const tmpFile = path.join(tmpDir, "tts.pcm");
|
|
228
|
-
try {
|
|
229
|
-
await tts.ttsPromise(text, tmpFile);
|
|
230
|
-
const pcmBuffer = fs.readFileSync(tmpFile);
|
|
231
|
-
console.log(`[tts:edge] Done: ${pcmBuffer.length} bytes, total=${Date.now() - startTime}ms`);
|
|
232
|
-
return { pcmBuffer, sampleRate };
|
|
233
|
-
}
|
|
234
|
-
catch (err) {
|
|
235
|
-
// raw PCM 格式可能不支持,回退到 mp3
|
|
236
|
-
console.log(`[tts:edge] PCM format failed, trying mp3 fallback: ${err instanceof Error ? err.message : String(err)}`);
|
|
237
|
-
const tmpMp3 = path.join(tmpDir, "tts.mp3");
|
|
238
|
-
try {
|
|
239
|
-
const ttsMp3 = new EdgeTTS({
|
|
240
|
-
voice: ttsCfg.voice,
|
|
241
|
-
outputFormat: "audio-24khz-96kbitrate-mono-mp3",
|
|
242
|
-
...(ttsCfg.rate ? { rate: ttsCfg.rate } : {}),
|
|
243
|
-
...(ttsCfg.pitch ? { pitch: ttsCfg.pitch } : {}),
|
|
244
|
-
timeout: 60000,
|
|
245
|
-
});
|
|
246
|
-
await ttsMp3.ttsPromise(text, tmpMp3);
|
|
247
|
-
const mp3Buffer = fs.readFileSync(tmpMp3);
|
|
248
|
-
console.log(`[tts:edge] mp3 generated: ${mp3Buffer.length} bytes`);
|
|
249
|
-
const ffmpegCmd = await checkFfmpeg();
|
|
250
|
-
if (ffmpegCmd) {
|
|
251
|
-
const pcmBuf = await ffmpegToPCM(ffmpegCmd, tmpMp3, sampleRate);
|
|
252
|
-
console.log(`[tts:edge] Done: mp3→PCM (ffmpeg), ${pcmBuf.length} bytes, total=${Date.now() - startTime}ms`);
|
|
253
|
-
return { pcmBuffer: pcmBuf, sampleRate };
|
|
254
|
-
}
|
|
255
|
-
const pcmBuf = await wasmDecodeMp3ToPCM(mp3Buffer, sampleRate);
|
|
256
|
-
if (pcmBuf) {
|
|
257
|
-
console.log(`[tts:edge] Done: mp3→PCM (wasm), ${pcmBuf.length} bytes, total=${Date.now() - startTime}ms`);
|
|
258
|
-
return { pcmBuffer: pcmBuf, sampleRate };
|
|
259
|
-
}
|
|
260
|
-
throw new Error("Edge TTS: no decoder available for mp3");
|
|
261
|
-
}
|
|
262
|
-
finally {
|
|
263
|
-
try {
|
|
264
|
-
fs.unlinkSync(tmpMp3);
|
|
265
|
-
}
|
|
266
|
-
catch { }
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
finally {
|
|
270
|
-
try {
|
|
271
|
-
fs.unlinkSync(tmpFile);
|
|
272
|
-
fs.rmdirSync(tmpDir);
|
|
273
|
-
}
|
|
274
|
-
catch { }
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
async function openaiTTSToPCM(text, ttsCfg) {
|
|
278
189
|
const sampleRate = 24000;
|
|
279
190
|
const { url, headers } = buildTTSRequest(ttsCfg);
|
|
280
191
|
console.log(`[tts] Request: model=${ttsCfg.model}, voice=${ttsCfg.voice}, authStyle=${ttsCfg.authStyle ?? "bearer"}, url=${url}`);
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
2
|
+
# qqbot 插件升级脚本
|
|
3
3
|
# 用于清理旧版本插件并重新安装
|
|
4
4
|
# 兼容 clawdbot 和 openclaw 两种安装
|
|
5
5
|
|
|
6
6
|
set -e
|
|
7
7
|
|
|
8
|
-
echo "===
|
|
8
|
+
echo "=== qqbot 插件升级脚本 ==="
|
|
9
9
|
|
|
10
10
|
# 检测使用的是 clawdbot 还是 openclaw
|
|
11
11
|
detect_installation() {
|
|
@@ -123,5 +123,5 @@ echo ""
|
|
|
123
123
|
echo "接下来请执行以下命令重新安装插件:"
|
|
124
124
|
echo " cd /path/to/openclaw-qqbot"
|
|
125
125
|
echo " $CMD plugins install ."
|
|
126
|
-
echo " $CMD channels add --channel qqbot --token \"
|
|
126
|
+
echo " $CMD channels add --channel qqbot --token \"appid:appsecret\""
|
|
127
127
|
echo " $CMD gateway restart"
|
package/scripts/set-markdown.sh
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
# 用于单独设置是否启用
|
|
3
|
+
# qqbot markdown 配置脚本
|
|
4
|
+
# 用于单独设置是否启用 markdown 消息格式
|
|
5
5
|
# 直接编辑 JSON 配置文件,避免框架验证拒绝未注册的 channel
|
|
6
6
|
|
|
7
7
|
set -e
|
|
@@ -29,18 +29,18 @@ show_help() {
|
|
|
29
29
|
echo "用法: $0 [选项]"
|
|
30
30
|
echo ""
|
|
31
31
|
echo "选项:"
|
|
32
|
-
echo " enable, on, yes 启用
|
|
33
|
-
echo " disable, off, no 禁用
|
|
34
|
-
echo " status 显示当前
|
|
32
|
+
echo " enable, on, yes 启用 markdown 消息格式"
|
|
33
|
+
echo " disable, off, no 禁用 markdown 消息格式(使用纯文本)"
|
|
34
|
+
echo " status 显示当前 markdown 配置状态"
|
|
35
35
|
echo " -h, --help 显示帮助信息"
|
|
36
36
|
echo ""
|
|
37
37
|
echo "示例:"
|
|
38
|
-
echo " $0 enable 启用
|
|
39
|
-
echo " $0 disable 禁用
|
|
38
|
+
echo " $0 enable 启用 markdown"
|
|
39
|
+
echo " $0 disable 禁用 markdown"
|
|
40
40
|
echo " $0 status 查看当前状态"
|
|
41
41
|
echo " $0 交互式选择"
|
|
42
42
|
echo ""
|
|
43
|
-
echo "⚠️ 注意: 启用
|
|
43
|
+
echo "⚠️ 注意: 启用 markdown 需要在 QQ 开放平台申请 markdown 消息权限"
|
|
44
44
|
echo " 如果没有权限,消息将无法正常发送!"
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -57,22 +57,22 @@ set_markdown_value() {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
enable_markdown() {
|
|
60
|
-
echo "✅ 启用
|
|
60
|
+
echo "✅ 启用 markdown 消息格式..."
|
|
61
61
|
set_markdown_value true
|
|
62
62
|
echo ""
|
|
63
|
-
echo "
|
|
64
|
-
echo "⚠️ 请确保您已在 QQ 开放平台申请了
|
|
63
|
+
echo "markdown 已启用。"
|
|
64
|
+
echo "⚠️ 请确保您已在 QQ 开放平台申请了 markdown 消息权限。"
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
disable_markdown() {
|
|
68
|
-
echo "❌ 禁用
|
|
68
|
+
echo "❌ 禁用 markdown 消息格式(使用纯文本)..."
|
|
69
69
|
set_markdown_value false
|
|
70
70
|
echo ""
|
|
71
|
-
echo "
|
|
71
|
+
echo "markdown 已禁用,将使用纯文本格式发送消息。"
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
show_status() {
|
|
75
|
-
echo "当前
|
|
75
|
+
echo "当前 markdown 配置状态:"
|
|
76
76
|
echo " 配置文件: $OPENCLAW_CONFIG"
|
|
77
77
|
echo ""
|
|
78
78
|
current=$(node -e "
|
|
@@ -82,7 +82,7 @@ show_status() {
|
|
|
82
82
|
if [ "$current" = "true" ]; then
|
|
83
83
|
echo " 状态: ✅ 已启用"
|
|
84
84
|
echo ""
|
|
85
|
-
echo " ⚠️ 请确保您已在 QQ 开放平台申请了
|
|
85
|
+
echo " ⚠️ 请确保您已在 QQ 开放平台申请了 markdown 消息权限。"
|
|
86
86
|
elif [ "$current" = "false" ]; then
|
|
87
87
|
echo " 状态: ❌ 已禁用(纯文本模式)"
|
|
88
88
|
else
|
|
@@ -92,20 +92,20 @@ show_status() {
|
|
|
92
92
|
|
|
93
93
|
interactive_select() {
|
|
94
94
|
echo "========================================="
|
|
95
|
-
echo "
|
|
95
|
+
echo " qqbot markdown 配置"
|
|
96
96
|
echo "========================================="
|
|
97
97
|
echo ""
|
|
98
98
|
show_status
|
|
99
99
|
echo ""
|
|
100
100
|
echo "-----------------------------------------"
|
|
101
101
|
echo ""
|
|
102
|
-
echo "是否启用
|
|
102
|
+
echo "是否启用 markdown 消息格式?"
|
|
103
103
|
echo ""
|
|
104
|
-
echo "⚠️ 注意: 启用
|
|
104
|
+
echo "⚠️ 注意: 启用 markdown 需要在 QQ 开放平台申请 markdown 消息权限"
|
|
105
105
|
echo " 如果没有权限,消息将无法正常发送!"
|
|
106
106
|
echo ""
|
|
107
|
-
echo " 1) 启用
|
|
108
|
-
echo " 2) 禁用
|
|
107
|
+
echo " 1) 启用 markdown"
|
|
108
|
+
echo " 2) 禁用 markdown(纯文本)"
|
|
109
109
|
echo " 3) 取消"
|
|
110
110
|
echo ""
|
|
111
111
|
read -t 10 -p "请选择 [1-3] (默认: 2): " choice || choice="2"
|