mioku-plugin-chat 2.10.1 → 3.0.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 -1
- package/context.ts +2 -2
- package/core/base.ts +57 -62
- package/core/chat-engine.ts +1 -1
- package/core/chat-turn.ts +9 -9
- package/core/media/audio.ts +4 -4
- package/core/media/gif-extractor.ts +4 -4
- package/core/media/history-media.ts +9 -14
- package/core/media/image-analyzer.ts +1 -1
- package/core/media/image-compress.ts +1 -1
- package/core/media/segment.ts +10 -14
- package/core/multimodal.ts +15 -10
- package/core/prompt.ts +3 -3
- package/core/tools/info.ts +8 -7
- package/core/tools/web.ts +86 -56
- package/core/tools.ts +1 -1
- package/core/web/searxng.ts +1 -1
- package/core/web/web-reader.ts +2 -2
- package/handlers/idle-debug.ts +3 -3
- 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 +4 -4
- package/manage/idle-check.ts +4 -4
- package/manage/queue-processor.ts +4 -4
- package/manage/types.ts +37 -14
- package/package.json +3 -5
- package/types.ts +2 -2
- package/utils/message.ts +92 -60
package/core/multimodal.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { AIInstance } from "mioku";
|
|
2
|
-
import type {
|
|
3
|
-
import { logger } from "
|
|
1
|
+
import type { AIInstance, Bot } from "mioku";
|
|
2
|
+
import type { MiokuContext } from "mioku";
|
|
3
|
+
import { logger } from "mioku";
|
|
4
4
|
import { prepareImageUrlsForModel } from "./media/image-compress";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -126,23 +126,26 @@ export type MediaByMessageId =
|
|
|
126
126
|
* @returns 媒体信息或 null
|
|
127
127
|
*/
|
|
128
128
|
export async function getMediaByMessageId(
|
|
129
|
-
ctx:
|
|
129
|
+
ctx: MiokuContext,
|
|
130
130
|
messageId: number,
|
|
131
131
|
e: any,
|
|
132
132
|
): Promise<MediaByMessageId> {
|
|
133
133
|
try {
|
|
134
|
-
const
|
|
134
|
+
const bot: Bot = e?.bot ?? ctx.pickBot(e.self_id ?? "");
|
|
135
|
+
if (!bot) return null;
|
|
136
|
+
const msg = await bot.getMessage(messageId);
|
|
135
137
|
if (!msg || !msg.message) {
|
|
136
138
|
return null;
|
|
137
139
|
}
|
|
138
140
|
|
|
139
|
-
for (const seg of msg.message
|
|
141
|
+
for (const seg of Array.from(msg.message)) {
|
|
140
142
|
if (seg.type === "image") {
|
|
141
|
-
const url = seg.url
|
|
143
|
+
const url = String(seg.data?.url ?? seg.data?.file ?? "");
|
|
142
144
|
if (url) return { kind: "image", url };
|
|
143
145
|
}
|
|
144
146
|
if (seg.type === "video") {
|
|
145
|
-
const { getSegmentSourceCandidates } =
|
|
147
|
+
const { getSegmentSourceCandidates } =
|
|
148
|
+
await import("./media/history-media");
|
|
146
149
|
const sources = getSegmentSourceCandidates(seg);
|
|
147
150
|
if (sources.length > 0) return { kind: "video", sources };
|
|
148
151
|
}
|
|
@@ -164,7 +167,7 @@ export async function getMediaByMessageId(
|
|
|
164
167
|
* @returns 图片 URL 或 null
|
|
165
168
|
*/
|
|
166
169
|
export async function getQuoteImageUrl(
|
|
167
|
-
ctx:
|
|
170
|
+
ctx: MiokuContext,
|
|
168
171
|
event: any,
|
|
169
172
|
): Promise<string | null> {
|
|
170
173
|
if (!event.quote_id) {
|
|
@@ -172,7 +175,9 @@ export async function getQuoteImageUrl(
|
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
try {
|
|
175
|
-
const
|
|
178
|
+
const bot = event?.bot;
|
|
179
|
+
if (!bot) return null;
|
|
180
|
+
const quotedMsg = await bot.getMessage(event.quote_id);
|
|
176
181
|
if (!quotedMsg || !quotedMsg.message) {
|
|
177
182
|
return null;
|
|
178
183
|
}
|
package/core/prompt.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChatConfig, ChatMessage, TargetMessage } from "../types";
|
|
2
|
-
import { logger } from "
|
|
2
|
+
import { logger } from "mioku";
|
|
3
3
|
import type {
|
|
4
4
|
SkillPermissionRole,
|
|
5
5
|
AIService,
|
|
@@ -293,7 +293,7 @@ ${AUDIO_MODE_LINE[audioStrength]}`);
|
|
|
293
293
|
### Optional Markdown Screenshot Format
|
|
294
294
|
- You MAY optionally send one rendered Markdown screenshot by wrapping content with exact tags: <MARKDOWN> ... </MARKDOWN>
|
|
295
295
|
- Put the Markdown block on its own message whenever possible.
|
|
296
|
-
- It is forbidden to use Markdown syntax or formulas in plain text; they must be rendered using <MARKDOWN> blocks.
|
|
296
|
+
- It is forbidden to use Markdown syntax or formulas in plain text); they must be rendered using <MARKDOWN> blocks.
|
|
297
297
|
${MARKDOWN_MODE_LINE[markdownStrength]}
|
|
298
298
|
- Inside <MARKDOWN>...</MARKDOWN>, there is NO length limit. If the user needs detail, explain clearly and thoroughly instead of over-compressing.
|
|
299
299
|
`);
|
|
@@ -304,7 +304,7 @@ ${MARKDOWN_MODE_LINE[markdownStrength]}
|
|
|
304
304
|
lines.push(`
|
|
305
305
|
### Tool Calling Format
|
|
306
306
|
- When you decide to use a tool, you MUST use the structured tool_calls mechanism provided by the API
|
|
307
|
-
- web_search and web_read_page are limited per conversation; do not retry excessively`);
|
|
307
|
+
- web_search and web_read_page are limited per conversation); do not retry excessively`);
|
|
308
308
|
|
|
309
309
|
appendEmojiSection(lines, ctx, emojiStrength);
|
|
310
310
|
|
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,12 @@ 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
|
|
|
77
75
|
const collected: ChatMessage[] = [];
|
|
78
76
|
const seenMessageIds = new Set<string>();
|
|
79
|
-
let cursorMessageId =
|
|
77
|
+
let cursorMessageId = "";
|
|
80
78
|
const maxPages = Math.max(1, Math.ceil(limit / 200) + 5);
|
|
81
79
|
let page = 0;
|
|
82
80
|
|
|
@@ -84,73 +82,75 @@ async function fetchGroupHistoryByMessageIdPaging(
|
|
|
84
82
|
const remaining = limit - collected.length;
|
|
85
83
|
const pageSize = Math.min(200, remaining);
|
|
86
84
|
|
|
87
|
-
let
|
|
85
|
+
let history: HistoryMessage[];
|
|
88
86
|
try {
|
|
89
|
-
|
|
90
|
-
group_id:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
disable_get_url: true,
|
|
95
|
-
parse_mult_msg: false,
|
|
96
|
-
quick_reply: false,
|
|
97
|
-
});
|
|
87
|
+
history = await bot.getHistory(
|
|
88
|
+
{ type: "group", group_id: toolCtx.groupId },
|
|
89
|
+
cursorMessageId || undefined,
|
|
90
|
+
pageSize,
|
|
91
|
+
);
|
|
98
92
|
} catch (err) {
|
|
99
93
|
const errText = String(err);
|
|
100
94
|
if (
|
|
101
|
-
cursorMessageId
|
|
102
|
-
(errText.includes("不存在") ||
|
|
95
|
+
cursorMessageId !== "" &&
|
|
96
|
+
(errText.includes("不存在") ||
|
|
97
|
+
errText.toLowerCase().includes("not exist"))
|
|
103
98
|
) {
|
|
104
|
-
logger.info(
|
|
99
|
+
logger.info(
|
|
100
|
+
`[recall_memory] get_group_msg_history stop at cursor ${cursorMessageId}: ${errText}`,
|
|
101
|
+
);
|
|
105
102
|
} else {
|
|
106
|
-
logger.warn(
|
|
103
|
+
logger.warn(
|
|
104
|
+
`[recall_memory] get_group_msg_history failed at cursor ${cursorMessageId}: ${errText}`,
|
|
105
|
+
);
|
|
107
106
|
}
|
|
108
107
|
break;
|
|
109
108
|
}
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
if (!Array.isArray(rawMessages) || rawMessages.length === 0) break;
|
|
110
|
+
if (!Array.isArray(history) || history.length === 0) break;
|
|
113
111
|
|
|
114
|
-
let oldestMessageId:
|
|
112
|
+
let oldestMessageId: string | null = null;
|
|
115
113
|
let newAdded = 0;
|
|
116
114
|
|
|
117
|
-
for (const raw of
|
|
118
|
-
const messageId =
|
|
115
|
+
for (const raw of history) {
|
|
116
|
+
const messageId = String(raw.message_id || "");
|
|
119
117
|
const key =
|
|
120
|
-
messageId
|
|
118
|
+
messageId !== ""
|
|
121
119
|
? `mid:${messageId}`
|
|
122
|
-
: `${String(raw
|
|
120
|
+
: `${String(raw.user_id || "unknown")}:${String(raw.time || "0")}:${extractGroupHistoryText(raw)}`;
|
|
123
121
|
if (seenMessageIds.has(key)) continue;
|
|
124
122
|
seenMessageIds.add(key);
|
|
125
123
|
|
|
126
124
|
const content = extractGroupHistoryText(raw);
|
|
127
125
|
if (!content.trim()) continue;
|
|
128
126
|
|
|
129
|
-
const ts =
|
|
127
|
+
const ts = raw.time ?? Date.now();
|
|
130
128
|
collected.push({
|
|
131
129
|
sessionId: toolCtx.sessionId,
|
|
132
|
-
role: String(raw
|
|
130
|
+
role: String(raw.user_id) === String(bot.bot_id) ? "assistant" : "user",
|
|
133
131
|
content,
|
|
134
|
-
userId:
|
|
135
|
-
userName: raw
|
|
136
|
-
userRole:
|
|
132
|
+
userId: Number(raw.user_id || 0),
|
|
133
|
+
userName: raw.nickname || String(raw.user_id || "unknown"),
|
|
134
|
+
userRole: "member",
|
|
137
135
|
groupId: toolCtx.groupId,
|
|
138
136
|
timestamp: ts,
|
|
139
|
-
messageId: messageId
|
|
137
|
+
messageId: messageId !== "" ? Number(messageId) : undefined,
|
|
140
138
|
});
|
|
141
139
|
newAdded += 1;
|
|
142
140
|
|
|
143
|
-
if (
|
|
141
|
+
if (
|
|
142
|
+
messageId !== "" &&
|
|
143
|
+
(oldestMessageId === null || messageId < oldestMessageId)
|
|
144
|
+
) {
|
|
144
145
|
oldestMessageId = messageId;
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
if (newAdded === 0 || oldestMessageId === null || oldestMessageId
|
|
149
|
+
if (newAdded === 0 || oldestMessageId === null || oldestMessageId === "")
|
|
150
|
+
break;
|
|
149
151
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (nextCursor === cursorMessageId) break;
|
|
153
|
-
cursorMessageId = nextCursor;
|
|
152
|
+
if (oldestMessageId === cursorMessageId) break;
|
|
153
|
+
cursorMessageId = oldestMessageId;
|
|
154
154
|
page += 1;
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -182,7 +182,8 @@ export function createWebSearchTool(toolCtx: ToolContext): AITool {
|
|
|
182
182
|
},
|
|
183
183
|
limit: {
|
|
184
184
|
type: "number",
|
|
185
|
-
description:
|
|
185
|
+
description:
|
|
186
|
+
"Max number of results to return. Will be clamped by config maxLimit.",
|
|
186
187
|
},
|
|
187
188
|
time_range: {
|
|
188
189
|
type: "string",
|
|
@@ -192,17 +193,20 @@ export function createWebSearchTool(toolCtx: ToolContext): AITool {
|
|
|
192
193
|
categories: {
|
|
193
194
|
type: "array",
|
|
194
195
|
items: { type: "string" },
|
|
195
|
-
description:
|
|
196
|
+
description:
|
|
197
|
+
'Optional categories, e.g. ["general"], ["news"], ["science"]',
|
|
196
198
|
},
|
|
197
199
|
engines: {
|
|
198
200
|
type: "array",
|
|
199
201
|
items: { type: "string" },
|
|
200
|
-
description:
|
|
202
|
+
description:
|
|
203
|
+
'Optional engines, e.g. ["google"], ["bing"], ["duckduckgo"]',
|
|
201
204
|
},
|
|
202
205
|
},
|
|
203
206
|
required: [],
|
|
204
207
|
},
|
|
205
|
-
handler: async (args) =>
|
|
208
|
+
handler: async (args) =>
|
|
209
|
+
searchWebWithSearxng(toolCtx.config.searxng, args || {}),
|
|
206
210
|
};
|
|
207
211
|
}
|
|
208
212
|
|
|
@@ -217,7 +221,10 @@ export function createWebReadPageTool(toolCtx: ToolContext): AITool {
|
|
|
217
221
|
parameters: {
|
|
218
222
|
type: "object",
|
|
219
223
|
properties: {
|
|
220
|
-
url: {
|
|
224
|
+
url: {
|
|
225
|
+
type: "string",
|
|
226
|
+
description: "The http/https URL of the webpage to read",
|
|
227
|
+
},
|
|
221
228
|
render_js: {
|
|
222
229
|
type: "boolean",
|
|
223
230
|
description:
|
|
@@ -234,7 +241,8 @@ export function createWebReadPageTool(toolCtx: ToolContext): AITool {
|
|
|
234
241
|
handler: async (args) => {
|
|
235
242
|
try {
|
|
236
243
|
const ai = toolCtx.config.webReader.useWorkingModel
|
|
237
|
-
? (toolCtx.aiService.getInstanceByRole?.("working") ??
|
|
244
|
+
? (toolCtx.aiService.getInstanceByRole?.("working") ??
|
|
245
|
+
toolCtx.aiService.getDefault())
|
|
238
246
|
: undefined;
|
|
239
247
|
if (toolCtx.config.webReader.useWorkingModel && !ai) {
|
|
240
248
|
return { success: false, error: "AI instance not available" };
|
|
@@ -275,19 +283,41 @@ export function createRecallMemoryTool(toolCtx: ToolContext): AITool {
|
|
|
275
283
|
const question = String(args?.question || "").trim();
|
|
276
284
|
if (!question) return { success: false, error: "question is required" };
|
|
277
285
|
|
|
278
|
-
const ai =
|
|
286
|
+
const ai =
|
|
287
|
+
toolCtx.aiService.getInstanceByRole?.("working") ??
|
|
288
|
+
toolCtx.aiService.getDefault();
|
|
279
289
|
if (!ai) return { success: false, error: "AI instance not available" };
|
|
280
290
|
|
|
281
|
-
const groupHistoryLimit = resolveGroupRecallLimit(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
291
|
+
const groupHistoryLimit = resolveGroupRecallLimit(
|
|
292
|
+
toolCtx.config.memory?.groupHistoryLimit,
|
|
293
|
+
);
|
|
294
|
+
const userHistoryLimit = resolveUserHistoryLimit(
|
|
295
|
+
toolCtx.config.memory?.userHistoryLimit,
|
|
296
|
+
);
|
|
297
|
+
const groupHistoryMessages = await fetchGroupHistoryByMessageIdPaging(
|
|
298
|
+
toolCtx,
|
|
299
|
+
groupHistoryLimit,
|
|
300
|
+
);
|
|
301
|
+
const targetUserIds = extractTargetUserIdsFromQuestion(
|
|
302
|
+
question,
|
|
303
|
+
toolCtx.userId,
|
|
304
|
+
);
|
|
305
|
+
const userHistories: MemoryUserHistoryChunk[] = targetUserIds.map(
|
|
306
|
+
(userId) => ({
|
|
307
|
+
userId,
|
|
308
|
+
messages: toolCtx.db.getMessagesByUser(
|
|
309
|
+
userId,
|
|
310
|
+
toolCtx.sessionId,
|
|
311
|
+
userHistoryLimit,
|
|
312
|
+
),
|
|
313
|
+
}),
|
|
314
|
+
);
|
|
289
315
|
|
|
290
|
-
const retriever = new MemoryRetrieval(
|
|
316
|
+
const retriever = new MemoryRetrieval(
|
|
317
|
+
ai,
|
|
318
|
+
() => toolCtx.config,
|
|
319
|
+
toolCtx.db,
|
|
320
|
+
);
|
|
291
321
|
const answer = await retriever.retrieveByQuestion({
|
|
292
322
|
sessionId: toolCtx.sessionId,
|
|
293
323
|
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/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(
|
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";
|