koishi-plugin-chat-patch 2.1.1 → 2.2.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/client/vue/chat-logic.ts +683 -678
- package/client/vue/composables/useVideoCache.ts +130 -0
- package/client/vue/index.vue +658 -612
- package/dist/index.js +2 -2
- package/lib/api-handlers.d.ts +1 -0
- package/lib/index.js +91 -20
- package/lib/utils.d.ts +13 -0
- package/package.json +52 -49
- package/src/api-handlers.ts +750 -704
- package/src/index.ts +148 -125
- package/src/message-handler.ts +413 -407
- package/src/utils.ts +163 -144
package/src/message-handler.ts
CHANGED
|
@@ -1,407 +1,413 @@
|
|
|
1
|
-
import { BotInfo, ChannelInfo, MessageInfo, QuoteInfo } from './types'
|
|
2
|
-
import { Context, Session, h, Logger } from 'koishi'
|
|
3
|
-
import { FileManager } from './file-manager'
|
|
4
|
-
import { Config } from './config'
|
|
5
|
-
import { Utils } from './utils'
|
|
6
|
-
import { } from '@koishijs/plugin-console'
|
|
7
|
-
|
|
8
|
-
export class MessageHandler {
|
|
9
|
-
private logger: Logger
|
|
10
|
-
private utils: Utils
|
|
11
|
-
// 存储正确的 channelId 映射,key 是 selfId,value 是正确的 channelId
|
|
12
|
-
private correctChannelIds: Map<string, string> = new Map()
|
|
13
|
-
|
|
14
|
-
constructor(
|
|
15
|
-
private ctx: Context,
|
|
16
|
-
private config: Config,
|
|
17
|
-
private fileManager: FileManager
|
|
18
|
-
) {
|
|
19
|
-
this.logger = ctx.logger('chat-patch')
|
|
20
|
-
this.utils = new Utils(config)
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// 设置正确的 channelId
|
|
24
|
-
setCorrectChannelId(selfId: string, channelId: string) {
|
|
25
|
-
this.correctChannelIds.set(selfId, channelId)
|
|
26
|
-
this.logInfo('设置正确的 channelId:', { selfId, channelId })
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// 获取正确的 channelId
|
|
30
|
-
getCorrectChannelId(selfId: string): string | undefined {
|
|
31
|
-
return this.correctChannelIds.get(selfId)
|
|
32
|
-
}
|
|
33
|
-
|
|
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
|
|
57
|
-
|
|
58
|
-
const data = this.fileManager.readChatDataFromFile()
|
|
59
|
-
|
|
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
|
|
66
|
-
}
|
|
67
|
-
|
|
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
|
-
}
|
|
76
|
-
}
|
|
77
|
-
} catch (error) {
|
|
78
|
-
this.logInfo('获取频道信息失败,使用频道ID作为备用:', error)
|
|
79
|
-
guildName = session.channelId
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (!data.channels[session.selfId]) {
|
|
84
|
-
data.channels[session.selfId] = {}
|
|
85
|
-
}
|
|
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 if (session.platform && session.platform.toLowerCase().includes('sandbox')) {
|
|
100
|
-
// sandbox 平台直接使用 userId 作为频道名称
|
|
101
|
-
finalName = `私聊(${session.userId})`
|
|
102
|
-
} else {
|
|
103
|
-
finalName = '私聊(未知用户)'
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
106
|
-
finalName = guildName || session.channelId
|
|
107
|
-
}
|
|
108
|
-
|
|
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
|
-
}
|
|
117
|
-
|
|
118
|
-
// 记录名称变化
|
|
119
|
-
if (existingChannel && existingChannel.name !== finalName) {
|
|
120
|
-
this.logInfo('更新频道名称:', {
|
|
121
|
-
channelId: session.channelId,
|
|
122
|
-
oldName: existingChannel.name,
|
|
123
|
-
newName: finalName
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
data.channels[session.selfId][session.channelId] = channelInfo
|
|
128
|
-
this.fileManager.writeChatDataToFile(data)
|
|
129
|
-
this.logInfo('更新频道信息到文件:', channelInfo.name)
|
|
130
|
-
|
|
131
|
-
return guildName
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// 下载并缓存媒体文件
|
|
135
|
-
public async downloadAndCacheMedia(url: string, type: 'image' | 'media' | 'avatar') {
|
|
136
|
-
try {
|
|
137
|
-
if (!url || url.startsWith('data:') || url.startsWith('file:')) return url
|
|
138
|
-
|
|
139
|
-
let folder = 'media'
|
|
140
|
-
if (type === 'image') folder = 'images'
|
|
141
|
-
else if (type === 'avatar') folder = 'avatars'
|
|
142
|
-
|
|
143
|
-
const dir = require('node:path').join(this.ctx.baseDir, 'data', 'chat-patch', 'persist-media', folder)
|
|
144
|
-
if (!require('node:fs').existsSync(dir)) {
|
|
145
|
-
require('node:fs').mkdirSync(dir, { recursive: true })
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 使用 URL 的 hash 作为文件名,避免重复下载
|
|
149
|
-
const crypto = require('node:crypto')
|
|
150
|
-
const hash = crypto.createHash('md5').update(url).digest('hex')
|
|
151
|
-
const ext = require('node:path').extname(new URL(url).pathname) || (type === 'image' ? '.jpg' : '.mp4')
|
|
152
|
-
const filename = `${hash}${ext}`
|
|
153
|
-
const filePath = require('node:path').join(dir, filename)
|
|
154
|
-
|
|
155
|
-
if (require('node:fs').existsSync(filePath)) {
|
|
156
|
-
return require('node:url').pathToFileURL(filePath).href
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const buffer = await this.ctx.http.get(url, { responseType: 'arraybuffer' })
|
|
160
|
-
require('node:fs').writeFileSync(filePath, Buffer.from(buffer))
|
|
161
|
-
|
|
162
|
-
return require('node:url').pathToFileURL(filePath).href
|
|
163
|
-
} catch (e) {
|
|
164
|
-
return url
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// 处理消息中的媒体元素并缓存
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (el.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
1
|
+
import { BotInfo, ChannelInfo, MessageInfo, QuoteInfo } from './types'
|
|
2
|
+
import { Context, Session, h, Logger } from 'koishi'
|
|
3
|
+
import { FileManager } from './file-manager'
|
|
4
|
+
import { Config } from './config'
|
|
5
|
+
import { Utils } from './utils'
|
|
6
|
+
import { } from '@koishijs/plugin-console'
|
|
7
|
+
|
|
8
|
+
export class MessageHandler {
|
|
9
|
+
private logger: Logger
|
|
10
|
+
private utils: Utils
|
|
11
|
+
// 存储正确的 channelId 映射,key 是 selfId,value 是正确的 channelId
|
|
12
|
+
private correctChannelIds: Map<string, string> = new Map()
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
private ctx: Context,
|
|
16
|
+
private config: Config,
|
|
17
|
+
private fileManager: FileManager
|
|
18
|
+
) {
|
|
19
|
+
this.logger = ctx.logger('chat-patch')
|
|
20
|
+
this.utils = new Utils(config)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 设置正确的 channelId
|
|
24
|
+
setCorrectChannelId(selfId: string, channelId: string) {
|
|
25
|
+
this.correctChannelIds.set(selfId, channelId)
|
|
26
|
+
this.logInfo('设置正确的 channelId:', { selfId, channelId })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 获取正确的 channelId
|
|
30
|
+
getCorrectChannelId(selfId: string): string | undefined {
|
|
31
|
+
return this.correctChannelIds.get(selfId)
|
|
32
|
+
}
|
|
33
|
+
|
|
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
|
|
57
|
+
|
|
58
|
+
const data = this.fileManager.readChatDataFromFile()
|
|
59
|
+
|
|
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
|
|
66
|
+
}
|
|
67
|
+
|
|
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
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
this.logInfo('获取频道信息失败,使用频道ID作为备用:', error)
|
|
79
|
+
guildName = session.channelId
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!data.channels[session.selfId]) {
|
|
84
|
+
data.channels[session.selfId] = {}
|
|
85
|
+
}
|
|
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 if (session.platform && session.platform.toLowerCase().includes('sandbox')) {
|
|
100
|
+
// sandbox 平台直接使用 userId 作为频道名称
|
|
101
|
+
finalName = `私聊(${session.userId})`
|
|
102
|
+
} else {
|
|
103
|
+
finalName = '私聊(未知用户)'
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
finalName = guildName || session.channelId
|
|
107
|
+
}
|
|
108
|
+
|
|
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
|
+
}
|
|
117
|
+
|
|
118
|
+
// 记录名称变化
|
|
119
|
+
if (existingChannel && existingChannel.name !== finalName) {
|
|
120
|
+
this.logInfo('更新频道名称:', {
|
|
121
|
+
channelId: session.channelId,
|
|
122
|
+
oldName: existingChannel.name,
|
|
123
|
+
newName: finalName
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
data.channels[session.selfId][session.channelId] = channelInfo
|
|
128
|
+
this.fileManager.writeChatDataToFile(data)
|
|
129
|
+
this.logInfo('更新频道信息到文件:', channelInfo.name)
|
|
130
|
+
|
|
131
|
+
return guildName
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 下载并缓存媒体文件
|
|
135
|
+
public async downloadAndCacheMedia(url: string, type: 'image' | 'media' | 'avatar') {
|
|
136
|
+
try {
|
|
137
|
+
if (!url || url.startsWith('data:') || url.startsWith('file:')) return url
|
|
138
|
+
|
|
139
|
+
let folder = 'media'
|
|
140
|
+
if (type === 'image') folder = 'images'
|
|
141
|
+
else if (type === 'avatar') folder = 'avatars'
|
|
142
|
+
|
|
143
|
+
const dir = require('node:path').join(this.ctx.baseDir, 'data', 'chat-patch', 'persist-media', folder)
|
|
144
|
+
if (!require('node:fs').existsSync(dir)) {
|
|
145
|
+
require('node:fs').mkdirSync(dir, { recursive: true })
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// 使用 URL 的 hash 作为文件名,避免重复下载
|
|
149
|
+
const crypto = require('node:crypto')
|
|
150
|
+
const hash = crypto.createHash('md5').update(url).digest('hex')
|
|
151
|
+
const ext = require('node:path').extname(new URL(url).pathname) || (type === 'image' ? '.jpg' : '.mp4')
|
|
152
|
+
const filename = `${hash}${ext}`
|
|
153
|
+
const filePath = require('node:path').join(dir, filename)
|
|
154
|
+
|
|
155
|
+
if (require('node:fs').existsSync(filePath)) {
|
|
156
|
+
return require('node:url').pathToFileURL(filePath).href
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const buffer = await this.ctx.http.get(url, { responseType: 'arraybuffer' })
|
|
160
|
+
require('node:fs').writeFileSync(filePath, Buffer.from(buffer))
|
|
161
|
+
|
|
162
|
+
return require('node:url').pathToFileURL(filePath).href
|
|
163
|
+
} catch (e) {
|
|
164
|
+
return url
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
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')
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// 视频和文件不再缓存,保持原始URL
|
|
187
|
+
if (el.children) await this.processMediaElements(el.children, isUserMessage)
|
|
188
|
+
}
|
|
189
|
+
return elements
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async broadcastMessageEvent(session: Session) {
|
|
193
|
+
try {
|
|
194
|
+
await this.updateBotInfoToFile(session)
|
|
195
|
+
const guildName = await this.updateChannelInfoToFile(session)
|
|
196
|
+
const isDirect = session.isDirect || session.channelId?.includes('private')
|
|
197
|
+
|
|
198
|
+
const timestamp = Date.now()
|
|
199
|
+
|
|
200
|
+
// 处理媒体缓存 - 用户消息只缓存图片和语音
|
|
201
|
+
if (session.elements) {
|
|
202
|
+
await this.processMediaElements(session.elements, true)
|
|
203
|
+
}
|
|
204
|
+
if (session.quote?.elements) {
|
|
205
|
+
await this.processMediaElements(session.quote.elements, true)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 处理 quote 信息
|
|
209
|
+
let quoteInfo: QuoteInfo | undefined = undefined
|
|
210
|
+
if (session.quote) {
|
|
211
|
+
quoteInfo = {
|
|
212
|
+
messageId: session.quote.messageId || session.quote.id,
|
|
213
|
+
id: session.quote.id,
|
|
214
|
+
content: session.quote.content || '',
|
|
215
|
+
elements: session.quote.elements,
|
|
216
|
+
user: {
|
|
217
|
+
id: session.quote.user?.id || session.quote.user?.userId || 'unknown',
|
|
218
|
+
name: session.quote.user?.name || session.quote.user?.username || 'unknown',
|
|
219
|
+
userId: session.quote.user?.userId || session.quote.user?.id || 'unknown',
|
|
220
|
+
avatar: session.quote.user?.avatar,
|
|
221
|
+
username: session.quote.user?.username || session.quote.user?.name || 'unknown'
|
|
222
|
+
},
|
|
223
|
+
timestamp: session.quote.timestamp || Date.now()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 从 session 中提取用户消息内容
|
|
228
|
+
let content = ''
|
|
229
|
+
let elements: h[] = []
|
|
230
|
+
|
|
231
|
+
if (session.content) {
|
|
232
|
+
content = session.content
|
|
233
|
+
} else if (session.stripped?.content) {
|
|
234
|
+
content = session.stripped.content
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (session.elements) {
|
|
238
|
+
elements = session.elements
|
|
239
|
+
if (!content) {
|
|
240
|
+
content = session.elements
|
|
241
|
+
.filter((element: any) => element.type === 'text')
|
|
242
|
+
.map((element: any) => element.attrs?.content || '')
|
|
243
|
+
.join('')
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 创建消息信息对象
|
|
248
|
+
const messageInfo: MessageInfo = {
|
|
249
|
+
id: session.event?.message?.id || `msg-${timestamp}`,
|
|
250
|
+
content: content || session.content || '',
|
|
251
|
+
userId: session.userId || session.event?.user?.id || 'unknown',
|
|
252
|
+
username: session.username || session.event?.user?.name || session.userId || 'unknown',
|
|
253
|
+
avatar: session.event?.user?.avatar,
|
|
254
|
+
timestamp: timestamp,
|
|
255
|
+
channelId: session.channelId,
|
|
256
|
+
selfId: session.selfId,
|
|
257
|
+
elements: this.utils.cleanBase64Content(elements, false),
|
|
258
|
+
type: 'user',
|
|
259
|
+
guildName: guildName,
|
|
260
|
+
platform: session.platform || 'unknown',
|
|
261
|
+
quote: quoteInfo ? this.utils.cleanBase64Content(quoteInfo, false) : undefined,
|
|
262
|
+
isDirect: !!isDirect
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
await this.fileManager.addMessageToFile(messageInfo)
|
|
266
|
+
|
|
267
|
+
const messageEvent = {
|
|
268
|
+
type: 'message',
|
|
269
|
+
selfId: session.selfId,
|
|
270
|
+
platform: session.platform || 'unknown',
|
|
271
|
+
channelId: session.channelId,
|
|
272
|
+
messageId: session.event?.message?.id || `msg-${timestamp}`,
|
|
273
|
+
content: content || session.content || '',
|
|
274
|
+
userId: session.userId || session.event?.user?.id || 'unknown',
|
|
275
|
+
username: session.username || session.event?.user?.name || session.userId || 'unknown',
|
|
276
|
+
avatar: session.event?.user?.avatar,
|
|
277
|
+
timestamp: timestamp,
|
|
278
|
+
guildName: guildName,
|
|
279
|
+
channelType: session.type || 0,
|
|
280
|
+
elements: this.utils.cleanBase64Content(elements, false),
|
|
281
|
+
quote: quoteInfo ? this.utils.cleanBase64Content(quoteInfo, false) : undefined,
|
|
282
|
+
isDirect: session.isDirect,
|
|
283
|
+
bot: {
|
|
284
|
+
avatar: session.bot.user?.avatar,
|
|
285
|
+
name: session.bot.user?.name,
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
this.ctx.console.broadcast('chat-message-event', messageEvent)
|
|
290
|
+
} catch (error) {
|
|
291
|
+
this.logger.error('广播消息事件失败:', error)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 处理机器人发送的消息
|
|
296
|
+
async broadcastBotMessageEvent(session: Session) {
|
|
297
|
+
try {
|
|
298
|
+
// 获取正确的 channelId
|
|
299
|
+
const correctChannelId = this.getCorrectChannelId(session.selfId)
|
|
300
|
+
|
|
301
|
+
// 使用正确的 channelId,如果没有则使用 session.channelId
|
|
302
|
+
const finalChannelId = correctChannelId || session.channelId
|
|
303
|
+
|
|
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()
|
|
309
|
+
|
|
310
|
+
// 机器人消息不缓存任何媒体
|
|
311
|
+
// 保持原始URL,不进行缓存处理
|
|
312
|
+
|
|
313
|
+
// 优先使用 session.content,它包含了完整的消息内容(含标签)
|
|
314
|
+
let content = session.content || ''
|
|
315
|
+
|
|
316
|
+
if (!content && session.event?.message?.elements) {
|
|
317
|
+
content = this.utils.extractTextContent(session.event.message.elements).trim()
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 尝试从 content 中提取 quote id 并构建 quote 对象
|
|
321
|
+
let quoteInfo: QuoteInfo | undefined = undefined
|
|
322
|
+
const quoteMatch = content.match(/<quote id="([^"]+)"\/>/)
|
|
323
|
+
if (quoteMatch) {
|
|
324
|
+
const quoteId = quoteMatch[1]
|
|
325
|
+
const data = this.fileManager.readChatDataFromFile()
|
|
326
|
+
const channelKey = `${session.selfId}:${finalChannelId}`
|
|
327
|
+
// 在当前频道的消息历史中查找被引用的消息
|
|
328
|
+
const quotedMsg = data.messages[channelKey]?.find(m => m.id === quoteId)
|
|
329
|
+
|
|
330
|
+
if (quotedMsg) {
|
|
331
|
+
// 如果是虚拟 ID,尝试获取其真实的 messageId
|
|
332
|
+
const realId = quotedMsg.id.startsWith('bot-msg-') ? (quotedMsg as any).realId : quotedMsg.id
|
|
333
|
+
|
|
334
|
+
quoteInfo = {
|
|
335
|
+
messageId: realId || quotedMsg.id,
|
|
336
|
+
id: realId || quotedMsg.id,
|
|
337
|
+
content: quotedMsg.content,
|
|
338
|
+
elements: quotedMsg.elements,
|
|
339
|
+
user: {
|
|
340
|
+
id: quotedMsg.userId,
|
|
341
|
+
name: quotedMsg.username,
|
|
342
|
+
userId: quotedMsg.userId,
|
|
343
|
+
avatar: quotedMsg.avatar,
|
|
344
|
+
username: quotedMsg.username
|
|
345
|
+
},
|
|
346
|
+
timestamp: quotedMsg.timestamp
|
|
347
|
+
}
|
|
348
|
+
// 移除 content 中的 quote 标签,避免重复显示
|
|
349
|
+
content = content.replace(/<quote id="[^"]+"\/>\s*/, '')
|
|
350
|
+
|
|
351
|
+
// 修正 content 中的 quote 标签为真实 ID,以便 bot.sendMessage 能够正确识别
|
|
352
|
+
if (realId) {
|
|
353
|
+
session.content = session.content.replace(/id="[^"]+"/, `id="${realId}"`)
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// 创建机器人消息信息对象
|
|
359
|
+
const messageInfo: MessageInfo = {
|
|
360
|
+
id: `bot-msg-${timestamp}`,
|
|
361
|
+
content: content,
|
|
362
|
+
userId: session.selfId,
|
|
363
|
+
username: session.bot.user?.name || `Bot-${session.selfId}`,
|
|
364
|
+
avatar: session.bot.user?.avatar,
|
|
365
|
+
timestamp: timestamp,
|
|
366
|
+
channelId: finalChannelId,
|
|
367
|
+
selfId: session.selfId,
|
|
368
|
+
elements: this.utils.cleanBase64Content(session.event?.message?.elements, true),
|
|
369
|
+
type: 'bot',
|
|
370
|
+
guildName: guildName,
|
|
371
|
+
platform: session.platform || 'unknown',
|
|
372
|
+
quote: quoteInfo,
|
|
373
|
+
isDirect: !!isDirect,
|
|
374
|
+
sending: true // 标记为正在发送
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
await this.fileManager.addMessageToFile(messageInfo)
|
|
378
|
+
|
|
379
|
+
const messageEvent = {
|
|
380
|
+
type: 'bot-message',
|
|
381
|
+
selfId: session.selfId,
|
|
382
|
+
platform: session.platform || 'unknown',
|
|
383
|
+
channelId: finalChannelId,
|
|
384
|
+
messageId: `bot-msg-${timestamp}`,
|
|
385
|
+
content: content,
|
|
386
|
+
userId: session.selfId,
|
|
387
|
+
username: session.bot.user?.name || `Bot-${session.selfId}`,
|
|
388
|
+
avatar: session.bot.user?.avatar,
|
|
389
|
+
timestamp: timestamp,
|
|
390
|
+
guildName: guildName,
|
|
391
|
+
channelType: session.event?.channel?.type || session.type || 0,
|
|
392
|
+
elements: this.utils.cleanBase64Content(session.event?.message?.elements, true),
|
|
393
|
+
quote: quoteInfo,
|
|
394
|
+
isDirect: !!isDirect,
|
|
395
|
+
sending: true,
|
|
396
|
+
bot: {
|
|
397
|
+
avatar: session.bot.user?.avatar,
|
|
398
|
+
name: session.bot.user?.name,
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
this.ctx.console.broadcast('chat-bot-message-event', messageEvent)
|
|
403
|
+
} catch (error) {
|
|
404
|
+
this.logger.error('广播机器人消息事件失败:', error)
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private logInfo(...args: any[]) {
|
|
409
|
+
if (this.config.loggerinfo) {
|
|
410
|
+
(this.logger.info as (...args: any[]) => void)(...args)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|