koishi-plugin-chat-patch 0.8.2 → 1.0.6
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/index.vue +608 -22
- package/client/vue/style.css +355 -0
- package/dist/index.js +8 -8
- package/dist/style.css +1 -1
- package/lib/api-handlers.d.ts +17 -0
- package/lib/config.d.ts +13 -0
- package/lib/file-manager.d.ts +17 -0
- package/lib/index.d.ts +4 -13
- package/lib/index.js +857 -454
- package/lib/message-handler.d.ts +16 -0
- package/lib/types.d.ts +53 -0
- package/lib/utils.d.ts +7 -0
- package/package.json +3 -3
- package/src/api-handlers.ts +593 -0
- package/src/config.ts +45 -0
- package/src/file-manager.ts +162 -0
- package/src/index.ts +45 -753
- package/src/message-handler.ts +247 -0
- package/src/types.ts +58 -0
- package/src/utils.ts +43 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { Context, Session, h, Logger } from 'koishi'
|
|
2
|
+
import { Config } from './config'
|
|
3
|
+
import { FileManager } from './file-manager'
|
|
4
|
+
import { Utils } from './utils'
|
|
5
|
+
import { BotInfo, ChannelInfo, MessageInfo, QuoteInfo } from './types'
|
|
6
|
+
|
|
7
|
+
export class MessageHandler {
|
|
8
|
+
private logger: Logger
|
|
9
|
+
private utils: Utils
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
private ctx: Context,
|
|
13
|
+
private config: Config,
|
|
14
|
+
private fileManager: FileManager
|
|
15
|
+
) {
|
|
16
|
+
this.logger = ctx.logger('chat-patch')
|
|
17
|
+
this.utils = new Utils(config)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 更新机器人信息到JSON文件
|
|
21
|
+
async updateBotInfoToFile(session: Session) {
|
|
22
|
+
const data = this.fileManager.readChatDataFromFile()
|
|
23
|
+
|
|
24
|
+
const botInfo: BotInfo = {
|
|
25
|
+
selfId: session.selfId,
|
|
26
|
+
platform: session.platform || 'unknown',
|
|
27
|
+
username: session.bot.user?.name || `Bot-${session.selfId}`,
|
|
28
|
+
avatar: session.bot.user?.avatar,
|
|
29
|
+
status: 'online'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
data.bots[session.selfId] = botInfo
|
|
33
|
+
this.fileManager.writeChatDataToFile(data)
|
|
34
|
+
this.logInfo('更新机器人信息到文件:', botInfo.username)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 更新频道信息到JSON文件
|
|
38
|
+
async updateChannelInfoToFile(session: Session): Promise<string> {
|
|
39
|
+
let guildName = session.channelId
|
|
40
|
+
|
|
41
|
+
const data = this.fileManager.readChatDataFromFile()
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
if (session.guildId && session.bot.getGuild && typeof session.bot.getGuild === 'function') {
|
|
45
|
+
const guild = await session.bot.getGuild(session.guildId)
|
|
46
|
+
guildName = guild?.name || session.channelId
|
|
47
|
+
}
|
|
48
|
+
} catch (guildError) {
|
|
49
|
+
this.logInfo('获取群组信息失败,使用频道ID作为备用:', guildError)
|
|
50
|
+
guildName = session.channelId
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!data.channels[session.selfId]) {
|
|
54
|
+
data.channels[session.selfId] = {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const channelInfo: ChannelInfo = {
|
|
58
|
+
id: session.channelId,
|
|
59
|
+
name: session.guildId
|
|
60
|
+
? `${guildName} (${session.channelId})`
|
|
61
|
+
: `私信 ${session.channelId}`,
|
|
62
|
+
type: session.type || 0,
|
|
63
|
+
guildId: session.guildId,
|
|
64
|
+
guildName: guildName
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
data.channels[session.selfId][session.channelId] = channelInfo
|
|
68
|
+
this.fileManager.writeChatDataToFile(data)
|
|
69
|
+
this.logInfo('更新频道信息到文件:', channelInfo.name)
|
|
70
|
+
|
|
71
|
+
return guildName
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async broadcastMessageEvent(session: Session) {
|
|
75
|
+
try {
|
|
76
|
+
await this.updateBotInfoToFile(session)
|
|
77
|
+
const guildName = await this.updateChannelInfoToFile(session)
|
|
78
|
+
|
|
79
|
+
const timestamp = Date.now()
|
|
80
|
+
|
|
81
|
+
// 处理 quote 信息
|
|
82
|
+
let quoteInfo: QuoteInfo | undefined = undefined
|
|
83
|
+
if (session.quote) {
|
|
84
|
+
quoteInfo = {
|
|
85
|
+
messageId: session.quote.messageId || session.quote.id,
|
|
86
|
+
id: session.quote.id,
|
|
87
|
+
content: session.quote.content || '',
|
|
88
|
+
elements: session.quote.elements,
|
|
89
|
+
user: {
|
|
90
|
+
id: session.quote.user?.id || session.quote.user?.userId || 'unknown',
|
|
91
|
+
name: session.quote.user?.name || session.quote.user?.username || 'unknown',
|
|
92
|
+
userId: session.quote.user?.userId || session.quote.user?.id || 'unknown',
|
|
93
|
+
avatar: session.quote.user?.avatar,
|
|
94
|
+
username: session.quote.user?.username || session.quote.user?.name || 'unknown'
|
|
95
|
+
},
|
|
96
|
+
timestamp: session.quote.timestamp || Date.now()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 从 session 中提取用户消息内容
|
|
101
|
+
let content = ''
|
|
102
|
+
let elements: h[] = []
|
|
103
|
+
|
|
104
|
+
if (session.content) {
|
|
105
|
+
content = session.content
|
|
106
|
+
} else if (session.stripped?.content) {
|
|
107
|
+
content = session.stripped.content
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (session.elements) {
|
|
111
|
+
elements = session.elements
|
|
112
|
+
if (!content) {
|
|
113
|
+
content = session.elements
|
|
114
|
+
.filter((element: any) => element.type === 'text')
|
|
115
|
+
.map((element: any) => element.attrs?.content || '')
|
|
116
|
+
.join('')
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 创建消息信息对象
|
|
121
|
+
const messageInfo: MessageInfo = {
|
|
122
|
+
id: session.event?.message?.id || `msg-${timestamp}`,
|
|
123
|
+
content: content || session.content || '',
|
|
124
|
+
userId: session.userId || session.event?.user?.id || 'unknown',
|
|
125
|
+
username: session.username || session.event?.user?.name || session.userId || 'unknown',
|
|
126
|
+
avatar: session.event?.user?.avatar,
|
|
127
|
+
timestamp: timestamp,
|
|
128
|
+
channelId: session.channelId,
|
|
129
|
+
selfId: session.selfId,
|
|
130
|
+
elements: elements,
|
|
131
|
+
type: 'user',
|
|
132
|
+
guildId: session.guildId,
|
|
133
|
+
guildName: guildName,
|
|
134
|
+
platform: session.platform || 'unknown',
|
|
135
|
+
quote: quoteInfo
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await this.fileManager.addMessageToFile(messageInfo)
|
|
139
|
+
|
|
140
|
+
const messageEvent = {
|
|
141
|
+
type: 'message',
|
|
142
|
+
selfId: session.selfId,
|
|
143
|
+
platform: session.platform || 'unknown',
|
|
144
|
+
channelId: session.channelId,
|
|
145
|
+
messageId: session.event?.message?.id || `msg-${timestamp}`,
|
|
146
|
+
content: content || session.content || '',
|
|
147
|
+
userId: session.userId || session.event?.user?.id || 'unknown',
|
|
148
|
+
username: session.username || session.event?.user?.name || session.userId || 'unknown',
|
|
149
|
+
avatar: session.event?.user?.avatar,
|
|
150
|
+
timestamp: timestamp,
|
|
151
|
+
guildId: session.guildId,
|
|
152
|
+
guildName: guildName,
|
|
153
|
+
channelType: session.type || 0,
|
|
154
|
+
elements: elements,
|
|
155
|
+
quote: quoteInfo,
|
|
156
|
+
bot: {
|
|
157
|
+
avatar: session.bot.user?.avatar,
|
|
158
|
+
name: session.bot.user?.name,
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.ctx.console.broadcast('chat-message-event', messageEvent)
|
|
163
|
+
} catch (error) {
|
|
164
|
+
this.logger.error('广播消息事件失败:', error)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 处理机器人发送的消息
|
|
169
|
+
async broadcastBotMessageEvent(session: Session) {
|
|
170
|
+
try {
|
|
171
|
+
await this.updateBotInfoToFile(session)
|
|
172
|
+
const guildName = await this.updateChannelInfoToFile(session)
|
|
173
|
+
|
|
174
|
+
const timestamp = Date.now()
|
|
175
|
+
|
|
176
|
+
let content = ''
|
|
177
|
+
|
|
178
|
+
if (session.event?.message?.elements) {
|
|
179
|
+
content = this.utils.extractTextContent(session.event.message.elements).trim()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// 创建机器人消息信息对象
|
|
183
|
+
const messageInfo: MessageInfo = {
|
|
184
|
+
id: `bot-msg-${timestamp}`,
|
|
185
|
+
content: content,
|
|
186
|
+
userId: session.selfId,
|
|
187
|
+
username: session.bot.user?.name || `Bot-${session.selfId}`,
|
|
188
|
+
avatar: session.bot.user?.avatar,
|
|
189
|
+
timestamp: timestamp,
|
|
190
|
+
channelId: session.event?.channel?.id || session.channelId,
|
|
191
|
+
selfId: session.selfId,
|
|
192
|
+
elements: session.event?.message?.elements,
|
|
193
|
+
type: 'bot',
|
|
194
|
+
guildId: session.event?.guild?.id || session.guildId,
|
|
195
|
+
guildName: guildName,
|
|
196
|
+
platform: session.platform || 'unknown'
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
await this.fileManager.addMessageToFile(messageInfo)
|
|
200
|
+
|
|
201
|
+
const messageEvent = {
|
|
202
|
+
type: 'bot-message',
|
|
203
|
+
selfId: session.selfId,
|
|
204
|
+
platform: session.platform || 'unknown',
|
|
205
|
+
channelId: session.event?.channel?.id || session.channelId,
|
|
206
|
+
messageId: `bot-msg-${timestamp}`,
|
|
207
|
+
content: content,
|
|
208
|
+
userId: session.selfId,
|
|
209
|
+
username: session.bot.user?.name || `Bot-${session.selfId}`,
|
|
210
|
+
avatar: session.bot.user?.avatar,
|
|
211
|
+
timestamp: timestamp,
|
|
212
|
+
guildId: session.event?.guild?.id || session.guildId,
|
|
213
|
+
guildName: guildName,
|
|
214
|
+
channelType: session.event?.channel?.type || session.type || 0,
|
|
215
|
+
elements: session.event?.message?.elements,
|
|
216
|
+
bot: {
|
|
217
|
+
avatar: session.bot.user?.avatar,
|
|
218
|
+
name: session.bot.user?.name,
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// 检查是否包含图片元素
|
|
223
|
+
const imageElements = session.event?.message?.elements?.filter((element: any) =>
|
|
224
|
+
element.type === 'img' || element.type === 'image' || element.type === 'mface'
|
|
225
|
+
) || []
|
|
226
|
+
|
|
227
|
+
this.logInfo('机器人发送消息 (before-send):', {
|
|
228
|
+
selfId: session.selfId,
|
|
229
|
+
channelId: messageEvent.channelId,
|
|
230
|
+
content: content,
|
|
231
|
+
platform: session.platform,
|
|
232
|
+
imageCount: imageElements.length,
|
|
233
|
+
imageUrls: imageElements.map((el: any) => el.attrs?.src || el.attrs?.url || el.attrs?.file)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
this.ctx.console.broadcast('chat-bot-message-event', messageEvent)
|
|
237
|
+
} catch (error) {
|
|
238
|
+
this.logger.error('广播机器人消息事件失败:', error)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private logInfo(...args: any[]) {
|
|
243
|
+
if (this.config.loggerinfo) {
|
|
244
|
+
(this.logger.info as (...args: any[]) => void)(...args)
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
guildId?: string
|
|
16
|
+
guildName?: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface QuoteInfo {
|
|
20
|
+
messageId: string
|
|
21
|
+
id: string
|
|
22
|
+
content: string
|
|
23
|
+
elements?: h[]
|
|
24
|
+
user: {
|
|
25
|
+
id: string
|
|
26
|
+
name: string
|
|
27
|
+
userId: string
|
|
28
|
+
avatar?: string
|
|
29
|
+
username: string
|
|
30
|
+
}
|
|
31
|
+
timestamp: number
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface MessageInfo {
|
|
35
|
+
id: string
|
|
36
|
+
content: string
|
|
37
|
+
userId: string
|
|
38
|
+
username: string
|
|
39
|
+
avatar?: string
|
|
40
|
+
timestamp: number
|
|
41
|
+
channelId: string
|
|
42
|
+
selfId: string
|
|
43
|
+
elements?: h[]
|
|
44
|
+
type: 'user' | 'bot'
|
|
45
|
+
guildId?: string
|
|
46
|
+
guildName?: string
|
|
47
|
+
platform: string
|
|
48
|
+
quote?: QuoteInfo
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ChatData {
|
|
52
|
+
bots: Record<string, BotInfo>
|
|
53
|
+
channels: Record<string, Record<string, ChannelInfo>>
|
|
54
|
+
messages: Record<string, MessageInfo[]>
|
|
55
|
+
pinnedBots: string[]
|
|
56
|
+
pinnedChannels: string[]
|
|
57
|
+
lastSaveTime?: number
|
|
58
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { Config } from './config'
|
|
2
|
+
|
|
3
|
+
export class Utils {
|
|
4
|
+
constructor(private config: Config) { }
|
|
5
|
+
|
|
6
|
+
// 检查平台是否被屏蔽
|
|
7
|
+
isPlatformBlocked(platform: string): boolean {
|
|
8
|
+
if (!this.config.blockedPlatforms || this.config.blockedPlatforms.length === 0) {
|
|
9
|
+
return false
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
for (const blockedPlatform of this.config.blockedPlatforms) {
|
|
13
|
+
if (blockedPlatform.exactMatch) {
|
|
14
|
+
if (platform === blockedPlatform.platformName) {
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
} else {
|
|
18
|
+
if (platform.includes(blockedPlatform.platformName)) {
|
|
19
|
+
return true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 递归提取所有文本内容的函数
|
|
28
|
+
extractTextContent(elements: any[]): string {
|
|
29
|
+
let text = ''
|
|
30
|
+
for (const element of elements) {
|
|
31
|
+
if (element.type === 'text') {
|
|
32
|
+
text += element.attrs?.content || ''
|
|
33
|
+
} else if (element.type === 'p') {
|
|
34
|
+
if (element.children && element.children.length > 0) {
|
|
35
|
+
text += this.extractTextContent(element.children) + '\n'
|
|
36
|
+
}
|
|
37
|
+
} else if (element.children && element.children.length > 0) {
|
|
38
|
+
text += this.extractTextContent(element.children)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return text
|
|
42
|
+
}
|
|
43
|
+
}
|