@sliverp/qqbot 1.3.7 → 1.3.8
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 +150 -114
- package/dist/index.js +2 -2
- package/dist/src/channel.d.ts +1 -1
- package/dist/src/channel.js +140 -5
- package/dist/src/config.js +8 -0
- package/dist/src/gateway.d.ts +1 -0
- package/dist/src/gateway.js +893 -140
- package/dist/src/onboarding.js +5 -0
- package/package.json +1 -1
- package/src/channel.ts +14 -12
- package/src/config.ts +10 -0
- package/src/gateway.ts +28 -5
- package/src/onboarding.ts +8 -0
package/dist/src/onboarding.js
CHANGED
|
@@ -84,6 +84,7 @@ export const qqbotOnboardingAdapter = {
|
|
|
84
84
|
qqbot: {
|
|
85
85
|
...(next.channels?.qqbot || {}),
|
|
86
86
|
enabled: true,
|
|
87
|
+
allowFrom: resolvedAccount.config?.allowFrom ?? ["*"],
|
|
87
88
|
},
|
|
88
89
|
},
|
|
89
90
|
};
|
|
@@ -137,6 +138,8 @@ export const qqbotOnboardingAdapter = {
|
|
|
137
138
|
validate: (value) => (value?.trim() ? undefined : "ClientSecret 不能为空"),
|
|
138
139
|
})).trim();
|
|
139
140
|
}
|
|
141
|
+
// 默认允许所有人执行命令(用户无感知)
|
|
142
|
+
const allowFrom = resolvedAccount.config?.allowFrom ?? ["*"];
|
|
140
143
|
// 应用配置
|
|
141
144
|
if (appId && clientSecret) {
|
|
142
145
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
@@ -149,6 +152,7 @@ export const qqbotOnboardingAdapter = {
|
|
|
149
152
|
enabled: true,
|
|
150
153
|
appId,
|
|
151
154
|
clientSecret,
|
|
155
|
+
allowFrom,
|
|
152
156
|
},
|
|
153
157
|
},
|
|
154
158
|
};
|
|
@@ -168,6 +172,7 @@ export const qqbotOnboardingAdapter = {
|
|
|
168
172
|
enabled: true,
|
|
169
173
|
appId,
|
|
170
174
|
clientSecret,
|
|
175
|
+
allowFrom,
|
|
171
176
|
},
|
|
172
177
|
},
|
|
173
178
|
},
|
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -120,6 +120,20 @@ export const qqbotPlugin: ChannelPlugin<ResolvedQQBotAccount> = {
|
|
|
120
120
|
configured: Boolean(account?.appId && account?.clientSecret),
|
|
121
121
|
tokenSource: account?.secretSource,
|
|
122
122
|
}),
|
|
123
|
+
// 关键:解析 allowFrom 配置,用于命令授权
|
|
124
|
+
resolveAllowFrom: ({ cfg, accountId }: { cfg: OpenClawConfig; accountId?: string }) => {
|
|
125
|
+
const account = resolveQQBotAccount(cfg, accountId);
|
|
126
|
+
const allowFrom = account.config?.allowFrom ?? [];
|
|
127
|
+
console.log(`[qqbot] resolveAllowFrom: accountId=${accountId}, allowFrom=${JSON.stringify(allowFrom)}`);
|
|
128
|
+
return allowFrom.map((entry: string | number) => String(entry));
|
|
129
|
+
},
|
|
130
|
+
// 格式化 allowFrom 条目(移除 qqbot: 前缀,统一大写)
|
|
131
|
+
formatAllowFrom: ({ allowFrom }: { allowFrom: Array<string | number> }) =>
|
|
132
|
+
allowFrom
|
|
133
|
+
.map((entry: string | number) => String(entry).trim())
|
|
134
|
+
.filter(Boolean)
|
|
135
|
+
.map((entry: string) => entry.replace(/^qqbot:/i, ""))
|
|
136
|
+
.map((entry: string) => entry.toUpperCase()), // QQ openid 是大写的
|
|
123
137
|
},
|
|
124
138
|
setup: {
|
|
125
139
|
// 新增:规范化账户 ID
|
|
@@ -159,18 +173,6 @@ export const qqbotPlugin: ChannelPlugin<ResolvedQQBotAccount> = {
|
|
|
159
173
|
});
|
|
160
174
|
},
|
|
161
175
|
},
|
|
162
|
-
// 新增:消息目标解析
|
|
163
|
-
messaging: {
|
|
164
|
-
normalizeTarget: (target) => {
|
|
165
|
-
// 支持格式: qqbot:openid, qqbot:group:xxx, openid, group:xxx
|
|
166
|
-
const normalized = target.replace(/^qqbot:/i, "");
|
|
167
|
-
return { ok: true, to: normalized };
|
|
168
|
-
},
|
|
169
|
-
targetResolver: {
|
|
170
|
-
looksLikeId: (id) => /^[A-F0-9]{32}$/i.test(id) || id.startsWith("group:") || id.startsWith("channel:"),
|
|
171
|
-
hint: "<openid> or group:<groupOpenid>",
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
176
|
outbound: {
|
|
175
177
|
deliveryMode: "direct",
|
|
176
178
|
chunker: chunkText,
|
package/src/config.ts
CHANGED
|
@@ -128,11 +128,16 @@ export function applyQQBotAccountConfig(
|
|
|
128
128
|
const next = { ...cfg };
|
|
129
129
|
|
|
130
130
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
131
|
+
// 如果没有设置过 allowFrom,默认设置为 ["*"]
|
|
132
|
+
const existingConfig = (next.channels?.qqbot as QQBotChannelConfig) || {};
|
|
133
|
+
const allowFrom = existingConfig.allowFrom ?? ["*"];
|
|
134
|
+
|
|
131
135
|
next.channels = {
|
|
132
136
|
...next.channels,
|
|
133
137
|
qqbot: {
|
|
134
138
|
...(next.channels?.qqbot as Record<string, unknown> || {}),
|
|
135
139
|
enabled: true,
|
|
140
|
+
allowFrom,
|
|
136
141
|
...(input.appId ? { appId: input.appId } : {}),
|
|
137
142
|
...(input.clientSecret
|
|
138
143
|
? { clientSecret: input.clientSecret }
|
|
@@ -144,6 +149,10 @@ export function applyQQBotAccountConfig(
|
|
|
144
149
|
},
|
|
145
150
|
};
|
|
146
151
|
} else {
|
|
152
|
+
// 如果没有设置过 allowFrom,默认设置为 ["*"]
|
|
153
|
+
const existingAccountConfig = (next.channels?.qqbot as QQBotChannelConfig)?.accounts?.[accountId] || {};
|
|
154
|
+
const allowFrom = existingAccountConfig.allowFrom ?? ["*"];
|
|
155
|
+
|
|
147
156
|
next.channels = {
|
|
148
157
|
...next.channels,
|
|
149
158
|
qqbot: {
|
|
@@ -154,6 +163,7 @@ export function applyQQBotAccountConfig(
|
|
|
154
163
|
[accountId]: {
|
|
155
164
|
...((next.channels?.qqbot as QQBotChannelConfig)?.accounts?.[accountId] || {}),
|
|
156
165
|
enabled: true,
|
|
166
|
+
allowFrom,
|
|
157
167
|
...(input.appId ? { appId: input.appId } : {}),
|
|
158
168
|
...(input.clientSecret
|
|
159
169
|
? { clientSecret: input.clientSecret }
|
package/src/gateway.ts
CHANGED
|
@@ -2,7 +2,7 @@ import WebSocket from "ws";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
4
|
import type { ResolvedQQBotAccount, WSPayload, C2CMessageEvent, GuildMessageEvent, GroupMessageEvent } from "./types.js";
|
|
5
|
-
import { getAccessToken, getGatewayUrl, sendC2CMessage, sendChannelMessage, sendGroupMessage, clearTokenCache, sendC2CImageMessage, sendGroupImageMessage, initApiConfig, startBackgroundTokenRefresh, stopBackgroundTokenRefresh } from "./api.js";
|
|
5
|
+
import { getAccessToken, getGatewayUrl, sendC2CMessage, sendChannelMessage, sendGroupMessage, clearTokenCache, sendC2CImageMessage, sendGroupImageMessage, initApiConfig, startBackgroundTokenRefresh, stopBackgroundTokenRefresh, sendC2CInputNotify } from "./api.js";
|
|
6
6
|
import { loadSession, saveSession, clearSession, type SessionState } from "./session-store.js";
|
|
7
7
|
import { recordKnownUser, flushKnownUsers } from "./known-users.js";
|
|
8
8
|
import { getQQBotRuntime } from "./runtime.js";
|
|
@@ -412,6 +412,13 @@ export async function startGateway(ctx: GatewayContext): Promise<void> {
|
|
|
412
412
|
direction: "inbound",
|
|
413
413
|
});
|
|
414
414
|
|
|
415
|
+
try{
|
|
416
|
+
await sendC2CInputNotify(accessToken, event.senderId, event.messageId, 60);
|
|
417
|
+
log?.info(`[qqbot:${account.accountId}] Sent input notify to ${event.senderId}`);
|
|
418
|
+
}catch(err){
|
|
419
|
+
log?.error(`[qqbot:${account.accountId}] sendC2CInputNotify error: ${err}`);
|
|
420
|
+
}
|
|
421
|
+
|
|
415
422
|
const isGroup = event.type === "guild" || event.type === "group";
|
|
416
423
|
const peerId = event.type === "guild" ? `channel:${event.channelId}`
|
|
417
424
|
: event.type === "group" ? `group:${event.groupOpenid}`
|
|
@@ -568,10 +575,15 @@ openclaw cron add \\
|
|
|
568
575
|
}
|
|
569
576
|
|
|
570
577
|
const userContent = event.content + attachmentInfo;
|
|
571
|
-
|
|
578
|
+
let messageBody = `【系统提示】\n${systemPrompts.join("\n")}\n\n【用户输入】\n${userContent}`;
|
|
579
|
+
|
|
580
|
+
if(userContent.startsWith("/")){ // 保留Openclaw原始命令
|
|
581
|
+
messageBody = userContent
|
|
582
|
+
}
|
|
583
|
+
log?.info(`[qqbot:${account.accountId}] messageBody: ${messageBody}`);
|
|
572
584
|
|
|
573
585
|
const body = pluginRuntime.channel.reply.formatInboundEnvelope({
|
|
574
|
-
channel: "
|
|
586
|
+
channel: "qqbot",
|
|
575
587
|
from: event.senderName ?? event.senderId,
|
|
576
588
|
timestamp: new Date(event.timestamp).getTime(),
|
|
577
589
|
body: messageBody,
|
|
@@ -590,6 +602,14 @@ openclaw cron add \\
|
|
|
590
602
|
: `qqbot:c2c:${event.senderId}`;
|
|
591
603
|
const toAddress = fromAddress;
|
|
592
604
|
|
|
605
|
+
// 计算命令授权状态
|
|
606
|
+
// allowFrom: ["*"] 表示允许所有人,否则检查 senderId 是否在 allowFrom 列表中
|
|
607
|
+
const allowFromList = account.config?.allowFrom ?? [];
|
|
608
|
+
const allowAll = allowFromList.length === 0 || allowFromList.some((entry: string) => entry === "*");
|
|
609
|
+
const commandAuthorized = allowAll || allowFromList.some((entry: string) =>
|
|
610
|
+
entry.toUpperCase() === event.senderId.toUpperCase()
|
|
611
|
+
);
|
|
612
|
+
|
|
593
613
|
const ctxPayload = pluginRuntime.channel.reply.finalizeInboundContext({
|
|
594
614
|
Body: body,
|
|
595
615
|
RawBody: event.content,
|
|
@@ -610,6 +630,7 @@ openclaw cron add \\
|
|
|
610
630
|
QQChannelId: event.channelId,
|
|
611
631
|
QQGuildId: event.guildId,
|
|
612
632
|
QQGroupOpenid: event.groupOpenid,
|
|
633
|
+
CommandAuthorized: commandAuthorized,
|
|
613
634
|
});
|
|
614
635
|
|
|
615
636
|
// 发送消息的辅助函数,带 token 过期重试
|
|
@@ -810,7 +831,7 @@ openclaw cron add \\
|
|
|
810
831
|
log?.info(`[qqbot:${account.accountId}] Sent image via <qqimg> tag: ${imagePath.slice(0, 60)}...`);
|
|
811
832
|
} catch (err) {
|
|
812
833
|
log?.error(`[qqbot:${account.accountId}] Failed to send image from <qqimg>: ${err}`);
|
|
813
|
-
await sendErrorMessage(
|
|
834
|
+
await sendErrorMessage(`图片发送失败,图片似乎不存在哦,图片路径:${imagePath}`);
|
|
814
835
|
}
|
|
815
836
|
}
|
|
816
837
|
}
|
|
@@ -1250,7 +1271,9 @@ openclaw cron add \\
|
|
|
1250
1271
|
}
|
|
1251
1272
|
},
|
|
1252
1273
|
},
|
|
1253
|
-
replyOptions: {
|
|
1274
|
+
replyOptions: {
|
|
1275
|
+
disableBlockStreaming: false,
|
|
1276
|
+
},
|
|
1254
1277
|
});
|
|
1255
1278
|
|
|
1256
1279
|
// 等待分发完成或超时
|
package/src/onboarding.ts
CHANGED
|
@@ -21,6 +21,7 @@ interface QQBotChannelConfig {
|
|
|
21
21
|
clientSecretFile?: string;
|
|
22
22
|
name?: string;
|
|
23
23
|
imageServerBaseUrl?: string;
|
|
24
|
+
allowFrom?: string[];
|
|
24
25
|
accounts?: Record<string, {
|
|
25
26
|
enabled?: boolean;
|
|
26
27
|
appId?: string;
|
|
@@ -28,6 +29,7 @@ interface QQBotChannelConfig {
|
|
|
28
29
|
clientSecretFile?: string;
|
|
29
30
|
name?: string;
|
|
30
31
|
imageServerBaseUrl?: string;
|
|
32
|
+
allowFrom?: string[];
|
|
31
33
|
}>;
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -137,6 +139,7 @@ statusLines: [`QQ Bot: ${configured ? "已配置" : "需要 AppID 和 ClientSecr
|
|
|
137
139
|
qqbot: {
|
|
138
140
|
...(next.channels?.qqbot as Record<string, unknown> || {}),
|
|
139
141
|
enabled: true,
|
|
142
|
+
allowFrom: resolvedAccount.config?.allowFrom ?? ["*"],
|
|
140
143
|
},
|
|
141
144
|
},
|
|
142
145
|
};
|
|
@@ -200,6 +203,9 @@ statusLines: [`QQ Bot: ${configured ? "已配置" : "需要 AppID 和 ClientSecr
|
|
|
200
203
|
).trim();
|
|
201
204
|
}
|
|
202
205
|
|
|
206
|
+
// 默认允许所有人执行命令(用户无感知)
|
|
207
|
+
const allowFrom: string[] = resolvedAccount.config?.allowFrom ?? ["*"];
|
|
208
|
+
|
|
203
209
|
// 应用配置
|
|
204
210
|
if (appId && clientSecret) {
|
|
205
211
|
if (accountId === DEFAULT_ACCOUNT_ID) {
|
|
@@ -212,6 +218,7 @@ statusLines: [`QQ Bot: ${configured ? "已配置" : "需要 AppID 和 ClientSecr
|
|
|
212
218
|
enabled: true,
|
|
213
219
|
appId,
|
|
214
220
|
clientSecret,
|
|
221
|
+
allowFrom,
|
|
215
222
|
},
|
|
216
223
|
},
|
|
217
224
|
};
|
|
@@ -230,6 +237,7 @@ statusLines: [`QQ Bot: ${configured ? "已配置" : "需要 AppID 和 ClientSecr
|
|
|
230
237
|
enabled: true,
|
|
231
238
|
appId,
|
|
232
239
|
clientSecret,
|
|
240
|
+
allowFrom,
|
|
233
241
|
},
|
|
234
242
|
},
|
|
235
243
|
},
|