mioku-plugin-chat 2.3.0 → 2.4.0
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/configs/base.ts +1 -0
- package/configs/settings.ts +1 -1
- package/context.ts +17 -0
- package/core/chat-engine.ts +6 -1
- package/core/chat-turn.ts +540 -0
- package/core/media/history-media.ts +215 -70
- package/core/media/image-analyzer.ts +28 -19
- package/core/media/image-compress.ts +75 -0
- package/core/media/segment.ts +91 -0
- package/core/multimodal.ts +24 -12
- package/core/prompt.ts +332 -445
- package/core/tools/info.ts +284 -0
- package/core/tools/load-skill.ts +149 -0
- package/core/tools/web.ts +306 -0
- package/core/tools.ts +11 -822
- package/db.ts +45 -142
- package/handlers/idle-debug.ts +135 -0
- package/handlers/message.ts +316 -0
- package/handlers/poke.ts +165 -0
- package/humanize/emotion-agent.ts +3 -4
- package/humanize/expression.ts +3 -4
- package/humanize/planner.ts +2 -2
- package/humanize/topic.ts +3 -4
- package/index.ts +73 -1705
- package/manage/cooldown.ts +52 -27
- package/manage/group-structured-history.ts +35 -0
- package/manage/idle-check.ts +40 -20
- package/manage/queue-processor.ts +51 -25
- package/manage/rate-limit-guard.ts +70 -0
- package/package.json +1 -1
- package/runtime/chat-runtime.ts +124 -0
- package/types.ts +1 -0
- package/utils/index.ts +2 -0
- package/utils/json.ts +8 -0
- package/utils/message.ts +185 -175
package/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type {
|
|
3
|
-
import type { ScreenshotService } from "mioku";
|
|
4
|
-
import {
|
|
1
|
+
import { definePlugin } from "mioki";
|
|
2
|
+
import type { MiokiContext } from "mioki";
|
|
3
|
+
import type { AIInstance, AIService, ConfigService, ScreenshotService } from "mioku";
|
|
4
|
+
import { getPluginRuntimeState } from "mioku";
|
|
5
5
|
import type { ChatConfig, ChatMessage, TargetMessage } from "./types";
|
|
6
6
|
import { initDatabase } from "./db";
|
|
7
7
|
import { SessionManager } from "./manage/session";
|
|
@@ -9,17 +9,6 @@ import { RateLimiter } from "./manage/rate-limiter";
|
|
|
9
9
|
import { runChat } from "./core/chat-engine";
|
|
10
10
|
import { HumanizeEngine } from "./humanize";
|
|
11
11
|
import { SkillSessionManager } from "./manage/skill-session";
|
|
12
|
-
import {
|
|
13
|
-
getBotRole,
|
|
14
|
-
getGroupHistory,
|
|
15
|
-
getQuotedContent,
|
|
16
|
-
isGroupAllowed,
|
|
17
|
-
isQuotingBot,
|
|
18
|
-
shouldTrigger,
|
|
19
|
-
} from "./utils";
|
|
20
|
-
import { BASE_CONFIG } from "./configs/base";
|
|
21
|
-
import { SETTINGS_CONFIG } from "./configs/settings";
|
|
22
|
-
import { PERSONALIZATION_CONFIG } from "./configs/personalization";
|
|
23
12
|
import { MessageQueueManager } from "./utils/queue";
|
|
24
13
|
import {
|
|
25
14
|
buildToolContext,
|
|
@@ -31,128 +20,24 @@ import {
|
|
|
31
20
|
saveBotMessages,
|
|
32
21
|
sendEmoji,
|
|
33
22
|
} from "./core/base";
|
|
34
|
-
import {
|
|
35
|
-
summarizeGroupNotice,
|
|
36
|
-
summarizeHistoryCard,
|
|
37
|
-
summarizeHistoryForward,
|
|
38
|
-
summarizeHistoryVideo,
|
|
39
|
-
type HistoryMediaProcessingOptions,
|
|
40
|
-
} from "./core/media/history-media";
|
|
41
23
|
import { CooldownManager } from "./manage/cooldown";
|
|
42
24
|
import { IdleCheckManager } from "./manage/idle-check";
|
|
43
25
|
import { QueueProcessor } from "./manage/queue-processor";
|
|
44
|
-
import {
|
|
45
|
-
ChatDatabaseCleanup,
|
|
46
|
-
DEFAULT_CLEANUP_CONFIG,
|
|
47
|
-
} from "./manage/cleanup";
|
|
26
|
+
import { ChatDatabaseCleanup, DEFAULT_CLEANUP_CONFIG } from "./manage/cleanup";
|
|
48
27
|
import {
|
|
49
28
|
GroupStructuredHistoryManager,
|
|
50
|
-
type StructuredUserInput,
|
|
51
29
|
buildStructuredUserMessages,
|
|
30
|
+
buildStructuredUserInputFromTarget,
|
|
52
31
|
} from "./manage/group-structured-history";
|
|
53
|
-
import
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
} from "./
|
|
62
|
-
import type { ChatPluginContext, ChatRuntime } from "./context";
|
|
63
|
-
import type { ChatRuntimeState } from "./context";
|
|
64
|
-
import type {
|
|
65
|
-
ChatRuntimeNoticeOptions,
|
|
66
|
-
ChatRuntimeResult,
|
|
67
|
-
ChatRuntimePromptInjection,
|
|
68
|
-
ChatRuntimeInformationRequestOptions,
|
|
69
|
-
ChatRuntimeCollectedInfo,
|
|
70
|
-
} from "mioku";
|
|
71
|
-
import type { AITool } from "mioku";
|
|
72
|
-
|
|
73
|
-
type RuntimeReplyContextType =
|
|
74
|
-
| "reply"
|
|
75
|
-
| "comment"
|
|
76
|
-
| "idle"
|
|
77
|
-
| "review"
|
|
78
|
-
| "poked";
|
|
79
|
-
|
|
80
|
-
interface ExecuteChatRuntimeRequestOptions {
|
|
81
|
-
event?: any;
|
|
82
|
-
selfId?: number;
|
|
83
|
-
groupId?: number;
|
|
84
|
-
userId?: number;
|
|
85
|
-
config: ChatConfig;
|
|
86
|
-
targetMessageContent?: string;
|
|
87
|
-
promptInjections?: ChatRuntimePromptInjection[];
|
|
88
|
-
extraTools?: AITool[];
|
|
89
|
-
send?: boolean;
|
|
90
|
-
replyContextType?: RuntimeReplyContextType;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function buildStructuredUserInputFromEvent(
|
|
94
|
-
event: any,
|
|
95
|
-
content: string,
|
|
96
|
-
fallbackTimestamp: number = Date.now(),
|
|
97
|
-
): StructuredUserInput {
|
|
98
|
-
return {
|
|
99
|
-
userName:
|
|
100
|
-
event?.sender?.card ||
|
|
101
|
-
event?.sender?.nickname ||
|
|
102
|
-
String(event?.user_id || event?.sender?.user_id || 0),
|
|
103
|
-
userId: event?.user_id || event?.sender?.user_id || 0,
|
|
104
|
-
userRole: event?.sender?.role || "member",
|
|
105
|
-
userTitle: event?.sender?.title,
|
|
106
|
-
content,
|
|
107
|
-
messageId: event?.message_id,
|
|
108
|
-
timestamp:
|
|
109
|
-
typeof event?.time === "number" ? event.time * 1000 : fallbackTimestamp,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function buildChatMessageFromEvent(
|
|
114
|
-
e: any,
|
|
115
|
-
text: string,
|
|
116
|
-
isGroup: boolean,
|
|
117
|
-
groupId: number | undefined,
|
|
118
|
-
): ChatMessage {
|
|
119
|
-
const userId: number = e.user_id || e.sender?.user_id;
|
|
120
|
-
return {
|
|
121
|
-
sessionId: groupId ? `group:${groupId}` : `personal:${userId}`,
|
|
122
|
-
role: "user" as const,
|
|
123
|
-
content: text,
|
|
124
|
-
userId,
|
|
125
|
-
userName: e.sender?.card || e.sender?.nickname || String(userId),
|
|
126
|
-
userRole: e.sender?.role || "member",
|
|
127
|
-
userTitle: (e.sender as any)?.title || undefined,
|
|
128
|
-
groupId,
|
|
129
|
-
groupName: isGroup ? e.group_name : undefined,
|
|
130
|
-
timestamp: Date.now(),
|
|
131
|
-
messageId: e.message_id,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function buildStructuredUserInputFromTarget(
|
|
136
|
-
targetMessage: TargetMessage,
|
|
137
|
-
): StructuredUserInput {
|
|
138
|
-
return {
|
|
139
|
-
userName: targetMessage.userName,
|
|
140
|
-
userId: targetMessage.userId,
|
|
141
|
-
userRole: targetMessage.userRole,
|
|
142
|
-
userTitle: targetMessage.userTitle,
|
|
143
|
-
content: targetMessage.content,
|
|
144
|
-
messageId: targetMessage.messageId,
|
|
145
|
-
timestamp: targetMessage.timestamp,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function buildHistoryMediaOptions(ai: AIInstance, config: ChatConfig) {
|
|
150
|
-
return {
|
|
151
|
-
ai,
|
|
152
|
-
workingModel: config.workingModel || config.model,
|
|
153
|
-
multimodalWorkingModel: config.multimodalWorkingModel || config.model,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
32
|
+
import { BASE_CONFIG } from "./configs/base";
|
|
33
|
+
import { SETTINGS_CONFIG } from "./configs/settings";
|
|
34
|
+
import { PERSONALIZATION_CONFIG } from "./configs/personalization";
|
|
35
|
+
import { RateLimitGuard } from "./manage/rate-limit-guard";
|
|
36
|
+
import { createChatRuntime } from "./runtime/chat-runtime";
|
|
37
|
+
import { createMessageHandler } from "./handlers/message";
|
|
38
|
+
import { createPokeHandler } from "./handlers/poke";
|
|
39
|
+
import { buildHistoryMediaOptions } from "./core/media/segment";
|
|
40
|
+
import type { ChatPluginContext, ChatHandlerState } from "./context";
|
|
156
41
|
|
|
157
42
|
function normalizeIdList(input: unknown): number[] {
|
|
158
43
|
if (!Array.isArray(input)) return [];
|
|
@@ -165,128 +50,22 @@ function normalizeIdList(input: unknown): number[] {
|
|
|
165
50
|
);
|
|
166
51
|
}
|
|
167
52
|
|
|
168
|
-
|
|
169
|
-
url?: string;
|
|
170
|
-
data?: { url?: string };
|
|
171
|
-
}): string | null {
|
|
172
|
-
const url = seg?.url || seg?.data?.url;
|
|
173
|
-
return typeof url === "string" && url.trim() ? url.trim() : null;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function getSegmentSourceCandidates(seg: {
|
|
177
|
-
file?: string;
|
|
178
|
-
data?: { file?: string; path?: string; url?: string };
|
|
179
|
-
path?: string;
|
|
180
|
-
url?: string;
|
|
181
|
-
}): string[] {
|
|
182
|
-
return Array.from(
|
|
183
|
-
new Set(
|
|
184
|
-
[
|
|
185
|
-
seg?.file,
|
|
186
|
-
seg?.data?.file,
|
|
187
|
-
seg?.path,
|
|
188
|
-
seg?.data?.path,
|
|
189
|
-
seg?.url,
|
|
190
|
-
seg?.data?.url,
|
|
191
|
-
]
|
|
192
|
-
.map((v) => (typeof v === "string" ? v.trim() : ""))
|
|
193
|
-
.filter(Boolean),
|
|
194
|
-
),
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
async function getVideoSourceCandidatesFromMessage(
|
|
199
|
-
bot: {
|
|
200
|
-
api(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
201
|
-
},
|
|
202
|
-
messageId: number | string | undefined,
|
|
203
|
-
): Promise<string[]> {
|
|
204
|
-
if (messageId == null) return [];
|
|
205
|
-
const result = (await bot.api("get_msg", { message_id: messageId })) as {
|
|
206
|
-
message?: unknown[];
|
|
207
|
-
data?: { message?: unknown[] };
|
|
208
|
-
};
|
|
209
|
-
const segments = result?.message || result?.data?.message || [];
|
|
210
|
-
if (!Array.isArray(segments)) return [];
|
|
211
|
-
const candidates: string[] = [];
|
|
212
|
-
for (const seg of segments as { type?: string }[]) {
|
|
213
|
-
if (seg?.type !== "video") continue;
|
|
214
|
-
candidates.push(
|
|
215
|
-
...getSegmentSourceCandidates(
|
|
216
|
-
seg as Parameters<typeof getSegmentSourceCandidates>[0],
|
|
217
|
-
),
|
|
218
|
-
);
|
|
219
|
-
}
|
|
220
|
-
return candidates;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function getForwardId(seg: {
|
|
224
|
-
id?: unknown;
|
|
225
|
-
data?: { id?: unknown };
|
|
226
|
-
}): string | null {
|
|
227
|
-
return String(seg?.id || seg?.data?.id || "");
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function getCardData(seg: { data?: unknown }): string | null {
|
|
231
|
-
const data = seg?.data;
|
|
232
|
-
if (!data) return null;
|
|
233
|
-
return typeof data === "string" ? data : JSON.stringify(data);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function isMediaAnalysisBlocked(config: ChatConfig, userId: number): boolean {
|
|
237
|
-
return Boolean(config.mediaAnalysisBlacklistUsers?.includes(userId));
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function buildHistoryMediaProcessingOptions(
|
|
241
|
-
ai: AIInstance,
|
|
242
|
-
config: ChatConfig,
|
|
243
|
-
db: {
|
|
244
|
-
getMediaSummary(key: string): import("./types").MediaSummaryRecord | null;
|
|
245
|
-
saveMediaSummary(summary: import("./types").MediaSummaryRecord): void;
|
|
246
|
-
},
|
|
247
|
-
bot: {
|
|
248
|
-
api<T = unknown>(
|
|
249
|
-
action: string,
|
|
250
|
-
params?: Record<string, unknown>,
|
|
251
|
-
): Promise<T>;
|
|
252
|
-
},
|
|
253
|
-
groupId: number,
|
|
254
|
-
log: HistoryMediaProcessingOptions["logger"],
|
|
255
|
-
runAIRequest?: <T>(request: () => Promise<T>) => Promise<T | null>,
|
|
256
|
-
): HistoryMediaProcessingOptions {
|
|
257
|
-
return {
|
|
258
|
-
...buildHistoryMediaOptions(ai, config),
|
|
259
|
-
db,
|
|
260
|
-
logger: log,
|
|
261
|
-
bot,
|
|
262
|
-
groupId,
|
|
263
|
-
runAIRequest,
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const chatPlugin = definePlugin({
|
|
53
|
+
export default definePlugin({
|
|
268
54
|
name: "chat",
|
|
269
55
|
version: "1.0.0",
|
|
270
56
|
description: "AI 智能聊天插件",
|
|
271
|
-
|
|
272
57
|
async setup(ctx: MiokiContext) {
|
|
273
58
|
ctx.logger.info("聊天插件正在初始化...");
|
|
274
59
|
|
|
275
60
|
const aiService = ctx.services?.ai as AIService | undefined;
|
|
276
61
|
const configService = ctx.services?.config as ConfigService | undefined;
|
|
277
|
-
const screenshotService = ctx.services?.screenshot as
|
|
278
|
-
| ScreenshotService
|
|
279
|
-
| undefined;
|
|
62
|
+
const screenshotService = ctx.services?.screenshot as ScreenshotService | undefined;
|
|
280
63
|
let warnedMarkdownScreenshotUnavailable = false;
|
|
281
64
|
|
|
282
65
|
if (configService) {
|
|
283
66
|
await configService.registerConfig("chat", "base", BASE_CONFIG);
|
|
284
67
|
await configService.registerConfig("chat", "settings", SETTINGS_CONFIG);
|
|
285
|
-
await configService.registerConfig(
|
|
286
|
-
"chat",
|
|
287
|
-
"personalization",
|
|
288
|
-
PERSONALIZATION_CONFIG,
|
|
289
|
-
);
|
|
68
|
+
await configService.registerConfig("chat", "personalization", PERSONALIZATION_CONFIG);
|
|
290
69
|
}
|
|
291
70
|
|
|
292
71
|
const getConfig = async (): Promise<ChatConfig> => {
|
|
@@ -299,10 +78,7 @@ const chatPlugin = definePlugin({
|
|
|
299
78
|
}
|
|
300
79
|
const base = await configService.getConfig("chat", "base");
|
|
301
80
|
const settings = await configService.getConfig("chat", "settings");
|
|
302
|
-
const personalization = await configService.getConfig(
|
|
303
|
-
"chat",
|
|
304
|
-
"personalization",
|
|
305
|
-
);
|
|
81
|
+
const personalization = await configService.getConfig("chat", "personalization");
|
|
306
82
|
const merged = {
|
|
307
83
|
...BASE_CONFIG,
|
|
308
84
|
...SETTINGS_CONFIG,
|
|
@@ -313,22 +89,20 @@ const chatPlugin = definePlugin({
|
|
|
313
89
|
} as any;
|
|
314
90
|
|
|
315
91
|
if (typeof merged.stream !== "boolean") merged.stream = true;
|
|
316
|
-
if (typeof merged.enableMarkdownScreenshot !== "boolean")
|
|
92
|
+
if (typeof merged.enableMarkdownScreenshot !== "boolean") {
|
|
317
93
|
merged.enableMarkdownScreenshot = true;
|
|
94
|
+
}
|
|
318
95
|
if (!screenshotService && merged.enableMarkdownScreenshot) {
|
|
319
96
|
merged.enableMarkdownScreenshot = false;
|
|
320
97
|
if (!warnedMarkdownScreenshotUnavailable) {
|
|
321
|
-
ctx.logger.warn(
|
|
322
|
-
"聊天插件未加载 screenshot 服务,Markdown 截图渲染已自动关闭",
|
|
323
|
-
);
|
|
98
|
+
ctx.logger.warn("聊天插件未加载 screenshot 服务,Markdown 截图渲染已自动关闭");
|
|
324
99
|
warnedMarkdownScreenshotUnavailable = true;
|
|
325
100
|
}
|
|
326
101
|
}
|
|
327
102
|
merged.whitelistGroups = normalizeIdList(merged.whitelistGroups);
|
|
328
103
|
merged.blacklistGroups = normalizeIdList(merged.blacklistGroups);
|
|
329
104
|
merged.mediaAnalysisBlacklistUsers = normalizeIdList(
|
|
330
|
-
merged.mediaAnalysisBlacklistUsers ??
|
|
331
|
-
merged.imageAnalysisBlacklistUsers,
|
|
105
|
+
merged.mediaAnalysisBlacklistUsers ?? merged.imageAnalysisBlacklistUsers,
|
|
332
106
|
);
|
|
333
107
|
delete merged.imageAnalysisBlacklistUsers;
|
|
334
108
|
return merged as ChatConfig;
|
|
@@ -337,9 +111,7 @@ const chatPlugin = definePlugin({
|
|
|
337
111
|
const config = await getConfig();
|
|
338
112
|
|
|
339
113
|
if (!config.apiKey) {
|
|
340
|
-
ctx.logger.warn(
|
|
341
|
-
"聊天插件未配置 API Key,请在 config/chat/base.json 中配置",
|
|
342
|
-
);
|
|
114
|
+
ctx.logger.warn("聊天插件未配置 API Key,请在 config/chat/base.json 中配置");
|
|
343
115
|
return;
|
|
344
116
|
}
|
|
345
117
|
|
|
@@ -352,9 +124,9 @@ const chatPlugin = definePlugin({
|
|
|
352
124
|
});
|
|
353
125
|
const skillManager = new SkillSessionManager();
|
|
354
126
|
|
|
355
|
-
rateLimiter.setQueueLengthGetter((groupId: number) =>
|
|
356
|
-
|
|
357
|
-
|
|
127
|
+
rateLimiter.setQueueLengthGetter((groupId: number) =>
|
|
128
|
+
queueManager.getQueueLength(`group:${groupId}`),
|
|
129
|
+
);
|
|
358
130
|
|
|
359
131
|
if (!aiService) {
|
|
360
132
|
ctx.logger.error("聊天插件需要 AI 服务,但 AI 服务不可用");
|
|
@@ -368,7 +140,6 @@ const chatPlugin = definePlugin({
|
|
|
368
140
|
modelType: config.isMultimodal ? "multimodal" : "text",
|
|
369
141
|
model: config.model,
|
|
370
142
|
});
|
|
371
|
-
|
|
372
143
|
const workAIInstance = await aiService.create({
|
|
373
144
|
name: "work",
|
|
374
145
|
apiUrl: config.apiUrl,
|
|
@@ -376,7 +147,6 @@ const chatPlugin = definePlugin({
|
|
|
376
147
|
modelType: "text",
|
|
377
148
|
model: config.workingModel,
|
|
378
149
|
});
|
|
379
|
-
|
|
380
150
|
const visionAIInstance = await aiService.create({
|
|
381
151
|
name: "vision",
|
|
382
152
|
apiUrl: config.apiUrl,
|
|
@@ -384,203 +154,39 @@ const chatPlugin = definePlugin({
|
|
|
384
154
|
modelType: "multimodal",
|
|
385
155
|
model: config.multimodalWorkingModel,
|
|
386
156
|
});
|
|
387
|
-
|
|
388
157
|
aiService.setDefault("main");
|
|
389
158
|
|
|
390
|
-
const humanize = new HumanizeEngine(
|
|
391
|
-
mainAIInstance,
|
|
392
|
-
workAIInstance,
|
|
393
|
-
config,
|
|
394
|
-
db,
|
|
395
|
-
);
|
|
159
|
+
const humanize = new HumanizeEngine(mainAIInstance, workAIInstance, config, db);
|
|
396
160
|
await humanize.init();
|
|
397
161
|
|
|
398
162
|
const pokeCooldowns = new Map<number, number>();
|
|
399
|
-
const POKE_COOLDOWN_MS = 10 * 60_000;
|
|
400
|
-
const RATE_LIMIT_RETRY_DELAY_MS = 5_000;
|
|
401
|
-
const RATE_LIMIT_MAX_RETRIES = 2;
|
|
402
|
-
let rateLimitBlockedUntil = 0;
|
|
403
163
|
const processingSet = new Set<string>();
|
|
404
164
|
const groupStructuredHistory = new GroupStructuredHistoryManager();
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
function isRateLimitBlocked(): boolean {
|
|
414
|
-
return Date.now() < rateLimitBlockedUntil;
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
function markRateLimitBlocked(): void {
|
|
418
|
-
rateLimitBlockedUntil = Date.now() + RATE_LIMIT_RETRY_DELAY_MS;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
async function runWithRateLimitGuard<T>(
|
|
422
|
-
request: () => Promise<T>,
|
|
423
|
-
options?: { userId?: number; groupId?: number; label?: string },
|
|
424
|
-
): Promise<T | null> {
|
|
425
|
-
if (isRateLimitBlocked()) {
|
|
426
|
-
ctx.logger.warn(
|
|
427
|
-
`[Chat] AI request skipped due to rate limit block${options?.label ? ` (${options.label})` : ""}`,
|
|
428
|
-
);
|
|
429
|
-
return null;
|
|
430
|
-
}
|
|
431
|
-
if (!rateLimiter.canRunAIRequest(options?.userId, options?.groupId)) {
|
|
432
|
-
ctx.logger.warn(
|
|
433
|
-
`[Chat] AI request skipped due to RPM limit${options?.label ? ` (${options.label})` : ""}`,
|
|
434
|
-
);
|
|
435
|
-
return null;
|
|
436
|
-
}
|
|
437
|
-
rateLimiter.recordAIRequest(options?.userId, options?.groupId);
|
|
438
|
-
|
|
439
|
-
let retries = 0;
|
|
440
|
-
while (true) {
|
|
441
|
-
try {
|
|
442
|
-
const result = await request();
|
|
443
|
-
rateLimitBlockedUntil = 0;
|
|
444
|
-
return result;
|
|
445
|
-
} catch (err) {
|
|
446
|
-
if (!isRateLimitError(err)) throw err;
|
|
447
|
-
markRateLimitBlocked();
|
|
448
|
-
if (retries >= RATE_LIMIT_MAX_RETRIES) throw err;
|
|
449
|
-
retries += 1;
|
|
450
|
-
ctx.logger.warn(
|
|
451
|
-
`[Chat] Rate limit hit, waiting ${RATE_LIMIT_RETRY_DELAY_MS / 1000}s...`,
|
|
452
|
-
);
|
|
453
|
-
await new Promise((resolve) =>
|
|
454
|
-
setTimeout(resolve, RATE_LIMIT_RETRY_DELAY_MS),
|
|
455
|
-
);
|
|
456
|
-
if (isRateLimitBlocked()) rateLimitBlockedUntil = 0;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
let cooldownManager!: CooldownManager;
|
|
462
|
-
let idleCheckManager!: IdleCheckManager;
|
|
463
|
-
let queueProcessor!: QueueProcessor;
|
|
464
|
-
|
|
465
|
-
const makeStartCooldownTimer =
|
|
466
|
-
() => (groupSessionId: string, groupId: number, selfId: number) => {
|
|
467
|
-
cooldownManager.startCooldownTimer(groupSessionId, groupId, selfId);
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
idleCheckManager = new IdleCheckManager(
|
|
471
|
-
ctx,
|
|
472
|
-
config,
|
|
473
|
-
db,
|
|
474
|
-
humanize,
|
|
475
|
-
mainAIInstance,
|
|
476
|
-
aiService,
|
|
477
|
-
skillManager,
|
|
478
|
-
groupStructuredHistory,
|
|
479
|
-
sessionManager,
|
|
480
|
-
runWithRateLimitGuard,
|
|
481
|
-
buildHistoryMediaOptions,
|
|
482
|
-
getGroupHistoryMessages,
|
|
483
|
-
getGroupInfoData,
|
|
484
|
-
sendAIResponse,
|
|
485
|
-
saveBotMessages,
|
|
486
|
-
runChat,
|
|
487
|
-
buildToolContext,
|
|
488
|
-
makeStartCooldownTimer(),
|
|
489
|
-
);
|
|
490
|
-
|
|
491
|
-
cooldownManager = new CooldownManager(
|
|
492
|
-
ctx,
|
|
493
|
-
config,
|
|
494
|
-
db,
|
|
495
|
-
humanize,
|
|
496
|
-
mainAIInstance,
|
|
497
|
-
aiService,
|
|
498
|
-
skillManager,
|
|
165
|
+
const rateLimitGuard = new RateLimitGuard(rateLimiter, ctx.logger);
|
|
166
|
+
const runWithRateLimitGuard = rateLimitGuard.run.bind(rateLimitGuard);
|
|
167
|
+
|
|
168
|
+
const pluginCtx = {
|
|
169
|
+
ctx, config, db,
|
|
170
|
+
aiInstance: mainAIInstance, workAIInstance, visionAIInstance,
|
|
171
|
+
aiService, humanize,
|
|
172
|
+
sessionManager, skillManager, rateLimiter, queueManager,
|
|
499
173
|
groupStructuredHistory,
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
174
|
+
cooldownManager: undefined as unknown as CooldownManager,
|
|
175
|
+
idleCheckManager: undefined as unknown as IdleCheckManager,
|
|
176
|
+
queueProcessor: undefined as unknown as QueueProcessor,
|
|
503
177
|
runWithRateLimitGuard,
|
|
504
178
|
buildHistoryMediaOptions,
|
|
505
|
-
getGroupHistoryMessages,
|
|
506
|
-
|
|
507
|
-
getHumanizeContexts,
|
|
508
|
-
sendAIResponse,
|
|
509
|
-
saveBotMessages,
|
|
510
|
-
sendEmoji,
|
|
511
|
-
runChat,
|
|
512
|
-
buildToolContext,
|
|
513
|
-
buildStructuredUserInputFromEvent,
|
|
514
|
-
);
|
|
515
|
-
|
|
516
|
-
queueProcessor = new QueueProcessor(
|
|
517
|
-
ctx,
|
|
518
|
-
config,
|
|
519
|
-
db,
|
|
520
|
-
humanize,
|
|
521
|
-
mainAIInstance,
|
|
522
|
-
aiService,
|
|
523
|
-
skillManager,
|
|
524
|
-
groupStructuredHistory,
|
|
525
|
-
sessionManager,
|
|
526
|
-
rateLimiter,
|
|
527
|
-
queueManager,
|
|
528
|
-
runWithRateLimitGuard,
|
|
529
|
-
buildHistoryMediaOptions,
|
|
530
|
-
getGroupHistoryMessages,
|
|
531
|
-
getGroupInfoData,
|
|
532
|
-
getHumanizeContexts,
|
|
533
|
-
sendAIResponse,
|
|
534
|
-
saveBotMessages,
|
|
535
|
-
sendEmoji,
|
|
536
|
-
runChat,
|
|
537
|
-
buildToolContext,
|
|
538
|
-
buildStructuredUserInputFromEvent,
|
|
539
|
-
makeStartCooldownTimer(),
|
|
540
|
-
);
|
|
541
|
-
|
|
542
|
-
const cleanupInterval = setInterval(
|
|
543
|
-
() => skillManager.cleanup(),
|
|
544
|
-
10 * 60_000,
|
|
545
|
-
);
|
|
546
|
-
const dbCleanup = new ChatDatabaseCleanup(
|
|
547
|
-
db,
|
|
548
|
-
config.retention ?? DEFAULT_CLEANUP_CONFIG,
|
|
549
|
-
);
|
|
550
|
-
dbCleanup.start();
|
|
551
|
-
idleCheckManager.start();
|
|
552
|
-
|
|
553
|
-
// Create plugin context for passing to functions
|
|
554
|
-
const pluginCtx: ChatPluginContext = {
|
|
555
|
-
ctx,
|
|
556
|
-
config,
|
|
557
|
-
db,
|
|
558
|
-
aiInstance: mainAIInstance,
|
|
559
|
-
workAIInstance,
|
|
560
|
-
visionAIInstance,
|
|
561
|
-
aiService,
|
|
562
|
-
humanize,
|
|
563
|
-
sessionManager,
|
|
564
|
-
skillManager,
|
|
565
|
-
rateLimiter,
|
|
566
|
-
queueManager,
|
|
567
|
-
groupStructuredHistory,
|
|
568
|
-
cooldownManager,
|
|
569
|
-
idleCheckManager,
|
|
570
|
-
queueProcessor,
|
|
571
|
-
runWithRateLimitGuard,
|
|
572
|
-
buildHistoryMediaOptions,
|
|
573
|
-
getGroupHistoryMessages,
|
|
574
|
-
getGroupInfoData,
|
|
575
|
-
getHumanizeContexts,
|
|
576
|
-
sendAIResponse,
|
|
577
|
-
sendMessage,
|
|
578
|
-
saveBotMessages,
|
|
579
|
-
sendEmoji,
|
|
179
|
+
getGroupHistoryMessages, getGroupInfoData, getHumanizeContexts,
|
|
180
|
+
sendAIResponse, sendMessage, saveBotMessages, sendEmoji,
|
|
580
181
|
buildToolContext,
|
|
581
182
|
buildStructuredUserInputFromTarget,
|
|
582
183
|
runChat,
|
|
583
|
-
|
|
184
|
+
startCooldownTimer: (
|
|
185
|
+
groupSessionId: string,
|
|
186
|
+
groupId: number,
|
|
187
|
+
selfId: number,
|
|
188
|
+
) => cooldownManager.startCooldownTimer(groupSessionId, groupId, selfId),
|
|
189
|
+
async recordGroupMessageForLearning(userMsg: ChatMessage, groupSessionId: string) {
|
|
584
190
|
db.saveMessage(userMsg);
|
|
585
191
|
await Promise.all([
|
|
586
192
|
humanize.expressionLearner.onMessage(userMsg),
|
|
@@ -590,898 +196,45 @@ const chatPlugin = definePlugin({
|
|
|
590
196
|
groupStructuredHistory.append(
|
|
591
197
|
groupSessionId,
|
|
592
198
|
buildStructuredUserMessages([
|
|
593
|
-
buildStructuredUserInputFromTarget(
|
|
594
|
-
userMsg as unknown as TargetMessage,
|
|
595
|
-
),
|
|
199
|
+
buildStructuredUserInputFromTarget(userMsg as unknown as TargetMessage),
|
|
596
200
|
]),
|
|
597
201
|
config.groupStructuredHistoryTtlMs,
|
|
598
202
|
);
|
|
599
203
|
}
|
|
600
204
|
},
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
const runtimeState: ChatRuntimeState = {
|
|
604
|
-
isRateLimitBlocked,
|
|
605
|
-
processingSet,
|
|
606
|
-
};
|
|
205
|
+
} as ChatPluginContext;
|
|
607
206
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
// If no event or event has no message property (synthetic event like group_member_increase),
|
|
616
|
-
// return the default prompt
|
|
617
|
-
if (!event || !event.message || !Array.isArray(event.message)) {
|
|
618
|
-
return "[No new user message in this turn. Reply naturally based on the runtime instruction and recent context.]";
|
|
619
|
-
}
|
|
620
|
-
const text = ctx.text(event)?.trim() || "";
|
|
621
|
-
if (text) {
|
|
622
|
-
return text;
|
|
623
|
-
}
|
|
624
|
-
return "[No new user message in this turn. Reply naturally based on the runtime instruction and recent context.]";
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
interface ResolvedChatRuntimeContext {
|
|
628
|
-
event: any;
|
|
629
|
-
isGroup: boolean;
|
|
630
|
-
groupId?: number;
|
|
631
|
-
userId: number;
|
|
632
|
-
selfId: number;
|
|
633
|
-
sessionId: string;
|
|
634
|
-
personalSessionId?: string;
|
|
635
|
-
senderName: string;
|
|
636
|
-
userRole: "owner" | "admin" | "member";
|
|
637
|
-
userTitle?: string;
|
|
638
|
-
groupName?: string;
|
|
639
|
-
messageId?: number;
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
function resolveChatRuntimeContext(
|
|
643
|
-
options: ExecuteChatRuntimeRequestOptions,
|
|
644
|
-
): ResolvedChatRuntimeContext {
|
|
645
|
-
if (options.event) {
|
|
646
|
-
const event = options.event;
|
|
647
|
-
const isGroup = event.message_type === "group";
|
|
648
|
-
const groupId: number | undefined = isGroup
|
|
649
|
-
? event.group_id
|
|
650
|
-
: undefined;
|
|
651
|
-
const userId: number = event.user_id || event.sender?.user_id || 0;
|
|
652
|
-
const selfId: number = event.self_id;
|
|
653
|
-
return {
|
|
654
|
-
event,
|
|
655
|
-
isGroup,
|
|
656
|
-
groupId,
|
|
657
|
-
userId,
|
|
658
|
-
selfId,
|
|
659
|
-
sessionId: groupId ? `group:${groupId}` : `personal:${userId}`,
|
|
660
|
-
personalSessionId: groupId ? `personal:${userId}` : undefined,
|
|
661
|
-
senderName:
|
|
662
|
-
event.sender?.card || event.sender?.nickname || String(userId),
|
|
663
|
-
userRole: event.sender?.role || "member",
|
|
664
|
-
userTitle: event.sender?.title || undefined,
|
|
665
|
-
groupName: event.group_name,
|
|
666
|
-
messageId: event.message_id,
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
if (typeof options.selfId !== "number") {
|
|
671
|
-
throw new Error("Chat runtime requires either event or selfId");
|
|
672
|
-
}
|
|
673
|
-
if (
|
|
674
|
-
typeof options.groupId !== "number" &&
|
|
675
|
-
typeof options.userId !== "number"
|
|
676
|
-
) {
|
|
677
|
-
throw new Error("Chat runtime requires groupId or userId");
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
const isGroup = typeof options.groupId === "number";
|
|
681
|
-
const userId = options.userId ?? 0;
|
|
682
|
-
const event = {
|
|
683
|
-
self_id: options.selfId,
|
|
684
|
-
message_type: isGroup ? "group" : "private",
|
|
685
|
-
group_id: options.groupId,
|
|
686
|
-
user_id: userId,
|
|
687
|
-
group_name: undefined,
|
|
688
|
-
sender: {
|
|
689
|
-
user_id: userId,
|
|
690
|
-
card: undefined,
|
|
691
|
-
nickname: undefined,
|
|
692
|
-
role: "member",
|
|
693
|
-
title: undefined,
|
|
694
|
-
},
|
|
695
|
-
};
|
|
696
|
-
|
|
697
|
-
return {
|
|
698
|
-
event,
|
|
699
|
-
isGroup,
|
|
700
|
-
groupId: options.groupId,
|
|
701
|
-
userId,
|
|
702
|
-
selfId: options.selfId,
|
|
703
|
-
sessionId: options.groupId
|
|
704
|
-
? `group:${options.groupId}`
|
|
705
|
-
: `personal:${userId}`,
|
|
706
|
-
personalSessionId:
|
|
707
|
-
options.groupId && userId ? `personal:${userId}` : undefined,
|
|
708
|
-
senderName: options.groupId ? "system" : String(userId),
|
|
709
|
-
userRole: "member",
|
|
710
|
-
userTitle: undefined,
|
|
711
|
-
groupName: undefined,
|
|
712
|
-
messageId: undefined,
|
|
713
|
-
};
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
async function executeChatRuntimeRequest(
|
|
717
|
-
options: ExecuteChatRuntimeRequestOptions,
|
|
718
|
-
): Promise<ChatRuntimeResult> {
|
|
719
|
-
const cfg = options.config;
|
|
720
|
-
const runtimeCtx = resolveChatRuntimeContext(options);
|
|
721
|
-
const {
|
|
722
|
-
event,
|
|
723
|
-
isGroup,
|
|
724
|
-
groupId,
|
|
725
|
-
userId,
|
|
726
|
-
selfId,
|
|
727
|
-
sessionId,
|
|
728
|
-
personalSessionId,
|
|
729
|
-
senderName,
|
|
730
|
-
userRole,
|
|
731
|
-
userTitle,
|
|
732
|
-
groupName: runtimeGroupName,
|
|
733
|
-
messageId,
|
|
734
|
-
} = runtimeCtx;
|
|
735
|
-
const targetContent = buildRuntimeTargetMessageContent(
|
|
736
|
-
event,
|
|
737
|
-
options.targetMessageContent,
|
|
738
|
-
);
|
|
739
|
-
|
|
740
|
-
sessionManager.getOrCreate(
|
|
741
|
-
sessionId,
|
|
742
|
-
groupId ? "group" : "personal",
|
|
743
|
-
groupId ?? userId,
|
|
744
|
-
);
|
|
745
|
-
if (groupId && personalSessionId) {
|
|
746
|
-
sessionManager.getOrCreate(personalSessionId, "personal", userId);
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
const rawHistory = groupId
|
|
750
|
-
? await getGroupHistory(groupId, ctx, cfg.historyCount, selfId, db)
|
|
751
|
-
: [];
|
|
752
|
-
const history: ChatMessage[] = rawHistory.map((msg) => ({
|
|
753
|
-
sessionId,
|
|
754
|
-
role: "user",
|
|
755
|
-
content: msg.content,
|
|
756
|
-
userId: msg.userId,
|
|
757
|
-
userName: msg.userName,
|
|
758
|
-
userRole: msg.userRole,
|
|
759
|
-
groupId,
|
|
760
|
-
timestamp: msg.timestamp,
|
|
761
|
-
messageId: msg.messageId,
|
|
762
|
-
}));
|
|
763
|
-
|
|
764
|
-
const botRole = groupId
|
|
765
|
-
? await getBotRole(groupId, ctx, selfId)
|
|
766
|
-
: "member";
|
|
767
|
-
const botNickname =
|
|
768
|
-
cfg.nicknames[0] || ctx.pickBot(selfId)?.nickname || "Bot";
|
|
769
|
-
|
|
770
|
-
let groupName: string | undefined;
|
|
771
|
-
let memberCount: number | undefined;
|
|
772
|
-
if (groupId) {
|
|
773
|
-
const groupInfo = await getGroupInfoData(
|
|
774
|
-
ctx,
|
|
775
|
-
groupId,
|
|
776
|
-
selfId,
|
|
777
|
-
runtimeGroupName,
|
|
778
|
-
);
|
|
779
|
-
groupName = groupInfo.groupName;
|
|
780
|
-
memberCount = groupInfo.memberCount;
|
|
781
|
-
} else {
|
|
782
|
-
groupName = runtimeGroupName;
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
const contexts = await getHumanizeContexts(
|
|
786
|
-
humanize,
|
|
787
|
-
sessionId,
|
|
788
|
-
senderName,
|
|
789
|
-
history,
|
|
790
|
-
);
|
|
791
|
-
|
|
792
|
-
const targetMessage: TargetMessage = {
|
|
793
|
-
userName: senderName,
|
|
794
|
-
userId,
|
|
795
|
-
userRole,
|
|
796
|
-
userTitle,
|
|
797
|
-
content: targetContent,
|
|
798
|
-
messageId,
|
|
799
|
-
timestamp: Date.now(),
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
const toolCtx = buildToolContext({
|
|
803
|
-
ctx,
|
|
804
|
-
event,
|
|
805
|
-
groupSessionId: sessionId,
|
|
806
|
-
groupId,
|
|
807
|
-
userId,
|
|
808
|
-
config: cfg,
|
|
809
|
-
aiService: aiService!,
|
|
810
|
-
db,
|
|
811
|
-
botRole,
|
|
812
|
-
humanize,
|
|
813
|
-
targetMessage,
|
|
814
|
-
selfId,
|
|
815
|
-
});
|
|
816
|
-
|
|
817
|
-
if (options.send === false) {
|
|
818
|
-
toolCtx.onTextContent = undefined;
|
|
819
|
-
toolCtx.sentMessageIndices = undefined;
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
const result = await runChat(
|
|
823
|
-
mainAIInstance,
|
|
824
|
-
toolCtx,
|
|
825
|
-
history,
|
|
826
|
-
targetMessage,
|
|
827
|
-
{
|
|
828
|
-
config: cfg,
|
|
829
|
-
groupName,
|
|
830
|
-
memberCount,
|
|
831
|
-
botNickname,
|
|
832
|
-
botRole,
|
|
833
|
-
aiService: aiService!,
|
|
834
|
-
isGroup,
|
|
835
|
-
memoryContext: contexts.memoryContext,
|
|
836
|
-
topicContext: contexts.topicContext,
|
|
837
|
-
expressionContext: contexts.expressionContext,
|
|
838
|
-
replyContext: {
|
|
839
|
-
type: options.replyContextType || "reply",
|
|
840
|
-
targetUser: targetMessage.userName,
|
|
841
|
-
targetMessage: targetMessage.content,
|
|
842
|
-
},
|
|
843
|
-
promptInjections: options.promptInjections,
|
|
844
|
-
},
|
|
845
|
-
humanize,
|
|
846
|
-
skillManager,
|
|
847
|
-
undefined,
|
|
848
|
-
{
|
|
849
|
-
extraTools: options.extraTools,
|
|
850
|
-
},
|
|
851
|
-
);
|
|
852
|
-
|
|
853
|
-
if (options.send !== false) {
|
|
854
|
-
if (groupId) {
|
|
855
|
-
await sendAIResponse(
|
|
856
|
-
{
|
|
857
|
-
ctx,
|
|
858
|
-
groupId,
|
|
859
|
-
messages: result.messages,
|
|
860
|
-
config: cfg,
|
|
861
|
-
sentIndices: toolCtx.sentMessageIndices,
|
|
862
|
-
},
|
|
863
|
-
selfId,
|
|
864
|
-
);
|
|
865
|
-
|
|
866
|
-
await sendEmoji(ctx, groupId, result.emojiPath, selfId);
|
|
867
|
-
|
|
868
|
-
const now = Date.now();
|
|
869
|
-
saveBotMessages(
|
|
870
|
-
groupId,
|
|
871
|
-
sessionId,
|
|
872
|
-
result.messages,
|
|
873
|
-
now,
|
|
874
|
-
cfg,
|
|
875
|
-
db,
|
|
876
|
-
ctx,
|
|
877
|
-
selfId,
|
|
878
|
-
);
|
|
207
|
+
const idleCheckManager = new IdleCheckManager(pluginCtx);
|
|
208
|
+
pluginCtx.idleCheckManager = idleCheckManager;
|
|
209
|
+
const cooldownManager = new CooldownManager(pluginCtx);
|
|
210
|
+
pluginCtx.cooldownManager = cooldownManager;
|
|
211
|
+
const queueProcessor = new QueueProcessor(pluginCtx);
|
|
212
|
+
pluginCtx.queueProcessor = queueProcessor;
|
|
879
213
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
if (sentIndices?.has(i)) continue;
|
|
885
|
-
await sendMessage(
|
|
886
|
-
ctx,
|
|
887
|
-
undefined,
|
|
888
|
-
userId,
|
|
889
|
-
result.messages[i],
|
|
890
|
-
cfg,
|
|
891
|
-
selfId,
|
|
892
|
-
);
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
if (result.emojiPath) {
|
|
896
|
-
try {
|
|
897
|
-
const emojiSegment = ctx.segment.image(
|
|
898
|
-
`file://${result.emojiPath}`,
|
|
899
|
-
);
|
|
900
|
-
const bot = ctx.pickBot(selfId);
|
|
901
|
-
if (!bot) {
|
|
902
|
-
throw new Error(`bot ${String(selfId)} not found`);
|
|
903
|
-
}
|
|
904
|
-
await bot.sendPrivateMsg(userId, [emojiSegment]);
|
|
905
|
-
} catch (err) {
|
|
906
|
-
ctx.logger.warn(`[chat-runtime] Send emoji failed: ${err}`);
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
sessionManager.touch(sessionId);
|
|
913
|
-
|
|
914
|
-
return {
|
|
915
|
-
messages: result.messages,
|
|
916
|
-
toolCalls: result.toolCalls.map((toolCall) => ({
|
|
917
|
-
name: toolCall.name,
|
|
918
|
-
arguments: toolCall.args,
|
|
919
|
-
result: toolCall.result,
|
|
920
|
-
})),
|
|
921
|
-
collectedInfo: null,
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
const runtime: ChatRuntime = {
|
|
926
|
-
async requestInformation(
|
|
927
|
-
options: ChatRuntimeInformationRequestOptions,
|
|
928
|
-
): Promise<ChatRuntimeResult> {
|
|
929
|
-
let collectedInfo: ChatRuntimeCollectedInfo | null = null;
|
|
930
|
-
const toolName = options.toolName || "submit_requested_information";
|
|
931
|
-
const extraTools: AITool[] = [
|
|
932
|
-
{
|
|
933
|
-
name: toolName,
|
|
934
|
-
description:
|
|
935
|
-
options.toolDescription ||
|
|
936
|
-
"Submit structured information extracted from the conversation when enough details are known.",
|
|
937
|
-
parameters: {
|
|
938
|
-
type: "object",
|
|
939
|
-
properties: {
|
|
940
|
-
data: options.schema,
|
|
941
|
-
isComplete: {
|
|
942
|
-
type: "boolean",
|
|
943
|
-
description:
|
|
944
|
-
"Whether the collected information is complete enough for the caller to continue.",
|
|
945
|
-
},
|
|
946
|
-
confidence: {
|
|
947
|
-
type: "number",
|
|
948
|
-
description:
|
|
949
|
-
"Confidence score between 0 and 1 for the submitted information.",
|
|
950
|
-
},
|
|
951
|
-
notes: {
|
|
952
|
-
type: "string",
|
|
953
|
-
description:
|
|
954
|
-
"Optional notes about ambiguity, assumptions, or remaining follow-up needs.",
|
|
955
|
-
},
|
|
956
|
-
},
|
|
957
|
-
required: ["data"],
|
|
958
|
-
},
|
|
959
|
-
handler: async (args) => {
|
|
960
|
-
collectedInfo = {
|
|
961
|
-
data: args?.data,
|
|
962
|
-
isComplete: args?.isComplete,
|
|
963
|
-
confidence: args?.confidence,
|
|
964
|
-
notes: args?.notes,
|
|
965
|
-
};
|
|
966
|
-
return {
|
|
967
|
-
success: true,
|
|
968
|
-
accepted: true,
|
|
969
|
-
collectedInfo,
|
|
970
|
-
};
|
|
971
|
-
},
|
|
972
|
-
},
|
|
973
|
-
];
|
|
974
|
-
|
|
975
|
-
const promptInjections: ChatRuntimePromptInjection[] = [
|
|
976
|
-
{
|
|
977
|
-
title: "Information Collection Goal",
|
|
978
|
-
content: [
|
|
979
|
-
"Another plugin needs you to gather specific information from the current user while staying fully in your existing persona.",
|
|
980
|
-
`Task: ${options.task}`,
|
|
981
|
-
`Target schema: ${JSON.stringify(options.schema)}`,
|
|
982
|
-
`If the needed information is already clear from the target message, recent chat history, or obvious context, call the tool "${toolName}" immediately with structured data.`,
|
|
983
|
-
"If the information is incomplete, ask only the smallest natural follow-up question needed.",
|
|
984
|
-
"Do not mention schemas, forms, prompts, plugins, or tools to the user.",
|
|
985
|
-
"Keep the response natural and in-character rather than sounding like a questionnaire.",
|
|
986
|
-
].join("\n"),
|
|
987
|
-
},
|
|
988
|
-
...(options.promptInjections || []),
|
|
989
|
-
];
|
|
990
|
-
|
|
991
|
-
const result = await executeChatRuntimeRequest({
|
|
992
|
-
...options,
|
|
993
|
-
config: await getConfig(),
|
|
994
|
-
targetMessageContent: options.targetMessage,
|
|
995
|
-
promptInjections,
|
|
996
|
-
extraTools,
|
|
997
|
-
send: options.send,
|
|
998
|
-
replyContextType: "reply",
|
|
999
|
-
});
|
|
1000
|
-
|
|
1001
|
-
return {
|
|
1002
|
-
...result,
|
|
1003
|
-
collectedInfo,
|
|
1004
|
-
};
|
|
1005
|
-
},
|
|
1006
|
-
|
|
1007
|
-
async generateNotice(
|
|
1008
|
-
options: ChatRuntimeNoticeOptions,
|
|
1009
|
-
): Promise<ChatRuntimeResult> {
|
|
1010
|
-
const promptInjections: ChatRuntimePromptInjection[] = [
|
|
1011
|
-
{
|
|
1012
|
-
title: "Notification Goal",
|
|
1013
|
-
content: [
|
|
1014
|
-
"Another plugin needs you to deliver a notification to the current user, but it must still sound like you and fit the current conversation.",
|
|
1015
|
-
`Goal: ${options.instruction}`,
|
|
1016
|
-
"Blend the notice into your normal speaking style instead of sounding like a rigid system announcement unless the situation clearly requires firmness.",
|
|
1017
|
-
"Do not mention prompts, plugins, tools, or hidden instructions.",
|
|
1018
|
-
].join("\n"),
|
|
1019
|
-
},
|
|
1020
|
-
...(options.promptInjections || []),
|
|
1021
|
-
];
|
|
214
|
+
const cleanupInterval = setInterval(() => skillManager.cleanup(), 10 * 60_000);
|
|
215
|
+
const dbCleanup = new ChatDatabaseCleanup(db, config.retention ?? DEFAULT_CLEANUP_CONFIG);
|
|
216
|
+
dbCleanup.start();
|
|
217
|
+
idleCheckManager.start();
|
|
1022
218
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
send: options.send,
|
|
1029
|
-
replyContextType: "reply",
|
|
1030
|
-
});
|
|
219
|
+
const handlerState: ChatHandlerState = {
|
|
220
|
+
getConfig,
|
|
221
|
+
runtimeState: {
|
|
222
|
+
isRateLimitBlocked: () => rateLimitGuard.isBlocked(),
|
|
223
|
+
processingSet,
|
|
1031
224
|
},
|
|
225
|
+
matchMessageCommands: (
|
|
226
|
+
getPluginRuntimeState("boot") as
|
|
227
|
+
| { matchMessageCommands?: (text: string) => Array<{ plugin: string; command: string }> }
|
|
228
|
+
| undefined
|
|
229
|
+
)?.matchMessageCommands,
|
|
230
|
+
pokeCooldowns,
|
|
1032
231
|
};
|
|
1033
232
|
|
|
1034
|
-
|
|
233
|
+
const runtime = createChatRuntime(pluginCtx, getConfig);
|
|
1035
234
|
aiService.registerChatRuntime(runtime);
|
|
1036
235
|
|
|
1037
|
-
|
|
1038
|
-
ctx.handle("
|
|
1039
|
-
const cfg = await getConfig();
|
|
1040
|
-
if (!cfg.apiKey) return;
|
|
1041
|
-
|
|
1042
|
-
if (!e?.message || !Array.isArray(e.message)) return;
|
|
1043
|
-
|
|
1044
|
-
const text = ctx.text(e) || "";
|
|
1045
|
-
const isGroup = e.message_type === "group";
|
|
1046
|
-
const groupId: number | undefined = isGroup ? e.group_id : undefined;
|
|
1047
|
-
const userId: number = e.user_id || e.sender?.user_id;
|
|
1048
|
-
|
|
1049
|
-
if (userId === e.self_id) return;
|
|
1050
|
-
|
|
1051
|
-
// 命令处理
|
|
1052
|
-
if (text.startsWith("/空闲检查 ")) {
|
|
1053
|
-
await handleIdleCheckDebug(
|
|
1054
|
-
ctx,
|
|
1055
|
-
e,
|
|
1056
|
-
cfg,
|
|
1057
|
-
mainAIInstance,
|
|
1058
|
-
db,
|
|
1059
|
-
humanize,
|
|
1060
|
-
skillManager,
|
|
1061
|
-
runWithRateLimitGuard,
|
|
1062
|
-
getGroupHistoryMessages,
|
|
1063
|
-
buildHistoryMediaOptions,
|
|
1064
|
-
runChat,
|
|
1065
|
-
buildToolContext,
|
|
1066
|
-
sendAIResponse,
|
|
1067
|
-
saveBotMessages,
|
|
1068
|
-
);
|
|
1069
|
-
return;
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
if (text === "/重置会话") {
|
|
1073
|
-
if (groupId) {
|
|
1074
|
-
sessionManager.resetBotMessages(`group:${groupId}`);
|
|
1075
|
-
groupStructuredHistory.clear(`group:${groupId}`);
|
|
1076
|
-
await e.reply("已清除本群会话中 AI 发送的消息~");
|
|
1077
|
-
} else {
|
|
1078
|
-
sessionManager.resetBotMessages(`personal:${userId}`);
|
|
1079
|
-
groupStructuredHistory.clear(`personal:${userId}`);
|
|
1080
|
-
await e.reply("已清除你的个人会话中 AI 发送的消息~");
|
|
1081
|
-
}
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
if (groupId && !isGroupAllowed(groupId, cfg)) return;
|
|
1086
|
-
|
|
1087
|
-
// 媒体分析
|
|
1088
|
-
if (
|
|
1089
|
-
isGroup &&
|
|
1090
|
-
groupId &&
|
|
1091
|
-
e.message &&
|
|
1092
|
-
!isMediaAnalysisBlocked(cfg, userId)
|
|
1093
|
-
) {
|
|
1094
|
-
const ai = aiService!.getDefault();
|
|
1095
|
-
const bot = ctx.pickBot(e.self_id) as any;
|
|
1096
|
-
const mediaOptions = ai
|
|
1097
|
-
? buildHistoryMediaProcessingOptions(
|
|
1098
|
-
ai,
|
|
1099
|
-
cfg,
|
|
1100
|
-
db,
|
|
1101
|
-
bot,
|
|
1102
|
-
groupId,
|
|
1103
|
-
{
|
|
1104
|
-
info: (m) => ctx.logger.info(m),
|
|
1105
|
-
warn: (m) => ctx.logger.warn(m),
|
|
1106
|
-
error: (m) => ctx.logger.error(m),
|
|
1107
|
-
},
|
|
1108
|
-
(request) =>
|
|
1109
|
-
runWithRateLimitGuard(request, {
|
|
1110
|
-
userId,
|
|
1111
|
-
groupId,
|
|
1112
|
-
label: "history-media",
|
|
1113
|
-
}),
|
|
1114
|
-
)
|
|
1115
|
-
: undefined;
|
|
1116
|
-
|
|
1117
|
-
if (ai && cfg.isMultimodal) {
|
|
1118
|
-
const { processImage } = await import("./core/media/image-analyzer");
|
|
1119
|
-
for (const seg of e.message) {
|
|
1120
|
-
if (seg.type === "image") {
|
|
1121
|
-
const imageUrl = getSegmentUrl(seg);
|
|
1122
|
-
if (imageUrl) {
|
|
1123
|
-
processImage(ai, imageUrl, cfg.multimodalWorkingModel, db, {
|
|
1124
|
-
runAIRequest: (request) =>
|
|
1125
|
-
runWithRateLimitGuard(request, {
|
|
1126
|
-
userId,
|
|
1127
|
-
groupId,
|
|
1128
|
-
label: "image-analysis",
|
|
1129
|
-
}),
|
|
1130
|
-
}).catch((err) =>
|
|
1131
|
-
ctx.logger.error(`[image-analyzer] Failed: ${err}`),
|
|
1132
|
-
);
|
|
1133
|
-
}
|
|
1134
|
-
} else if (seg.type === "video" && mediaOptions) {
|
|
1135
|
-
const videoSources = [
|
|
1136
|
-
...getSegmentSourceCandidates(seg),
|
|
1137
|
-
...(await getVideoSourceCandidatesFromMessage(
|
|
1138
|
-
bot,
|
|
1139
|
-
e.message_id,
|
|
1140
|
-
).catch(() => [])),
|
|
1141
|
-
];
|
|
1142
|
-
if (videoSources.length > 0) {
|
|
1143
|
-
summarizeHistoryVideo(videoSources, mediaOptions).catch((err) =>
|
|
1144
|
-
ctx.logger.error(
|
|
1145
|
-
`[history-media] Failed to process video: ${err}`,
|
|
1146
|
-
),
|
|
1147
|
-
);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
if (mediaOptions) {
|
|
1154
|
-
for (const seg of e.message) {
|
|
1155
|
-
if (seg.type === "forward") {
|
|
1156
|
-
const forwardId = getForwardId(seg);
|
|
1157
|
-
if (forwardId)
|
|
1158
|
-
summarizeHistoryForward(forwardId, mediaOptions).catch((err) =>
|
|
1159
|
-
ctx.logger.error(
|
|
1160
|
-
`[history-media] Failed to process forward: ${err}`,
|
|
1161
|
-
),
|
|
1162
|
-
);
|
|
1163
|
-
} else if (["xml", "json", "lightapp", "ark"].includes(seg.type)) {
|
|
1164
|
-
const cardData = getCardData(seg);
|
|
1165
|
-
if (cardData)
|
|
1166
|
-
summarizeHistoryCard(cardData, mediaOptions).catch((err) =>
|
|
1167
|
-
ctx.logger.error(
|
|
1168
|
-
`[history-media] Failed to process card: ${err}`,
|
|
1169
|
-
),
|
|
1170
|
-
);
|
|
1171
|
-
}
|
|
1172
|
-
}
|
|
1173
|
-
if (e.sub_type === "notice") {
|
|
1174
|
-
summarizeGroupNotice(e, mediaOptions)
|
|
1175
|
-
.then((noticeMessage) => {
|
|
1176
|
-
if (!noticeMessage) return;
|
|
1177
|
-
db.saveMessage({
|
|
1178
|
-
sessionId: `group:${groupId}`,
|
|
1179
|
-
role: "user",
|
|
1180
|
-
content: noticeMessage.content,
|
|
1181
|
-
userId: noticeMessage.userId,
|
|
1182
|
-
userName: noticeMessage.userName,
|
|
1183
|
-
userRole: noticeMessage.userRole,
|
|
1184
|
-
groupId,
|
|
1185
|
-
groupName: e.group_name,
|
|
1186
|
-
timestamp: noticeMessage.timestamp,
|
|
1187
|
-
messageId: noticeMessage.messageId,
|
|
1188
|
-
});
|
|
1189
|
-
})
|
|
1190
|
-
.catch((err) =>
|
|
1191
|
-
ctx.logger.error(
|
|
1192
|
-
`[history-media] Failed to process group notice: ${err}`,
|
|
1193
|
-
),
|
|
1194
|
-
);
|
|
1195
|
-
}
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
if (isGroup && groupId) {
|
|
1200
|
-
const _learnSessionId = `group:${groupId}`;
|
|
1201
|
-
const _learnMsg = buildChatMessageFromEvent(e, text, true, groupId);
|
|
1202
|
-
const _hasText = !!_learnMsg.content?.trim();
|
|
1203
|
-
const _hasMultimodal =
|
|
1204
|
-
!!cfg.isMultimodal && !!cfg.multimodalWorkingModel;
|
|
1205
|
-
if (_hasText || _hasMultimodal) {
|
|
1206
|
-
pluginCtx
|
|
1207
|
-
.recordGroupMessageForLearning(_learnMsg, _learnSessionId)
|
|
1208
|
-
.catch((err) =>
|
|
1209
|
-
ctx.logger.error(`[learning] always-on record failed: ${err}`),
|
|
1210
|
-
);
|
|
1211
|
-
} else {
|
|
1212
|
-
pluginCtx.humanize.topicTracker
|
|
1213
|
-
.onMessage(_learnSessionId)
|
|
1214
|
-
.catch((err) =>
|
|
1215
|
-
ctx.logger.error(`[topic] window advance failed: ${err}`),
|
|
1216
|
-
);
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
|
|
1220
|
-
const atBot = shouldTrigger(e, text, cfg, ctx);
|
|
1221
|
-
const quotedBot = isGroup ? await isQuotingBot(e, ctx) : null;
|
|
1222
|
-
const mentionedNickname =
|
|
1223
|
-
cfg.nicknames.length > 0 &&
|
|
1224
|
-
cfg.nicknames.some((n) => text.toLowerCase().includes(n.toLowerCase()));
|
|
1225
|
-
|
|
1226
|
-
const groupSessionId = groupId ? `group:${groupId}` : undefined;
|
|
1227
|
-
|
|
1228
|
-
// 记录群活动
|
|
1229
|
-
if (isGroup && groupId && groupSessionId) {
|
|
1230
|
-
idleCheckManager.recordActivity(groupSessionId);
|
|
1231
|
-
|
|
1232
|
-
if (cooldownManager.isInCooldown(groupSessionId)) {
|
|
1233
|
-
cooldownManager.collectMessage(
|
|
1234
|
-
groupSessionId,
|
|
1235
|
-
groupId,
|
|
1236
|
-
e,
|
|
1237
|
-
text,
|
|
1238
|
-
atBot,
|
|
1239
|
-
);
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
if (queueProcessor.isInDynamicDelay(groupSessionId)) {
|
|
1244
|
-
if (atBot && !isRateLimitBlocked()) {
|
|
1245
|
-
rateLimiter.recordInteraction(groupId, userId);
|
|
1246
|
-
queueProcessor.collectDynamicDelayMessage(groupSessionId, e, text);
|
|
1247
|
-
}
|
|
1248
|
-
return;
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
// 检查是否在处理中
|
|
1253
|
-
if (isGroup && groupId && groupSessionId) {
|
|
1254
|
-
if (processingSet.has(groupSessionId)) {
|
|
1255
|
-
if ((atBot || mentionedNickname) && !isRateLimitBlocked()) {
|
|
1256
|
-
queueManager.enqueue(groupSessionId, e, cfg);
|
|
1257
|
-
rateLimiter.recordInteraction(groupId, userId);
|
|
1258
|
-
}
|
|
1259
|
-
return;
|
|
1260
|
-
}
|
|
1261
|
-
processingSet.add(groupSessionId);
|
|
1262
|
-
} else {
|
|
1263
|
-
const triggerKey = `personal:${userId}`;
|
|
1264
|
-
if (processingSet.has(triggerKey)) return;
|
|
1265
|
-
processingSet.add(triggerKey);
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
try {
|
|
1269
|
-
if (atBot) {
|
|
1270
|
-
if (!rateLimiter.canProcess(userId, groupId, text)) return;
|
|
1271
|
-
|
|
1272
|
-
if (
|
|
1273
|
-
isGroup &&
|
|
1274
|
-
groupId &&
|
|
1275
|
-
groupSessionId &&
|
|
1276
|
-
cfg.dynamicDelay?.enabled
|
|
1277
|
-
) {
|
|
1278
|
-
rateLimiter.recordInteraction(groupId, userId);
|
|
1279
|
-
const delayInfo = rateLimiter.getDelayInfo(groupId);
|
|
1280
|
-
if (delayInfo.shouldDelay) {
|
|
1281
|
-
rateLimiter.record(userId, groupId, text);
|
|
1282
|
-
queueProcessor.collectDynamicDelayMessage(
|
|
1283
|
-
groupSessionId,
|
|
1284
|
-
e,
|
|
1285
|
-
text,
|
|
1286
|
-
);
|
|
1287
|
-
queueProcessor.startDynamicDelayTimer(
|
|
1288
|
-
groupSessionId,
|
|
1289
|
-
groupId,
|
|
1290
|
-
delayInfo.delayMs,
|
|
1291
|
-
e.self_id,
|
|
1292
|
-
);
|
|
1293
|
-
return;
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
|
|
1297
|
-
rateLimiter.record(userId, groupId, text);
|
|
1298
|
-
await processChat(e, pluginCtx, runtimeState);
|
|
1299
|
-
return;
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
if (quotedBot || mentionedNickname) {
|
|
1303
|
-
const { history } = await getGroupHistoryMessages(
|
|
1304
|
-
groupId!,
|
|
1305
|
-
groupSessionId!,
|
|
1306
|
-
ctx,
|
|
1307
|
-
cfg.historyCount,
|
|
1308
|
-
db,
|
|
1309
|
-
e.self_id,
|
|
1310
|
-
pluginCtx.buildHistoryMediaOptions(pluginCtx.aiInstance, cfg),
|
|
1311
|
-
);
|
|
1312
|
-
const botNickname =
|
|
1313
|
-
cfg.nicknames[0] || ctx.pickBot(e.self_id).nickname || "Bot";
|
|
1314
|
-
const planResult = await humanize.actionPlanner.plan(
|
|
1315
|
-
groupSessionId!,
|
|
1316
|
-
botNickname,
|
|
1317
|
-
history,
|
|
1318
|
-
text,
|
|
1319
|
-
);
|
|
1320
|
-
if (planResult.action !== "reply") return;
|
|
1321
|
-
if (!rateLimiter.canProcess(userId, groupId, text)) return;
|
|
1322
|
-
rateLimiter.record(userId, groupId, text);
|
|
1323
|
-
await processChat(e, pluginCtx, runtimeState);
|
|
1324
|
-
return;
|
|
1325
|
-
}
|
|
1326
|
-
} finally {
|
|
1327
|
-
if (isGroup && groupId && groupSessionId) {
|
|
1328
|
-
processingSet.delete(groupSessionId);
|
|
1329
|
-
await queueProcessor.processQueuedMessages(groupSessionId, e.self_id);
|
|
1330
|
-
} else {
|
|
1331
|
-
processingSet.delete(`personal:${userId}`);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
|
-
});
|
|
1335
|
-
|
|
1336
|
-
// ==================== 戳一戳处理 ====================
|
|
1337
|
-
ctx.handle("notice.group.poke" as any, async (e: any) => {
|
|
1338
|
-
if (e.target_id !== e.self_id) return;
|
|
1339
|
-
const cfg = await getConfig();
|
|
1340
|
-
if (!cfg.apiKey) return;
|
|
1341
|
-
const groupId = e.group_id;
|
|
1342
|
-
if (!groupId || !isGroupAllowed(groupId, cfg)) return;
|
|
1343
|
-
|
|
1344
|
-
const lastPoke = pokeCooldowns.get(groupId) ?? 0;
|
|
1345
|
-
if (Date.now() - lastPoke < POKE_COOLDOWN_MS) return;
|
|
1346
|
-
pokeCooldowns.set(groupId, Date.now());
|
|
1347
|
-
|
|
1348
|
-
const groupSessionId = `group:${groupId}`;
|
|
1349
|
-
if (isRateLimitBlocked()) return;
|
|
1350
|
-
|
|
1351
|
-
if (processingSet.has(groupSessionId)) {
|
|
1352
|
-
if (!isRateLimitBlocked()) queueManager.enqueue(groupSessionId, e, cfg);
|
|
1353
|
-
return;
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
processingSet.add(groupSessionId);
|
|
1357
|
-
sessionManager.getOrCreate(groupSessionId, "group", groupId);
|
|
1358
|
-
|
|
1359
|
-
try {
|
|
1360
|
-
const userId = e.user_id || e.operator_id;
|
|
1361
|
-
const botRole = await getBotRole(groupId, ctx, e.self_id);
|
|
1362
|
-
const botNickname =
|
|
1363
|
-
cfg.nicknames[0] || ctx.pickBot(e.self_id).nickname || "Bot";
|
|
1364
|
-
|
|
1365
|
-
let senderName = String(userId);
|
|
1366
|
-
try {
|
|
1367
|
-
const memberInfo = await ctx
|
|
1368
|
-
.pickBot(e.self_id)
|
|
1369
|
-
.getGroupMemberInfo(groupId, userId);
|
|
1370
|
-
senderName =
|
|
1371
|
-
(memberInfo as any).card ||
|
|
1372
|
-
(memberInfo as any).nickname ||
|
|
1373
|
-
String(userId);
|
|
1374
|
-
} catch {}
|
|
1375
|
-
|
|
1376
|
-
const targetMessage: TargetMessage = {
|
|
1377
|
-
userName: senderName,
|
|
1378
|
-
userId,
|
|
1379
|
-
userRole: "member",
|
|
1380
|
-
content: `[${senderName} poked you]`,
|
|
1381
|
-
timestamp: Date.now(),
|
|
1382
|
-
};
|
|
1383
|
-
|
|
1384
|
-
const { history } = await getGroupHistoryMessages(
|
|
1385
|
-
groupId,
|
|
1386
|
-
groupSessionId,
|
|
1387
|
-
ctx,
|
|
1388
|
-
cfg.historyCount,
|
|
1389
|
-
db,
|
|
1390
|
-
e.self_id,
|
|
1391
|
-
pluginCtx.buildHistoryMediaOptions(pluginCtx.aiInstance, cfg),
|
|
1392
|
-
);
|
|
1393
|
-
const { groupName, memberCount } = await getGroupInfoData(
|
|
1394
|
-
ctx,
|
|
1395
|
-
groupId,
|
|
1396
|
-
e.self_id,
|
|
1397
|
-
);
|
|
1398
|
-
|
|
1399
|
-
const toolCtx = buildToolContext({
|
|
1400
|
-
ctx,
|
|
1401
|
-
event: e,
|
|
1402
|
-
groupSessionId,
|
|
1403
|
-
groupId,
|
|
1404
|
-
userId,
|
|
1405
|
-
config: cfg,
|
|
1406
|
-
aiService: aiService!,
|
|
1407
|
-
db,
|
|
1408
|
-
botRole,
|
|
1409
|
-
humanize,
|
|
1410
|
-
targetMessage,
|
|
1411
|
-
selfId: e.self_id,
|
|
1412
|
-
});
|
|
1413
|
-
|
|
1414
|
-
const result = await runWithRateLimitGuard(
|
|
1415
|
-
() =>
|
|
1416
|
-
runChat(
|
|
1417
|
-
mainAIInstance,
|
|
1418
|
-
toolCtx,
|
|
1419
|
-
history,
|
|
1420
|
-
targetMessage,
|
|
1421
|
-
{
|
|
1422
|
-
config: cfg,
|
|
1423
|
-
groupName,
|
|
1424
|
-
memberCount,
|
|
1425
|
-
botNickname,
|
|
1426
|
-
botRole,
|
|
1427
|
-
aiService: aiService!,
|
|
1428
|
-
isGroup: true,
|
|
1429
|
-
replyContext: {
|
|
1430
|
-
type: "poked",
|
|
1431
|
-
targetUser: targetMessage.userName,
|
|
1432
|
-
targetMessage: targetMessage.content,
|
|
1433
|
-
},
|
|
1434
|
-
},
|
|
1435
|
-
humanize,
|
|
1436
|
-
skillManager,
|
|
1437
|
-
{
|
|
1438
|
-
manager: groupStructuredHistory,
|
|
1439
|
-
ttlMs: cfg.groupStructuredHistoryTtlMs,
|
|
1440
|
-
currentUserInputs: [
|
|
1441
|
-
buildStructuredUserInputFromTarget(targetMessage),
|
|
1442
|
-
],
|
|
1443
|
-
},
|
|
1444
|
-
),
|
|
1445
|
-
{ userId, groupId, label: "poke" },
|
|
1446
|
-
);
|
|
1447
|
-
if (!result) return;
|
|
1448
|
-
|
|
1449
|
-
await sendAIResponse(
|
|
1450
|
-
{
|
|
1451
|
-
ctx,
|
|
1452
|
-
groupId,
|
|
1453
|
-
messages: result.messages,
|
|
1454
|
-
config: cfg,
|
|
1455
|
-
sentIndices: toolCtx.sentMessageIndices,
|
|
1456
|
-
},
|
|
1457
|
-
e.self_id,
|
|
1458
|
-
);
|
|
1459
|
-
|
|
1460
|
-
const now = Date.now();
|
|
1461
|
-
saveBotMessages(
|
|
1462
|
-
groupId,
|
|
1463
|
-
groupSessionId,
|
|
1464
|
-
result.messages,
|
|
1465
|
-
now,
|
|
1466
|
-
cfg,
|
|
1467
|
-
db,
|
|
1468
|
-
ctx,
|
|
1469
|
-
e.self_id,
|
|
1470
|
-
);
|
|
1471
|
-
idleCheckManager.recordBotMessages(
|
|
1472
|
-
groupSessionId,
|
|
1473
|
-
result.messages.length,
|
|
1474
|
-
e.self_id,
|
|
1475
|
-
);
|
|
1476
|
-
|
|
1477
|
-
await sendEmoji(ctx, groupId, result.emojiPath, e.self_id);
|
|
1478
|
-
sessionManager.touch(groupSessionId);
|
|
1479
|
-
} catch (err) {
|
|
1480
|
-
ctx.logger.error(`Poke processing failed: ${err}`);
|
|
1481
|
-
} finally {
|
|
1482
|
-
processingSet.delete(groupSessionId);
|
|
1483
|
-
}
|
|
1484
|
-
});
|
|
236
|
+
ctx.handle("message", createMessageHandler(pluginCtx, handlerState));
|
|
237
|
+
ctx.handle("notice.group.poke" as any, createPokeHandler(pluginCtx, handlerState));
|
|
1485
238
|
|
|
1486
239
|
ctx.logger.info("聊天插件加载成功");
|
|
1487
240
|
|
|
@@ -1499,388 +252,3 @@ const chatPlugin = definePlugin({
|
|
|
1499
252
|
};
|
|
1500
253
|
},
|
|
1501
254
|
});
|
|
1502
|
-
|
|
1503
|
-
async function handleIdleCheckDebug(
|
|
1504
|
-
ctx: MiokiContext,
|
|
1505
|
-
e: any,
|
|
1506
|
-
cfg: ChatConfig,
|
|
1507
|
-
mainAIInstance: AIInstance,
|
|
1508
|
-
db: import("./db").ChatDatabase,
|
|
1509
|
-
humanize: HumanizeEngine,
|
|
1510
|
-
skillManager: SkillSessionManager,
|
|
1511
|
-
runWithRateLimitGuard: RunRateLimitGuard,
|
|
1512
|
-
getGroupHistoryMessages: GetGroupHistoryMessages,
|
|
1513
|
-
buildHistoryMediaOptions: (
|
|
1514
|
-
ai: AIInstance,
|
|
1515
|
-
cfg: ChatConfig,
|
|
1516
|
-
) => HistoryMediaOptions,
|
|
1517
|
-
runChat: RunChat,
|
|
1518
|
-
buildToolContext: BuildToolContext,
|
|
1519
|
-
sendAIResponse: SendAIResponse,
|
|
1520
|
-
saveBotMessages: SaveBotMessages,
|
|
1521
|
-
) {
|
|
1522
|
-
const isOwner = ctx.isOwner?.(e) ?? false;
|
|
1523
|
-
if (!isOwner) {
|
|
1524
|
-
await e.reply("只有主人才能使用这个指令~");
|
|
1525
|
-
return;
|
|
1526
|
-
}
|
|
1527
|
-
const groupIdStr = e.message[0]?.text?.replace("/空闲检查", "")?.trim() || "";
|
|
1528
|
-
const targetGroupId = parseInt(groupIdStr, 10);
|
|
1529
|
-
if (!targetGroupId) {
|
|
1530
|
-
await e.reply("请指定群号,如:/空闲检查 123456");
|
|
1531
|
-
return;
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
const groupSessionId = `group:${targetGroupId}`;
|
|
1535
|
-
try {
|
|
1536
|
-
const now = Date.now();
|
|
1537
|
-
const botNickname =
|
|
1538
|
-
cfg.nicknames[0] || ctx.pickBot(e.self_id).nickname || "Bot";
|
|
1539
|
-
ctx.logger.info(`[Debug] Manual idle check: group ${targetGroupId}`);
|
|
1540
|
-
|
|
1541
|
-
const { history } = await getGroupHistoryMessages(
|
|
1542
|
-
targetGroupId,
|
|
1543
|
-
groupSessionId,
|
|
1544
|
-
ctx,
|
|
1545
|
-
cfg.historyCount,
|
|
1546
|
-
db,
|
|
1547
|
-
e.self_id,
|
|
1548
|
-
buildHistoryMediaOptions(mainAIInstance, cfg),
|
|
1549
|
-
);
|
|
1550
|
-
const planResult = await humanize.actionPlanner.plan(
|
|
1551
|
-
groupSessionId,
|
|
1552
|
-
botNickname,
|
|
1553
|
-
history,
|
|
1554
|
-
"[Check if you want to answer the call]",
|
|
1555
|
-
true,
|
|
1556
|
-
);
|
|
1557
|
-
|
|
1558
|
-
if (planResult.action !== "reply") {
|
|
1559
|
-
await e.reply(
|
|
1560
|
-
`[空闲检测] 群 ${targetGroupId}\n决定: ${planResult.action}\n原因: ${planResult.reason}`,
|
|
1561
|
-
);
|
|
1562
|
-
return;
|
|
1563
|
-
}
|
|
1564
|
-
|
|
1565
|
-
const targetMessage: TargetMessage = {
|
|
1566
|
-
userName: "系统",
|
|
1567
|
-
userId: 0,
|
|
1568
|
-
userRole: "member",
|
|
1569
|
-
content: "[No one in the group is talking? I'll answer!]",
|
|
1570
|
-
messageId: 0,
|
|
1571
|
-
timestamp: now,
|
|
1572
|
-
};
|
|
1573
|
-
const botRole = await getBotRole(targetGroupId, ctx, e.self_id);
|
|
1574
|
-
const toolCtx = buildToolContext({
|
|
1575
|
-
ctx,
|
|
1576
|
-
event: null,
|
|
1577
|
-
groupSessionId,
|
|
1578
|
-
groupId: targetGroupId,
|
|
1579
|
-
userId: 0,
|
|
1580
|
-
config: cfg,
|
|
1581
|
-
aiService: ctx.services!.ai as AIService,
|
|
1582
|
-
db,
|
|
1583
|
-
botRole,
|
|
1584
|
-
humanize,
|
|
1585
|
-
targetMessage,
|
|
1586
|
-
selfId: e.self_id,
|
|
1587
|
-
});
|
|
1588
|
-
|
|
1589
|
-
const result = await runWithRateLimitGuard(
|
|
1590
|
-
() =>
|
|
1591
|
-
runChat(
|
|
1592
|
-
mainAIInstance,
|
|
1593
|
-
toolCtx,
|
|
1594
|
-
history,
|
|
1595
|
-
targetMessage,
|
|
1596
|
-
{
|
|
1597
|
-
config: cfg,
|
|
1598
|
-
botNickname,
|
|
1599
|
-
botRole: toolCtx.botRole,
|
|
1600
|
-
aiService: ctx.services!.ai as AIService,
|
|
1601
|
-
isGroup: true,
|
|
1602
|
-
plannerThoughts: `You stumbled upon some message in this group and decided to reply.\nQuote messages from group friends appropriately (using [reply:message ID] format).\nDon't mention your intentions like "I'm here to answer".`,
|
|
1603
|
-
replyContext: { type: "idle" },
|
|
1604
|
-
},
|
|
1605
|
-
humanize,
|
|
1606
|
-
skillManager,
|
|
1607
|
-
),
|
|
1608
|
-
{ groupId: targetGroupId, label: "idle-check" },
|
|
1609
|
-
);
|
|
1610
|
-
if (!result) {
|
|
1611
|
-
await e.reply(`[空闲检测] 群 ${targetGroupId} 因限流被跳过`);
|
|
1612
|
-
return;
|
|
1613
|
-
}
|
|
1614
|
-
|
|
1615
|
-
await sendAIResponse(
|
|
1616
|
-
{
|
|
1617
|
-
ctx,
|
|
1618
|
-
groupId: targetGroupId,
|
|
1619
|
-
messages: result.messages,
|
|
1620
|
-
config: cfg,
|
|
1621
|
-
sentIndices: toolCtx.sentMessageIndices,
|
|
1622
|
-
},
|
|
1623
|
-
e.self_id,
|
|
1624
|
-
);
|
|
1625
|
-
const now2 = Date.now();
|
|
1626
|
-
saveBotMessages(
|
|
1627
|
-
targetGroupId,
|
|
1628
|
-
groupSessionId,
|
|
1629
|
-
result.messages,
|
|
1630
|
-
now2,
|
|
1631
|
-
cfg,
|
|
1632
|
-
db,
|
|
1633
|
-
ctx,
|
|
1634
|
-
e.self_id,
|
|
1635
|
-
);
|
|
1636
|
-
await e.reply(
|
|
1637
|
-
`[空闲检测] 群 ${targetGroupId} 已发送回复: ${planResult.reason}`,
|
|
1638
|
-
);
|
|
1639
|
-
} catch (err) {
|
|
1640
|
-
ctx.logger.error(`[Debug] Idle check failed: ${err}`);
|
|
1641
|
-
await e.reply(`[空闲检测] 失败: ${err}`);
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
async function processChat(
|
|
1646
|
-
e: any,
|
|
1647
|
-
pluginCtx: ChatPluginContext,
|
|
1648
|
-
runtimeState: ChatRuntimeState,
|
|
1649
|
-
) {
|
|
1650
|
-
const { ctx, config: cfg } = pluginCtx;
|
|
1651
|
-
const isGroup = e.message_type === "group";
|
|
1652
|
-
const groupId: number | undefined = isGroup ? e.group_id : undefined;
|
|
1653
|
-
const userId: number = e.user_id || e.sender?.user_id;
|
|
1654
|
-
const selfId = e.self_id;
|
|
1655
|
-
|
|
1656
|
-
const personalSessionId = `personal:${userId}`;
|
|
1657
|
-
const groupSessionId = groupId ? `group:${groupId}` : personalSessionId;
|
|
1658
|
-
|
|
1659
|
-
if (runtimeState.isRateLimitBlocked()) {
|
|
1660
|
-
if (groupId) pluginCtx.queueManager.clearActiveTarget(groupSessionId);
|
|
1661
|
-
return;
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
try {
|
|
1665
|
-
pluginCtx.sessionManager.getOrCreate(
|
|
1666
|
-
groupSessionId,
|
|
1667
|
-
groupId ? "group" : "personal",
|
|
1668
|
-
groupId ?? userId,
|
|
1669
|
-
);
|
|
1670
|
-
if (groupId)
|
|
1671
|
-
pluginCtx.sessionManager.getOrCreate(
|
|
1672
|
-
personalSessionId,
|
|
1673
|
-
"personal",
|
|
1674
|
-
userId,
|
|
1675
|
-
);
|
|
1676
|
-
|
|
1677
|
-
const quotedInfo = await getQuotedContent(e, ctx);
|
|
1678
|
-
const imageUrls: string[] = [];
|
|
1679
|
-
|
|
1680
|
-
if (e.message) {
|
|
1681
|
-
for (const seg of e.message) {
|
|
1682
|
-
if (seg.type === "image" && (seg.url || seg.data?.url))
|
|
1683
|
-
imageUrls.push(seg.url || seg.data.url);
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
if (quotedInfo?.imageUrl) imageUrls.push(quotedInfo.imageUrl);
|
|
1687
|
-
|
|
1688
|
-
let messageContent = ctx.text(e) || "";
|
|
1689
|
-
let extraContext = "";
|
|
1690
|
-
if (quotedInfo) {
|
|
1691
|
-
const parts = [
|
|
1692
|
-
`[Quoted message #${quotedInfo.messageId} from ${quotedInfo.senderName}: ${quotedInfo.content}]`,
|
|
1693
|
-
];
|
|
1694
|
-
if (quotedInfo.imageUrl) parts.push("[Quoted message contains an image]");
|
|
1695
|
-
extraContext = parts.join(" ");
|
|
1696
|
-
}
|
|
1697
|
-
if (extraContext) messageContent = extraContext + " " + messageContent;
|
|
1698
|
-
|
|
1699
|
-
const rawHistory = groupId
|
|
1700
|
-
? await getGroupHistory(
|
|
1701
|
-
groupId,
|
|
1702
|
-
ctx,
|
|
1703
|
-
cfg.historyCount,
|
|
1704
|
-
e.self_id,
|
|
1705
|
-
pluginCtx.db,
|
|
1706
|
-
pluginCtx.buildHistoryMediaOptions(pluginCtx.aiInstance, cfg),
|
|
1707
|
-
)
|
|
1708
|
-
: [];
|
|
1709
|
-
const history: ChatMessage[] = rawHistory.map((msg: any) => ({
|
|
1710
|
-
sessionId: groupSessionId,
|
|
1711
|
-
role: "user" as const,
|
|
1712
|
-
content: msg.content,
|
|
1713
|
-
userId: msg.userId,
|
|
1714
|
-
userName: msg.userName,
|
|
1715
|
-
userRole: msg.userRole,
|
|
1716
|
-
groupId,
|
|
1717
|
-
timestamp: msg.timestamp,
|
|
1718
|
-
messageId: msg.messageId,
|
|
1719
|
-
}));
|
|
1720
|
-
|
|
1721
|
-
const botNickname =
|
|
1722
|
-
cfg.nicknames[0] || ctx.pickBot(e.self_id).nickname || "Bot";
|
|
1723
|
-
const botRole = groupId
|
|
1724
|
-
? await getBotRole(groupId, ctx, e.self_id)
|
|
1725
|
-
: "member";
|
|
1726
|
-
let groupName: string | undefined;
|
|
1727
|
-
let memberCount: number | undefined;
|
|
1728
|
-
if (groupId) {
|
|
1729
|
-
const groupInfo = await pluginCtx.getGroupInfoData(
|
|
1730
|
-
ctx,
|
|
1731
|
-
groupId,
|
|
1732
|
-
e.self_id,
|
|
1733
|
-
e.group_name,
|
|
1734
|
-
);
|
|
1735
|
-
groupName = groupInfo.groupName;
|
|
1736
|
-
memberCount = groupInfo.memberCount;
|
|
1737
|
-
}
|
|
1738
|
-
|
|
1739
|
-
const senderName = e.sender?.card || e.sender?.nickname || String(userId);
|
|
1740
|
-
const contexts = await pluginCtx.getHumanizeContexts(
|
|
1741
|
-
pluginCtx.humanize,
|
|
1742
|
-
groupSessionId,
|
|
1743
|
-
senderName,
|
|
1744
|
-
history,
|
|
1745
|
-
userId,
|
|
1746
|
-
);
|
|
1747
|
-
|
|
1748
|
-
const targetMessage: TargetMessage = {
|
|
1749
|
-
userName: senderName,
|
|
1750
|
-
userId,
|
|
1751
|
-
userRole: e.sender?.role || "member",
|
|
1752
|
-
userTitle: (e.sender as any)?.title || undefined,
|
|
1753
|
-
content: messageContent,
|
|
1754
|
-
messageId: e.message_id,
|
|
1755
|
-
timestamp: Date.now(),
|
|
1756
|
-
};
|
|
1757
|
-
|
|
1758
|
-
if (groupId)
|
|
1759
|
-
pluginCtx.queueManager.setActiveTarget(groupSessionId, targetMessage);
|
|
1760
|
-
|
|
1761
|
-
const toolCtx = pluginCtx.buildToolContext({
|
|
1762
|
-
ctx,
|
|
1763
|
-
event: e,
|
|
1764
|
-
groupSessionId,
|
|
1765
|
-
groupId,
|
|
1766
|
-
userId,
|
|
1767
|
-
config: cfg,
|
|
1768
|
-
aiService: pluginCtx.aiService,
|
|
1769
|
-
db: pluginCtx.db,
|
|
1770
|
-
botRole,
|
|
1771
|
-
pendingImageUrls: imageUrls,
|
|
1772
|
-
humanize: pluginCtx.humanize,
|
|
1773
|
-
targetMessage,
|
|
1774
|
-
selfId: e.self_id,
|
|
1775
|
-
});
|
|
1776
|
-
|
|
1777
|
-
const result = await pluginCtx.runWithRateLimitGuard(
|
|
1778
|
-
() =>
|
|
1779
|
-
pluginCtx.runChat(
|
|
1780
|
-
pluginCtx.aiInstance,
|
|
1781
|
-
toolCtx,
|
|
1782
|
-
history,
|
|
1783
|
-
targetMessage,
|
|
1784
|
-
{
|
|
1785
|
-
config: cfg,
|
|
1786
|
-
groupName,
|
|
1787
|
-
memberCount,
|
|
1788
|
-
botNickname,
|
|
1789
|
-
botRole,
|
|
1790
|
-
aiService: pluginCtx.aiService,
|
|
1791
|
-
isGroup,
|
|
1792
|
-
memoryContext: contexts.memoryContext,
|
|
1793
|
-
topicContext: contexts.topicContext,
|
|
1794
|
-
expressionContext: contexts.expressionContext,
|
|
1795
|
-
replyContext: {
|
|
1796
|
-
type: "reply",
|
|
1797
|
-
targetUser: targetMessage.userName,
|
|
1798
|
-
targetMessage: targetMessage.content,
|
|
1799
|
-
},
|
|
1800
|
-
},
|
|
1801
|
-
pluginCtx.humanize,
|
|
1802
|
-
pluginCtx.skillManager,
|
|
1803
|
-
groupId
|
|
1804
|
-
? {
|
|
1805
|
-
manager: pluginCtx.groupStructuredHistory,
|
|
1806
|
-
ttlMs: cfg.groupStructuredHistoryTtlMs,
|
|
1807
|
-
currentUserInputs: [
|
|
1808
|
-
pluginCtx.buildStructuredUserInputFromTarget(targetMessage),
|
|
1809
|
-
],
|
|
1810
|
-
}
|
|
1811
|
-
: undefined,
|
|
1812
|
-
),
|
|
1813
|
-
{ userId, groupId, label: isGroup ? "group-chat" : "private-chat" },
|
|
1814
|
-
);
|
|
1815
|
-
|
|
1816
|
-
if (!result) {
|
|
1817
|
-
if (groupId) pluginCtx.queueManager.clearActiveTarget(groupSessionId);
|
|
1818
|
-
return;
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
if (groupId) {
|
|
1822
|
-
await pluginCtx.sendAIResponse(
|
|
1823
|
-
{
|
|
1824
|
-
ctx,
|
|
1825
|
-
groupId,
|
|
1826
|
-
messages: result.messages,
|
|
1827
|
-
config: cfg,
|
|
1828
|
-
sentIndices: toolCtx.sentMessageIndices,
|
|
1829
|
-
},
|
|
1830
|
-
e.self_id,
|
|
1831
|
-
);
|
|
1832
|
-
await pluginCtx.sendEmoji(ctx, groupId, result.emojiPath, e.self_id);
|
|
1833
|
-
|
|
1834
|
-
const now = Date.now();
|
|
1835
|
-
pluginCtx.saveBotMessages(
|
|
1836
|
-
groupId,
|
|
1837
|
-
groupSessionId,
|
|
1838
|
-
result.messages,
|
|
1839
|
-
now,
|
|
1840
|
-
cfg,
|
|
1841
|
-
pluginCtx.db,
|
|
1842
|
-
ctx,
|
|
1843
|
-
e.self_id,
|
|
1844
|
-
);
|
|
1845
|
-
pluginCtx.idleCheckManager.recordBotMessages(
|
|
1846
|
-
groupSessionId,
|
|
1847
|
-
result.messages.length,
|
|
1848
|
-
e.self_id,
|
|
1849
|
-
);
|
|
1850
|
-
|
|
1851
|
-
pluginCtx.cooldownManager.startCooldownTimer(
|
|
1852
|
-
groupSessionId,
|
|
1853
|
-
groupId,
|
|
1854
|
-
e.self_id,
|
|
1855
|
-
);
|
|
1856
|
-
} else {
|
|
1857
|
-
const sentIndices = toolCtx.sentMessageIndices;
|
|
1858
|
-
for (let i = 0; i < result.messages.length; i++) {
|
|
1859
|
-
if (sentIndices?.has(i)) continue;
|
|
1860
|
-
await pluginCtx.sendMessage(
|
|
1861
|
-
ctx,
|
|
1862
|
-
undefined,
|
|
1863
|
-
userId,
|
|
1864
|
-
result.messages[i],
|
|
1865
|
-
cfg,
|
|
1866
|
-
e.self_id,
|
|
1867
|
-
);
|
|
1868
|
-
}
|
|
1869
|
-
if (result.emojiPath) {
|
|
1870
|
-
try {
|
|
1871
|
-
const emojiSegment = ctx.segment.image(`file://${result.emojiPath}`);
|
|
1872
|
-
await e.reply([emojiSegment]);
|
|
1873
|
-
} catch (err) {
|
|
1874
|
-
ctx.logger.warn(`[Emoticon] Send failed: ${err}`);
|
|
1875
|
-
}
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
pluginCtx.sessionManager.touch(groupSessionId);
|
|
1880
|
-
} catch (err) {
|
|
1881
|
-
ctx.logger.error(`Chat processing failed: ${err}`);
|
|
1882
|
-
if (groupId) pluginCtx.queueManager.clearActiveTarget(groupSessionId);
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
|
|
1886
|
-
export default chatPlugin;
|