mioku-plugin-chat 2.10.1 → 3.0.1
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 -1
- package/context.ts +2 -2
- package/core/base.ts +113 -82
- package/core/chat-engine.ts +44 -23
- package/core/chat-turn.ts +11 -11
- package/core/media/audio.ts +4 -4
- package/core/media/gif-extractor.ts +4 -4
- package/core/media/history-media.ts +11 -16
- package/core/media/image-analyzer.ts +1 -1
- package/core/media/image-compress.ts +1 -1
- package/core/media/markdown-message.ts +22 -0
- package/core/media/segment.ts +10 -14
- package/core/multimodal.ts +18 -13
- package/core/prompt.ts +12 -10
- package/core/tools/info.ts +8 -7
- package/core/tools/web.ts +96 -57
- package/core/tools.ts +1 -1
- package/core/web/searxng.ts +1 -1
- package/core/web/web-reader.ts +2 -2
- package/db.ts +6 -3
- package/handlers/idle-debug.ts +4 -4
- package/handlers/message.ts +44 -29
- package/handlers/poke.ts +22 -16
- package/humanize/emoji-agent.ts +1 -1
- package/humanize/emotion-agent.ts +1 -1
- package/humanize/expression.ts +1 -1
- package/humanize/memory.ts +1 -1
- package/humanize/planner.ts +1 -1
- package/humanize/topic.ts +1 -1
- package/index.ts +3 -3
- package/manage/cleanup.ts +1 -1
- package/manage/cooldown.ts +5 -5
- package/manage/group-structured-history.ts +1 -1
- package/manage/idle-check.ts +5 -5
- package/manage/queue-processor.ts +5 -5
- package/manage/types.ts +39 -15
- package/package.json +3 -5
- package/types.ts +5 -5
- package/utils/message.ts +111 -66
- package/utils/queue.ts +7 -6
package/core/tools/info.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {logger} from "mioku";
|
|
2
2
|
import type { AITool } from "mioku";
|
|
3
3
|
import { TOOL_RESULT_FOLLOWUP_KEY } from "mioku";
|
|
4
4
|
import type { ToolContext } from "../../types";
|
|
@@ -73,9 +73,10 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
|
|
|
73
73
|
},
|
|
74
74
|
handler: async (args) => {
|
|
75
75
|
try {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
const bot = toolCtx.event?.bot;
|
|
77
|
+
if (!bot) return { error: `Failed to get member info: bot not found` };
|
|
78
|
+
const info = await bot.getMemberInfo(toolCtx.groupId!, args.user_id);
|
|
79
|
+
if (!info) return { error: `Failed to get member info: member not found` };
|
|
79
80
|
return {
|
|
80
81
|
nickname: info.nickname,
|
|
81
82
|
card: info.card,
|
|
@@ -107,9 +108,9 @@ export function createInfoTools(toolCtx: ToolContext): AITool[] {
|
|
|
107
108
|
},
|
|
108
109
|
handler: async (args) => {
|
|
109
110
|
try {
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
const bot = toolCtx.event?.bot;
|
|
112
|
+
if (!bot) return { error: `Failed to get member list: bot not found` };
|
|
113
|
+
const list = await bot.getGroupMembers(toolCtx.groupId!);
|
|
113
114
|
const members = (list as any[]).map((m) => ({
|
|
114
115
|
user_id: m.user_id,
|
|
115
116
|
nickname: m.card || m.nickname,
|
package/core/tools/web.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { logger } from "
|
|
1
|
+
import { HistoryMessage, logger } from "mioku";
|
|
2
2
|
import type { AITool } from "mioku";
|
|
3
3
|
import type { ChatMessage, ToolContext } from "../../types";
|
|
4
4
|
import { searchWebWithSearxng } from "../web/searxng";
|
|
@@ -47,7 +47,8 @@ function extractGroupHistoryText(raw: any): string {
|
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
49
|
if (seg?.type === "at") {
|
|
50
|
-
const target =
|
|
50
|
+
const target =
|
|
51
|
+
seg?.qq || seg?.data?.qq || seg?.data?.id || seg?.data?.user_id;
|
|
51
52
|
if (target === "all" || target === "everyone") parts.push("@全体成员");
|
|
52
53
|
else if (target) parts.push(`@${target}`);
|
|
53
54
|
continue;
|
|
@@ -68,15 +69,18 @@ async function fetchGroupHistoryByMessageIdPaging(
|
|
|
68
69
|
): Promise<ChatMessage[]> {
|
|
69
70
|
if (!toolCtx.groupId || limit <= 0) return [];
|
|
70
71
|
|
|
71
|
-
const
|
|
72
|
-
if (!selfId) return [];
|
|
73
|
-
|
|
74
|
-
const bot = toolCtx.ctx.pickBot(selfId);
|
|
72
|
+
const bot = toolCtx.event?.bot;
|
|
75
73
|
if (!bot) return [];
|
|
76
74
|
|
|
75
|
+
const otherBotIds = new Set<string>(
|
|
76
|
+
toolCtx.ctx.bots
|
|
77
|
+
.map((b) => String(b.bot_id))
|
|
78
|
+
.filter((id) => id !== String(bot.bot_id)),
|
|
79
|
+
);
|
|
80
|
+
|
|
77
81
|
const collected: ChatMessage[] = [];
|
|
78
82
|
const seenMessageIds = new Set<string>();
|
|
79
|
-
let cursorMessageId =
|
|
83
|
+
let cursorMessageId = "";
|
|
80
84
|
const maxPages = Math.max(1, Math.ceil(limit / 200) + 5);
|
|
81
85
|
let page = 0;
|
|
82
86
|
|
|
@@ -84,79 +88,84 @@ async function fetchGroupHistoryByMessageIdPaging(
|
|
|
84
88
|
const remaining = limit - collected.length;
|
|
85
89
|
const pageSize = Math.min(200, remaining);
|
|
86
90
|
|
|
87
|
-
let
|
|
91
|
+
let history: HistoryMessage[];
|
|
88
92
|
try {
|
|
89
|
-
|
|
90
|
-
group_id:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
disable_get_url: true,
|
|
95
|
-
parse_mult_msg: false,
|
|
96
|
-
quick_reply: false,
|
|
97
|
-
});
|
|
93
|
+
history = await bot.getHistory(
|
|
94
|
+
{ type: "group", group_id: toolCtx.groupId },
|
|
95
|
+
cursorMessageId || undefined,
|
|
96
|
+
pageSize,
|
|
97
|
+
);
|
|
98
98
|
} catch (err) {
|
|
99
99
|
const errText = String(err);
|
|
100
100
|
if (
|
|
101
|
-
cursorMessageId
|
|
102
|
-
(errText.includes("不存在") ||
|
|
101
|
+
cursorMessageId !== "" &&
|
|
102
|
+
(errText.includes("不存在") ||
|
|
103
|
+
errText.toLowerCase().includes("not exist"))
|
|
103
104
|
) {
|
|
104
|
-
logger.info(
|
|
105
|
+
logger.info(
|
|
106
|
+
`[recall_memory] get_group_msg_history stop at cursor ${cursorMessageId}: ${errText}`,
|
|
107
|
+
);
|
|
105
108
|
} else {
|
|
106
|
-
logger.warn(
|
|
109
|
+
logger.warn(
|
|
110
|
+
`[recall_memory] get_group_msg_history failed at cursor ${cursorMessageId}: ${errText}`,
|
|
111
|
+
);
|
|
107
112
|
}
|
|
108
113
|
break;
|
|
109
114
|
}
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
if (!Array.isArray(rawMessages) || rawMessages.length === 0) break;
|
|
116
|
+
if (!Array.isArray(history) || history.length === 0) break;
|
|
113
117
|
|
|
114
|
-
let oldestMessageId:
|
|
118
|
+
let oldestMessageId: string | null = null;
|
|
115
119
|
let newAdded = 0;
|
|
116
120
|
|
|
117
|
-
for (const raw of
|
|
118
|
-
const
|
|
121
|
+
for (const raw of history) {
|
|
122
|
+
const rawUserId = String(raw.user_id || "");
|
|
123
|
+
if (rawUserId !== "" && otherBotIds.has(rawUserId)) continue;
|
|
124
|
+
|
|
125
|
+
const messageId = String(raw.message_id || "");
|
|
119
126
|
const key =
|
|
120
|
-
messageId
|
|
127
|
+
messageId !== ""
|
|
121
128
|
? `mid:${messageId}`
|
|
122
|
-
: `${String(raw
|
|
129
|
+
: `${String(raw.user_id || "unknown")}:${String(raw.time || "0")}:${extractGroupHistoryText(raw)}`;
|
|
123
130
|
if (seenMessageIds.has(key)) continue;
|
|
124
131
|
seenMessageIds.add(key);
|
|
125
132
|
|
|
126
133
|
const content = extractGroupHistoryText(raw);
|
|
127
134
|
if (!content.trim()) continue;
|
|
128
135
|
|
|
129
|
-
const ts =
|
|
136
|
+
const ts = raw.time ?? Date.now();
|
|
130
137
|
collected.push({
|
|
131
138
|
sessionId: toolCtx.sessionId,
|
|
132
|
-
role: String(raw
|
|
139
|
+
role: String(raw.user_id) === String(bot.bot_id) ? "assistant" : "user",
|
|
133
140
|
content,
|
|
134
|
-
userId:
|
|
135
|
-
userName: raw
|
|
136
|
-
userRole:
|
|
141
|
+
userId: Number(raw.user_id || 0),
|
|
142
|
+
userName: raw.nickname || String(raw.user_id || "unknown"),
|
|
143
|
+
userRole: "member",
|
|
137
144
|
groupId: toolCtx.groupId,
|
|
138
145
|
timestamp: ts,
|
|
139
|
-
messageId: messageId
|
|
146
|
+
messageId: messageId !== "" ? messageId : undefined,
|
|
140
147
|
});
|
|
141
148
|
newAdded += 1;
|
|
142
149
|
|
|
143
|
-
if (
|
|
150
|
+
if (
|
|
151
|
+
messageId !== "" &&
|
|
152
|
+
(oldestMessageId === null || messageId < oldestMessageId)
|
|
153
|
+
) {
|
|
144
154
|
oldestMessageId = messageId;
|
|
145
155
|
}
|
|
146
156
|
}
|
|
147
157
|
|
|
148
|
-
if (newAdded === 0 || oldestMessageId === null || oldestMessageId
|
|
158
|
+
if (newAdded === 0 || oldestMessageId === null || oldestMessageId === "")
|
|
159
|
+
break;
|
|
149
160
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (nextCursor === cursorMessageId) break;
|
|
153
|
-
cursorMessageId = nextCursor;
|
|
161
|
+
if (oldestMessageId === cursorMessageId) break;
|
|
162
|
+
cursorMessageId = oldestMessageId;
|
|
154
163
|
page += 1;
|
|
155
164
|
}
|
|
156
165
|
|
|
157
166
|
collected.sort((a, b) => {
|
|
158
167
|
if (a.timestamp !== b.timestamp) return a.timestamp - b.timestamp;
|
|
159
|
-
return (a.messageId
|
|
168
|
+
return String(a.messageId ?? "").localeCompare(String(b.messageId ?? ""));
|
|
160
169
|
});
|
|
161
170
|
|
|
162
171
|
return collected.length > limit ? collected.slice(-limit) : collected;
|
|
@@ -182,7 +191,8 @@ export function createWebSearchTool(toolCtx: ToolContext): AITool {
|
|
|
182
191
|
},
|
|
183
192
|
limit: {
|
|
184
193
|
type: "number",
|
|
185
|
-
description:
|
|
194
|
+
description:
|
|
195
|
+
"Max number of results to return. Will be clamped by config maxLimit.",
|
|
186
196
|
},
|
|
187
197
|
time_range: {
|
|
188
198
|
type: "string",
|
|
@@ -192,17 +202,20 @@ export function createWebSearchTool(toolCtx: ToolContext): AITool {
|
|
|
192
202
|
categories: {
|
|
193
203
|
type: "array",
|
|
194
204
|
items: { type: "string" },
|
|
195
|
-
description:
|
|
205
|
+
description:
|
|
206
|
+
'Optional categories, e.g. ["general"], ["news"], ["science"]',
|
|
196
207
|
},
|
|
197
208
|
engines: {
|
|
198
209
|
type: "array",
|
|
199
210
|
items: { type: "string" },
|
|
200
|
-
description:
|
|
211
|
+
description:
|
|
212
|
+
'Optional engines, e.g. ["google"], ["bing"], ["duckduckgo"]',
|
|
201
213
|
},
|
|
202
214
|
},
|
|
203
215
|
required: [],
|
|
204
216
|
},
|
|
205
|
-
handler: async (args) =>
|
|
217
|
+
handler: async (args) =>
|
|
218
|
+
searchWebWithSearxng(toolCtx.config.searxng, args || {}),
|
|
206
219
|
};
|
|
207
220
|
}
|
|
208
221
|
|
|
@@ -217,7 +230,10 @@ export function createWebReadPageTool(toolCtx: ToolContext): AITool {
|
|
|
217
230
|
parameters: {
|
|
218
231
|
type: "object",
|
|
219
232
|
properties: {
|
|
220
|
-
url: {
|
|
233
|
+
url: {
|
|
234
|
+
type: "string",
|
|
235
|
+
description: "The http/https URL of the webpage to read",
|
|
236
|
+
},
|
|
221
237
|
render_js: {
|
|
222
238
|
type: "boolean",
|
|
223
239
|
description:
|
|
@@ -234,7 +250,8 @@ export function createWebReadPageTool(toolCtx: ToolContext): AITool {
|
|
|
234
250
|
handler: async (args) => {
|
|
235
251
|
try {
|
|
236
252
|
const ai = toolCtx.config.webReader.useWorkingModel
|
|
237
|
-
? (toolCtx.aiService.getInstanceByRole?.("working") ??
|
|
253
|
+
? (toolCtx.aiService.getInstanceByRole?.("working") ??
|
|
254
|
+
toolCtx.aiService.getDefault())
|
|
238
255
|
: undefined;
|
|
239
256
|
if (toolCtx.config.webReader.useWorkingModel && !ai) {
|
|
240
257
|
return { success: false, error: "AI instance not available" };
|
|
@@ -275,19 +292,41 @@ export function createRecallMemoryTool(toolCtx: ToolContext): AITool {
|
|
|
275
292
|
const question = String(args?.question || "").trim();
|
|
276
293
|
if (!question) return { success: false, error: "question is required" };
|
|
277
294
|
|
|
278
|
-
const ai =
|
|
295
|
+
const ai =
|
|
296
|
+
toolCtx.aiService.getInstanceByRole?.("working") ??
|
|
297
|
+
toolCtx.aiService.getDefault();
|
|
279
298
|
if (!ai) return { success: false, error: "AI instance not available" };
|
|
280
299
|
|
|
281
|
-
const groupHistoryLimit = resolveGroupRecallLimit(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
300
|
+
const groupHistoryLimit = resolveGroupRecallLimit(
|
|
301
|
+
toolCtx.config.memory?.groupHistoryLimit,
|
|
302
|
+
);
|
|
303
|
+
const userHistoryLimit = resolveUserHistoryLimit(
|
|
304
|
+
toolCtx.config.memory?.userHistoryLimit,
|
|
305
|
+
);
|
|
306
|
+
const groupHistoryMessages = await fetchGroupHistoryByMessageIdPaging(
|
|
307
|
+
toolCtx,
|
|
308
|
+
groupHistoryLimit,
|
|
309
|
+
);
|
|
310
|
+
const targetUserIds = extractTargetUserIdsFromQuestion(
|
|
311
|
+
question,
|
|
312
|
+
toolCtx.userId,
|
|
313
|
+
);
|
|
314
|
+
const userHistories: MemoryUserHistoryChunk[] = targetUserIds.map(
|
|
315
|
+
(userId) => ({
|
|
316
|
+
userId,
|
|
317
|
+
messages: toolCtx.db.getMessagesByUser(
|
|
318
|
+
userId,
|
|
319
|
+
toolCtx.sessionId,
|
|
320
|
+
userHistoryLimit,
|
|
321
|
+
),
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
289
324
|
|
|
290
|
-
const retriever = new MemoryRetrieval(
|
|
325
|
+
const retriever = new MemoryRetrieval(
|
|
326
|
+
ai,
|
|
327
|
+
() => toolCtx.config,
|
|
328
|
+
toolCtx.db,
|
|
329
|
+
);
|
|
291
330
|
const answer = await retriever.retrieveByQuestion({
|
|
292
331
|
sessionId: toolCtx.sessionId,
|
|
293
332
|
question,
|
package/core/tools.ts
CHANGED
package/core/web/searxng.ts
CHANGED
package/core/web/web-reader.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { logger } from "
|
|
1
|
+
import { logger } from "mioku";
|
|
2
2
|
import puppeteer from "puppeteer";
|
|
3
3
|
import type { AIInstance } from "mioku";
|
|
4
4
|
import type { WebReaderConfig } from "../../types";
|
|
@@ -266,7 +266,7 @@ function pickBestParagraphs(text: string, maxChars: number): string {
|
|
|
266
266
|
|
|
267
267
|
const scored = paragraphs.map((paragraph) => {
|
|
268
268
|
const value = paragraph.text;
|
|
269
|
-
const punctuationCount = (value.match(/[
|
|
269
|
+
const punctuationCount = (value.match(/[,。!?;:,.!?);:]/g) || [])
|
|
270
270
|
.length;
|
|
271
271
|
const sentenceCount = (value.match(/[。!?.!?]/g) || []).length;
|
|
272
272
|
const linkLikeCount = (value.match(/https?:\/\/|www\./g) || []).length;
|
package/db.ts
CHANGED
|
@@ -24,7 +24,7 @@ function toChatMessage(row: any): ChatMessage {
|
|
|
24
24
|
groupId: row.group_id,
|
|
25
25
|
groupName: row.group_name,
|
|
26
26
|
timestamp: row.timestamp,
|
|
27
|
-
messageId: row.message_id,
|
|
27
|
+
messageId: row.message_id != null ? String(row.message_id) : undefined,
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -174,7 +174,7 @@ export async function initDatabase(): Promise<ChatDatabase> {
|
|
|
174
174
|
group_id INTEGER,
|
|
175
175
|
group_name TEXT,
|
|
176
176
|
timestamp INTEGER NOT NULL,
|
|
177
|
-
message_id
|
|
177
|
+
message_id TEXT
|
|
178
178
|
);
|
|
179
179
|
|
|
180
180
|
CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id, timestamp);
|
|
@@ -452,7 +452,10 @@ export async function initDatabase(): Promise<ChatDatabase> {
|
|
|
452
452
|
$groupId: msg.groupId ?? null,
|
|
453
453
|
$groupName: msg.groupName ?? null,
|
|
454
454
|
$timestamp: msg.timestamp,
|
|
455
|
-
$messageId:
|
|
455
|
+
$messageId:
|
|
456
|
+
msg.messageId == null || msg.messageId === ""
|
|
457
|
+
? null
|
|
458
|
+
: String(msg.messageId),
|
|
456
459
|
});
|
|
457
460
|
},
|
|
458
461
|
|
package/handlers/idle-debug.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MiokuContext } from "mioku";
|
|
2
2
|
import type { AIService } from "mioku";
|
|
3
3
|
import type { ChatPluginContext } from "../context";
|
|
4
4
|
import type { ChatConfig, TargetMessage } from "../types";
|
|
@@ -10,7 +10,7 @@ export async function handleIdleCheckDebug(
|
|
|
10
10
|
e: any,
|
|
11
11
|
cfg: ChatConfig,
|
|
12
12
|
): Promise<void> {
|
|
13
|
-
const ctx:
|
|
13
|
+
const ctx: MiokuContext = pluginCtx.ctx;
|
|
14
14
|
const isOwner = ctx.isOwner?.(e) ?? false;
|
|
15
15
|
if (!isOwner) {
|
|
16
16
|
await e.reply("只有主人才能使用这个指令~");
|
|
@@ -32,7 +32,7 @@ export async function handleIdleCheckDebug(
|
|
|
32
32
|
async () => {
|
|
33
33
|
const now = Date.now();
|
|
34
34
|
const botNickname =
|
|
35
|
-
cfg.nicknames[0] ||
|
|
35
|
+
cfg.nicknames[0] || e.bot?.nickname || "Bot";
|
|
36
36
|
ctx.logger.info(`[Debug] Manual idle check: group ${targetGroupId}`);
|
|
37
37
|
|
|
38
38
|
const { history } = await pluginCtx.getGroupHistoryMessages(
|
|
@@ -64,7 +64,7 @@ export async function handleIdleCheckDebug(
|
|
|
64
64
|
userId: 0,
|
|
65
65
|
userRole: "member",
|
|
66
66
|
content: "[No one in the group is talking? I'll answer!]",
|
|
67
|
-
messageId:
|
|
67
|
+
messageId: "",
|
|
68
68
|
timestamp: now,
|
|
69
69
|
};
|
|
70
70
|
const botRole = await getBotRole(targetGroupId, ctx, e.self_id);
|
package/handlers/message.ts
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import type { ChatPluginContext, ChatHandlerState } from "../context";
|
|
2
|
-
import type {
|
|
3
|
-
GroupMessageEvent,
|
|
4
|
-
PrivateMessageEvent,
|
|
5
|
-
} from "napcat-sdk";
|
|
2
|
+
import type { MessageEvent } from "mioku";
|
|
6
3
|
import {
|
|
7
4
|
isGroupAllowed,
|
|
8
5
|
shouldTrigger,
|
|
@@ -37,17 +34,19 @@ export function createMessageHandler(
|
|
|
37
34
|
const { ctx } = pluginCtx;
|
|
38
35
|
const { getConfig, matchMessageCommands, runtimeState } = state;
|
|
39
36
|
|
|
40
|
-
return async (e:
|
|
37
|
+
return async (e: MessageEvent) => {
|
|
41
38
|
const isGroup = e.message_type === "group";
|
|
42
|
-
const groupId: number | undefined = isGroup
|
|
39
|
+
const groupId: number | undefined = isGroup
|
|
40
|
+
? Number(e.group_id)
|
|
41
|
+
: undefined;
|
|
43
42
|
const cfg = await getConfig(groupId);
|
|
44
43
|
if (!cfg.model && !cfg.apiKey) return;
|
|
45
44
|
if (!e?.message || !Array.isArray(e.message)) return;
|
|
46
45
|
|
|
47
46
|
const text = ctx.text(e) || "";
|
|
48
|
-
const userId: number = e.user_id || e.sender?.user_id;
|
|
47
|
+
const userId: number = Number(e.user_id || e.sender?.user_id || 0);
|
|
49
48
|
|
|
50
|
-
if (userId === e.self_id) return;
|
|
49
|
+
if (userId === Number(e.self_id || 0)) return;
|
|
51
50
|
|
|
52
51
|
if (matchMessageCommands && matchMessageCommands(text).length > 0) return;
|
|
53
52
|
|
|
@@ -78,9 +77,15 @@ export function createMessageHandler(
|
|
|
78
77
|
if (groupId && !isGroupAllowed(groupId, cfg)) return;
|
|
79
78
|
|
|
80
79
|
// 媒体分析
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
80
|
+
if (
|
|
81
|
+
isGroup &&
|
|
82
|
+
groupId &&
|
|
83
|
+
e.message &&
|
|
84
|
+
!isMediaAnalysisBlocked(cfg, userId)
|
|
85
|
+
) {
|
|
86
|
+
const visionAI =
|
|
87
|
+
pluginCtx.visionAIInstance ?? pluginCtx.aiService.getDefault();
|
|
88
|
+
const bot = e.bot;
|
|
84
89
|
const mediaOptions = visionAI
|
|
85
90
|
? buildHistoryMediaProcessingOptions(
|
|
86
91
|
visionAI,
|
|
@@ -109,15 +114,21 @@ export function createMessageHandler(
|
|
|
109
114
|
if (seg.type === "image") {
|
|
110
115
|
const imageUrl = getSegmentUrl(seg);
|
|
111
116
|
if (imageUrl) {
|
|
112
|
-
processImage(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
processImage(
|
|
118
|
+
visionAI,
|
|
119
|
+
imageUrl,
|
|
120
|
+
cfg.multimodalWorkingModel,
|
|
121
|
+
pluginCtx.db,
|
|
122
|
+
{
|
|
123
|
+
runAIRequest: (request) =>
|
|
124
|
+
pluginCtx.runWithRateLimitGuard(request, {
|
|
125
|
+
userId,
|
|
126
|
+
groupId,
|
|
127
|
+
label: "image-analysis",
|
|
128
|
+
skipRetryOnRateLimit: true,
|
|
129
|
+
}),
|
|
130
|
+
},
|
|
131
|
+
).catch((err) =>
|
|
121
132
|
ctx.logger.error(`[image-analyzer] Failed: ${err}`),
|
|
122
133
|
);
|
|
123
134
|
}
|
|
@@ -162,7 +173,9 @@ export function createMessageHandler(
|
|
|
162
173
|
}
|
|
163
174
|
}
|
|
164
175
|
}
|
|
165
|
-
if (
|
|
176
|
+
if (
|
|
177
|
+
(e.raw as { sub_type?: string } | undefined)?.sub_type === "notice"
|
|
178
|
+
) {
|
|
166
179
|
summarizeGroupNotice(e, mediaOptions)
|
|
167
180
|
.then((noticeMessage) => {
|
|
168
181
|
if (!noticeMessage) return;
|
|
@@ -192,8 +205,7 @@ export function createMessageHandler(
|
|
|
192
205
|
const learnSessionId = `group:${groupId}`;
|
|
193
206
|
const learnMsg = buildChatMessageFromEvent(e, text, true, groupId);
|
|
194
207
|
const hasText = !!learnMsg.content?.trim();
|
|
195
|
-
const hasMultimodal =
|
|
196
|
-
!!cfg.isMultimodal && !!cfg.multimodalWorkingModel;
|
|
208
|
+
const hasMultimodal = !!cfg.isMultimodal && !!cfg.multimodalWorkingModel;
|
|
197
209
|
if (hasText || hasMultimodal) {
|
|
198
210
|
pluginCtx
|
|
199
211
|
.recordGroupMessageForLearning(learnMsg, learnSessionId)
|
|
@@ -235,7 +247,11 @@ export function createMessageHandler(
|
|
|
235
247
|
if (pluginCtx.queueProcessor.isInDynamicDelay(groupSessionId)) {
|
|
236
248
|
if (atBot && !runtimeState.isRateLimitBlocked()) {
|
|
237
249
|
pluginCtx.rateLimiter.recordInteraction(groupId, userId);
|
|
238
|
-
pluginCtx.queueProcessor.collectDynamicDelayMessage(
|
|
250
|
+
pluginCtx.queueProcessor.collectDynamicDelayMessage(
|
|
251
|
+
groupSessionId,
|
|
252
|
+
e,
|
|
253
|
+
text,
|
|
254
|
+
);
|
|
239
255
|
}
|
|
240
256
|
return;
|
|
241
257
|
}
|
|
@@ -259,7 +275,7 @@ export function createMessageHandler(
|
|
|
259
275
|
groupSessionId,
|
|
260
276
|
groupId,
|
|
261
277
|
delayInfo.delayMs,
|
|
262
|
-
e.self_id,
|
|
278
|
+
Number(e.self_id || 0),
|
|
263
279
|
);
|
|
264
280
|
return;
|
|
265
281
|
}
|
|
@@ -278,11 +294,10 @@ export function createMessageHandler(
|
|
|
278
294
|
ctx,
|
|
279
295
|
cfg.historyCount,
|
|
280
296
|
pluginCtx.db,
|
|
281
|
-
e.self_id,
|
|
297
|
+
Number(e.self_id || 0),
|
|
282
298
|
pluginCtx.buildHistoryMediaOptions(pluginCtx.aiInstance, cfg),
|
|
283
299
|
);
|
|
284
|
-
const botNickname =
|
|
285
|
-
cfg.nicknames[0] || ctx.pickBot(e.self_id).nickname || "Bot";
|
|
300
|
+
const botNickname = cfg.nicknames[0] || e.bot?.nickname || "Bot";
|
|
286
301
|
const planResult = await pluginCtx.humanize.actionPlanner.plan(
|
|
287
302
|
groupSessionId,
|
|
288
303
|
botNickname,
|
|
@@ -304,7 +319,7 @@ export function createMessageHandler(
|
|
|
304
319
|
pluginCtx.rateLimiter.recordInteraction(groupId, userId);
|
|
305
320
|
pluginCtx.queueProcessor.scheduleQueuedMessages(
|
|
306
321
|
groupSessionId,
|
|
307
|
-
e.self_id,
|
|
322
|
+
Number(e.self_id || 0),
|
|
308
323
|
);
|
|
309
324
|
}
|
|
310
325
|
return;
|
package/handlers/poke.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ChatPluginContext, ChatHandlerState } from "../context";
|
|
2
2
|
import type { TargetMessage } from "../types";
|
|
3
|
-
import type {
|
|
3
|
+
import type { Bot, NoticeEvent } from "mioku";
|
|
4
|
+
|
|
4
5
|
import { getBotRole, isGroupAllowed } from "../utils";
|
|
5
6
|
import { buildStructuredUserInputFromTarget } from "../manage/group-structured-history";
|
|
6
7
|
import { finalizeChatTurn } from "../core/chat-turn";
|
|
@@ -13,9 +14,13 @@ export function createPokeHandler(
|
|
|
13
14
|
const { ctx } = pluginCtx;
|
|
14
15
|
const { getConfig, runtimeState, pokeCooldowns } = state;
|
|
15
16
|
|
|
16
|
-
return async (e:
|
|
17
|
-
|
|
18
|
-
const
|
|
17
|
+
return async (e: NoticeEvent) => {
|
|
18
|
+
const selfId = Number(e.self_id || 0);
|
|
19
|
+
const targetId = Number(
|
|
20
|
+
(e.raw as { target_id?: string } | undefined)?.target_id ?? 0,
|
|
21
|
+
);
|
|
22
|
+
if (targetId !== selfId) return;
|
|
23
|
+
const groupId = Number(e.group_id || 0);
|
|
19
24
|
const cfg = groupId ? await getConfig(groupId) : await getConfig();
|
|
20
25
|
if (!cfg.model && !cfg.apiKey) return;
|
|
21
26
|
if (!groupId || !isGroupAllowed(groupId, cfg)) return;
|
|
@@ -37,19 +42,20 @@ export function createPokeHandler(
|
|
|
37
42
|
"group",
|
|
38
43
|
groupId,
|
|
39
44
|
);
|
|
40
|
-
const userId = e.user_id;
|
|
41
|
-
const botRole = await getBotRole(groupId, ctx,
|
|
45
|
+
const userId = Number(e.user_id || 0);
|
|
46
|
+
const botRole = await getBotRole(groupId, ctx, selfId);
|
|
42
47
|
const botNickname =
|
|
43
|
-
cfg.nicknames[0] ||
|
|
48
|
+
cfg.nicknames[0] || e.bot?.nickname || "Bot";
|
|
44
49
|
|
|
45
50
|
let senderName = String(userId);
|
|
46
51
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
.
|
|
52
|
+
const bot: Bot | undefined = e.bot;
|
|
53
|
+
const memberInfo = bot
|
|
54
|
+
? await bot.getMemberInfo(groupId, userId)
|
|
55
|
+
: undefined;
|
|
50
56
|
senderName =
|
|
51
|
-
|
|
52
|
-
|
|
57
|
+
memberInfo?.card ||
|
|
58
|
+
memberInfo?.nickname ||
|
|
53
59
|
String(userId);
|
|
54
60
|
} catch {}
|
|
55
61
|
|
|
@@ -67,11 +73,11 @@ export function createPokeHandler(
|
|
|
67
73
|
ctx,
|
|
68
74
|
cfg.historyCount,
|
|
69
75
|
pluginCtx.db,
|
|
70
|
-
|
|
76
|
+
selfId,
|
|
71
77
|
pluginCtx.buildHistoryMediaOptions(pluginCtx.aiInstance, cfg),
|
|
72
78
|
);
|
|
73
79
|
const { groupName, memberCount } =
|
|
74
|
-
await pluginCtx.getGroupInfoData(ctx, groupId,
|
|
80
|
+
await pluginCtx.getGroupInfoData(ctx, groupId, selfId);
|
|
75
81
|
|
|
76
82
|
const toolCtx = pluginCtx.buildToolContext({
|
|
77
83
|
ctx,
|
|
@@ -85,7 +91,7 @@ export function createPokeHandler(
|
|
|
85
91
|
botRole,
|
|
86
92
|
humanize: pluginCtx.humanize,
|
|
87
93
|
targetMessage,
|
|
88
|
-
selfId
|
|
94
|
+
selfId,
|
|
89
95
|
audioService: pluginCtx.audioService,
|
|
90
96
|
});
|
|
91
97
|
|
|
@@ -131,7 +137,7 @@ export function createPokeHandler(
|
|
|
131
137
|
groupId,
|
|
132
138
|
groupSessionId,
|
|
133
139
|
userId,
|
|
134
|
-
selfId
|
|
140
|
+
selfId,
|
|
135
141
|
toolCtx,
|
|
136
142
|
send: true,
|
|
137
143
|
isLive: true,
|
package/humanize/emoji-agent.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readdirSync } from "fs";
|
|
|
2
2
|
import * as fs from "fs/promises";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import { getPluginDataDir, type AIInstance } from "mioku";
|
|
5
|
-
import { logger } from "
|
|
5
|
+
import { logger } from "mioku";
|
|
6
6
|
import type { ChatConfig, ChatMessage, TargetMessage } from "../types";
|
|
7
7
|
import { extractGroupIdFromSession } from "../utils/group-config";
|
|
8
8
|
import { extractJsonObject } from "../utils/json";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AIInstance } from "mioku";
|
|
2
|
-
import { logger } from "
|
|
2
|
+
import { logger } from "mioku";
|
|
3
3
|
import { extractJsonObject } from "../utils/json";
|
|
4
4
|
import type { ChatConfig, ChatMessage, TargetMessage } from "../types";
|
|
5
5
|
import { extractGroupIdFromSession } from "../utils/group-config";
|
package/humanize/expression.ts
CHANGED
package/humanize/memory.ts
CHANGED
package/humanize/planner.ts
CHANGED