koishi-plugin-chat-patch 0.8.3 → 1.0.7

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,162 @@
1
+ import path from 'node:path'
2
+ import fs from 'node:fs'
3
+ import { Context, Logger } from 'koishi'
4
+ import { ChatData, MessageInfo } from './types'
5
+ import { Config } from './config'
6
+
7
+ export class FileManager {
8
+ private dataFilePath: string
9
+ private fileOperationLock = Promise.resolve()
10
+ private logger: Logger
11
+
12
+ constructor(
13
+ private ctx: Context,
14
+ private config: Config
15
+ ) {
16
+ this.dataFilePath = path.resolve(ctx.baseDir, 'data', 'chat-patch', 'chat-data.json')
17
+ this.logger = ctx.logger('chat-patch')
18
+ }
19
+
20
+ // 确保目录存在
21
+ private ensureDataDir() {
22
+ const dir = path.dirname(this.dataFilePath)
23
+ if (!fs.existsSync(dir)) {
24
+ fs.mkdirSync(dir, { recursive: true })
25
+ }
26
+ }
27
+
28
+ // 从JSON文件读取数据
29
+ readChatDataFromFile(): ChatData {
30
+ try {
31
+ if (fs.existsSync(this.dataFilePath)) {
32
+ const jsonData = fs.readFileSync(this.dataFilePath, 'utf8')
33
+ const data = JSON.parse(jsonData)
34
+ return {
35
+ bots: data.bots || {},
36
+ channels: data.channels || {},
37
+ messages: data.messages || {},
38
+ pinnedBots: data.pinnedBots || [],
39
+ pinnedChannels: data.pinnedChannels || [],
40
+ lastSaveTime: data.lastSaveTime
41
+ }
42
+ }
43
+ } catch (error) {
44
+ this.logger.error('读取聊天数据失败:', error)
45
+ }
46
+ return {
47
+ bots: {},
48
+ channels: {},
49
+ messages: {},
50
+ pinnedBots: [],
51
+ pinnedChannels: []
52
+ }
53
+ }
54
+
55
+ // 写入数据到JSON文件
56
+ writeChatDataToFile(data: ChatData) {
57
+ try {
58
+ this.ensureDataDir()
59
+ data.lastSaveTime = Date.now()
60
+ const jsonData = JSON.stringify(data, null, 2)
61
+ fs.writeFileSync(this.dataFilePath, jsonData, 'utf8')
62
+ } catch (error) {
63
+ this.logger.error('写入聊天数据失败:', error)
64
+ }
65
+ }
66
+
67
+ // 清理超量消息
68
+ cleanExcessMessages(data: ChatData): ChatData {
69
+ let cleanedCount = 0
70
+ const cleanedMessages: Record<string, MessageInfo[]> = {}
71
+
72
+ for (const [channelKey, messages] of Object.entries(data.messages)) {
73
+ if (messages.length > this.config.maxMessagesPerChannel) {
74
+ const sortedMessages = [...messages].sort((a, b) => a.timestamp - b.timestamp)
75
+ const keptMessages = sortedMessages.slice(-this.config.maxMessagesPerChannel)
76
+ cleanedCount += messages.length - keptMessages.length
77
+ cleanedMessages[channelKey] = keptMessages
78
+ this.logInfo(`频道 ${channelKey} 清理了 ${messages.length - keptMessages.length} 条旧消息,保留最新 ${keptMessages.length} 条`)
79
+ } else {
80
+ cleanedMessages[channelKey] = messages
81
+ }
82
+ }
83
+
84
+ if (cleanedCount > 0) {
85
+ this.logInfo('总共清理超量消息:', cleanedCount, '条')
86
+ }
87
+
88
+ return {
89
+ ...data,
90
+ messages: cleanedMessages
91
+ }
92
+ }
93
+
94
+ // 添加消息到JSON文件(使用锁机制防止并发冲突)
95
+ async addMessageToFile(messageInfo: MessageInfo) {
96
+ this.fileOperationLock = this.fileOperationLock.then(async () => {
97
+ const data = this.readChatDataFromFile()
98
+ const channelKey = `${messageInfo.selfId}:${messageInfo.channelId}`
99
+
100
+ if (!data.messages[channelKey]) {
101
+ data.messages[channelKey] = []
102
+ }
103
+
104
+ // 检查消息是否已存在
105
+ const existingMessage = data.messages[channelKey].find(m => m.id === messageInfo.id)
106
+ if (existingMessage) {
107
+ this.logInfo('消息已存在,跳过保存:', {
108
+ channelKey: channelKey,
109
+ messageId: messageInfo.id,
110
+ existingType: existingMessage.type,
111
+ existingContent: existingMessage.content,
112
+ newType: messageInfo.type,
113
+ newContent: messageInfo.content
114
+ })
115
+ return
116
+ }
117
+
118
+ if (!messageInfo.timestamp) {
119
+ messageInfo.timestamp = Date.now()
120
+ }
121
+
122
+ const beforeCount = data.messages[channelKey].length
123
+ data.messages[channelKey].push(messageInfo)
124
+ const afterCount = data.messages[channelKey].length
125
+
126
+ // 限制消息数量 - 保留最新的消息
127
+ if (data.messages[channelKey].length > this.config.maxMessagesPerChannel) {
128
+ data.messages[channelKey].sort((a, b) => a.timestamp - b.timestamp)
129
+ const removedCount = data.messages[channelKey].length - this.config.maxMessagesPerChannel
130
+ data.messages[channelKey] = data.messages[channelKey].slice(-this.config.maxMessagesPerChannel)
131
+ this.logInfo(`频道 ${channelKey} 达到消息上限,清理了 ${removedCount} 条旧消息`)
132
+ }
133
+
134
+ this.writeChatDataToFile(data)
135
+
136
+ const isCommandMessage = messageInfo.content?.startsWith('++') || messageInfo.content?.startsWith('.')
137
+
138
+ this.logInfo('添加消息到文件:', {
139
+ channelKey: channelKey,
140
+ messageId: messageInfo.id,
141
+ content: messageInfo.content,
142
+ type: messageInfo.type,
143
+ userId: messageInfo.userId,
144
+ username: messageInfo.username,
145
+ timestamp: messageInfo.timestamp,
146
+ isCommandMessage: isCommandMessage,
147
+ 消息数变化: `${beforeCount} -> ${afterCount} -> ${data.messages[channelKey].length}`
148
+ })
149
+
150
+ }).catch(error => {
151
+ this.logger.error('保存消息时发生错误:', error)
152
+ })
153
+
154
+ await this.fileOperationLock
155
+ }
156
+
157
+ private logInfo(...args: any[]) {
158
+ if (this.config.loggerinfo) {
159
+ (this.logger.info as (...args: any[]) => void)(...args)
160
+ }
161
+ }
162
+ }