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/configs/base.ts
CHANGED
package/context.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MiokuContext } from "mioku";
|
|
2
2
|
import type { AIInstance, AIModelRole, AIService } from "mioku";
|
|
3
3
|
import type { ChatConfig } from "./types";
|
|
4
4
|
import type { ChatDatabase } from "./db";
|
|
@@ -35,7 +35,7 @@ import type { AudioServiceApi } from "mioku-service-audio";
|
|
|
35
35
|
import type { ChatMessage } from "./types";
|
|
36
36
|
|
|
37
37
|
export interface ChatPluginContext {
|
|
38
|
-
ctx:
|
|
38
|
+
ctx: MiokuContext;
|
|
39
39
|
defaultConfig: ChatConfig;
|
|
40
40
|
configProvider: ChatConfigProvider;
|
|
41
41
|
getConfig: (groupId?: number) => Promise<ChatConfig>;
|
package/core/base.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import { logger } from "
|
|
1
|
+
import type { Bot, MiokuContext } from "mioku";
|
|
2
|
+
import { createGroupRef, logger } from "mioku";
|
|
3
3
|
import type { SkillPermissionRole } from "mioku";
|
|
4
4
|
import type { AIInstance, AIService } from "mioku";
|
|
5
5
|
import type { ScreenshotService } from "mioku";
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
} from "./media/markdown-message";
|
|
25
25
|
|
|
26
26
|
export interface SendAIResponseOptions {
|
|
27
|
-
ctx:
|
|
27
|
+
ctx: MiokuContext;
|
|
28
28
|
groupId: number;
|
|
29
29
|
messages: string[];
|
|
30
30
|
config: ChatConfig;
|
|
@@ -141,10 +141,7 @@ export async function sendAIResponse(
|
|
|
141
141
|
|
|
142
142
|
if (pokeUsers.length > 0) {
|
|
143
143
|
for (const pokeId of pokeUsers) {
|
|
144
|
-
await bot.
|
|
145
|
-
group_id: groupId,
|
|
146
|
-
user_id: pokeId,
|
|
147
|
-
});
|
|
144
|
+
await bot.pokeMember(String(groupId), String(pokeId));
|
|
148
145
|
}
|
|
149
146
|
}
|
|
150
147
|
|
|
@@ -152,12 +149,13 @@ export async function sendAIResponse(
|
|
|
152
149
|
|
|
153
150
|
const finalQuoteId = pendingReply;
|
|
154
151
|
if (finalQuoteId !== undefined) {
|
|
155
|
-
lineSegments.push(
|
|
152
|
+
lineSegments.push(ctx.segment.reply(finalQuoteId));
|
|
156
153
|
pendingReply = undefined;
|
|
157
154
|
}
|
|
158
155
|
|
|
159
156
|
for (const atId of atUsers) {
|
|
160
|
-
|
|
157
|
+
if (String(atId) === String(selfId)) continue;
|
|
158
|
+
lineSegments.push(ctx.segment.at(String(atId)));
|
|
161
159
|
}
|
|
162
160
|
|
|
163
161
|
if (markdownContent) {
|
|
@@ -178,13 +176,11 @@ export async function sendAIResponse(
|
|
|
178
176
|
(imageSource?: string) => {
|
|
179
177
|
const segments: any[] = [];
|
|
180
178
|
if (finalQuoteIdForImage !== undefined) {
|
|
181
|
-
segments.push(
|
|
182
|
-
type: "reply",
|
|
183
|
-
id: String(finalQuoteIdForImage),
|
|
184
|
-
});
|
|
179
|
+
segments.push(ctx.segment.reply(finalQuoteIdForImage));
|
|
185
180
|
}
|
|
186
181
|
for (const atId of atUsers) {
|
|
187
|
-
|
|
182
|
+
if (String(atId) === String(selfId)) continue;
|
|
183
|
+
segments.push(ctx.segment.at(String(atId)));
|
|
188
184
|
}
|
|
189
185
|
segments.push(
|
|
190
186
|
ctx.segment.image(
|
|
@@ -217,11 +213,11 @@ export async function sendAIResponse(
|
|
|
217
213
|
lineSegments.push(ctx.segment.text(fallbackText));
|
|
218
214
|
}
|
|
219
215
|
if (audioSource) {
|
|
220
|
-
lineSegments.push(ctx.segment.record
|
|
216
|
+
lineSegments.push(ctx.segment.raw("record", { file: audioSource }));
|
|
221
217
|
}
|
|
222
218
|
|
|
223
219
|
if (lineSegments.length > 0) {
|
|
224
|
-
await bot.
|
|
220
|
+
await bot.sendMessage({ type: "group", group_id: groupId}, lineSegments);
|
|
225
221
|
lastDelayBasisText = sendableText || fallbackText || audioText || line;
|
|
226
222
|
}
|
|
227
223
|
|
|
@@ -241,7 +237,7 @@ export async function sendAIResponse(
|
|
|
241
237
|
}
|
|
242
238
|
|
|
243
239
|
export async function sendMessage(
|
|
244
|
-
ctx:
|
|
240
|
+
ctx: MiokuContext,
|
|
245
241
|
groupId: number | undefined,
|
|
246
242
|
userId: number,
|
|
247
243
|
text: string,
|
|
@@ -296,13 +292,9 @@ export async function sendMessage(
|
|
|
296
292
|
continue;
|
|
297
293
|
}
|
|
298
294
|
|
|
299
|
-
// 戳人 - 立即执行
|
|
300
295
|
if (groupId && pokeUsers.length > 0) {
|
|
301
296
|
for (const pokeId of pokeUsers) {
|
|
302
|
-
await bot.
|
|
303
|
-
group_id: groupId,
|
|
304
|
-
user_id: pokeId,
|
|
305
|
-
});
|
|
297
|
+
await bot.pokeMember(String(groupId), String(pokeId));
|
|
306
298
|
}
|
|
307
299
|
}
|
|
308
300
|
|
|
@@ -327,14 +319,11 @@ export async function sendMessage(
|
|
|
327
319
|
(imageSource?: string) => {
|
|
328
320
|
const segments: any[] = [];
|
|
329
321
|
if (finalQuoteIdForImage !== undefined) {
|
|
330
|
-
segments.push(
|
|
331
|
-
type: "reply",
|
|
332
|
-
id: String(finalQuoteIdForImage),
|
|
333
|
-
});
|
|
322
|
+
segments.push(ctx.segment.reply(finalQuoteIdForImage));
|
|
334
323
|
}
|
|
335
324
|
for (const atId of atUsers) {
|
|
336
325
|
if (String(atId) !== String(selfId)) {
|
|
337
|
-
segments.push(ctx.segment.at(atId));
|
|
326
|
+
segments.push(ctx.segment.at(String(atId)));
|
|
338
327
|
}
|
|
339
328
|
}
|
|
340
329
|
segments.push(
|
|
@@ -366,13 +355,13 @@ export async function sendMessage(
|
|
|
366
355
|
const sendableText = markdownContent ?? cleanText;
|
|
367
356
|
const segments: any[] = [];
|
|
368
357
|
if (pendingReply !== undefined) {
|
|
369
|
-
segments.push(
|
|
358
|
+
segments.push(ctx.segment.reply(pendingReply));
|
|
370
359
|
pendingReply = undefined;
|
|
371
360
|
}
|
|
372
361
|
if (markdownContent) {
|
|
373
362
|
for (const atId of atUsers) {
|
|
374
363
|
if (String(atId) !== String(selfId)) {
|
|
375
|
-
segments.push(ctx.segment.at(atId));
|
|
364
|
+
segments.push(ctx.segment.at(String(atId)));
|
|
376
365
|
}
|
|
377
366
|
}
|
|
378
367
|
if (sendableText) {
|
|
@@ -381,11 +370,11 @@ export async function sendMessage(
|
|
|
381
370
|
segments.push(ctx.segment.text(fallbackText));
|
|
382
371
|
}
|
|
383
372
|
if (audioSource) {
|
|
384
|
-
segments.push(ctx.segment.record
|
|
373
|
+
segments.push(ctx.segment.raw("record", { file: audioSource }));
|
|
385
374
|
}
|
|
386
375
|
|
|
387
376
|
if (segments.length > 0 && groupId) {
|
|
388
|
-
await bot.
|
|
377
|
+
await bot.sendMessage({ type: "group", group_id: groupId}, segments);
|
|
389
378
|
lastDelayBasisText =
|
|
390
379
|
sendableText || fallbackText || audioText || line;
|
|
391
380
|
}
|
|
@@ -398,7 +387,7 @@ export async function sendMessage(
|
|
|
398
387
|
// 有 @ 用户时,构建消息保持原始位置
|
|
399
388
|
// 先将原始行按 @ 标记分割,然后重新构建
|
|
400
389
|
let remaining = line;
|
|
401
|
-
const atPattern = /\[at:
|
|
390
|
+
const atPattern = /\[at: \d+\]/g;
|
|
402
391
|
|
|
403
392
|
let lastIndex = 0;
|
|
404
393
|
let match;
|
|
@@ -412,13 +401,13 @@ export async function sendMessage(
|
|
|
412
401
|
.replace(/\[audio:[^\]]+\]/gi, "")
|
|
413
402
|
.trim();
|
|
414
403
|
if (cleaned) {
|
|
415
|
-
segments.push(
|
|
404
|
+
segments.push(ctx.segment.text(cleaned));
|
|
416
405
|
}
|
|
417
406
|
}
|
|
418
407
|
|
|
419
408
|
const atId = match[1];
|
|
420
409
|
if (String(atId) !== String(selfId)) {
|
|
421
|
-
segments.push(ctx.segment.at(atId));
|
|
410
|
+
segments.push(ctx.segment.at(String(atId)));
|
|
422
411
|
}
|
|
423
412
|
|
|
424
413
|
lastIndex = match.index + match[0].length;
|
|
@@ -433,20 +422,20 @@ export async function sendMessage(
|
|
|
433
422
|
.replace(/\[audio:[^\]]+\]/gi, "")
|
|
434
423
|
.trim();
|
|
435
424
|
if (cleaned) {
|
|
436
|
-
segments.push(
|
|
425
|
+
segments.push(ctx.segment.text(cleaned));
|
|
437
426
|
}
|
|
438
427
|
}
|
|
439
428
|
|
|
440
429
|
if (audioSource) {
|
|
441
|
-
segments.push(ctx.segment.record
|
|
430
|
+
segments.push(ctx.segment.raw("record", { file: audioSource }));
|
|
442
431
|
} else if (fallbackText) {
|
|
443
|
-
segments.push(
|
|
432
|
+
segments.push(ctx.segment.text(fallbackText));
|
|
444
433
|
}
|
|
445
434
|
|
|
446
435
|
// 发送消息
|
|
447
436
|
if (segments.length > 0) {
|
|
448
437
|
if (groupId) {
|
|
449
|
-
await bot.
|
|
438
|
+
await bot.sendMessage({ type: "group", group_id: groupId}, segments);
|
|
450
439
|
lastDelayBasisText =
|
|
451
440
|
sendableText || fallbackText || audioText || line;
|
|
452
441
|
}
|
|
@@ -462,7 +451,7 @@ export async function sendMessage(
|
|
|
462
451
|
) {
|
|
463
452
|
const sendSegments: any[] = [];
|
|
464
453
|
if (pendingReply !== undefined) {
|
|
465
|
-
sendSegments.push(
|
|
454
|
+
sendSegments.push(ctx.segment.reply(pendingReply));
|
|
466
455
|
pendingReply = undefined;
|
|
467
456
|
}
|
|
468
457
|
if (sendableText) {
|
|
@@ -471,7 +460,7 @@ export async function sendMessage(
|
|
|
471
460
|
sendSegments.push(ctx.segment.text(fallbackText));
|
|
472
461
|
}
|
|
473
462
|
if (audioSource) {
|
|
474
|
-
sendSegments.push(ctx.segment.record
|
|
463
|
+
sendSegments.push(ctx.segment.raw("record", { file: audioSource }));
|
|
475
464
|
}
|
|
476
465
|
if (sendSegments.length > 0) {
|
|
477
466
|
await dispatchSegments(bot, groupId, userId, () => sendSegments);
|
|
@@ -527,7 +516,7 @@ function normalizeActionLineBreaks(text: string): string {
|
|
|
527
516
|
}
|
|
528
517
|
|
|
529
518
|
async function buildMarkdownImage(
|
|
530
|
-
ctx:
|
|
519
|
+
ctx: MiokuContext,
|
|
531
520
|
markdownContent: string,
|
|
532
521
|
screenshotService: ScreenshotService | undefined,
|
|
533
522
|
enableMarkdownScreenshot: boolean,
|
|
@@ -566,18 +555,18 @@ async function dispatchSegments(
|
|
|
566
555
|
}
|
|
567
556
|
|
|
568
557
|
async function sendByTarget(
|
|
569
|
-
bot:
|
|
558
|
+
bot: Bot,
|
|
570
559
|
groupId: number | undefined,
|
|
571
560
|
userId: number | undefined,
|
|
572
|
-
segments: any[],
|
|
561
|
+
segments: readonly any[],
|
|
573
562
|
): Promise<void> {
|
|
574
563
|
if (groupId) {
|
|
575
|
-
await bot.
|
|
564
|
+
await bot.sendMessage({ type: "group", group_id: groupId}, segments);
|
|
576
565
|
return;
|
|
577
566
|
}
|
|
578
567
|
|
|
579
568
|
if (userId) {
|
|
580
|
-
await bot.
|
|
569
|
+
await bot.sendMessage({ type: "private", user_id: userId}, segments);
|
|
581
570
|
return;
|
|
582
571
|
}
|
|
583
572
|
|
|
@@ -612,7 +601,7 @@ function isLocalFilePath(value: string): boolean {
|
|
|
612
601
|
}
|
|
613
602
|
|
|
614
603
|
async function resolveAudioSource(
|
|
615
|
-
ctx:
|
|
604
|
+
ctx: MiokuContext,
|
|
616
605
|
options: {
|
|
617
606
|
audioText?: string;
|
|
618
607
|
config: ChatConfig;
|
|
@@ -650,7 +639,7 @@ export interface GroupHistoryResult {
|
|
|
650
639
|
export async function getGroupHistoryMessages(
|
|
651
640
|
groupId: number,
|
|
652
641
|
groupSessionId: string,
|
|
653
|
-
ctx:
|
|
642
|
+
ctx: MiokuContext,
|
|
654
643
|
historyCount: number,
|
|
655
644
|
db: ChatDatabase,
|
|
656
645
|
selfId: number,
|
|
@@ -689,7 +678,7 @@ export interface GroupInfoResult {
|
|
|
689
678
|
}
|
|
690
679
|
|
|
691
680
|
export async function getGroupInfoData(
|
|
692
|
-
ctx:
|
|
681
|
+
ctx: MiokuContext,
|
|
693
682
|
groupId: number,
|
|
694
683
|
selfId: number,
|
|
695
684
|
fallbackGroupName?: string,
|
|
@@ -698,9 +687,16 @@ export async function getGroupInfoData(
|
|
|
698
687
|
let memberCount: number | undefined;
|
|
699
688
|
|
|
700
689
|
try {
|
|
701
|
-
const
|
|
702
|
-
|
|
703
|
-
|
|
690
|
+
const bot = ctx.pickBot(selfId);
|
|
691
|
+
if (!bot) {
|
|
692
|
+
groupName = fallbackGroupName;
|
|
693
|
+
return { groupName, memberCount };
|
|
694
|
+
}
|
|
695
|
+
const groupInfo = await createGroupRef(bot, String(groupId)).getInfo();
|
|
696
|
+
if (groupInfo) {
|
|
697
|
+
groupName = groupInfo.group_name || fallbackGroupName;
|
|
698
|
+
memberCount = groupInfo.member_count;
|
|
699
|
+
}
|
|
704
700
|
} catch {
|
|
705
701
|
groupName = fallbackGroupName;
|
|
706
702
|
}
|
|
@@ -744,7 +740,7 @@ export async function getHumanizeContexts(
|
|
|
744
740
|
}
|
|
745
741
|
|
|
746
742
|
export interface BuildToolContextOptions {
|
|
747
|
-
ctx:
|
|
743
|
+
ctx: MiokuContext;
|
|
748
744
|
event: any;
|
|
749
745
|
selfId: number;
|
|
750
746
|
groupSessionId: string;
|
|
@@ -761,7 +757,7 @@ export interface BuildToolContextOptions {
|
|
|
761
757
|
}
|
|
762
758
|
|
|
763
759
|
function resolveTriggerSkillRole(
|
|
764
|
-
ctx:
|
|
760
|
+
ctx: MiokuContext,
|
|
765
761
|
event: any,
|
|
766
762
|
): SkillPermissionRole {
|
|
767
763
|
const userId = event?.user_id || event?.sender?.user_id;
|
|
@@ -844,11 +840,11 @@ export function saveBotMessages(
|
|
|
844
840
|
timestamp: number,
|
|
845
841
|
config: ChatConfig,
|
|
846
842
|
db: ChatDatabase,
|
|
847
|
-
ctx:
|
|
848
|
-
|
|
843
|
+
ctx: MiokuContext,
|
|
844
|
+
bot: Bot | undefined,
|
|
849
845
|
): void {
|
|
850
|
-
const bot = ctx.pickBot(selfId);
|
|
851
846
|
const botNickname = config.nicknames[0] || (bot?.nickname ?? "Miku");
|
|
847
|
+
const selfId = bot ? Number(bot.bot_id) : 0;
|
|
852
848
|
|
|
853
849
|
if (!bot) {
|
|
854
850
|
ctx.logger.warn(`[saveBotMessages] bot ${selfId} not available`);
|
|
@@ -871,23 +867,22 @@ export function saveBotMessages(
|
|
|
871
867
|
}
|
|
872
868
|
|
|
873
869
|
export async function sendEmoji(
|
|
874
|
-
ctx:
|
|
870
|
+
ctx: MiokuContext,
|
|
875
871
|
groupId: number,
|
|
876
872
|
emojiPath: string | null | undefined,
|
|
877
|
-
|
|
873
|
+
bot: Bot | undefined,
|
|
878
874
|
): Promise<void> {
|
|
879
875
|
if (!emojiPath) return;
|
|
880
|
-
const bot = ctx.pickBot(selfId);
|
|
881
876
|
if (!bot) {
|
|
882
877
|
ctx.logger.error(
|
|
883
|
-
`[sendEmoji] bot
|
|
878
|
+
`[sendEmoji] bot not available, skip sending emoji`,
|
|
884
879
|
);
|
|
885
880
|
return;
|
|
886
881
|
}
|
|
887
882
|
|
|
888
883
|
try {
|
|
889
884
|
const emojiSegment = ctx.segment.image(`file://${emojiPath}`);
|
|
890
|
-
await bot.
|
|
885
|
+
await bot.sendMessage({ type: "group", group_id: groupId}, [emojiSegment]);
|
|
891
886
|
} catch (err) {
|
|
892
887
|
try {
|
|
893
888
|
const fsPromises = await import("fs/promises");
|
|
@@ -922,7 +917,7 @@ export async function sendEmoji(
|
|
|
922
917
|
|
|
923
918
|
const base64DataUrl = `data:${mimeType};base64,${base64}`;
|
|
924
919
|
const base64Segment = ctx.segment.image(base64DataUrl);
|
|
925
|
-
await bot.
|
|
920
|
+
await bot.sendMessage({ type: "group", group_id: groupId}, [base64Segment]);
|
|
926
921
|
ctx.logger.info(`[Emoji] Sent via base64: ${path.basename(emojiPath)}`);
|
|
927
922
|
} catch (base64Err) {
|
|
928
923
|
ctx.logger.error(`[Emoji] Base64 also failed: ${base64Err}`);
|
package/core/chat-engine.ts
CHANGED
package/core/chat-turn.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MiokuContext } from "mioku";
|
|
2
2
|
import type { AITool, ChatRuntimePromptInjection } from "mioku";
|
|
3
3
|
import type { ChatPluginContext, ChatRuntimeState } from "../context";
|
|
4
4
|
import type { ChatConfig, ChatMessage, TargetMessage } from "../types";
|
|
@@ -48,7 +48,7 @@ const NO_NEW_MESSAGE =
|
|
|
48
48
|
"[No new user message in this turn. Reply naturally based on the runtime instruction and recent context.]";
|
|
49
49
|
|
|
50
50
|
function buildRuntimeTargetMessageContent(
|
|
51
|
-
ctx:
|
|
51
|
+
ctx: MiokuContext,
|
|
52
52
|
event: any,
|
|
53
53
|
overrideContent?: string,
|
|
54
54
|
): string {
|
|
@@ -58,7 +58,7 @@ function buildRuntimeTargetMessageContent(
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function resolveRuntimeContext(
|
|
61
|
-
ctx:
|
|
61
|
+
ctx: MiokuContext,
|
|
62
62
|
options: ExecuteChatRuntimeRequestOptions,
|
|
63
63
|
): ResolvedRuntimeContext {
|
|
64
64
|
if (options.event) {
|
|
@@ -151,9 +151,9 @@ export async function finalizeChatTurn(
|
|
|
151
151
|
{ ctx, groupId, messages: result.messages, config: cfg, sentIndices: toolCtx.sentMessageIndices, audioService: pluginCtx.audioService },
|
|
152
152
|
selfId,
|
|
153
153
|
);
|
|
154
|
-
await pluginCtx.sendEmoji(ctx, groupId, result.emojiPath,
|
|
154
|
+
await pluginCtx.sendEmoji(ctx, groupId, result.emojiPath, event?.bot);
|
|
155
155
|
const now = Date.now();
|
|
156
|
-
pluginCtx.saveBotMessages(groupId, groupSessionId, result.messages, now, cfg, pluginCtx.db, ctx,
|
|
156
|
+
pluginCtx.saveBotMessages(groupId, groupSessionId, result.messages, now, cfg, pluginCtx.db, ctx, event?.bot);
|
|
157
157
|
if (args.isLive) {
|
|
158
158
|
pluginCtx.idleCheckManager.recordBotMessages(groupSessionId, result.messages.length, selfId);
|
|
159
159
|
}
|
|
@@ -170,9 +170,9 @@ export async function finalizeChatTurn(
|
|
|
170
170
|
if (args.isLive) {
|
|
171
171
|
await event.reply([emojiSegment]);
|
|
172
172
|
} else {
|
|
173
|
-
const bot =
|
|
173
|
+
const bot = args.event?.bot;
|
|
174
174
|
if (!bot) throw new Error(`bot ${selfId} not found`);
|
|
175
|
-
await bot.
|
|
175
|
+
await bot.sendMessage({ type: "private", user_id: userId}, [emojiSegment]);
|
|
176
176
|
}
|
|
177
177
|
} catch (err) {
|
|
178
178
|
ctx.logger.warn(
|
|
@@ -260,7 +260,7 @@ export async function processChat(
|
|
|
260
260
|
}));
|
|
261
261
|
|
|
262
262
|
const botNickname =
|
|
263
|
-
cfg.nicknames[0] ||
|
|
263
|
+
cfg.nicknames[0] || e.bot?.nickname || "Bot";
|
|
264
264
|
const botRole = groupId ? await getBotRole(groupId, ctx, e.self_id) : "member";
|
|
265
265
|
let groupName: string | undefined;
|
|
266
266
|
let memberCount: number | undefined;
|
|
@@ -451,7 +451,7 @@ async function executeChatRuntimeRequestNow(
|
|
|
451
451
|
|
|
452
452
|
const botRole = groupId ? await getBotRole(groupId, pluginCtx.ctx, selfId) : "member";
|
|
453
453
|
const botNickname =
|
|
454
|
-
cfg.nicknames[0] ||
|
|
454
|
+
cfg.nicknames[0] || event?.bot?.nickname || "Bot";
|
|
455
455
|
|
|
456
456
|
let groupName: string | undefined;
|
|
457
457
|
let memberCount: number | undefined;
|
package/core/media/audio.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readFile } from "node:fs/promises";
|
|
2
|
-
import type {
|
|
2
|
+
import type { MiokuContext } from "mioku";
|
|
3
3
|
import type { AudioServiceApi, SupportedLang } from "mioku-service-audio";
|
|
4
4
|
|
|
5
5
|
function detectAudioLanguage(text: string): SupportedLang {
|
|
@@ -33,7 +33,7 @@ export async function synthesizeAudioSource(
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export async function sendVoice(
|
|
36
|
-
ctx:
|
|
36
|
+
ctx: MiokuContext,
|
|
37
37
|
e: any,
|
|
38
38
|
filePath: string,
|
|
39
39
|
logTag = "[audio]",
|
|
@@ -42,7 +42,7 @@ export async function sendVoice(
|
|
|
42
42
|
? filePath
|
|
43
43
|
: `file://${filePath}`;
|
|
44
44
|
try {
|
|
45
|
-
await e.reply([ctx.segment.record
|
|
45
|
+
await e.reply([ctx.segment.raw("record", { file: fileUrl })]);
|
|
46
46
|
return;
|
|
47
47
|
} catch (err: any) {
|
|
48
48
|
const msg = String(err?.message ?? err);
|
|
@@ -59,6 +59,6 @@ export async function sendVoice(
|
|
|
59
59
|
ctx.logger.info(
|
|
60
60
|
`${logTag} file:// 发送失败 (${msg}),回退 base64 (${buf.length} bytes)`,
|
|
61
61
|
);
|
|
62
|
-
await e.reply([ctx.segment.record
|
|
62
|
+
await e.reply([ctx.segment.raw("record", { file: b64 })]);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { logger } from "
|
|
1
|
+
import { logger } from "mioku";
|
|
2
2
|
import sharp from "sharp";
|
|
3
3
|
import { QQ_IMAGE_FETCH_HEADERS } from "./image-compress";
|
|
4
4
|
|
|
@@ -57,7 +57,7 @@ export async function extractGifFrames(
|
|
|
57
57
|
})
|
|
58
58
|
.png()
|
|
59
59
|
.toBuffer();
|
|
60
|
-
frames.push(`data:image/png;base64,${firstFrame.toString("base64")}`);
|
|
60
|
+
frames.push(`data:image/png);base64,${firstFrame.toString("base64")}`);
|
|
61
61
|
|
|
62
62
|
// 提取中间帧
|
|
63
63
|
if (middleIndex !== firstIndex && middleIndex !== lastIndex) {
|
|
@@ -67,7 +67,7 @@ export async function extractGifFrames(
|
|
|
67
67
|
})
|
|
68
68
|
.png()
|
|
69
69
|
.toBuffer();
|
|
70
|
-
frames.push(`data:image/png;base64,${middleFrame.toString("base64")}`);
|
|
70
|
+
frames.push(`data:image/png);base64,${middleFrame.toString("base64")}`);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// 提取末尾帧
|
|
@@ -78,7 +78,7 @@ export async function extractGifFrames(
|
|
|
78
78
|
})
|
|
79
79
|
.png()
|
|
80
80
|
.toBuffer();
|
|
81
|
-
frames.push(`data:image/png;base64,${lastFrame.toString("base64")}`);
|
|
81
|
+
frames.push(`data:image/png);base64,${lastFrame.toString("base64")}`);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
logger.info(
|
|
@@ -4,7 +4,7 @@ import * as fs from "fs/promises";
|
|
|
4
4
|
import * as os from "os";
|
|
5
5
|
import * as path from "path";
|
|
6
6
|
import { promisify } from "util";
|
|
7
|
-
import { logger } from "
|
|
7
|
+
import { Bot, ForwardNode, logger } from "mioku";
|
|
8
8
|
import type { AIInstance } from "mioku";
|
|
9
9
|
import type { MediaSummaryKind, MediaSummaryRecord } from "../../types";
|
|
10
10
|
|
|
@@ -69,9 +69,7 @@ export interface HistoryMediaProcessingOptions {
|
|
|
69
69
|
warn(message: string): void;
|
|
70
70
|
error(message: string): void;
|
|
71
71
|
};
|
|
72
|
-
bot?:
|
|
73
|
-
api<T = any>(action: string, params?: Record<string, any>): Promise<T>;
|
|
74
|
-
};
|
|
72
|
+
bot?: Bot;
|
|
75
73
|
groupId?: number;
|
|
76
74
|
runAIRequest?<T>(request: () => Promise<T>): Promise<T | null>;
|
|
77
75
|
}
|
|
@@ -252,7 +250,7 @@ export async function summarizeHistoryForward(
|
|
|
252
250
|
return "[forward]";
|
|
253
251
|
}
|
|
254
252
|
|
|
255
|
-
const forwardResult = await options.bot.
|
|
253
|
+
const forwardResult = await options.bot.getForwardMessage(id);
|
|
256
254
|
const text = extractForwardText(forwardResult);
|
|
257
255
|
const source = `forward:${id}`;
|
|
258
256
|
if (!text) {
|
|
@@ -293,7 +291,7 @@ export async function getCachedHistoryForwardTag(
|
|
|
293
291
|
if (!id) return "[forward]";
|
|
294
292
|
if (!options.bot) return "[forward]";
|
|
295
293
|
try {
|
|
296
|
-
const forwardResult = await options.bot.
|
|
294
|
+
const forwardResult = await options.bot.getForwardMessage(id);
|
|
297
295
|
const text = extractForwardText(forwardResult);
|
|
298
296
|
const summary = getCachedSummaryByHash(
|
|
299
297
|
"forward",
|
|
@@ -558,7 +556,7 @@ async function extractVideoFrames(
|
|
|
558
556
|
{ timeout: 30_000 },
|
|
559
557
|
);
|
|
560
558
|
const buffer = await fs.readFile(outputPath);
|
|
561
|
-
frames.push(`data:image/jpeg;base64,${buffer.toString("base64")}`);
|
|
559
|
+
frames.push(`data:image/jpeg);base64,${buffer.toString("base64")}`);
|
|
562
560
|
}
|
|
563
561
|
|
|
564
562
|
return frames;
|
|
@@ -645,16 +643,13 @@ function buildFrameTimestamps(duration: number, frameCount: number): number[] {
|
|
|
645
643
|
});
|
|
646
644
|
}
|
|
647
645
|
|
|
648
|
-
function extractForwardText(result:
|
|
649
|
-
const
|
|
650
|
-
result?.messages || result?.data?.messages || result?.data || [];
|
|
651
|
-
const nodes = Array.isArray(messages) ? messages : [];
|
|
646
|
+
function extractForwardText(result: ForwardNode[]): string {
|
|
647
|
+
const nodes = Array.isArray(result) ? result : [];
|
|
652
648
|
const lines: string[] = [];
|
|
653
649
|
|
|
654
650
|
for (const node of nodes) {
|
|
655
|
-
const senderName =
|
|
656
|
-
|
|
657
|
-
const content = extractMessageSegmentsText(node.message || node.content);
|
|
651
|
+
const senderName = node.nickname || String(node.user_id || "unknown");
|
|
652
|
+
const content = extractMessageSegmentsText(node.message);
|
|
658
653
|
if (content) {
|
|
659
654
|
lines.push(`${senderName}: ${content}`);
|
|
660
655
|
}
|
package/core/media/segment.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AIInstance } from "mioku";
|
|
1
|
+
import type { AIInstance, Bot } from "mioku";
|
|
2
2
|
import type { ChatConfig, MediaSummaryRecord } from "../../types";
|
|
3
3
|
import type { HistoryMediaOptions } from "../../manage/types";
|
|
4
4
|
import {
|
|
@@ -27,9 +27,7 @@ export function buildHistoryMediaProcessingOptions(
|
|
|
27
27
|
getMediaSummary(key: string): MediaSummaryRecord | null;
|
|
28
28
|
saveMediaSummary(summary: MediaSummaryRecord): void;
|
|
29
29
|
},
|
|
30
|
-
bot:
|
|
31
|
-
api<T = unknown>(action: string, params?: Record<string, unknown>): Promise<T>;
|
|
32
|
-
},
|
|
30
|
+
bot: Bot,
|
|
33
31
|
groupId: number,
|
|
34
32
|
log: HistoryMediaProcessingOptions["logger"],
|
|
35
33
|
runAIRequest?: <T>(request: () => Promise<T>) => Promise<T | null>,
|
|
@@ -53,17 +51,12 @@ export function getSegmentUrl(seg: {
|
|
|
53
51
|
}
|
|
54
52
|
|
|
55
53
|
export async function getVideoSourceCandidatesFromMessage(
|
|
56
|
-
bot:
|
|
57
|
-
api(action: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
58
|
-
},
|
|
54
|
+
bot: Bot,
|
|
59
55
|
messageId: number | string | undefined,
|
|
60
56
|
): Promise<string[]> {
|
|
61
|
-
if (messageId == null) return [];
|
|
62
|
-
const result =
|
|
63
|
-
|
|
64
|
-
data?: { message?: unknown[] };
|
|
65
|
-
};
|
|
66
|
-
const segments = result?.message || result?.data?.message || [];
|
|
57
|
+
if (messageId == null || !bot) return [];
|
|
58
|
+
const result = await bot.getMessage(messageId);
|
|
59
|
+
const segments = result?.message ?? [];
|
|
67
60
|
if (!Array.isArray(segments)) return [];
|
|
68
61
|
const candidates: string[] = [];
|
|
69
62
|
for (const seg of segments as MediaMessageSegment[]) {
|
|
@@ -86,6 +79,9 @@ export function getCardData(seg: unknown): string | null {
|
|
|
86
79
|
return typeof data === "string" ? data : JSON.stringify(data);
|
|
87
80
|
}
|
|
88
81
|
|
|
89
|
-
export function isMediaAnalysisBlocked(
|
|
82
|
+
export function isMediaAnalysisBlocked(
|
|
83
|
+
config: ChatConfig,
|
|
84
|
+
userId: number,
|
|
85
|
+
): boolean {
|
|
90
86
|
return Boolean(config.mediaAnalysisBlacklistUsers?.includes(userId));
|
|
91
87
|
}
|