mioku-plugin-chat 3.0.1 → 3.0.3
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/core/base.ts +1 -1
- package/core/chat-turn.ts +109 -35
- package/handlers/message.ts +11 -6
- package/package.json +4 -4
- package/utils/message.ts +10 -15
package/core/base.ts
CHANGED
package/core/chat-turn.ts
CHANGED
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import type { MiokuContext } from "mioku";
|
|
2
|
-
import type { AITool, ChatRuntimePromptInjection } from "mioku";
|
|
2
|
+
import type { AITool, Bot, ChatRuntimePromptInjection } from "mioku";
|
|
3
3
|
import type { ChatPluginContext, ChatRuntimeState } from "../context";
|
|
4
4
|
import type { ChatConfig, ChatMessage, TargetMessage } from "../types";
|
|
5
|
-
import {
|
|
6
|
-
getGroupHistory,
|
|
7
|
-
getBotRole,
|
|
8
|
-
getQuotedContent,
|
|
9
|
-
} from "../utils";
|
|
5
|
+
import { getGroupHistory, getBotRole, getQuotedContent } from "../utils";
|
|
10
6
|
import { buildStructuredUserInputFromTarget } from "../manage/group-structured-history";
|
|
11
7
|
|
|
12
8
|
export type RuntimeReplyContextType =
|
|
@@ -53,7 +49,8 @@ function buildRuntimeTargetMessageContent(
|
|
|
53
49
|
overrideContent?: string,
|
|
54
50
|
): string {
|
|
55
51
|
if (overrideContent?.trim()) return overrideContent.trim();
|
|
56
|
-
if (!event || !event.message || !Array.isArray(event.message))
|
|
52
|
+
if (!event || !event.message || !Array.isArray(event.message))
|
|
53
|
+
return NO_NEW_MESSAGE;
|
|
57
54
|
return ctx.text(event)?.trim() || NO_NEW_MESSAGE;
|
|
58
55
|
}
|
|
59
56
|
|
|
@@ -74,7 +71,8 @@ function resolveRuntimeContext(
|
|
|
74
71
|
selfId: event.self_id,
|
|
75
72
|
sessionId: groupId ? `group:${groupId}` : `personal:${userId}`,
|
|
76
73
|
personalSessionId: groupId ? `personal:${userId}` : undefined,
|
|
77
|
-
senderName:
|
|
74
|
+
senderName:
|
|
75
|
+
event.sender?.card || event.sender?.nickname || String(userId),
|
|
78
76
|
userRole: event.sender?.role || "member",
|
|
79
77
|
userTitle: event.sender?.title || undefined,
|
|
80
78
|
groupName: event.group_name,
|
|
@@ -85,7 +83,10 @@ function resolveRuntimeContext(
|
|
|
85
83
|
if (typeof options.selfId !== "number") {
|
|
86
84
|
throw new Error("Chat runtime requires either event or selfId");
|
|
87
85
|
}
|
|
88
|
-
if (
|
|
86
|
+
if (
|
|
87
|
+
typeof options.groupId !== "number" &&
|
|
88
|
+
typeof options.userId !== "number"
|
|
89
|
+
) {
|
|
89
90
|
throw new Error("Chat runtime requires groupId or userId");
|
|
90
91
|
}
|
|
91
92
|
|
|
@@ -112,7 +113,9 @@ function resolveRuntimeContext(
|
|
|
112
113
|
groupId: options.groupId,
|
|
113
114
|
userId,
|
|
114
115
|
selfId: options.selfId,
|
|
115
|
-
sessionId: options.groupId
|
|
116
|
+
sessionId: options.groupId
|
|
117
|
+
? `group:${options.groupId}`
|
|
118
|
+
: `personal:${userId}`,
|
|
116
119
|
personalSessionId:
|
|
117
120
|
options.groupId && userId ? `personal:${userId}` : undefined,
|
|
118
121
|
senderName: options.groupId ? "system" : String(userId),
|
|
@@ -144,39 +147,90 @@ export async function finalizeChatTurn(
|
|
|
144
147
|
return;
|
|
145
148
|
}
|
|
146
149
|
const { ctx } = pluginCtx;
|
|
147
|
-
const {
|
|
150
|
+
const {
|
|
151
|
+
groupId,
|
|
152
|
+
groupSessionId,
|
|
153
|
+
userId,
|
|
154
|
+
selfId,
|
|
155
|
+
cfg,
|
|
156
|
+
toolCtx,
|
|
157
|
+
result,
|
|
158
|
+
event,
|
|
159
|
+
} = args;
|
|
160
|
+
|
|
161
|
+
const actingBot = (selfId ? ctx.pickBot(selfId) : undefined) ?? event?.bot;
|
|
162
|
+
const eventBotId = event?.bot ? String(event.bot.bot_id) : undefined;
|
|
163
|
+
const actingIsEventBot =
|
|
164
|
+
eventBotId == null ||
|
|
165
|
+
(actingBot != null && String(actingBot.bot_id) === eventBotId);
|
|
148
166
|
|
|
149
167
|
if (groupId) {
|
|
150
168
|
const sentMessageIds = await pluginCtx.sendAIResponse(
|
|
151
|
-
{
|
|
169
|
+
{
|
|
170
|
+
ctx,
|
|
171
|
+
groupId,
|
|
172
|
+
messages: result.messages,
|
|
173
|
+
config: cfg,
|
|
174
|
+
sentIndices: toolCtx.sentMessageIndices,
|
|
175
|
+
audioService: pluginCtx.audioService,
|
|
176
|
+
},
|
|
152
177
|
selfId,
|
|
153
178
|
);
|
|
154
|
-
await pluginCtx.sendEmoji(ctx, groupId, result.emojiPath,
|
|
179
|
+
await pluginCtx.sendEmoji(ctx, groupId, result.emojiPath, actingBot);
|
|
155
180
|
const now = Date.now();
|
|
156
|
-
pluginCtx.saveBotMessages(
|
|
181
|
+
pluginCtx.saveBotMessages(
|
|
182
|
+
groupId,
|
|
183
|
+
groupSessionId,
|
|
184
|
+
result.messages,
|
|
185
|
+
now,
|
|
186
|
+
cfg,
|
|
187
|
+
pluginCtx.db,
|
|
188
|
+
ctx,
|
|
189
|
+
actingBot,
|
|
190
|
+
sentMessageIds,
|
|
191
|
+
);
|
|
157
192
|
if (args.isLive) {
|
|
158
|
-
pluginCtx.idleCheckManager.recordBotMessages(
|
|
193
|
+
pluginCtx.idleCheckManager.recordBotMessages(
|
|
194
|
+
groupSessionId,
|
|
195
|
+
result.messages.length,
|
|
196
|
+
selfId,
|
|
197
|
+
);
|
|
159
198
|
}
|
|
160
|
-
pluginCtx.cooldownManager.startCooldownTimer(
|
|
199
|
+
pluginCtx.cooldownManager.startCooldownTimer(
|
|
200
|
+
groupSessionId,
|
|
201
|
+
groupId,
|
|
202
|
+
selfId,
|
|
203
|
+
);
|
|
161
204
|
} else {
|
|
162
205
|
const sentIndices = toolCtx.sentMessageIndices;
|
|
163
206
|
for (let i = 0; i < result.messages.length; i++) {
|
|
164
207
|
if (sentIndices?.has(i)) continue;
|
|
165
|
-
await pluginCtx.sendMessage(
|
|
208
|
+
await pluginCtx.sendMessage(
|
|
209
|
+
ctx,
|
|
210
|
+
undefined,
|
|
211
|
+
userId,
|
|
212
|
+
result.messages[i],
|
|
213
|
+
cfg,
|
|
214
|
+
selfId,
|
|
215
|
+
pluginCtx.audioService,
|
|
216
|
+
);
|
|
166
217
|
}
|
|
167
218
|
if (result.emojiPath) {
|
|
168
219
|
try {
|
|
169
220
|
const emojiSegment = ctx.segment.image(`file://${result.emojiPath}`);
|
|
170
|
-
if (args.isLive) {
|
|
221
|
+
if (args.isLive && actingIsEventBot && event?.reply) {
|
|
171
222
|
await event.reply([emojiSegment]);
|
|
172
223
|
} else {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
224
|
+
if (!actingBot) throw new Error(`bot ${selfId} not found`);
|
|
225
|
+
await actingBot.sendMessage({ type: "private", user_id: userId }, [
|
|
226
|
+
emojiSegment,
|
|
227
|
+
]);
|
|
176
228
|
}
|
|
177
229
|
} catch (err) {
|
|
178
230
|
ctx.logger.warn(
|
|
179
|
-
args.isLive
|
|
231
|
+
args.isLive
|
|
232
|
+
? `[Emoticon] Send failed: ${err}`
|
|
233
|
+
: `[chat-runtime] Send emoji failed: ${err}`,
|
|
180
234
|
);
|
|
181
235
|
}
|
|
182
236
|
}
|
|
@@ -189,12 +243,13 @@ export async function processChat(
|
|
|
189
243
|
e: any,
|
|
190
244
|
pluginCtx: ChatPluginContext,
|
|
191
245
|
runtimeState: ChatRuntimeState,
|
|
246
|
+
options: { replyBot?: Bot } = {},
|
|
192
247
|
): Promise<void> {
|
|
193
248
|
const { ctx } = pluginCtx;
|
|
194
249
|
const isGroup = e.message_type === "group";
|
|
195
250
|
const groupId: number | undefined = isGroup ? e.group_id : undefined;
|
|
196
251
|
const userId: number = e.user_id || e.sender?.user_id;
|
|
197
|
-
const selfId = e.self_id;
|
|
252
|
+
const selfId = options.replyBot ? Number(options.replyBot.bot_id) : e.self_id;
|
|
198
253
|
const cfg = await pluginCtx.getConfig(groupId);
|
|
199
254
|
|
|
200
255
|
const personalSessionId = `personal:${userId}`;
|
|
@@ -212,7 +267,11 @@ export async function processChat(
|
|
|
212
267
|
groupId ?? userId,
|
|
213
268
|
);
|
|
214
269
|
if (groupId) {
|
|
215
|
-
pluginCtx.sessionManager.getOrCreate(
|
|
270
|
+
pluginCtx.sessionManager.getOrCreate(
|
|
271
|
+
personalSessionId,
|
|
272
|
+
"personal",
|
|
273
|
+
userId,
|
|
274
|
+
);
|
|
216
275
|
}
|
|
217
276
|
|
|
218
277
|
const quotedInfo = await getQuotedContent(e, ctx);
|
|
@@ -242,7 +301,7 @@ export async function processChat(
|
|
|
242
301
|
groupId,
|
|
243
302
|
ctx,
|
|
244
303
|
cfg.historyCount,
|
|
245
|
-
|
|
304
|
+
selfId,
|
|
246
305
|
pluginCtx.db,
|
|
247
306
|
pluginCtx.buildHistoryMediaOptions(pluginCtx.aiInstance, cfg),
|
|
248
307
|
)
|
|
@@ -260,15 +319,18 @@ export async function processChat(
|
|
|
260
319
|
}));
|
|
261
320
|
|
|
262
321
|
const botNickname =
|
|
263
|
-
cfg.nicknames[0] ||
|
|
264
|
-
|
|
322
|
+
cfg.nicknames[0] ||
|
|
323
|
+
options.replyBot?.nickname ||
|
|
324
|
+
e.bot?.nickname ||
|
|
325
|
+
"Bot";
|
|
326
|
+
const botRole = groupId ? await getBotRole(groupId, ctx, selfId) : "member";
|
|
265
327
|
let groupName: string | undefined;
|
|
266
328
|
let memberCount: number | undefined;
|
|
267
329
|
if (groupId) {
|
|
268
330
|
const groupInfo = await pluginCtx.getGroupInfoData(
|
|
269
331
|
ctx,
|
|
270
332
|
groupId,
|
|
271
|
-
|
|
333
|
+
selfId,
|
|
272
334
|
e.group_name,
|
|
273
335
|
);
|
|
274
336
|
groupName = groupInfo.groupName;
|
|
@@ -311,7 +373,7 @@ export async function processChat(
|
|
|
311
373
|
pendingImageUrls: imageUrls,
|
|
312
374
|
humanize: pluginCtx.humanize,
|
|
313
375
|
targetMessage,
|
|
314
|
-
selfId
|
|
376
|
+
selfId,
|
|
315
377
|
audioService: pluginCtx.audioService,
|
|
316
378
|
});
|
|
317
379
|
|
|
@@ -351,7 +413,12 @@ export async function processChat(
|
|
|
351
413
|
}
|
|
352
414
|
: undefined,
|
|
353
415
|
),
|
|
354
|
-
{
|
|
416
|
+
{
|
|
417
|
+
userId,
|
|
418
|
+
groupId,
|
|
419
|
+
label: isGroup ? "group-chat" : "private-chat",
|
|
420
|
+
skipRetryOnRateLimit: true,
|
|
421
|
+
},
|
|
355
422
|
);
|
|
356
423
|
|
|
357
424
|
if (!result) {
|
|
@@ -366,7 +433,7 @@ export async function processChat(
|
|
|
366
433
|
groupId,
|
|
367
434
|
groupSessionId,
|
|
368
435
|
userId,
|
|
369
|
-
selfId
|
|
436
|
+
selfId,
|
|
370
437
|
toolCtx,
|
|
371
438
|
send: true,
|
|
372
439
|
isLive: true,
|
|
@@ -435,7 +502,13 @@ async function executeChatRuntimeRequestNow(
|
|
|
435
502
|
}
|
|
436
503
|
|
|
437
504
|
const rawHistory = groupId
|
|
438
|
-
? await getGroupHistory(
|
|
505
|
+
? await getGroupHistory(
|
|
506
|
+
groupId,
|
|
507
|
+
pluginCtx.ctx,
|
|
508
|
+
cfg.historyCount,
|
|
509
|
+
selfId,
|
|
510
|
+
pluginCtx.db,
|
|
511
|
+
)
|
|
439
512
|
: [];
|
|
440
513
|
const history: ChatMessage[] = rawHistory.map((msg) => ({
|
|
441
514
|
sessionId,
|
|
@@ -449,9 +522,10 @@ async function executeChatRuntimeRequestNow(
|
|
|
449
522
|
messageId: msg.messageId,
|
|
450
523
|
}));
|
|
451
524
|
|
|
452
|
-
const botRole = groupId
|
|
453
|
-
|
|
454
|
-
|
|
525
|
+
const botRole = groupId
|
|
526
|
+
? await getBotRole(groupId, pluginCtx.ctx, selfId)
|
|
527
|
+
: "member";
|
|
528
|
+
const botNickname = cfg.nicknames[0] || event?.bot?.nickname || "Bot";
|
|
455
529
|
|
|
456
530
|
let groupName: string | undefined;
|
|
457
531
|
let memberCount: number | undefined;
|
package/handlers/message.ts
CHANGED
|
@@ -222,6 +222,10 @@ export function createMessageHandler(
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
const atBot = shouldTrigger(e, text, cfg, ctx);
|
|
225
|
+
const replyBot = ctx.pickReplyBot(e);
|
|
226
|
+
const actorSelfId = replyBot
|
|
227
|
+
? Number(replyBot.bot_id)
|
|
228
|
+
: Number(e.self_id || 0);
|
|
225
229
|
const quotedBot = isGroup ? await isQuotingBot(e, ctx) : null;
|
|
226
230
|
const mentionedNickname =
|
|
227
231
|
cfg.nicknames.length > 0 &&
|
|
@@ -275,14 +279,14 @@ export function createMessageHandler(
|
|
|
275
279
|
groupSessionId,
|
|
276
280
|
groupId,
|
|
277
281
|
delayInfo.delayMs,
|
|
278
|
-
|
|
282
|
+
actorSelfId,
|
|
279
283
|
);
|
|
280
284
|
return;
|
|
281
285
|
}
|
|
282
286
|
}
|
|
283
287
|
|
|
284
288
|
pluginCtx.rateLimiter.record(userId, groupId, text);
|
|
285
|
-
await processChat(e, pluginCtx, runtimeState);
|
|
289
|
+
await processChat(e, pluginCtx, runtimeState, { replyBot });
|
|
286
290
|
return;
|
|
287
291
|
}
|
|
288
292
|
|
|
@@ -294,10 +298,11 @@ export function createMessageHandler(
|
|
|
294
298
|
ctx,
|
|
295
299
|
cfg.historyCount,
|
|
296
300
|
pluginCtx.db,
|
|
297
|
-
|
|
301
|
+
actorSelfId,
|
|
298
302
|
pluginCtx.buildHistoryMediaOptions(pluginCtx.aiInstance, cfg),
|
|
299
303
|
);
|
|
300
|
-
const botNickname =
|
|
304
|
+
const botNickname =
|
|
305
|
+
cfg.nicknames[0] || replyBot?.nickname || e.bot?.nickname || "Bot";
|
|
301
306
|
const planResult = await pluginCtx.humanize.actionPlanner.plan(
|
|
302
307
|
groupSessionId,
|
|
303
308
|
botNickname,
|
|
@@ -307,7 +312,7 @@ export function createMessageHandler(
|
|
|
307
312
|
if (planResult.action !== "reply") return;
|
|
308
313
|
if (!pluginCtx.rateLimiter.canProcess(userId, groupId, text)) return;
|
|
309
314
|
pluginCtx.rateLimiter.record(userId, groupId, text);
|
|
310
|
-
await processChat(e, pluginCtx, runtimeState);
|
|
315
|
+
await processChat(e, pluginCtx, runtimeState, { replyBot });
|
|
311
316
|
}
|
|
312
317
|
};
|
|
313
318
|
|
|
@@ -319,7 +324,7 @@ export function createMessageHandler(
|
|
|
319
324
|
pluginCtx.rateLimiter.recordInteraction(groupId, userId);
|
|
320
325
|
pluginCtx.queueProcessor.scheduleQueuedMessages(
|
|
321
326
|
groupSessionId,
|
|
322
|
-
|
|
327
|
+
actorSelfId,
|
|
323
328
|
);
|
|
324
329
|
}
|
|
325
330
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-plugin-chat",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.3",
|
|
4
4
|
"description": "AI 智能聊天插件",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
},
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@types/bun": "^1.3.14",
|
|
57
|
-
"mioku": "workspace:*"
|
|
57
|
+
"mioku": "workspace:*",
|
|
58
|
+
"mioku-service-audio": "workspace:*"
|
|
58
59
|
},
|
|
59
60
|
"peerDependencies": {
|
|
60
|
-
"mioku": "^1.0.0"
|
|
61
|
-
"mioku-service-audio": "workspace:*"
|
|
61
|
+
"mioku": "^1.0.0"
|
|
62
62
|
}
|
|
63
63
|
}
|
package/utils/message.ts
CHANGED
|
@@ -27,11 +27,7 @@ export function shouldTrigger(
|
|
|
27
27
|
): boolean {
|
|
28
28
|
if (e.message_type === "private") return false;
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
if (!atSeg) return false;
|
|
32
|
-
const data = (atSeg.data ?? {}) as { qq?: unknown; target?: unknown };
|
|
33
|
-
const qq = data.qq ?? data.target;
|
|
34
|
-
return String(qq ?? "") === String(e.self_id ?? "");
|
|
30
|
+
return ctx.mentionedBots(e).length > 0;
|
|
35
31
|
}
|
|
36
32
|
|
|
37
33
|
/**
|
|
@@ -45,8 +41,13 @@ export async function isQuotingBot(
|
|
|
45
41
|
if (e.quote_id) {
|
|
46
42
|
try {
|
|
47
43
|
const quoteMsg = await fetchQuotedMessage(e, ctx);
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
const quotedUserId = String(quoteMsg?.sender?.user_id ?? "");
|
|
45
|
+
const quotingAnyBot =
|
|
46
|
+
quoteMsg != null &&
|
|
47
|
+
quotedUserId !== "" &&
|
|
48
|
+
ctx.bots.some((bot) => String(bot.bot_id) === quotedUserId);
|
|
49
|
+
if (quotingAnyBot) {
|
|
50
|
+
const quotedText = quoteMsg!.message
|
|
50
51
|
?.filter((s: any) => s.type === "text")
|
|
51
52
|
.map((s: any) => String(s.data?.text ?? s.text ?? ""))
|
|
52
53
|
.join("");
|
|
@@ -130,15 +131,9 @@ export function extractContent(
|
|
|
130
131
|
text = ctx.text(e) || "";
|
|
131
132
|
} catch {}
|
|
132
133
|
|
|
133
|
-
// If text is empty but
|
|
134
|
+
// If text is empty but the message @'d a connected bot, describe the action
|
|
134
135
|
if (!text.trim() && e.message) {
|
|
135
|
-
|
|
136
|
-
if (seg.type !== "at") return false;
|
|
137
|
-
const data = (seg.data ?? {}) as { qq?: unknown; target?: unknown };
|
|
138
|
-
const qq = data.qq ?? data.target;
|
|
139
|
-
return String(qq ?? "") === String(e.self_id ?? "");
|
|
140
|
-
});
|
|
141
|
-
if (hasAt) {
|
|
136
|
+
if (ctx.mentionedBots(e).length > 0) {
|
|
142
137
|
text = "[@you with no text]";
|
|
143
138
|
}
|
|
144
139
|
}
|