koishi-plugin-chat-patch 2.2.0 → 2.3.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.
@@ -8,7 +8,7 @@ import { } from '@koishijs/plugin-console'
8
8
  export class MessageHandler {
9
9
  private logger: Logger
10
10
  private utils: Utils
11
- // 存储正确的 channelId 映射,key 是 selfId,value 是正确的 channelId
11
+
12
12
  private correctChannelIds: Map<string, string> = new Map()
13
13
 
14
14
  constructor(
@@ -20,118 +20,152 @@ export class MessageHandler {
20
20
  this.utils = new Utils(config)
21
21
  }
22
22
 
23
- // 设置正确的 channelId
23
+ recordUserMessage(session: Session, timestamp: number) {
24
+
25
+ setImmediate(() => {
26
+ this.processUserMessage(session, timestamp).catch(error => {
27
+ this.logger.error('记录用户消息失败:', error)
28
+ })
29
+ })
30
+ }
31
+
32
+ recordBotMessage(session: Session, timestamp: number) {
33
+
34
+ setImmediate(() => {
35
+ this.processBotMessage(session, timestamp).catch(error => {
36
+ this.logger.error('记录机器人消息失败:', error)
37
+ })
38
+ })
39
+ }
40
+
24
41
  setCorrectChannelId(selfId: string, channelId: string) {
25
42
  this.correctChannelIds.set(selfId, channelId)
26
43
  this.logInfo('设置正确的 channelId:', { selfId, channelId })
27
44
  }
28
45
 
29
- // 获取正确的 channelId
30
46
  getCorrectChannelId(selfId: string): string | undefined {
31
47
  return this.correctChannelIds.get(selfId)
32
48
  }
33
49
 
34
- // 更新机器人信息到JSON文件
35
- async updateBotInfoToFile(session: Session) {
36
- const data = this.fileManager.readChatDataFromFile()
37
-
38
- const botInfo: BotInfo = {
39
- selfId: session.selfId,
40
- platform: session.platform || 'unknown',
41
- username: session.bot.user?.name || `Bot-${session.selfId}`,
42
- avatar: session.bot.user?.avatar,
43
- status: 'online'
44
- }
45
-
46
- data.bots[session.selfId] = botInfo
47
- this.fileManager.writeChatDataToFile(data)
48
- this.logInfo('更新机器人信息到文件:', botInfo.username)
49
- }
50
-
51
- // 更新频道信息到JSON文件
52
- async updateChannelInfoToFile(session: Session): Promise<string> {
53
- const isDirect = session.isDirect || session.channelId?.includes('private')
54
- let guildName = session.channelId
55
- // 直接使用 session.username 作为私聊用户名
56
- const directUserName = session.username || session.event?.user?.name || session.userId
50
+ updateBotInfoToFile(session: Session) {
57
51
 
58
- const data = this.fileManager.readChatDataFromFile()
59
-
60
- if (!isDirect) {
52
+ setImmediate(() => {
61
53
  try {
62
- // 获取群组名称
63
- if (session.guildId && session.bot.getGuild && typeof session.bot.getGuild === 'function') {
64
- const guild = await session.bot.getGuild(session.guildId)
65
- guildName = guild?.name || session.channelId
66
- }
54
+ const data = this.fileManager.readChatDataFromFile()
67
55
 
68
- // 如果没有getGuild方法,尝试使用getChannel方法
69
- if (session.guildId && !session.bot.getGuild && session.bot.getChannel && typeof session.bot.getChannel === 'function') {
70
- try {
71
- const channel = await session.bot.getChannel(session.guildId)
72
- guildName = channel?.name || session.channelId
73
- } catch (channelError) {
74
- this.logInfo('获取频道信息失败,使用频道ID作为备用:', channelError)
75
- }
56
+ const botInfo: BotInfo = {
57
+ selfId: session.selfId,
58
+ platform: session.platform || 'unknown',
59
+ username: session.bot.user?.name || `Bot-${session.selfId}`,
60
+ avatar: session.bot.user?.avatar,
61
+ status: 'online'
76
62
  }
63
+
64
+ data.bots[session.selfId] = botInfo
65
+ this.fileManager.writeChatDataToFile(data)
66
+ this.logInfo('更新机器人信息到文件:', botInfo.username)
77
67
  } catch (error) {
78
- this.logInfo('获取频道信息失败,使用频道ID作为备用:', error)
79
- guildName = session.channelId
68
+ this.logger.error('更新机器人信息失败:', error)
80
69
  }
81
- }
70
+ })
71
+ }
82
72
 
83
- if (!data.channels[session.selfId]) {
84
- data.channels[session.selfId] = {}
85
- }
73
+ updateChannelInfoToFile(session: Session): string {
74
+ const isDirect = session.isDirect || session.channelId?.includes('private')
75
+ const directUserName = session.username || session.event?.user?.name || session.userId
86
76
 
87
- // 获取现有频道信息
88
- const existingChannel = data.channels[session.selfId][session.channelId]
77
+ const data = this.fileManager.readChatDataFromFile()
78
+ const existingChannel = data.channels[session.selfId]?.[session.channelId]
89
79
 
90
- // 构造频道名称
91
- let finalName: string
80
+ let immediateName = session.channelId
92
81
  if (isDirect) {
93
- // 私聊频道:优先使用真实用户名
94
82
  if (directUserName && directUserName !== session.userId) {
95
- finalName = `私聊(${directUserName})`
83
+ immediateName = `私聊(${directUserName})`
96
84
  } else if (existingChannel?.name && !existingChannel.name.includes('未知')) {
97
- // 如果已有名称且不是"未知用户",保持原名称
98
- finalName = existingChannel.name
85
+ immediateName = existingChannel.name
99
86
  } else if (session.platform && session.platform.toLowerCase().includes('sandbox')) {
100
- // sandbox 平台直接使用 userId 作为频道名称
101
- finalName = `私聊(${session.userId})`
87
+ immediateName = `私聊(${session.userId})`
102
88
  } else {
103
- finalName = '私聊(未知用户)'
89
+ immediateName = '私聊(未知用户)'
104
90
  }
105
- } else {
106
- finalName = guildName || session.channelId
91
+ } else if (existingChannel?.guildName) {
92
+ immediateName = existingChannel.guildName
107
93
  }
108
94
 
109
- const channelInfo: ChannelInfo = {
110
- id: session.channelId,
111
- name: finalName,
112
- type: session.type || 0,
113
- channelId: session.channelId,
114
- guildName: guildName,
115
- isDirect: !!isDirect
116
- }
95
+ setImmediate(async () => {
96
+ try {
97
+ let guildName = session.channelId
117
98
 
118
- // 记录名称变化
119
- if (existingChannel && existingChannel.name !== finalName) {
120
- this.logInfo('更新频道名称:', {
121
- channelId: session.channelId,
122
- oldName: existingChannel.name,
123
- newName: finalName
124
- })
125
- }
99
+ if (!isDirect) {
100
+ try {
126
101
 
127
- data.channels[session.selfId][session.channelId] = channelInfo
128
- this.fileManager.writeChatDataToFile(data)
129
- this.logInfo('更新频道信息到文件:', channelInfo.name)
102
+ if (session.guildId && session.bot.getGuild && typeof session.bot.getGuild === 'function') {
103
+ const guild = await session.bot.getGuild(session.guildId)
104
+ guildName = guild?.name || session.channelId
105
+ } else if (session.guildId && !session.bot.getGuild && session.bot.getChannel && typeof session.bot.getChannel === 'function') {
106
+ try {
107
+ const channel = await session.bot.getChannel(session.guildId)
108
+ guildName = channel?.name || session.channelId
109
+ } catch (channelError) {
110
+ this.logInfo('获取频道信息失败,使用频道ID作为备用:', channelError)
111
+ }
112
+ }
113
+ } catch (error) {
114
+ this.logInfo('获取频道信息失败,使用频道ID作为备用:', error)
115
+ guildName = session.channelId
116
+ }
117
+ }
118
+
119
+ const freshData = this.fileManager.readChatDataFromFile()
120
+ if (!freshData.channels[session.selfId]) {
121
+ freshData.channels[session.selfId] = {}
122
+ }
130
123
 
131
- return guildName
124
+ const existingChannel = freshData.channels[session.selfId][session.channelId]
125
+
126
+ let finalName: string
127
+ if (isDirect) {
128
+ if (directUserName && directUserName !== session.userId) {
129
+ finalName = `私聊(${directUserName})`
130
+ } else if (existingChannel?.name && !existingChannel.name.includes('未知')) {
131
+ finalName = existingChannel.name
132
+ } else if (session.platform && session.platform.toLowerCase().includes('sandbox')) {
133
+ finalName = `私聊(${session.userId})`
134
+ } else {
135
+ finalName = '私聊(未知用户)'
136
+ }
137
+ } else {
138
+ finalName = guildName || session.channelId
139
+ }
140
+
141
+ const channelInfo: ChannelInfo = {
142
+ id: session.channelId,
143
+ name: finalName,
144
+ type: session.type || 0,
145
+ channelId: session.channelId,
146
+ guildName: guildName,
147
+ isDirect: !!isDirect
148
+ }
149
+
150
+ if (existingChannel && existingChannel.name !== finalName) {
151
+ this.logInfo('更新频道名称:', {
152
+ channelId: session.channelId,
153
+ oldName: existingChannel.name,
154
+ newName: finalName
155
+ })
156
+ }
157
+
158
+ freshData.channels[session.selfId][session.channelId] = channelInfo
159
+ this.fileManager.writeChatDataToFile(freshData)
160
+ this.logInfo('更新频道信息到文件:', channelInfo.name)
161
+ } catch (error) {
162
+ this.logger.error('异步更新频道信息失败:', error)
163
+ }
164
+ })
165
+
166
+ return immediateName
132
167
  }
133
168
 
134
- // 下载并缓存媒体文件
135
169
  public async downloadAndCacheMedia(url: string, type: 'image' | 'media' | 'avatar') {
136
170
  try {
137
171
  if (!url || url.startsWith('data:') || url.startsWith('file:')) return url
@@ -145,7 +179,6 @@ export class MessageHandler {
145
179
  require('node:fs').mkdirSync(dir, { recursive: true })
146
180
  }
147
181
 
148
- // 使用 URL 的 hash 作为文件名,避免重复下载
149
182
  const crypto = require('node:crypto')
150
183
  const hash = crypto.createHash('md5').update(url).digest('hex')
151
184
  const ext = require('node:path').extname(new URL(url).pathname) || (type === 'image' ? '.jpg' : '.mp4')
@@ -165,47 +198,54 @@ export class MessageHandler {
165
198
  }
166
199
  }
167
200
 
168
- // 处理消息中的媒体元素并缓存
169
- // 只缓存用户的图片和语音,不缓存视频和文件
170
- private async processMediaElements(elements: h[], isUserMessage: boolean = true) {
171
- if (!elements) return elements
172
- for (const el of elements) {
173
- if (['image', 'img', 'mface'].includes(el.type)) {
174
- const src = el.attrs.src || el.attrs.url || el.attrs.file
175
- if (src && isUserMessage) {
176
- // 只缓存用户消息的图片
177
- el.attrs.src = await this.downloadAndCacheMedia(src, 'image')
178
- }
179
- } else if (el.type === 'audio') {
180
- const src = el.attrs.src || el.attrs.url || el.attrs.file
181
- if (src && isUserMessage) {
182
- // 只缓存用户消息的语音
183
- el.attrs.src = await this.downloadAndCacheMedia(src, 'media')
201
+ private processMediaElementsAsync(elements: h[], isUserMessage: boolean = true) {
202
+ if (!elements) return
203
+
204
+ setImmediate(async () => {
205
+ try {
206
+ for (const el of elements) {
207
+ if (['image', 'img', 'mface'].includes(el.type)) {
208
+ const src = el.attrs.src || el.attrs.url || el.attrs.file
209
+ if (src && isUserMessage) {
210
+
211
+ this.downloadAndCacheMedia(src, 'image').catch(e => {
212
+ this.logger.warn('缓存图片失败:', e)
213
+ })
214
+ }
215
+ } else if (el.type === 'audio') {
216
+ const src = el.attrs.src || el.attrs.url || el.attrs.file
217
+ if (src && isUserMessage) {
218
+
219
+ this.downloadAndCacheMedia(src, 'media').catch(e => {
220
+ this.logger.warn('缓存语音失败:', e)
221
+ })
222
+ }
223
+ }
224
+
225
+ if (el.children) this.processMediaElementsAsync(el.children, isUserMessage)
184
226
  }
227
+ } catch (error) {
228
+ this.logger.error('处理媒体元素失败:', error)
185
229
  }
186
- // 视频和文件不再缓存,保持原始URL
187
- if (el.children) await this.processMediaElements(el.children, isUserMessage)
188
- }
189
- return elements
230
+ })
190
231
  }
191
232
 
192
- async broadcastMessageEvent(session: Session) {
233
+ private async processUserMessage(session: Session, timestamp?: number) {
193
234
  try {
194
- await this.updateBotInfoToFile(session)
195
- const guildName = await this.updateChannelInfoToFile(session)
196
- const isDirect = session.isDirect || session.channelId?.includes('private')
235
+ if (!timestamp) timestamp = Date.now()
236
+
237
+ this.updateBotInfoToFile(session)
197
238
 
198
- const timestamp = Date.now()
239
+ const guildName = this.updateChannelInfoToFile(session)
240
+ const isDirect = session.isDirect || session.channelId?.includes('private')
199
241
 
200
- // 处理媒体缓存 - 用户消息只缓存图片和语音
201
242
  if (session.elements) {
202
- await this.processMediaElements(session.elements, true)
243
+ this.processMediaElementsAsync(session.elements, true)
203
244
  }
204
245
  if (session.quote?.elements) {
205
- await this.processMediaElements(session.quote.elements, true)
246
+ this.processMediaElementsAsync(session.quote.elements, true)
206
247
  }
207
248
 
208
- // 处理 quote 信息
209
249
  let quoteInfo: QuoteInfo | undefined = undefined
210
250
  if (session.quote) {
211
251
  quoteInfo = {
@@ -224,7 +264,6 @@ export class MessageHandler {
224
264
  }
225
265
  }
226
266
 
227
- // 从 session 中提取用户消息内容
228
267
  let content = ''
229
268
  let elements: h[] = []
230
269
 
@@ -244,7 +283,6 @@ export class MessageHandler {
244
283
  }
245
284
  }
246
285
 
247
- // 创建消息信息对象
248
286
  const messageInfo: MessageInfo = {
249
287
  id: session.event?.message?.id || `msg-${timestamp}`,
250
288
  content: content || session.content || '',
@@ -288,47 +326,37 @@ export class MessageHandler {
288
326
 
289
327
  this.ctx.console.broadcast('chat-message-event', messageEvent)
290
328
  } catch (error) {
291
- this.logger.error('广播消息事件失败:', error)
329
+ this.logger.error('处理用户消息失败:', error)
292
330
  }
293
331
  }
294
332
 
295
- // 处理机器人发送的消息
296
- async broadcastBotMessageEvent(session: Session) {
333
+ private async processBotMessage(session: Session, timestamp?: number) {
297
334
  try {
298
- // 获取正确的 channelId
299
- const correctChannelId = this.getCorrectChannelId(session.selfId)
335
+ if (!timestamp) timestamp = Date.now()
300
336
 
301
- // 使用正确的 channelId,如果没有则使用 session.channelId
337
+ const correctChannelId = this.getCorrectChannelId(session.selfId)
302
338
  const finalChannelId = correctChannelId || session.channelId
303
339
 
304
- await this.updateBotInfoToFile(session)
305
- const guildName = await this.updateChannelInfoToFile(session)
306
- const isDirect = session.isDirect || finalChannelId?.includes('private')
307
-
308
- const timestamp = Date.now()
340
+ this.updateBotInfoToFile(session)
309
341
 
310
- // 机器人消息不缓存任何媒体
311
- // 保持原始URL,不进行缓存处理
342
+ const guildName = this.updateChannelInfoToFile(session)
343
+ const isDirect = session.isDirect || finalChannelId?.includes('private')
312
344
 
313
- // 优先使用 session.content,它包含了完整的消息内容(含标签)
314
345
  let content = session.content || ''
315
346
 
316
347
  if (!content && session.event?.message?.elements) {
317
348
  content = this.utils.extractTextContent(session.event.message.elements).trim()
318
349
  }
319
350
 
320
- // 尝试从 content 中提取 quote id 并构建 quote 对象
321
351
  let quoteInfo: QuoteInfo | undefined = undefined
322
352
  const quoteMatch = content.match(/<quote id="([^"]+)"\/>/)
323
353
  if (quoteMatch) {
324
354
  const quoteId = quoteMatch[1]
325
355
  const data = this.fileManager.readChatDataFromFile()
326
356
  const channelKey = `${session.selfId}:${finalChannelId}`
327
- // 在当前频道的消息历史中查找被引用的消息
328
357
  const quotedMsg = data.messages[channelKey]?.find(m => m.id === quoteId)
329
358
 
330
359
  if (quotedMsg) {
331
- // 如果是虚拟 ID,尝试获取其真实的 messageId
332
360
  const realId = quotedMsg.id.startsWith('bot-msg-') ? (quotedMsg as any).realId : quotedMsg.id
333
361
 
334
362
  quoteInfo = {
@@ -345,10 +373,8 @@ export class MessageHandler {
345
373
  },
346
374
  timestamp: quotedMsg.timestamp
347
375
  }
348
- // 移除 content 中的 quote 标签,避免重复显示
349
376
  content = content.replace(/<quote id="[^"]+"\/>\s*/, '')
350
377
 
351
- // 修正 content 中的 quote 标签为真实 ID,以便 bot.sendMessage 能够正确识别
352
378
  if (realId) {
353
379
  session.content = session.content.replace(/id="[^"]+"/, `id="${realId}"`)
354
380
  }
@@ -374,6 +400,7 @@ export class MessageHandler {
374
400
  sending: true // 标记为正在发送
375
401
  }
376
402
 
403
+ // 异步保存消息(不阻塞)
377
404
  await this.fileManager.addMessageToFile(messageInfo)
378
405
 
379
406
  const messageEvent = {
@@ -401,7 +428,7 @@ export class MessageHandler {
401
428
 
402
429
  this.ctx.console.broadcast('chat-bot-message-event', messageEvent)
403
430
  } catch (error) {
404
- this.logger.error('广播机器人消息事件失败:', error)
431
+ this.logger.error('处理机器人消息失败:', error)
405
432
  }
406
433
  }
407
434
 
package/src/types.ts CHANGED
@@ -1,62 +1,62 @@
1
- import { h } from 'koishi'
2
-
3
- export interface BotInfo {
4
- selfId: string
5
- platform: string
6
- username: string
7
- avatar?: string
8
- status: 'online' | 'offline'
9
- }
10
-
11
- export interface ChannelInfo {
12
- id: string
13
- name: string
14
- type: number | string
15
- channelId?: string
16
- guildName?: string
17
- isDirect?: boolean
18
- }
19
-
20
- export interface QuoteInfo {
21
- messageId: string
22
- id: string
23
- content: string
24
- elements?: h[]
25
- user: {
26
- id: string
27
- name: string
28
- userId: string
29
- avatar?: string
30
- username: string
31
- }
32
- timestamp: number
33
- }
34
-
35
- export interface MessageInfo {
36
- id: string
37
- content: string
38
- userId: string
39
- username: string
40
- avatar?: string
41
- timestamp: number
42
- channelId: string
43
- selfId: string
44
- elements?: h[]
45
- type: 'user' | 'bot'
46
- guildId?: string
47
- guildName?: string
48
- platform: string
49
- quote?: QuoteInfo
50
- isDirect?: boolean
51
- sending?: boolean
52
- realId?: string
53
- }
54
-
55
- export interface ChatData {
56
- bots: Record<string, BotInfo>
57
- channels: Record<string, Record<string, ChannelInfo>>
58
- messages: Record<string, MessageInfo[]>
59
- pinnedBots: string[]
60
- pinnedChannels: string[]
61
- lastSaveTime?: number
62
- }
1
+ import { h } from 'koishi'
2
+
3
+ export interface BotInfo {
4
+ selfId: string
5
+ platform: string
6
+ username: string
7
+ avatar?: string
8
+ status: 'online' | 'offline'
9
+ }
10
+
11
+ export interface ChannelInfo {
12
+ id: string
13
+ name: string
14
+ type: number | string
15
+ channelId?: string
16
+ guildName?: string
17
+ isDirect?: boolean
18
+ }
19
+
20
+ export interface QuoteInfo {
21
+ messageId: string
22
+ id: string
23
+ content: string
24
+ elements?: h[]
25
+ user: {
26
+ id: string
27
+ name: string
28
+ userId: string
29
+ avatar?: string
30
+ username: string
31
+ }
32
+ timestamp: number
33
+ }
34
+
35
+ export interface MessageInfo {
36
+ id: string
37
+ content: string
38
+ userId: string
39
+ username: string
40
+ avatar?: string
41
+ timestamp: number
42
+ channelId: string
43
+ selfId: string
44
+ elements?: h[]
45
+ type: 'user' | 'bot'
46
+ guildId?: string
47
+ guildName?: string
48
+ platform: string
49
+ quote?: QuoteInfo
50
+ isDirect?: boolean
51
+ sending?: boolean
52
+ realId?: string
53
+ }
54
+
55
+ export interface ChatData {
56
+ bots: Record<string, BotInfo>
57
+ channels: Record<string, Record<string, ChannelInfo>>
58
+ messages: Record<string, MessageInfo[]>
59
+ pinnedBots: string[]
60
+ pinnedChannels: string[]
61
+ lastSaveTime?: number
62
+ }
package/src/utils.ts CHANGED
@@ -8,7 +8,6 @@ import { pathToFileURL } from 'node:url'
8
8
  export class Utils {
9
9
  constructor(private config: Config, private ctx?: Context) { }
10
10
 
11
- // 检查平台是否被屏蔽
12
11
  isPlatformBlocked(platform: string): boolean {
13
12
  if (!this.config.blockedPlatforms || this.config.blockedPlatforms.length === 0) {
14
13
  return false
@@ -29,7 +28,6 @@ export class Utils {
29
28
  return false
30
29
  }
31
30
 
32
- // 递归提取所有文本内容的函数
33
31
  extractTextContent(elements: any[]): string {
34
32
  let text = ''
35
33
  for (const element of elements) {
@@ -46,16 +44,13 @@ export class Utils {
46
44
  return text
47
45
  }
48
46
 
49
- // 检查字符串是否为base64格式
50
47
  private isBase64(str: string): boolean {
51
48
  if (!str || typeof str !== 'string') return false
52
49
 
53
- // 检查是否以data:开头的base64格式
54
50
  if (str.startsWith('data:')) {
55
51
  return str.includes('base64,')
56
52
  }
57
53
 
58
- // 检查纯base64字符串(长度大于100且符合base64格式)
59
54
  if (str.length > 100) {
60
55
  const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/
61
56
  return base64Regex.test(str)
@@ -64,7 +59,6 @@ export class Utils {
64
59
  return false
65
60
  }
66
61
 
67
- // 持久化 base64 图片并返回本地文件 URL
68
62
  persistBase64Image(base64Data: string): string {
69
63
  if (!this.ctx || !base64Data.startsWith('data:image/')) return base64Data
70
64
 
@@ -72,17 +66,14 @@ export class Utils {
72
66
  const dir = join(this.ctx.baseDir, 'data', 'chat-patch', 'persist-images')
73
67
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
74
68
 
75
- // 生成文件名:时间戳 + 内容哈希
76
69
  const hash = createHash('md5').update(base64Data).digest('hex')
77
70
  const ext = base64Data.split(';')[0].split('/')[1] || 'png'
78
71
  const filename = `${Date.now()}_${hash}.${ext}`
79
72
  const filePath = join(dir, filename)
80
73
 
81
- // 写入文件
82
74
  const base64Content = base64Data.split(',')[1]
83
75
  writeFileSync(filePath, Buffer.from(base64Content, 'base64'))
84
76
 
85
- // 清理旧图片
86
77
  this.cleanupPersistImages(dir)
87
78
 
88
79
  return pathToFileURL(filePath).href
@@ -103,8 +94,6 @@ export class Utils {
103
94
  } catch (e) { }
104
95
  }
105
96
 
106
- // 清理对象中的base64内容,改为持久化存储
107
- // isBotMessage: 是否为机器人发送的消息
108
97
  cleanBase64Content(obj: any, isBotMessage: boolean = false): any {
109
98
  if (obj === null || obj === undefined) {
110
99
  return obj
@@ -112,7 +101,7 @@ export class Utils {
112
101
 
113
102
  if (typeof obj === 'string') {
114
103
  if (this.isBase64(obj)) {
115
- // 如果是机器人消息且是非图片的Base64,直接返回占位符
104
+
116
105
  if (isBotMessage && !obj.startsWith('data:image/')) {
117
106
  return '[富媒体内容已省略]'
118
107
  }
@@ -128,16 +117,15 @@ export class Utils {
128
117
  if (typeof obj === 'object') {
129
118
  const cleaned: any = {}
130
119
  for (const [key, value] of Object.entries(obj)) {
131
- // 检查元素类型,如果是机器人消息的非图片元素,跳过Base64处理
120
+
132
121
  if (isBotMessage && obj.type && !['text', 'image', 'img'].includes(obj.type)) {
133
- // 对于video、audio等元素,如果src是Base64,替换为占位符
122
+
134
123
  if (typeof value === 'string' && (key === 'src' || key === 'url' || key === 'file') && this.isBase64(value)) {
135
124
  cleaned[key] = '[富媒体内容已省略]'
136
125
  continue
137
126
  }
138
127
  }
139
128
 
140
- // 特别处理常见的base64字段
141
129
  if (typeof value === 'string' && (
142
130
  key === 'src' ||
143
131
  key === 'url' ||
@@ -145,7 +133,7 @@ export class Utils {
145
133
  key === 'data' ||
146
134
  key === 'content'
147
135
  ) && this.isBase64(value)) {
148
- // 如果是机器人消息且不是图片Base64,跳过
136
+
149
137
  if (isBotMessage && !value.startsWith('data:image/')) {
150
138
  cleaned[key] = '[富媒体内容已省略]'
151
139
  } else {