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 CHANGED
@@ -1,5 +1,5 @@
1
1
  export const BASE_CONFIG = {
2
- maxContextTokens: 128,
2
+ maxContextTokens: 256,
3
3
  temperature: 0.8,
4
4
  historyCount: 100,
5
5
  maxIterations: 20,
package/context.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { MiokiContext } from "mioki";
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: MiokiContext;
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 { MiokiContext } from "mioki";
2
- import { logger } from "mioki";
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: MiokiContext;
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.api("group_poke", {
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({ type: "reply", id: String(finalQuoteId) });
152
+ lineSegments.push(ctx.segment.reply(finalQuoteId));
156
153
  pendingReply = undefined;
157
154
  }
158
155
 
159
156
  for (const atId of atUsers) {
160
- lineSegments.push(ctx.segment.at(atId));
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
- segments.push(ctx.segment.at(atId));
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(audioSource));
216
+ lineSegments.push(ctx.segment.raw("record", { file: audioSource }));
221
217
  }
222
218
 
223
219
  if (lineSegments.length > 0) {
224
- await bot.sendGroupMsg(groupId, lineSegments);
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: MiokiContext,
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.api("group_poke", {
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({ type: "reply", id: String(pendingReply) });
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(audioSource));
373
+ segments.push(ctx.segment.raw("record", { file: audioSource }));
385
374
  }
386
375
 
387
376
  if (segments.length > 0 && groupId) {
388
- await bot.sendGroupMsg(groupId, segments);
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:(\d+)\]/g;
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({ type: "text", text: cleaned });
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({ type: "text", text: cleaned });
425
+ segments.push(ctx.segment.text(cleaned));
437
426
  }
438
427
  }
439
428
 
440
429
  if (audioSource) {
441
- segments.push(ctx.segment.record(audioSource));
430
+ segments.push(ctx.segment.raw("record", { file: audioSource }));
442
431
  } else if (fallbackText) {
443
- segments.push({ type: "text", text: fallbackText });
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.sendGroupMsg(groupId, segments);
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({ type: "reply", id: String(pendingReply) });
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(audioSource));
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: MiokiContext,
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: any,
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.sendGroupMsg(groupId, segments);
564
+ await bot.sendMessage({ type: "group", group_id: groupId}, segments);
576
565
  return;
577
566
  }
578
567
 
579
568
  if (userId) {
580
- await bot.sendPrivateMsg(userId, segments);
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: MiokiContext,
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: MiokiContext,
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: MiokiContext,
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 groupInfo = await ctx.pickBot(selfId).getGroupInfo(groupId);
702
- groupName = (groupInfo as any)?.group_name || fallbackGroupName;
703
- memberCount = (groupInfo as any)?.member_count;
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: MiokiContext;
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: MiokiContext,
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: MiokiContext,
848
- selfId: number,
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: MiokiContext,
870
+ ctx: MiokuContext,
875
871
  groupId: number,
876
872
  emojiPath: string | null | undefined,
877
- selfId: number,
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 ${String(selfId)} not found, skip sending emoji`,
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.sendGroupMsg(groupId, [emojiSegment]);
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.sendGroupMsg(groupId, [base64Segment]);
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}`);
@@ -1,5 +1,5 @@
1
1
  import type { AIInstance, SessionToolDefinition } from "mioku";
2
- import { logger } from "mioki";
2
+ import { logger } from "mioku";
3
3
  import type { AITool } from "mioku";
4
4
  import type {
5
5
  ToolContext,
package/core/chat-turn.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { MiokiContext } from "mioki";
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: MiokiContext,
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: MiokiContext,
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, selfId);
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, selfId);
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 = ctx.pickBot(selfId);
173
+ const bot = args.event?.bot;
174
174
  if (!bot) throw new Error(`bot ${selfId} not found`);
175
- await bot.sendPrivateMsg(userId, [emojiSegment]);
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] || ctx.pickBot(e.self_id).nickname || "Bot";
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] || pluginCtx.ctx.pickBot(selfId)?.nickname || "Bot";
454
+ cfg.nicknames[0] || event?.bot?.nickname || "Bot";
455
455
 
456
456
  let groupName: string | undefined;
457
457
  let memberCount: number | undefined;
@@ -1,5 +1,5 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import type { MiokiContext } from "mioki";
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: MiokiContext,
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(fileUrl)]);
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(b64)]);
62
+ await e.reply([ctx.segment.raw("record", { file: b64 })]);
63
63
  }
64
64
  }
@@ -1,4 +1,4 @@
1
- import { logger } from "mioki";
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 "mioki";
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.api("get_forward_msg", { id });
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.api("get_forward_msg", { id });
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: any): string {
649
- const messages =
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
- node.sender?.nickname || node.sender?.card || node.nickname || "unknown";
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
  }
@@ -1,6 +1,6 @@
1
1
  import * as crypto from "crypto";
2
2
  import type { AIInstance } from "mioku";
3
- import { logger } from "mioki";
3
+ import { logger } from "mioku";
4
4
  import type { ChatDatabase } from "../../db";
5
5
  import type { ImageRecord } from "../../types";
6
6
  import {
@@ -1,5 +1,5 @@
1
1
  import sharp from "sharp";
2
- import { logger } from "mioki";
2
+ import { logger } from "mioku";
3
3
 
4
4
  const IMAGE_MAX_BYTES = 1 * 1024 * 1024;
5
5
  const COMPRESS_MAX_WIDTH = 1280;
@@ -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 = (await bot.api("get_msg", { message_id: messageId })) as {
63
- message?: unknown[];
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(config: ChatConfig, userId: number): boolean {
82
+ export function isMediaAnalysisBlocked(
83
+ config: ChatConfig,
84
+ userId: number,
85
+ ): boolean {
90
86
  return Boolean(config.mediaAnalysisBlacklistUsers?.includes(userId));
91
87
  }