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.
@@ -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
+ }