koishi-plugin-chat-patch 2.0.1 → 2.1.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.
@@ -34,23 +34,20 @@ export class ApiHandlers {
34
34
  // 获取所有聊天数据的 API
35
35
  this.ctx.console.addListener('get-chat-data' as any, async () => {
36
36
  try {
37
+ // 优化性能:只读取基础信息,不读取庞大的消息体
37
38
  const data = this.fileManager.readChatDataFromFile()
38
- const cleanedData = this.fileManager.cleanExcessMessages(data)
39
-
40
- this.logInfo('获取聊天数据:', {
41
- 机器人数量: Object.keys(cleanedData.bots).length,
42
- 频道数量: Object.keys(cleanedData.channels).reduce((total, botId) =>
43
- total + Object.keys(cleanedData.channels[botId] || {}).length, 0),
44
- 消息频道数: Object.keys(cleanedData.messages).length,
45
- 总消息数: Object.values(cleanedData.messages).reduce((total, msgs) => total + msgs.length, 0)
46
- })
39
+
40
+ this.logInfo('获取基础聊天数据')
47
41
 
48
42
  return {
49
43
  success: true,
50
44
  data: {
51
- ...cleanedData,
52
- pinnedBots: cleanedData.pinnedBots,
53
- pinnedChannels: cleanedData.pinnedChannels
45
+ bots: data.bots || {},
46
+ channels: data.channels || {},
47
+ pinnedBots: data.pinnedBots || [],
48
+ pinnedChannels: data.pinnedChannels || [],
49
+ // 不返回 messages,由前端按需拉取
50
+ messages: {}
54
51
  }
55
52
  }
56
53
  } catch (error: any) {
@@ -257,32 +254,31 @@ export class ApiHandlers {
257
254
  const result = await bot.sendMessage(data.channelId, parsedContent)
258
255
  this.logInfo('消息发送成功:', result)
259
256
 
260
- // 广播机器人消息发送成功事件,包含真实的 messageId
261
257
  const messageId = Array.isArray(result) ? result[0] : result;
258
+
259
+ // 发送成功后,更新本地存储中的虚拟消息为真实 ID
262
260
  if (messageId) {
263
- const bot = this.ctx.bots.find((b: any) => b.selfId === data.selfId);
264
- const botMessageEvent = {
265
- type: 'bot-message-sent',
266
- selfId: data.selfId,
267
- platform: bot?.platform || 'unknown',
268
- channelId: data.channelId,
269
- messageId: messageId,
270
- content: messageContent,
271
- userId: data.selfId,
272
- username: bot?.user?.name || `Bot-${data.selfId}`,
273
- avatar: bot?.user?.avatar,
274
- timestamp: Date.now(),
275
- guildName: '', // 这个信息在前端会补充
276
- channelType: 0, // 这个信息在前端会补充
277
- elements: [], // 这个信息在前端会补充
278
- isDirect: false // 这个信息在前端会补充
279
- };
280
-
281
- this.ctx.console.broadcast('bot-message-sent-event', botMessageEvent);
261
+ const chatData = this.fileManager.readChatDataFromFile()
262
+ const channelKey = `${data.selfId}:${data.channelId}`
263
+ const messages = chatData.messages[channelKey] || []
264
+ // 找到最近的一条正在发送的机器人消息
265
+ const msg = [...messages].reverse().find(m => m.type === 'bot' && m.sending)
266
+ if (msg) {
267
+ msg.realId = messageId;
268
+ msg.sending = false;
269
+ this.fileManager.writeChatDataToFile(chatData)
270
+
271
+ // 广播更新事件给前端
272
+ this.ctx.console.broadcast('bot-message-updated', {
273
+ channelKey,
274
+ tempId: msg.id,
275
+ realId: messageId
276
+ })
277
+ }
282
278
  }
283
279
 
284
280
  return {
285
- success: true,
281
+ success: !!messageId,
286
282
  messageId: messageId,
287
283
  tempImageIds: data.images?.map(img => img.tempId) || []
288
284
  }
@@ -515,6 +511,77 @@ export class ApiHandlers {
515
511
  }
516
512
  })
517
513
 
514
+ // 获取用户信息 API
515
+ this.ctx.console.addListener('get-user-info' as any, async (data: { selfId: string, userId: string, guildId?: string }) => {
516
+ try {
517
+ const bot = this.ctx.bots.find(b => b.selfId === data.selfId)
518
+ if (!bot) return { success: false, error: '机器人不存在' }
519
+
520
+ const user = await bot.getUser(data.userId, data.guildId)
521
+
522
+ if (user) {
523
+ // 缓存头像到本地
524
+ if (user.avatar) {
525
+ user.avatar = await this.messageHandler.downloadAndCacheMedia(user.avatar, 'avatar')
526
+ }
527
+
528
+ const chatData = this.fileManager.readChatDataFromFile()
529
+ let changed = false
530
+
531
+ // 1. 如果是私聊,更新频道名 - 支持多种私聊ID格式
532
+ const botChannels = chatData.channels[data.selfId] || {}
533
+
534
+ // 查找所有可能的私聊频道ID格式
535
+ const possibleChannelIds = [
536
+ data.userId,
537
+ `private:${data.userId}`,
538
+ `direct:${data.userId}`
539
+ ]
540
+
541
+ for (const channelId of possibleChannelIds) {
542
+ const channel = botChannels[channelId]
543
+ if (channel && channel.isDirect) {
544
+ const newName = `私聊(${user.name})`
545
+ if (channel.name !== newName) {
546
+ channel.name = newName
547
+ changed = true
548
+ this.logInfo('更新私聊频道名称:', { channelId, oldName: channel.name, newName })
549
+ }
550
+ }
551
+ }
552
+
553
+ // 2. 更新该用户在所有历史消息中的头像和昵称(持久化缓存)
554
+ const channelKeyPrefix = `${data.selfId}:`
555
+ for (const [key, messages] of Object.entries(chatData.messages)) {
556
+ if (key.startsWith(channelKeyPrefix)) {
557
+ messages.forEach(msg => {
558
+ if (msg.userId === data.userId) {
559
+ if (user.name && msg.username !== user.name) {
560
+ msg.username = user.name
561
+ changed = true
562
+ }
563
+ if (user.avatar && msg.avatar !== user.avatar) {
564
+ msg.avatar = user.avatar
565
+ changed = true
566
+ }
567
+ }
568
+ });
569
+ }
570
+ }
571
+
572
+ if (changed) {
573
+ this.fileManager.writeChatDataToFile(chatData)
574
+ // 广播更新事件,让前端实时刷新
575
+ this.ctx.console.broadcast('chat-data-updated', {})
576
+ }
577
+ }
578
+
579
+ return { success: true, data: user }
580
+ } catch (error: any) {
581
+ return { success: false, error: error?.message || '获取用户信息失败' }
582
+ }
583
+ })
584
+
518
585
  // 调试API:获取原始文件数据
519
586
  this.ctx.console.addListener('debug-get-raw-data' as any, async () => {
520
587
  try {
@@ -585,75 +652,36 @@ export class ApiHandlers {
585
652
  private setupTempFileCleanup() {
586
653
  // 每5分钟执行一次基于数量的清理
587
654
  setInterval(() => {
588
- this.cleanupTempImagesByCount()
589
- }, 5 * 60 * 1000) // 5分钟
655
+ this.cleanupMediaCache()
656
+ }, 5 * 60 * 1000)
590
657
  }
591
658
 
592
- // 基于数量清理临时图片(保留最新的N张)
593
- private async cleanupTempImagesByCount() {
594
- try {
595
- const tempDir = this.ctx.baseDir + '/data/chat-patch/temp'
596
- if (!require('fs').existsSync(tempDir)) {
597
- return
598
- }
659
+ // 统一清理媒体缓存
660
+ private async cleanupMediaCache() {
661
+ const baseDir = this.ctx.baseDir + '/data/chat-patch/persist-media'
662
+ if (!require('node:fs').existsSync(baseDir)) return
599
663
 
600
- const now = Date.now()
601
- const protectionTime = 30 * 1000 // 30秒保护期,刚上传的图片不会被删除
664
+ const cleanupDir = (dirName: string, limit: number) => {
665
+ const dirPath = require('node:path').join(baseDir, dirName)
666
+ if (!require('node:fs').existsSync(dirPath)) return
602
667
 
603
- const files = require('fs').readdirSync(tempDir)
604
- .filter((file: string) => file.startsWith('temp_'))
605
- .map((file: string) => {
606
- const filePath = `${tempDir}/${file}`
607
- try {
608
- const stats = require('fs').statSync(filePath)
609
- const fileAge = now - stats.mtime.getTime()
610
- return {
611
- name: file,
612
- path: filePath,
613
- mtime: stats.mtime.getTime(),
614
- age: fileAge,
615
- protected: fileAge < protectionTime // 是否在保护期内
616
- }
617
- } catch (error) {
618
- return null
619
- }
668
+ const files = require('node:fs').readdirSync(dirPath)
669
+ .map(file => {
670
+ const filePath = require('node:path').join(dirPath, file)
671
+ const stats = require('node:fs').statSync(filePath)
672
+ return { name: file, path: filePath, mtime: stats.mtimeMs }
620
673
  })
621
- .filter((file: any) => file !== null)
622
- .sort((a: any, b: any) => b.mtime - a.mtime) // 按修改时间降序排列(最新的在前)
623
-
624
- const keepCount = this.config.keepTempImages
625
- let cleanedCount = 0
626
- let protectedCount = 0
627
-
628
- // 分离受保护的文件和可删除的文件
629
- const protectedFiles = files.filter((file: any) => file.protected)
630
- const deletableFiles = files.filter((file: any) => !file.protected)
674
+ .sort((a, b) => b.mtime - a.mtime)
631
675
 
632
- protectedCount = protectedFiles.length
633
-
634
- // 如果可删除的文件数量超过了保留数量,则删除多余的
635
- if (deletableFiles.length > keepCount) {
636
- const filesToDelete = deletableFiles.slice(keepCount) // 保留前N个,删除其余的
637
-
638
- for (const file of filesToDelete) {
639
- try {
640
- if (require('fs').existsSync(file.path)) {
641
- require('fs').unlinkSync(file.path)
642
- cleanedCount++
643
- this.logInfo('清理多余临时图片:', file.path)
644
- }
645
- } catch (fileError) {
646
- this.logger.warn('删除临时文件失败:', { file: file.name, error: fileError })
647
- }
648
- }
649
- }
650
-
651
- if (cleanedCount > 0 || protectedCount > 0) {
652
- this.logInfo(`基于数量清理完成,保留最新 ${keepCount} 张图片,清理了 ${cleanedCount} 张多余图片,保护了 ${protectedCount} 张新上传图片`)
676
+ if (files.length > limit) {
677
+ files.slice(limit).forEach(f => {
678
+ try { require('node:fs').unlinkSync(f.path) } catch { }
679
+ })
653
680
  }
654
- } catch (error) {
655
- this.logger.warn('基于数量清理临时文件失败:', error)
656
681
  }
682
+
683
+ cleanupDir('images', 100) // 图片缓存100个
684
+ cleanupDir('media', 20) // 富媒体缓存20个
657
685
  }
658
686
 
659
687
  private logInfo(...args: any[]) {
@@ -50,55 +50,75 @@ export class MessageHandler {
50
50
 
51
51
  // 更新频道信息到JSON文件
52
52
  async updateChannelInfoToFile(session: Session): Promise<string> {
53
+ const isDirect = session.isDirect || session.channelId?.includes('private')
53
54
  let guildName = session.channelId
54
- let directUserName = session.username || session.userId || '未知用户'
55
+ // 直接使用 session.username 作为私聊用户名
56
+ const directUserName = session.username || session.event?.user?.name || session.userId
55
57
 
56
58
  const data = this.fileManager.readChatDataFromFile()
57
59
 
58
- try {
59
- // 获取群组名称
60
- if (session.guildId && session.bot.getGuild && typeof session.bot.getGuild === 'function') {
61
- const guild = await session.bot.getGuild(session.guildId)
62
- guildName = guild?.name || session.channelId
63
- }
64
-
65
- // 获取私聊用户昵称
66
- if (session.userId && session.isDirect && session.bot.getUser && typeof session.bot.getUser === 'function') {
67
- try {
68
- const user = await session.bot.getUser(session.userId)
69
- directUserName = user?.name || session.username || session.userId || '未知用户'
70
- } catch (userError) {
71
- this.logInfo('获取用户信息失败,使用备用名称:', userError)
60
+ if (!isDirect) {
61
+ 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
72
66
  }
73
- }
74
67
 
75
- // 如果没有getGuild方法,尝试使用getChannel方法
76
- if (session.guildId && !session.bot.getGuild && session.bot.getChannel && typeof session.bot.getChannel === 'function') {
77
- try {
78
- const channel = await session.bot.getChannel(session.guildId)
79
- guildName = channel?.name || session.channelId
80
- } catch (channelError) {
81
- this.logInfo('获取频道信息失败,使用频道ID作为备用:', channelError)
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
+ }
82
76
  }
77
+ } catch (error) {
78
+ this.logInfo('获取频道信息失败,使用频道ID作为备用:', error)
79
+ guildName = session.channelId
83
80
  }
84
- } catch (error) {
85
- this.logInfo('获取频道信息失败,使用频道ID作为备用:', error)
86
- guildName = session.channelId
87
81
  }
88
82
 
89
83
  if (!data.channels[session.selfId]) {
90
84
  data.channels[session.selfId] = {}
91
85
  }
92
86
 
87
+ // 获取现有频道信息
88
+ const existingChannel = data.channels[session.selfId][session.channelId]
89
+
90
+ // 构造频道名称
91
+ let finalName: string
92
+ if (isDirect) {
93
+ // 私聊频道:优先使用真实用户名
94
+ if (directUserName && directUserName !== session.userId) {
95
+ finalName = `私聊(${directUserName})`
96
+ } else if (existingChannel?.name && !existingChannel.name.includes('未知')) {
97
+ // 如果已有名称且不是"未知用户",保持原名称
98
+ finalName = existingChannel.name
99
+ } else {
100
+ finalName = '私聊(未知用户)'
101
+ }
102
+ } else {
103
+ finalName = guildName || session.channelId
104
+ }
105
+
93
106
  const channelInfo: ChannelInfo = {
94
107
  id: session.channelId,
95
- name: session.isDirect
96
- ? `私聊(${session.username || session.userId || directUserName})`
97
- : `${guildName} (${session.channelId})`,
108
+ name: finalName,
98
109
  type: session.type || 0,
99
110
  channelId: session.channelId,
100
111
  guildName: guildName,
101
- isDirect: session.isDirect
112
+ isDirect: !!isDirect
113
+ }
114
+
115
+ // 记录名称变化
116
+ if (existingChannel && existingChannel.name !== finalName) {
117
+ this.logInfo('更新频道名称:', {
118
+ channelId: session.channelId,
119
+ oldName: existingChannel.name,
120
+ newName: finalName
121
+ })
102
122
  }
103
123
 
104
124
  data.channels[session.selfId][session.channelId] = channelInfo
@@ -108,13 +128,72 @@ export class MessageHandler {
108
128
  return guildName
109
129
  }
110
130
 
131
+ // 下载并缓存媒体文件
132
+ public async downloadAndCacheMedia(url: string, type: 'image' | 'media' | 'avatar') {
133
+ try {
134
+ if (!url || url.startsWith('data:') || url.startsWith('file:')) return url
135
+
136
+ let folder = 'media'
137
+ if (type === 'image') folder = 'images'
138
+ else if (type === 'avatar') folder = 'avatars'
139
+
140
+ const dir = require('node:path').join(this.ctx.baseDir, 'data', 'chat-patch', 'persist-media', folder)
141
+ if (!require('node:fs').existsSync(dir)) {
142
+ require('node:fs').mkdirSync(dir, { recursive: true })
143
+ }
144
+
145
+ // 使用 URL 的 hash 作为文件名,避免重复下载
146
+ const crypto = require('node:crypto')
147
+ const hash = crypto.createHash('md5').update(url).digest('hex')
148
+ const ext = require('node:path').extname(new URL(url).pathname) || (type === 'image' ? '.jpg' : '.mp4')
149
+ const filename = `${hash}${ext}`
150
+ const filePath = require('node:path').join(dir, filename)
151
+
152
+ if (require('node:fs').existsSync(filePath)) {
153
+ return require('node:url').pathToFileURL(filePath).href
154
+ }
155
+
156
+ const buffer = await this.ctx.http.get(url, { responseType: 'arraybuffer' })
157
+ require('node:fs').writeFileSync(filePath, Buffer.from(buffer))
158
+
159
+ return require('node:url').pathToFileURL(filePath).href
160
+ } catch (e) {
161
+ return url
162
+ }
163
+ }
164
+
165
+ // 处理消息中的媒体元素并缓存
166
+ private async processMediaElements(elements: h[]) {
167
+ if (!elements) return elements
168
+ for (const el of elements) {
169
+ if (['image', 'img', 'mface'].includes(el.type)) {
170
+ const src = el.attrs.src || el.attrs.url || el.attrs.file
171
+ if (src) el.attrs.src = await this.downloadAndCacheMedia(src, 'image')
172
+ } else if (['audio', 'video'].includes(el.type)) {
173
+ const src = el.attrs.src || el.attrs.url || el.attrs.file
174
+ if (src) el.attrs.src = await this.downloadAndCacheMedia(src, 'media')
175
+ }
176
+ if (el.children) await this.processMediaElements(el.children)
177
+ }
178
+ return elements
179
+ }
180
+
111
181
  async broadcastMessageEvent(session: Session) {
112
182
  try {
113
183
  await this.updateBotInfoToFile(session)
114
184
  const guildName = await this.updateChannelInfoToFile(session)
185
+ const isDirect = session.isDirect || session.channelId?.includes('private')
115
186
 
116
187
  const timestamp = Date.now()
117
188
 
189
+ // 处理媒体缓存
190
+ if (session.elements) {
191
+ await this.processMediaElements(session.elements)
192
+ }
193
+ if (session.quote?.elements) {
194
+ await this.processMediaElements(session.quote.elements)
195
+ }
196
+
118
197
  // 处理 quote 信息
119
198
  let quoteInfo: QuoteInfo | undefined = undefined
120
199
  if (session.quote) {
@@ -169,7 +248,7 @@ export class MessageHandler {
169
248
  guildName: guildName,
170
249
  platform: session.platform || 'unknown',
171
250
  quote: quoteInfo ? this.utils.cleanBase64Content(quoteInfo) : undefined,
172
- isDirect: session.isDirect
251
+ isDirect: !!isDirect
173
252
  }
174
253
 
175
254
  await this.fileManager.addMessageToFile(messageInfo)
@@ -208,24 +287,20 @@ export class MessageHandler {
208
287
  // 获取正确的 channelId
209
288
  const correctChannelId = this.getCorrectChannelId(session.selfId)
210
289
 
211
- // 调试日志:检查 channelId 的来源
212
- this.logInfo('机器人发送消息调试信息:', {
213
- 'session.channelId': session.channelId,
214
- 'session.event?.channel?.id': session.event?.channel?.id,
215
- 'correctChannelId': correctChannelId,
216
- 'session.guildId': session.guildId,
217
- 'session.isDirect': session.isDirect,
218
- 'session.platform': session.platform
219
- })
220
-
221
290
  // 使用正确的 channelId,如果没有则使用 session.channelId
222
291
  const finalChannelId = correctChannelId || session.channelId
223
292
 
224
293
  await this.updateBotInfoToFile(session)
225
294
  const guildName = await this.updateChannelInfoToFile(session)
295
+ const isDirect = session.isDirect || finalChannelId?.includes('private')
226
296
 
227
297
  const timestamp = Date.now()
228
298
 
299
+ // 处理媒体缓存
300
+ if (session.event?.message?.elements) {
301
+ await this.processMediaElements(session.event.message.elements)
302
+ }
303
+
229
304
  // 优先使用 session.content,它包含了完整的消息内容(含标签)
230
305
  let content = session.content || ''
231
306
 
@@ -233,6 +308,44 @@ export class MessageHandler {
233
308
  content = this.utils.extractTextContent(session.event.message.elements).trim()
234
309
  }
235
310
 
311
+ // 尝试从 content 中提取 quote id 并构建 quote 对象
312
+ let quoteInfo: QuoteInfo | undefined = undefined
313
+ const quoteMatch = content.match(/<quote id="([^"]+)"\/>/)
314
+ if (quoteMatch) {
315
+ const quoteId = quoteMatch[1]
316
+ const data = this.fileManager.readChatDataFromFile()
317
+ const channelKey = `${session.selfId}:${finalChannelId}`
318
+ // 在当前频道的消息历史中查找被引用的消息
319
+ const quotedMsg = data.messages[channelKey]?.find(m => m.id === quoteId)
320
+
321
+ if (quotedMsg) {
322
+ // 如果是虚拟 ID,尝试获取其真实的 messageId
323
+ const realId = quotedMsg.id.startsWith('bot-msg-') ? (quotedMsg as any).realId : quotedMsg.id
324
+
325
+ quoteInfo = {
326
+ messageId: realId || quotedMsg.id,
327
+ id: realId || quotedMsg.id,
328
+ content: quotedMsg.content,
329
+ elements: quotedMsg.elements,
330
+ user: {
331
+ id: quotedMsg.userId,
332
+ name: quotedMsg.username,
333
+ userId: quotedMsg.userId,
334
+ avatar: quotedMsg.avatar,
335
+ username: quotedMsg.username
336
+ },
337
+ timestamp: quotedMsg.timestamp
338
+ }
339
+ // 移除 content 中的 quote 标签,避免重复显示
340
+ content = content.replace(/<quote id="[^"]+"\/>\s*/, '')
341
+
342
+ // 修正 content 中的 quote 标签为真实 ID,以便 bot.sendMessage 能够正确识别
343
+ if (realId) {
344
+ session.content = session.content.replace(/id="[^"]+"/, `id="${realId}"`)
345
+ }
346
+ }
347
+ }
348
+
236
349
  // 创建机器人消息信息对象
237
350
  const messageInfo: MessageInfo = {
238
351
  id: `bot-msg-${timestamp}`,
@@ -247,7 +360,9 @@ export class MessageHandler {
247
360
  type: 'bot',
248
361
  guildName: guildName,
249
362
  platform: session.platform || 'unknown',
250
- isDirect: session.isDirect
363
+ quote: quoteInfo,
364
+ isDirect: !!isDirect,
365
+ sending: true // 标记为正在发送
251
366
  }
252
367
 
253
368
  await this.fileManager.addMessageToFile(messageInfo)
@@ -266,32 +381,15 @@ export class MessageHandler {
266
381
  guildName: guildName,
267
382
  channelType: session.event?.channel?.type || session.type || 0,
268
383
  elements: this.utils.cleanBase64Content(session.event?.message?.elements),
269
- isDirect: session.isDirect,
384
+ quote: quoteInfo,
385
+ isDirect: !!isDirect,
386
+ sending: true,
270
387
  bot: {
271
388
  avatar: session.bot.user?.avatar,
272
389
  name: session.bot.user?.name,
273
390
  }
274
391
  }
275
392
 
276
- // 检查是否包含图片元素
277
- const imageElements = session.event?.message?.elements?.filter((element: any) =>
278
- element.type === 'img' || element.type === 'image' || element.type === 'mface'
279
- ) || []
280
-
281
- this.logInfo('机器人发送消息 (before-send):', {
282
- selfId: session.selfId,
283
- channelId: messageEvent.channelId,
284
- originalChannelId: session.channelId,
285
- correctedChannelId: finalChannelId,
286
- content: content,
287
- platform: session.platform,
288
- imageCount: imageElements.length,
289
- imageUrls: imageElements.map((el: any) => el.attrs?.src || el.attrs?.url || el.attrs?.file)
290
- })
291
-
292
- // 清理已使用的 channelId 映射
293
- this.correctChannelIds.delete(session.selfId)
294
-
295
393
  this.ctx.console.broadcast('chat-bot-message-event', messageEvent)
296
394
  } catch (error) {
297
395
  this.logger.error('广播机器人消息事件失败:', error)
package/src/types.ts CHANGED
@@ -48,6 +48,8 @@ export interface MessageInfo {
48
48
  platform: string
49
49
  quote?: QuoteInfo
50
50
  isDirect?: boolean
51
+ sending?: boolean
52
+ realId?: string
51
53
  }
52
54
 
53
55
  export interface ChatData {