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.
@@ -1,5 +1,5 @@
1
1
  import type { AIInstance } from "mioku";
2
- import { logger } from "mioki";
2
+ import { logger } from "mioku";
3
3
  import { extractJsonObject } from "../utils/json";
4
4
  import type { ChatConfig, ChatMessage, TargetMessage } from "../types";
5
5
  import { extractGroupIdFromSession } from "../utils/group-config";
@@ -1,5 +1,5 @@
1
1
  import type { AIInstance } from "mioku";
2
- import { logger } from "mioki";
2
+ import { logger } from "mioku";
3
3
  import { extractJsonObject } from "../utils/json";
4
4
  import type { ChatDatabase } from "../db";
5
5
  import type { ChatMessage } from "../types";
@@ -1,5 +1,5 @@
1
1
  import type { AIInstance } from "mioku";
2
- import { logger } from "mioki";
2
+ import { logger } from "mioku";
3
3
  import type { ChatDatabase } from "../db";
4
4
  import type { ChatMessage } from "../types";
5
5
  import { extractGroupIdFromSession } from "../utils/group-config";
@@ -1,5 +1,5 @@
1
1
  import type { AIInstance } from "mioku";
2
- import { logger } from "mioki";
2
+ import { logger } from "mioku";
3
3
  import type {
4
4
  ChatMessage,
5
5
  PlannerAction,
package/humanize/topic.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { AIInstance } from "mioku";
2
- import { logger } from "mioki";
2
+ import { logger } from "mioku";
3
3
  import { extractJsonObject } from "../utils/json";
4
4
  import type { ChatDatabase } from "../db";
5
5
  import type { ChatMessage } from "../types";
package/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { definePlugin, type MiokiContext } from "mioki";
1
+ import { definePlugin, type MiokuContext } from "mioku";
2
2
  import type { AIInstance, AIModelRole, AIService } from "mioku";
3
3
  import type { AudioServiceApi } from "mioku-service-audio";
4
4
  import { getPluginRuntimeState, getService, Services } from "mioku";
@@ -117,7 +117,7 @@ export default definePlugin({
117
117
  name: "chat",
118
118
  version: "1.1.0",
119
119
  description: "AI 智能聊天插件",
120
- async setup(ctx: MiokiContext) {
120
+ async setup(ctx: MiokuContext) {
121
121
  ctx.logger.info("聊天插件正在初始化...");
122
122
 
123
123
  const aiService = getService(ctx, Services.AI);
@@ -497,7 +497,7 @@ roleModels = resolved.models;
497
497
  processingSet,
498
498
  },
499
499
  matchMessageCommands: (
500
- getPluginRuntimeState("boot") as
500
+ getPluginRuntimeState("core") as
501
501
  | {
502
502
  matchMessageCommands?: (
503
503
  text: string,
package/manage/cleanup.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { logger } from "mioki";
1
+ import { logger } from "mioku";
2
2
  import type { ChatDatabase } from "../db";
3
3
 
4
4
  export interface CleanupConfig {
@@ -1,4 +1,4 @@
1
- import type { MiokiContext } from "mioki";
1
+ import type { MiokuContext } from "mioku";
2
2
  import type { AIInstance } from "mioku";
3
3
  import type { ChatConfig, TargetMessage } from "../types";
4
4
  import type { ChatDatabase } from "../db";
@@ -39,7 +39,7 @@ export class CooldownManager {
39
39
  private pluginCtx: ChatPluginContext;
40
40
  private sessionTurnScheduler: SessionTurnScheduler;
41
41
 
42
- private ctx: MiokiContext;
42
+ private ctx: MiokuContext;
43
43
  private configProvider: ChatConfigProvider;
44
44
  private defaultConfig: ChatConfig;
45
45
  private db: ChatDatabase;
@@ -206,7 +206,7 @@ export class CooldownManager {
206
206
  this.buildHistoryMediaOptions(this.aiInstance, cfg),
207
207
  );
208
208
  const botNickname =
209
- cfg.nicknames[0] || this.ctx.pickBot(selfId).nickname || "Bot";
209
+ cfg.nicknames[0] || this.ctx.pickBot(selfId)?.nickname || "Bot";
210
210
  const botRole = await getBotRole(groupId, this.ctx, selfId);
211
211
  const { groupName, memberCount } = await this.getGroupInfoData(
212
212
  this.ctx,
@@ -316,7 +316,7 @@ export class CooldownManager {
316
316
  this.buildHistoryMediaOptions(this.aiInstance, cfg),
317
317
  );
318
318
  const botNickname =
319
- cfg.nicknames[0] || this.ctx.pickBot(selfId).nickname || "Bot";
319
+ cfg.nicknames[0] || this.ctx.pickBot(selfId)?.nickname || "Bot";
320
320
 
321
321
  const planResult = await this.humanize.actionPlanner.plan(
322
322
  groupSessionId,
@@ -1,4 +1,4 @@
1
- import type { MiokiContext } from "mioki";
1
+ import type { MiokuContext } from "mioku";
2
2
  import type { AIInstance } from "mioku";
3
3
  import type { ChatConfig, TargetMessage } from "../types";
4
4
  import type { ChatDatabase } from "../db";
@@ -28,7 +28,7 @@ export class IdleCheckManager {
28
28
  private groupBotsMapping = new Map<string, Set<number>>();
29
29
  private intervalHandle: NodeJS.Timeout | null = null;
30
30
 
31
- private ctx: MiokiContext;
31
+ private ctx: MiokuContext;
32
32
  private configProvider: ChatConfigProvider;
33
33
  private defaultConfig: ChatConfig;
34
34
  private db: ChatDatabase;
@@ -106,7 +106,7 @@ export class IdleCheckManager {
106
106
 
107
107
  const now = Date.now();
108
108
  const checkInterval = 60_000;
109
- const allBotIds = Array.from(this.ctx.bots).map((bot) => bot.uin);
109
+ const allBotIds = Array.from(this.ctx.bots).map((bot) => Number(bot.bot_id));
110
110
 
111
111
  for (const [groupSessionId, lastTime] of this.groupLastActivityTime) {
112
112
  const lastCheckTime =
@@ -216,7 +216,7 @@ export class IdleCheckManager {
216
216
  this.buildHistoryMediaOptions(this.aiInstance, cfg),
217
217
  );
218
218
  const botNickname =
219
- cfg.nicknames[0] || this.ctx.pickBot(selfId).nickname || "Bot";
219
+ cfg.nicknames[0] || this.ctx.pickBot(selfId)?.nickname || "Bot";
220
220
  const planResult = await this.humanize.actionPlanner.plan(
221
221
  groupSessionId,
222
222
  botNickname,
@@ -1,4 +1,4 @@
1
- import type { MiokiContext } from "mioki";
1
+ import type { MiokuContext } from "mioku";
2
2
  import type { AIInstance } from "mioku";
3
3
  import type { ChatConfig, TargetMessage } from "../types";
4
4
  import type { ChatDatabase } from "../db";
@@ -34,7 +34,7 @@ export class QueueProcessor {
34
34
  private dynamicDelayQueues = new Map<string, DynamicDelayQueueData>();
35
35
  private pluginCtx: ChatPluginContext;
36
36
 
37
- private ctx: MiokiContext;
37
+ private ctx: MiokuContext;
38
38
  private configProvider: ChatConfigProvider;
39
39
  private defaultConfig: ChatConfig;
40
40
  private db: ChatDatabase;
@@ -177,7 +177,7 @@ export class QueueProcessor {
177
177
 
178
178
  const cfg = this.configProvider(groupId);
179
179
  const botRole = await getBotRole(groupId, this.ctx, selfId);
180
- const botNickname = cfg.nicknames[0] || this.ctx.pickBot(selfId).nickname || "Bot";
180
+ const botNickname = cfg.nicknames[0] || this.ctx.pickBot(selfId)?.nickname || "Bot";
181
181
  const { groupName, memberCount } = await this.getGroupInfoData(this.ctx, groupId, selfId, String(groupId));
182
182
  const { history } = await this.getGroupHistoryMessages(groupId, groupSessionId, this.ctx, cfg.historyCount, this.db, selfId, this.buildHistoryMediaOptions(this.aiInstance, cfg));
183
183
 
@@ -261,7 +261,7 @@ export class QueueProcessor {
261
261
  };
262
262
 
263
263
  const botRole = await getBotRole(groupId, this.ctx, selfId);
264
- const botNickname = cfg.nicknames[0] || this.ctx.pickBot(selfId).nickname || "Bot";
264
+ const botNickname = cfg.nicknames[0] || this.ctx.pickBot(selfId)?.nickname || "Bot";
265
265
  const toolCtx = this.buildToolContext({
266
266
  ctx: this.ctx, event: null, groupSessionId, groupId, userId: targetMessage.userId,
267
267
  config: cfg, aiService: this.aiService, db: this.db, botRole, humanize: this.humanize, targetMessage, selfId,
package/manage/types.ts CHANGED
@@ -1,13 +1,22 @@
1
- import type { MiokiContext } from "mioki";
1
+ import type { Bot, MiokuContext } from "mioku";
2
2
  import type { AIInstance, AIService } from "mioku";
3
3
  import type { SkillPermissionRole } from "mioku";
4
4
  import type { ChatRuntimePromptInjection } from "mioku";
5
- import type { ChatConfig, ChatMessage, TargetMessage, ToolContext, ChatResult } from "../types";
5
+ import type {
6
+ ChatConfig,
7
+ ChatMessage,
8
+ TargetMessage,
9
+ ToolContext,
10
+ ChatResult,
11
+ } from "../types";
6
12
  import type { ChatDatabase } from "../db";
7
13
  import type { HumanizeEngine } from "../humanize";
8
14
  import type { EmojiAgent } from "../humanize";
9
15
  import type { SkillSessionManager } from "./skill-session";
10
- import type { GroupStructuredHistoryManager, StructuredUserInput } from "./group-structured-history";
16
+ import type {
17
+ GroupStructuredHistoryManager,
18
+ StructuredUserInput,
19
+ } from "./group-structured-history";
11
20
  import type { SendAIResponseOptions } from "../core/base";
12
21
  import type { GroupInfoResult, HumanizeContextsResult } from "../core/base";
13
22
 
@@ -32,7 +41,7 @@ export type RunRateLimitGuard = <T>(
32
41
  export type GetGroupHistoryMessages = (
33
42
  groupId: number,
34
43
  groupSessionId: string,
35
- ctx: MiokiContext,
44
+ ctx: MiokuContext,
36
45
  historyCount: number,
37
46
  db: ChatDatabase,
38
47
  selfId: number,
@@ -40,7 +49,7 @@ export type GetGroupHistoryMessages = (
40
49
  ) => Promise<{ history: ChatMessage[] }>;
41
50
 
42
51
  export type GetGroupInfoData = (
43
- ctx: MiokiContext,
52
+ ctx: MiokuContext,
44
53
  groupId: number,
45
54
  selfId: number,
46
55
  fallbackGroupName?: string,
@@ -54,7 +63,10 @@ export type GetHumanizeContexts = (
54
63
  triggerUserId?: number,
55
64
  ) => Promise<HumanizeContextsResult>;
56
65
 
57
- export type SendAIResponse = (options: SendAIResponseOptions, selfId: number) => Promise<void>;
66
+ export type SendAIResponse = (
67
+ options: SendAIResponseOptions,
68
+ selfId: number,
69
+ ) => Promise<void>;
58
70
 
59
71
  export type SaveBotMessages = (
60
72
  groupId: number,
@@ -63,14 +75,19 @@ export type SaveBotMessages = (
63
75
  timestamp: number,
64
76
  config: ChatConfig,
65
77
  db: ChatDatabase,
66
- ctx: MiokiContext,
67
- selfId: number,
78
+ ctx: MiokuContext,
79
+ bot?: Bot,
68
80
  ) => void;
69
81
 
70
- export type SendEmoji = (ctx: MiokiContext, groupId: number, emojiPath: string | null | undefined, selfId: number) => Promise<void>;
82
+ export type SendEmoji = (
83
+ ctx: MiokuContext,
84
+ groupId: number,
85
+ emojiPath: string | null | undefined,
86
+ bot?: import("mioku").Bot,
87
+ ) => Promise<void>;
71
88
 
72
89
  export type SendMessage = (
73
- ctx: MiokiContext,
90
+ ctx: MiokuContext,
74
91
  groupId: number | undefined,
75
92
  userId: number,
76
93
  text: string,
@@ -80,7 +97,7 @@ export type SendMessage = (
80
97
  ) => Promise<void>;
81
98
 
82
99
  export type BuildToolContext = (options: {
83
- ctx: MiokiContext;
100
+ ctx: MiokuContext;
84
101
  event: any;
85
102
  selfId: number;
86
103
  groupSessionId: string;
@@ -96,9 +113,15 @@ export type BuildToolContext = (options: {
96
113
  audioService?: import("mioku-service-audio").AudioServiceApi;
97
114
  }) => ToolContext;
98
115
 
99
- export type BuildStructuredUserInput = (event: any, content: string, fallbackTimestamp?: number) => StructuredUserInput;
116
+ export type BuildStructuredUserInput = (
117
+ event: any,
118
+ content: string,
119
+ fallbackTimestamp?: number,
120
+ ) => StructuredUserInput;
100
121
 
101
- export type BuildStructuredUserInputFromTarget = (targetMessage: TargetMessage) => StructuredUserInput;
122
+ export type BuildStructuredUserInputFromTarget = (
123
+ targetMessage: TargetMessage,
124
+ ) => StructuredUserInput;
102
125
 
103
126
  export type PromptCtxForRunChat = {
104
127
  config: ChatConfig;
@@ -143,4 +166,4 @@ export type RunChat = (
143
166
  currentUserInputs: StructuredUserInput[];
144
167
  },
145
168
  runtimeOptions?: { extraTools?: any[] },
146
- ) => Promise<ChatResult>;
169
+ ) => Promise<ChatResult>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mioku-plugin-chat",
3
- "version": "2.10.1",
3
+ "version": "3.0.0",
4
4
  "description": "AI 智能聊天插件",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -54,12 +54,10 @@
54
54
  },
55
55
  "devDependencies": {
56
56
  "@types/bun": "^1.3.14",
57
- "mioki": "^0.16.0",
58
57
  "mioku": "workspace:*"
59
58
  },
60
59
  "peerDependencies": {
61
- "mioku": "^0.9.0",
62
- "mioku-service-audio": "workspace:*",
63
- "mioki": "^0.16.0"
60
+ "mioku": "^1.0.0",
61
+ "mioku-service-audio": "workspace:*"
64
62
  }
65
63
  }
package/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ChatCompletionMessageParam } from "openai/resources/chat/completions";
2
- import type { MiokiContext } from "mioki";
2
+ import type { MiokuContext } from "mioku";
3
3
  import type { AITool, AIService, SkillPermissionRole } from "mioku";
4
4
  import type { ChatDatabase } from "./db";
5
5
 
@@ -300,7 +300,7 @@ export interface SkillSession {
300
300
  * 工具上下文
301
301
  */
302
302
  export interface ToolContext {
303
- ctx: MiokiContext;
303
+ ctx: MiokuContext;
304
304
  event: any;
305
305
  sessionId: string;
306
306
  groupId?: number;
package/utils/message.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { logger, MiokiContext } from "mioki";
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: MiokiContext,
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
- return !!(atSeg && String(atSeg.qq) === String(e.self_id));
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: MiokiContext,
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 ctx.getQuoteMsg(e);
45
- if (quoteMsg && String(quoteMsg.sender.user_id) === String(e.self_id)) {
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: MiokiContext,
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 ctx.getQuoteMsg(e);
84
+ const quotedMsg = await fetchQuotedMessage(e, ctx);
82
85
  if (quotedMsg && quotedMsg.message) {
83
- const senderName = quotedMsg.sender.nickname;
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: MiokiContext,
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: any) => seg.type === "at" && String(seg.qq) === String(e.self_id),
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: MiokiContext,
199
+ ctx: MiokuContext,
194
200
  selfId: number,
195
201
  ): Promise<"owner" | "admin" | "member"> {
196
202
  try {
197
- const memberInfo = await ctx
198
- .pickBot(selfId)
199
- .getGroupMemberInfo(groupId, selfId);
200
- return (memberInfo.role as "owner" | "admin" | "member") || "member";
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 any;
241
- if (!bot || typeof bot.getGroupMemberInfo !== "function") return key;
242
- const info = await bot.getGroupMemberInfo(
243
- ctx.historyMediaOptions.groupId,
244
- Number(userId),
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(source: any, ctx: HistoryFormatContext): string | null {
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 = ctx.memberNameCache.get(String(sourceUserId)) || String(sourceUserId);
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: any,
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.sender?.card || msg.sender?.nickname || String(msg.user_id),
355
- userRole: msg.sender?.role || "member",
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 ? msg.time * 1000 : Date.now(),
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(await getCachedHistoryVideoTag(videoSources, historyMediaOptions));
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((seg: any) => seg.type === "forward")) {
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(String(forwardId), historyMediaOptions),
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 || cardSeg.data?.xml || cardSeg.data || cardSeg.xml || "";
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((t: string) => t !== "text" && t !== "at");
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: MiokiContext,
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) as any;
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
- // 调用 OneBot API 获取群聊历史
568
- const result = await bot.api("get_group_msg_history", {
569
- group_id: String(groupId),
570
- message_seq: "0",
571
- count: Math.min(count, 200), // 最多获取200条
572
- reverse_order: false,
573
- disable_get_url: false,
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?.user_id;
589
- const senderName = m?.sender?.card || m?.sender?.nickname;
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
+ }