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/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
+ }