koishi-plugin-chat-analyse 0.3.1 → 0.3.3

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.
@@ -41,12 +41,11 @@ export declare class Collector {
41
41
  private static readonly FLUSH_INTERVAL;
42
42
  /** @const {number} BUFFER_THRESHOLD - 内存缓存区触发自动刷新的消息数量阈值。 */
43
43
  private static readonly BUFFER_THRESHOLD;
44
- /** @member {Omit<Tables['analyse_cache'], 'id'>[]} cacheBuffer - 用于暂存原始消息的内存缓冲区,以减少数据库写入频率。 */
45
- private cacheBuffer;
46
- /** @member {Map<string, number>} uidCache - 用户 uid 的内存缓存,避免重复查询数据库。*/
47
- private uidCache;
48
- /** @member {Map<string, Promise<number>>} pendingUidRequests - 用于处理并发获取 uid 的请求锁。*/
49
- private pendingUidRequests;
44
+ private msgStatBuffer;
45
+ private cmdStatBuffer;
46
+ private oriCacheBuffer;
47
+ private userCache;
48
+ private pendingUserRequests;
50
49
  private flushInterval;
51
50
  /**
52
51
  * @constructor
@@ -71,13 +70,13 @@ export declare class Collector {
71
70
  /**
72
71
  * @private
73
72
  * @async
74
- * @method getOrCreateUser
75
- * @description 高效地获取或创建用户的中央记录 (`analyse_user`)。
73
+ * @method getOrCreateCachedUser
74
+ * @description 高效地获取或创建用户的中央记录,并全面利用缓存。
76
75
  * @param {Session} session - Koishi 会话对象,用于获取用户信息和 Bot 实例。
77
76
  * @param {string} channelId - 消息所在的频道或群组 ID。
78
- * @returns {Promise<number | null>} 返回用户的唯一 `uid`,如果操作失败则返回 `null`。
77
+ * @returns {Promise<UserCache | null>} 返回用户的缓存对象,如果操作失败则返回 `null`。
79
78
  */
80
- private getOrCreateUser;
79
+ private getOrCreateCachedUser;
81
80
  /**
82
81
  * @private
83
82
  * @method sanitizeContent
@@ -89,8 +88,8 @@ export declare class Collector {
89
88
  /**
90
89
  * @private
91
90
  * @async
92
- * @method flushCacheBuffer
93
- * @description 将内存中的消息缓存 (`cacheBuffer`) 批量写入数据库。
91
+ * @method flushBuffers
92
+ * @description 将所有内存中的缓冲区数据批量写入数据库。
94
93
  */
95
- private flushCacheBuffer;
94
+ private flushBuffers;
96
95
  }
package/lib/index.js CHANGED
@@ -42,13 +42,11 @@ var Collector = class _Collector {
42
42
  this.config = config;
43
43
  this.defineModels();
44
44
  ctx.on("message", (session) => this.handleMessage(session));
45
- if (this.config.enableOriRecord) {
46
- this.flushInterval = setInterval(() => this.flushCacheBuffer(), _Collector.FLUSH_INTERVAL);
47
- ctx.on("dispose", () => {
48
- clearInterval(this.flushInterval);
49
- this.flushCacheBuffer();
50
- });
51
- }
45
+ this.flushInterval = setInterval(() => this.flushBuffers(), _Collector.FLUSH_INTERVAL);
46
+ ctx.on("dispose", () => {
47
+ clearInterval(this.flushInterval);
48
+ this.flushBuffers();
49
+ });
52
50
  }
53
51
  static {
54
52
  __name(this, "Collector");
@@ -57,12 +55,13 @@ var Collector = class _Collector {
57
55
  static FLUSH_INTERVAL = 60 * 1e3;
58
56
  /** @const {number} BUFFER_THRESHOLD - 内存缓存区触发自动刷新的消息数量阈值。 */
59
57
  static BUFFER_THRESHOLD = 100;
60
- /** @member {Omit<Tables['analyse_cache'], 'id'>[]} cacheBuffer - 用于暂存原始消息的内存缓冲区,以减少数据库写入频率。 */
61
- cacheBuffer = [];
62
- /** @member {Map<string, number>} uidCache - 用户 uid 的内存缓存,避免重复查询数据库。*/
63
- uidCache = /* @__PURE__ */ new Map();
64
- /** @member {Map<string, Promise<number>>} pendingUidRequests - 用于处理并发获取 uid 的请求锁。*/
65
- pendingUidRequests = /* @__PURE__ */ new Map();
58
+ // 统一的缓冲区
59
+ msgStatBuffer = /* @__PURE__ */ new Map();
60
+ cmdStatBuffer = /* @__PURE__ */ new Map();
61
+ oriCacheBuffer = [];
62
+ // 用户缓存
63
+ userCache = /* @__PURE__ */ new Map();
64
+ pendingUserRequests = /* @__PURE__ */ new Map();
66
65
  flushInterval;
67
66
  /**
68
67
  * @private
@@ -111,35 +110,39 @@ var Collector = class _Collector {
111
110
  const { userId, channelId, guildId, content, timestamp, argv, elements } = session;
112
111
  const effectiveId = channelId || guildId;
113
112
  if (!effectiveId || !userId || !timestamp || !content?.trim()) return;
114
- const uid = await this.getOrCreateUser(session, effectiveId);
115
- if (!uid) return;
113
+ const user = await this.getOrCreateCachedUser(session, effectiveId);
114
+ if (!user) return;
115
+ const { uid } = user;
116
+ const messageTime = new Date(timestamp);
116
117
  if (argv?.command) {
117
- await this.ctx.database.upsert("analyse_cmd", (row) => [{
118
- uid,
119
- command: argv.command.name,
120
- count: import_koishi.$.add(import_koishi.$.ifNull(row.count, import_koishi.$.literal(0)), 1),
121
- timestamp: /* @__PURE__ */ new Date()
122
- }]);
118
+ const key = `${uid}:${argv.command.name}`;
119
+ const existing = this.cmdStatBuffer.get(key);
120
+ if (existing) {
121
+ existing.count++;
122
+ existing.timestamp = messageTime;
123
+ } else {
124
+ this.cmdStatBuffer.set(key, { uid, command: argv.command.name, count: 1, timestamp: messageTime });
125
+ }
123
126
  }
124
- const messageTime = new Date(timestamp);
125
127
  const hourStart = new Date(messageTime.getFullYear(), messageTime.getMonth(), messageTime.getDate(), messageTime.getHours());
126
128
  const uniqueElementTypes = new Set(elements.map((e) => e.type));
127
129
  for (const type of uniqueElementTypes) {
128
- await this.ctx.database.upsert("analyse_msg", (row) => [{
129
- uid,
130
- type,
131
- hour: hourStart,
132
- count: import_koishi.$.add(import_koishi.$.ifNull(row.count, 0), 1),
133
- timestamp: messageTime
134
- }]);
130
+ const key = `${uid}:${type}:${hourStart.toISOString()}`;
131
+ const existing = this.msgStatBuffer.get(key);
132
+ if (existing) {
133
+ existing.count++;
134
+ existing.timestamp = messageTime;
135
+ } else {
136
+ this.msgStatBuffer.set(key, { uid, type, hour: hourStart, count: 1, timestamp: messageTime });
137
+ }
135
138
  }
136
139
  if (this.config.enableOriRecord) {
137
- this.cacheBuffer.push({
140
+ this.oriCacheBuffer.push({
138
141
  uid,
139
142
  content: this.sanitizeContent(elements),
140
- timestamp: new Date(timestamp)
143
+ timestamp: messageTime
141
144
  });
142
- if (this.cacheBuffer.length >= _Collector.BUFFER_THRESHOLD) await this.flushCacheBuffer();
145
+ if (this.oriCacheBuffer.length >= _Collector.BUFFER_THRESHOLD) await this.flushBuffers();
143
146
  }
144
147
  } catch (error) {
145
148
  this.ctx.logger.warn("消息处理出错:", error);
@@ -148,45 +151,50 @@ var Collector = class _Collector {
148
151
  /**
149
152
  * @private
150
153
  * @async
151
- * @method getOrCreateUser
152
- * @description 高效地获取或创建用户的中央记录 (`analyse_user`)。
154
+ * @method getOrCreateCachedUser
155
+ * @description 高效地获取或创建用户的中央记录,并全面利用缓存。
153
156
  * @param {Session} session - Koishi 会话对象,用于获取用户信息和 Bot 实例。
154
157
  * @param {string} channelId - 消息所在的频道或群组 ID。
155
- * @returns {Promise<number | null>} 返回用户的唯一 `uid`,如果操作失败则返回 `null`。
158
+ * @returns {Promise<UserCache | null>} 返回用户的缓存对象,如果操作失败则返回 `null`。
156
159
  */
157
- async getOrCreateUser(session, channelId) {
160
+ async getOrCreateCachedUser(session, channelId) {
158
161
  const { userId, bot, guildId } = session;
159
162
  const cacheKey = `${channelId}:${userId}`;
160
- if (this.uidCache.has(cacheKey)) return this.uidCache.get(cacheKey);
161
- if (this.pendingUidRequests.has(cacheKey)) return this.pendingUidRequests.get(cacheKey);
163
+ if (this.userCache.has(cacheKey)) return this.userCache.get(cacheKey);
164
+ if (this.pendingUserRequests.has(cacheKey)) return this.pendingUserRequests.get(cacheKey);
162
165
  const promise = (async () => {
163
166
  try {
164
- const existing = await this.ctx.database.get("analyse_user", { channelId, userId }, ["uid"]);
167
+ const existing = await this.ctx.database.get("analyse_user", { channelId, userId });
165
168
  if (existing.length > 0) {
166
- this.uidCache.set(cacheKey, existing[0].uid);
167
- return existing[0].uid;
169
+ const { uid: uid2, userName: userName2, channelName: channelName2 } = existing[0];
170
+ const cachedUser2 = { uid: uid2, userName: userName2, channelName: channelName2 };
171
+ this.userCache.set(cacheKey, cachedUser2);
172
+ return cachedUser2;
168
173
  }
169
174
  const [guild, member] = await Promise.all([
170
175
  guildId ? bot.getGuild(guildId).catch(() => null) : Promise.resolve(null),
171
176
  guildId ? bot.getGuildMember(guildId, userId).catch(() => null) : Promise.resolve(null)
172
177
  ]);
173
178
  const user = !member ? await bot.getUser(userId).catch(() => null) : null;
174
- const newUser = await this.ctx.database.create("analyse_user", {
179
+ const newUserRecord = {
175
180
  channelId,
176
181
  userId,
177
182
  channelName: guild?.name || channelId,
178
183
  userName: member?.nick || member?.name || user?.name || userId
179
- });
180
- this.uidCache.set(cacheKey, newUser.uid);
181
- return newUser.uid;
184
+ };
185
+ const createdUser = await this.ctx.database.create("analyse_user", newUserRecord);
186
+ const { uid, userName, channelName } = createdUser;
187
+ const cachedUser = { uid, userName, channelName };
188
+ this.userCache.set(cacheKey, cachedUser);
189
+ return cachedUser;
182
190
  } catch (error) {
183
- this.ctx.logger.error(`创建或获取用户(${cacheKey}) UID 失败:`, error);
191
+ this.ctx.logger.error(`创建或获取用户(${cacheKey})失败:`, error);
184
192
  return null;
185
193
  } finally {
186
- this.pendingUidRequests.delete(cacheKey);
194
+ this.pendingUserRequests.delete(cacheKey);
187
195
  }
188
196
  })();
189
- this.pendingUidRequests.set(cacheKey, promise);
197
+ this.pendingUserRequests.set(cacheKey, promise);
190
198
  return promise;
191
199
  }
192
200
  /**
@@ -211,17 +219,43 @@ var Collector = class _Collector {
211
219
  /**
212
220
  * @private
213
221
  * @async
214
- * @method flushCacheBuffer
215
- * @description 将内存中的消息缓存 (`cacheBuffer`) 批量写入数据库。
222
+ * @method flushBuffers
223
+ * @description 将所有内存中的缓冲区数据批量写入数据库。
216
224
  */
217
- async flushCacheBuffer() {
218
- if (this.cacheBuffer.length === 0) return;
219
- const bufferToFlush = this.cacheBuffer;
220
- this.cacheBuffer = [];
225
+ async flushBuffers() {
226
+ const cmdBufferToFlush = Array.from(this.cmdStatBuffer.values());
227
+ const msgBufferToFlush = Array.from(this.msgStatBuffer.values());
228
+ const advancedBufferToFlush = this.oriCacheBuffer;
229
+ this.cmdStatBuffer.clear();
230
+ this.msgStatBuffer.clear();
231
+ this.oriCacheBuffer = [];
221
232
  try {
222
- await this.ctx.database.upsert("analyse_cache", bufferToFlush);
233
+ if (cmdBufferToFlush.length > 0) {
234
+ await this.ctx.database.upsert(
235
+ "analyse_cmd",
236
+ (row) => cmdBufferToFlush.map((item) => ({
237
+ uid: item.uid,
238
+ command: item.command,
239
+ count: import_koishi.$.add(import_koishi.$.ifNull(row.count, 0), item.count),
240
+ timestamp: item.timestamp
241
+ }))
242
+ );
243
+ }
244
+ if (msgBufferToFlush.length > 0) {
245
+ await this.ctx.database.upsert(
246
+ "analyse_msg",
247
+ (row) => msgBufferToFlush.map((item) => ({
248
+ uid: item.uid,
249
+ type: item.type,
250
+ hour: item.hour,
251
+ count: import_koishi.$.add(import_koishi.$.ifNull(row.count, 0), item.count),
252
+ timestamp: item.timestamp
253
+ }))
254
+ );
255
+ }
256
+ if (advancedBufferToFlush.length > 0) await this.ctx.database.upsert("analyse_cache", advancedBufferToFlush);
223
257
  } catch (error) {
224
- this.ctx.logger.error("写入缓存出错:", error);
258
+ this.ctx.logger.error("写入数据出错:", error);
225
259
  }
226
260
  }
227
261
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chat-analyse",
3
3
  "description": "聊天记录分析",
4
- "version": "0.3.1",
4
+ "version": "0.3.3",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],