koishi-plugin-chat-patch 2.2.0 → 2.4.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/src/index.ts CHANGED
@@ -38,7 +38,6 @@ export { Config } from './config'
38
38
  export async function apply(ctx: Context, config: Config) {
39
39
  const logger = ctx.logger('chat-patch')
40
40
 
41
- // 启动时清空 media 文件夹(为旧版本做清理工作)
42
41
  const mediaDir = path.join(ctx.baseDir, 'data', 'chat-patch', 'persist-media', 'media')
43
42
  if (require('node:fs').existsSync(mediaDir)) {
44
43
  try {
@@ -61,17 +60,14 @@ export async function apply(ctx: Context, config: Config) {
61
60
  }
62
61
  }
63
62
 
64
- // 初始化各个模块
65
63
  const fileManager = new FileManager(ctx, config)
66
64
  const messageHandler = new MessageHandler(ctx, config, fileManager)
67
65
  const apiHandlers = new ApiHandlers(ctx, config, fileManager, messageHandler)
68
66
  const utils = new Utils(config, ctx)
69
67
 
70
- // 初始化数据
71
68
  const initialData = fileManager.readChatDataFromFile()
72
69
  const cleanedData = fileManager.cleanExcessMessages(initialData)
73
70
 
74
- // 如果清理了数据,立即写回文件
75
71
  const originalCount = Object.values(initialData.messages).reduce((total, msgs) => total + msgs.length, 0)
76
72
  const cleanedCount = Object.values(cleanedData.messages).reduce((total, msgs) => total + msgs.length, 0)
77
73
 
@@ -79,7 +75,6 @@ export async function apply(ctx: Context, config: Config) {
79
75
  fileManager.writeChatDataToFile(cleanedData)
80
76
  }
81
77
 
82
- // 日志调试函数
83
78
  function logInfo(...args: any[]) {
84
79
  if (config.loggerinfo) {
85
80
  (logger.info as (...args: any[]) => void)(...args)
@@ -94,39 +89,67 @@ export async function apply(ctx: Context, config: Config) {
94
89
  总消息数: Object.values(cleanedData.messages).reduce((total, msgs) => total + msgs.length, 0)
95
90
  })
96
91
 
97
- // 监听用户消息
98
- ctx.on('message', async (session) => {
99
- // 检查平台是否被屏蔽
100
- if (utils.isPlatformBlocked(session.platform || 'unknown')) {
101
- logInfo(`忽略来自被屏蔽平台的消息: ${session.platform}`)
102
- return
103
- }
104
- // logInfo(session)
105
- // 广播消息事件给前端处理
106
- await messageHandler.broadcastMessageEvent(session)
92
+ ctx.on('message', (session) => {
93
+ if (utils.isPlatformBlocked(session.platform || 'unknown')) return
94
+
95
+ const timestamp = Date.now()
96
+
97
+ ctx.console.broadcast('chat-message-event', {
98
+ type: 'message',
99
+ selfId: session.selfId,
100
+ platform: session.platform || 'unknown',
101
+ channelId: session.channelId,
102
+ messageId: session.event?.message?.id || `msg-${timestamp}`,
103
+ content: session.content || '',
104
+ userId: session.userId || 'unknown',
105
+ username: session.username || session.userId || 'unknown',
106
+ avatar: session.event?.user?.avatar,
107
+ timestamp: timestamp,
108
+ isDirect: session.isDirect,
109
+ elements: utils.cleanBase64Content(session.elements, false),
110
+ bot: {
111
+ avatar: session.bot.user?.avatar,
112
+ name: session.bot.user?.name,
113
+ }
114
+ })
115
+
116
+ messageHandler.recordUserMessage(session, timestamp)
107
117
  })
108
118
 
109
- // 监听机器人发送的消息
110
- ctx.on('before-send', async (session) => {
111
- // 检查平台是否被屏蔽
112
- if (utils.isPlatformBlocked(session.platform || 'unknown')) {
113
- logInfo(`忽略来自被屏蔽平台的机器人消息: ${session.platform}`)
114
- return
115
- }
116
- // 广播机器人消息事件给前端处理
117
- await messageHandler.broadcastBotMessageEvent(session)
119
+ ctx.on('before-send', (session) => {
120
+ if (utils.isPlatformBlocked(session.platform || 'unknown')) return
121
+
122
+ const timestamp = Date.now()
123
+
124
+ ctx.console.broadcast('chat-bot-message-event', {
125
+ type: 'bot-message',
126
+ selfId: session.selfId,
127
+ platform: session.platform || 'unknown',
128
+ channelId: session.channelId,
129
+ messageId: `bot-msg-${timestamp}`,
130
+ content: session.content || '',
131
+ userId: session.selfId,
132
+ username: session.bot.user?.name || `Bot-${session.selfId}`,
133
+ avatar: session.bot.user?.avatar,
134
+ timestamp: timestamp,
135
+ sending: true,
136
+ elements: utils.cleanBase64Content(session.event?.message?.elements, true),
137
+ bot: {
138
+ avatar: session.bot.user?.avatar,
139
+ name: session.bot.user?.name,
140
+ }
141
+ })
142
+
143
+ messageHandler.recordBotMessage(session, timestamp)
118
144
  })
119
145
 
120
- // 插件启动时设置定期清理过期消息
121
146
  ctx.on('ready', async () => {
122
147
  logInfo('插件启动完成,开始监听消息')
123
148
 
124
- // 定期清理超量消息
125
- setInterval(() => {
149
+ ctx.setInterval(() => {
126
150
  const data = fileManager.readChatDataFromFile()
127
151
  const cleanedData = fileManager.cleanExcessMessages(data)
128
152
 
129
- // 如果有消息被清理,写回文件
130
153
  const originalCount = Object.values(data.messages).reduce((total, msgs) => total + msgs.length, 0)
131
154
  const cleanedCount = Object.values(cleanedData.messages).reduce((total, msgs) => total + msgs.length, 0)
132
155
 
@@ -134,15 +157,19 @@ export async function apply(ctx: Context, config: Config) {
134
157
  fileManager.writeChatDataToFile(cleanedData)
135
158
  logInfo('定期清理完成,清理了', originalCount - cleanedCount, '条超量消息')
136
159
  }
137
- }, 300000) // 每5分钟清理一次
160
+ }, 300000)
138
161
  })
139
162
 
140
- // 注册所有 API 处理器
141
163
  apiHandlers.registerApiHandlers()
142
164
 
143
- // 注册控制台页面
144
165
  ctx.console.addEntry({
145
166
  dev: path.resolve(__dirname, '../client/index.ts'),
146
167
  prod: path.resolve(__dirname, '../dist'),
147
168
  })
169
+
170
+ ctx.on('dispose', () => {
171
+
172
+ fileManager.dispose()
173
+ logInfo('插件已卸载,所有待处理的消息已写入')
174
+ })
148
175
  }
@@ -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