mioku-plugin-chat 2.10.0 → 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 +10 -27
- 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/utils/message.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { logger,
|
|
2
|
-
import type { AIInstance } from "mioku";
|
|
1
|
+
import { HistoryMessage, logger, type MiokuContext } from "mioku";
|
|
2
|
+
import type { AIInstance, Bot } from "mioku";
|
|
3
|
+
|
|
3
4
|
import type {
|
|
4
5
|
ChatConfig,
|
|
5
6
|
ChatMessage,
|
|
@@ -22,13 +23,15 @@ export function shouldTrigger(
|
|
|
22
23
|
e: any,
|
|
23
24
|
text: string,
|
|
24
25
|
cfg: ChatConfig,
|
|
25
|
-
ctx:
|
|
26
|
+
ctx: MiokuContext,
|
|
26
27
|
): boolean {
|
|
27
28
|
if (e.message_type === "private") return false;
|
|
28
29
|
|
|
29
|
-
// Only check if message @s the bot (seg format: {type: "at", qq: "123456"})
|
|
30
30
|
const atSeg = e.message?.find((seg: any) => seg.type === "at");
|
|
31
|
-
|
|
31
|
+
if (!atSeg) return false;
|
|
32
|
+
const data = (atSeg.data ?? {}) as { qq?: unknown; target?: unknown };
|
|
33
|
+
const qq = data.qq ?? data.target;
|
|
34
|
+
return String(qq ?? "") === String(e.self_id ?? "");
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
/**
|
|
@@ -37,15 +40,15 @@ export function shouldTrigger(
|
|
|
37
40
|
*/
|
|
38
41
|
export async function isQuotingBot(
|
|
39
42
|
e: any,
|
|
40
|
-
ctx:
|
|
43
|
+
ctx: MiokuContext,
|
|
41
44
|
): Promise<{ quoted: true; messageId: string; content: string } | null> {
|
|
42
45
|
if (e.quote_id) {
|
|
43
46
|
try {
|
|
44
|
-
const quoteMsg = await
|
|
45
|
-
if (quoteMsg && String(quoteMsg.sender
|
|
47
|
+
const quoteMsg = await fetchQuotedMessage(e, ctx);
|
|
48
|
+
if (quoteMsg && String(quoteMsg.sender?.user_id) === String(e.self_id)) {
|
|
46
49
|
const quotedText = quoteMsg.message
|
|
47
50
|
?.filter((s: any) => s.type === "text")
|
|
48
|
-
.map((s: any) => s.text)
|
|
51
|
+
.map((s: any) => String(s.data?.text ?? s.text ?? ""))
|
|
49
52
|
.join("");
|
|
50
53
|
if (quotedText) {
|
|
51
54
|
return { quoted: true, messageId: e.quote_id, content: quotedText };
|
|
@@ -65,7 +68,7 @@ export async function isQuotingBot(
|
|
|
65
68
|
*/
|
|
66
69
|
export async function getQuotedContent(
|
|
67
70
|
e: any,
|
|
68
|
-
ctx:
|
|
71
|
+
ctx: MiokuContext,
|
|
69
72
|
): Promise<
|
|
70
73
|
| {
|
|
71
74
|
messageId: string;
|
|
@@ -78,13 +81,13 @@ export async function getQuotedContent(
|
|
|
78
81
|
> {
|
|
79
82
|
if (e.quote_id) {
|
|
80
83
|
try {
|
|
81
|
-
const quotedMsg = await
|
|
84
|
+
const quotedMsg = await fetchQuotedMessage(e, ctx);
|
|
82
85
|
if (quotedMsg && quotedMsg.message) {
|
|
83
|
-
const senderName = quotedMsg.sender
|
|
86
|
+
const senderName = quotedMsg.sender?.nickname || "";
|
|
84
87
|
// 提取文本内容
|
|
85
88
|
const textContent = quotedMsg.message
|
|
86
89
|
.filter((s: any) => s.type === "text")
|
|
87
|
-
.map((s: any) => s.text
|
|
90
|
+
.map((s: any) => String(s.data?.text ?? s.text ?? ""))
|
|
88
91
|
.join("");
|
|
89
92
|
|
|
90
93
|
// 检测是否有图片
|
|
@@ -120,7 +123,7 @@ export function isGroupAllowed(groupId: number, cfg: ChatConfig): boolean {
|
|
|
120
123
|
export function extractContent(
|
|
121
124
|
e: any,
|
|
122
125
|
cfg: ChatConfig,
|
|
123
|
-
ctx:
|
|
126
|
+
ctx: MiokuContext,
|
|
124
127
|
): { text: string; multimodal: any[] | null } {
|
|
125
128
|
let text = "";
|
|
126
129
|
try {
|
|
@@ -129,9 +132,12 @@ export function extractContent(
|
|
|
129
132
|
|
|
130
133
|
// If text is empty but user @'d the bot, describe the action
|
|
131
134
|
if (!text.trim() && e.message) {
|
|
132
|
-
const hasAt = e.message.some(
|
|
133
|
-
(seg
|
|
134
|
-
|
|
135
|
+
const hasAt = e.message.some((seg: any) => {
|
|
136
|
+
if (seg.type !== "at") return false;
|
|
137
|
+
const data = (seg.data ?? {}) as { qq?: unknown; target?: unknown };
|
|
138
|
+
const qq = data.qq ?? data.target;
|
|
139
|
+
return String(qq ?? "") === String(e.self_id ?? "");
|
|
140
|
+
});
|
|
135
141
|
if (hasAt) {
|
|
136
142
|
text = "[@you with no text]";
|
|
137
143
|
}
|
|
@@ -190,14 +196,14 @@ async function mapWithConcurrency<T, R>(
|
|
|
190
196
|
|
|
191
197
|
export async function getBotRole(
|
|
192
198
|
groupId: number,
|
|
193
|
-
ctx:
|
|
199
|
+
ctx: MiokuContext,
|
|
194
200
|
selfId: number,
|
|
195
201
|
): Promise<"owner" | "admin" | "member"> {
|
|
196
202
|
try {
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
return (memberInfo
|
|
203
|
+
const bot = ctx.pickBot(selfId);
|
|
204
|
+
if (!bot) return "member";
|
|
205
|
+
const memberInfo = await bot.getMemberInfo(groupId, selfId);
|
|
206
|
+
return (memberInfo?.role as "owner" | "admin" | "member") || "member";
|
|
201
207
|
} catch {
|
|
202
208
|
return "member";
|
|
203
209
|
}
|
|
@@ -237,11 +243,11 @@ async function resolveMemberName(
|
|
|
237
243
|
}
|
|
238
244
|
ctx.memberNameCache.set(key, key);
|
|
239
245
|
try {
|
|
240
|
-
const bot = ctx.historyMediaOptions.bot as
|
|
241
|
-
if (!bot
|
|
242
|
-
const info = await bot.
|
|
243
|
-
ctx.historyMediaOptions.groupId
|
|
244
|
-
|
|
246
|
+
const bot = ctx.historyMediaOptions.bot as Bot | undefined;
|
|
247
|
+
if (!bot) return key;
|
|
248
|
+
const info = await bot.getMemberInfo(
|
|
249
|
+
ctx.historyMediaOptions.groupId!,
|
|
250
|
+
userId,
|
|
245
251
|
);
|
|
246
252
|
const name = info?.card || info?.nickname || key;
|
|
247
253
|
ctx.memberNameCache.set(key, name);
|
|
@@ -278,7 +284,10 @@ function extractQuotedText(messageSegs: any): string {
|
|
|
278
284
|
return parts.join(" ").trim();
|
|
279
285
|
}
|
|
280
286
|
|
|
281
|
-
function buildReplyAnnotation(
|
|
287
|
+
function buildReplyAnnotation(
|
|
288
|
+
source: any,
|
|
289
|
+
ctx: HistoryFormatContext,
|
|
290
|
+
): string | null {
|
|
282
291
|
if (!source || typeof source !== "object") return null;
|
|
283
292
|
const sourceId = source.id ?? source.message_id ?? source.message_seq;
|
|
284
293
|
const sourceUserId = source.user_id;
|
|
@@ -291,7 +300,8 @@ function buildReplyAnnotation(source: any, ctx: HistoryFormatContext): string |
|
|
|
291
300
|
const idStr = sourceId != null ? String(sourceId) : "?";
|
|
292
301
|
let displayName: string | undefined;
|
|
293
302
|
if (sourceUserId != null) {
|
|
294
|
-
displayName =
|
|
303
|
+
displayName =
|
|
304
|
+
ctx.memberNameCache.get(String(sourceUserId)) || String(sourceUserId);
|
|
295
305
|
}
|
|
296
306
|
if (!displayName) displayName = sourceNickname || "unknown";
|
|
297
307
|
|
|
@@ -334,7 +344,7 @@ async function getImageTagWithHashCache(
|
|
|
334
344
|
}
|
|
335
345
|
|
|
336
346
|
async function formatHistoryMessage(
|
|
337
|
-
msg:
|
|
347
|
+
msg: HistoryMessage,
|
|
338
348
|
ctx: HistoryFormatContext,
|
|
339
349
|
): Promise<FormattedHistoryMessage | null> {
|
|
340
350
|
if (String(msg.user_id) === String(ctx.botUin)) return null;
|
|
@@ -350,12 +360,12 @@ async function formatHistoryMessage(
|
|
|
350
360
|
if (!content.trim()) return null;
|
|
351
361
|
|
|
352
362
|
return {
|
|
353
|
-
userId: msg.user_id,
|
|
354
|
-
userName: msg.
|
|
355
|
-
userRole:
|
|
363
|
+
userId: Number(msg.user_id || 0),
|
|
364
|
+
userName: msg.nickname || String(msg.user_id || "unknown"),
|
|
365
|
+
userRole: "member",
|
|
356
366
|
content,
|
|
357
|
-
messageId: msg.message_id,
|
|
358
|
-
timestamp: msg.time
|
|
367
|
+
messageId: Number(msg.message_id) || 0,
|
|
368
|
+
timestamp: msg.time ?? Date.now(),
|
|
359
369
|
role: "user",
|
|
360
370
|
};
|
|
361
371
|
}
|
|
@@ -427,17 +437,24 @@ async function buildMessageContent(
|
|
|
427
437
|
)) {
|
|
428
438
|
const videoSources = getSegmentSourceCandidates(videoSeg);
|
|
429
439
|
if (videoSources.length > 0) {
|
|
430
|
-
parts.push(
|
|
440
|
+
parts.push(
|
|
441
|
+
await getCachedHistoryVideoTag(videoSources, historyMediaOptions),
|
|
442
|
+
);
|
|
431
443
|
} else {
|
|
432
444
|
parts.push("[video]");
|
|
433
445
|
}
|
|
434
446
|
}
|
|
435
447
|
|
|
436
|
-
for (const forwardSeg of msg.message.filter(
|
|
448
|
+
for (const forwardSeg of msg.message.filter(
|
|
449
|
+
(seg: any) => seg.type === "forward",
|
|
450
|
+
)) {
|
|
437
451
|
const forwardId = forwardSeg.id || forwardSeg.data?.id;
|
|
438
452
|
if (forwardId) {
|
|
439
453
|
parts.push(
|
|
440
|
-
await getCachedHistoryForwardTag(
|
|
454
|
+
await getCachedHistoryForwardTag(
|
|
455
|
+
String(forwardId),
|
|
456
|
+
historyMediaOptions,
|
|
457
|
+
),
|
|
441
458
|
);
|
|
442
459
|
} else {
|
|
443
460
|
parts.push("[forward]");
|
|
@@ -448,7 +465,11 @@ async function buildMessageContent(
|
|
|
448
465
|
["xml", "json", "lightapp", "ark"].includes(seg.type),
|
|
449
466
|
)) {
|
|
450
467
|
const cardData =
|
|
451
|
-
cardSeg.data?.data ||
|
|
468
|
+
cardSeg.data?.data ||
|
|
469
|
+
cardSeg.data?.xml ||
|
|
470
|
+
cardSeg.data ||
|
|
471
|
+
cardSeg.xml ||
|
|
472
|
+
"";
|
|
452
473
|
if (cardData) {
|
|
453
474
|
parts.push(
|
|
454
475
|
getCachedHistoryCardTag(
|
|
@@ -464,7 +485,9 @@ async function buildMessageContent(
|
|
|
464
485
|
if (parts.length > 0) return parts.join(" ");
|
|
465
486
|
|
|
466
487
|
const segTypes = msg.message.map((seg: any) => seg.type);
|
|
467
|
-
const nonTextTypes = segTypes.filter(
|
|
488
|
+
const nonTextTypes = segTypes.filter(
|
|
489
|
+
(t: string) => t !== "text" && t !== "at",
|
|
490
|
+
);
|
|
468
491
|
return nonTextTypes.length > 0 ? `[${nonTextTypes.join(", ")}]` : "";
|
|
469
492
|
}
|
|
470
493
|
|
|
@@ -474,7 +497,7 @@ async function buildMessageContent(
|
|
|
474
497
|
*/
|
|
475
498
|
export async function getGroupHistory(
|
|
476
499
|
groupId: number,
|
|
477
|
-
ctx:
|
|
500
|
+
ctx: MiokuContext,
|
|
478
501
|
count: number = 100,
|
|
479
502
|
selfId: number,
|
|
480
503
|
db?: {
|
|
@@ -547,8 +570,9 @@ export async function getGroupHistory(
|
|
|
547
570
|
}
|
|
548
571
|
|
|
549
572
|
try {
|
|
550
|
-
const bot = ctx.pickBot(selfId)
|
|
573
|
+
const bot = ctx.pickBot(selfId);
|
|
551
574
|
const historyMediaOptions: HistoryMediaProcessingOptions = {
|
|
575
|
+
bot,
|
|
552
576
|
ai: mediaOptions?.ai,
|
|
553
577
|
workingModel: mediaOptions?.workingModel,
|
|
554
578
|
multimodalWorkingModel: mediaOptions?.multimodalWorkingModel,
|
|
@@ -561,32 +585,23 @@ export async function getGroupHistory(
|
|
|
561
585
|
saveMediaSummarySource: db.saveMediaSummarySource?.bind(db),
|
|
562
586
|
}
|
|
563
587
|
: undefined,
|
|
564
|
-
bot,
|
|
565
588
|
groupId,
|
|
566
589
|
};
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
parse_mult_msg: true,
|
|
575
|
-
quick_reply: false,
|
|
576
|
-
});
|
|
577
|
-
const messages = result?.messages || result?.data?.messages || [];
|
|
578
|
-
if (!Array.isArray(messages)) {
|
|
579
|
-
logger.warn("[getGroupHistory] API 返回格式异常:", result);
|
|
580
|
-
return botMessages;
|
|
581
|
-
}
|
|
590
|
+
const messages = bot
|
|
591
|
+
? await bot.getHistory(
|
|
592
|
+
{ type: "group", group_id: groupId },
|
|
593
|
+
undefined,
|
|
594
|
+
Math.min(count, 200),
|
|
595
|
+
)
|
|
596
|
+
: [];
|
|
582
597
|
|
|
583
598
|
const botUin = selfId;
|
|
584
599
|
const memberNameCache = new Map<string, string>();
|
|
585
600
|
const hashLookupCache = new Map<string, string | null>();
|
|
586
601
|
|
|
587
602
|
for (const m of messages) {
|
|
588
|
-
const uid = m
|
|
589
|
-
const senderName = m
|
|
603
|
+
const uid = m.user_id;
|
|
604
|
+
const senderName = m.nickname;
|
|
590
605
|
if (uid != null && senderName && !memberNameCache.has(String(uid))) {
|
|
591
606
|
memberNameCache.set(String(uid), String(senderName));
|
|
592
607
|
}
|
|
@@ -657,3 +672,20 @@ export function normalizeIdList(input: unknown): number[] {
|
|
|
657
672
|
),
|
|
658
673
|
);
|
|
659
674
|
}
|
|
675
|
+
|
|
676
|
+
async function fetchQuotedMessage(
|
|
677
|
+
e: any,
|
|
678
|
+
ctx: MiokuContext,
|
|
679
|
+
): Promise<{
|
|
680
|
+
message: any[];
|
|
681
|
+
sender?: { user_id?: string; nickname?: string };
|
|
682
|
+
} | null> {
|
|
683
|
+
const bot = e?.bot ?? ctx.pickBot(e?.self_id ?? "");
|
|
684
|
+
if (!bot || !e?.quote_id) return null;
|
|
685
|
+
try {
|
|
686
|
+
const result = await bot.getMessage(e.quote_id);
|
|
687
|
+
return result ?? null;
|
|
688
|
+
} catch {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
}
|