koishi-plugin-chat-analyse 0.2.2 → 0.2.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.
@@ -29,6 +29,7 @@ export declare class Collector {
29
29
  private msgBuffer;
30
30
  private flushInterval;
31
31
  private nameCache;
32
+ private pendingNameRequests;
32
33
  /**
33
34
  * @constructor
34
35
  * @param ctx {Context} Koishi 的上下文对象
@@ -41,9 +42,6 @@ export declare class Collector {
41
42
  private handleMessage;
42
43
  /**
43
44
  * 从消息元素中提取并汇总类型。
44
- * @example
45
- * // returns "[text][img]"
46
- * summarizeElementTypes([{type: 'text'}, {type: 'img'}])
47
45
  */
48
46
  private summarizeElementTypes;
49
47
  /**
@@ -56,7 +54,11 @@ export declare class Collector {
56
54
  private flushBuffer;
57
55
  /**
58
56
  * 检查并更新用户和群组的名称信息。
59
- * 如果名称不在缓存或缓存已过期 (超过24小时),则从 API 获取并更新到数据库。
60
57
  */
61
58
  private updateNameIfNeeded;
59
+ /**
60
+ * @private
61
+ * 封装了实际获取和更新名称的异步逻辑。
62
+ */
63
+ private fetchAndUpdateNames;
62
64
  }
package/lib/index.js CHANGED
@@ -74,6 +74,7 @@ var Collector = class _Collector {
74
74
  msgBuffer = [];
75
75
  flushInterval;
76
76
  nameCache = /* @__PURE__ */ new Map();
77
+ pendingNameRequests = /* @__PURE__ */ new Map();
77
78
  /**
78
79
  * 核心消息处理函数。
79
80
  * @param session {Session} 消息会话对象
@@ -82,7 +83,7 @@ var Collector = class _Collector {
82
83
  const { userId, channelId, guildId, content, timestamp, argv, elements } = session;
83
84
  const effectiveId = channelId || guildId;
84
85
  if (!effectiveId || !userId || !timestamp) return;
85
- this.updateNameIfNeeded(session, effectiveId);
86
+ await this.updateNameIfNeeded(session, effectiveId);
86
87
  const isCommand = !!argv?.command;
87
88
  const type = isCommand ? argv.command.name : this.summarizeElementTypes(elements);
88
89
  const finalContent = isCommand ? content : this.sanitizeContent(elements);
@@ -100,9 +101,6 @@ var Collector = class _Collector {
100
101
  }
101
102
  /**
102
103
  * 从消息元素中提取并汇总类型。
103
- * @example
104
- * // returns "[text][img]"
105
- * summarizeElementTypes([{type: 'text'}, {type: 'img'}])
106
104
  */
107
105
  summarizeElementTypes(elements) {
108
106
  return [...new Set(elements.map((e) => `[${e.type}]`))].join("");
@@ -140,31 +138,49 @@ var Collector = class _Collector {
140
138
  }
141
139
  /**
142
140
  * 检查并更新用户和群组的名称信息。
143
- * 如果名称不在缓存或缓存已过期 (超过24小时),则从 API 获取并更新到数据库。
144
141
  */
145
142
  async updateNameIfNeeded(session, effectiveId) {
146
- const { userId, guildId, bot } = session;
143
+ const { userId } = session;
144
+ if (!userId) return;
147
145
  const cacheKey = `${effectiveId}:${userId}`;
146
+ if (this.pendingNameRequests.has(cacheKey)) return this.pendingNameRequests.get(cacheKey);
148
147
  const cached = this.nameCache.get(cacheKey);
149
148
  const CACHE_EXPIRATION = 24 * 60 * 60 * 1e3;
150
149
  if (cached && Date.now() - cached.timestamp < CACHE_EXPIRATION) return;
150
+ const promise = this.fetchAndUpdateNames(session, effectiveId, cacheKey);
151
+ this.pendingNameRequests.set(cacheKey, promise);
151
152
  try {
153
+ await promise;
154
+ } finally {
155
+ this.pendingNameRequests.delete(cacheKey);
156
+ }
157
+ }
158
+ /**
159
+ * @private
160
+ * 封装了实际获取和更新名称的异步逻辑。
161
+ */
162
+ async fetchAndUpdateNames(session, effectiveId, cacheKey) {
163
+ try {
164
+ const { userId, guildId, bot } = session;
152
165
  const [guild, member] = await Promise.all([
153
- bot.getGuild(guildId),
154
- bot.getGuildMember(guildId, userId)
166
+ guildId ? bot.getGuild(guildId).catch(() => null) : Promise.resolve(null),
167
+ guildId && userId ? bot.getGuildMember(guildId, userId).catch(() => null) : Promise.resolve(null)
155
168
  ]);
156
169
  const channelName = guild?.name;
157
170
  const userName = member?.nick || member?.name;
158
- if (!channelName || !userName) return;
159
- await this.ctx.database.upsert("analyse_name", () => [{
171
+ if (!channelName || !userName) {
172
+ this.nameCache.set(cacheKey, { name: null, timestamp: Date.now() });
173
+ return;
174
+ }
175
+ await this.ctx.database.upsert("analyse_name", [{
160
176
  channelId: effectiveId,
161
- channelName,
162
177
  userId,
178
+ channelName,
163
179
  userName
164
180
  }]);
165
181
  this.nameCache.set(cacheKey, { name: userName, timestamp: Date.now() });
166
182
  } catch (error) {
167
- this.ctx.logger.warn("更新用户/群组名称失败:", error);
183
+ this.nameCache.set(cacheKey, { name: null, timestamp: Date.now() });
168
184
  }
169
185
  }
170
186
  };
@@ -328,7 +344,7 @@ var CmdStat = class {
328
344
  }, { primary: ["channelId", "userId", "command"] });
329
345
  this.ctx.on("command/before-execute", async ({ command, session }) => {
330
346
  const { userId, guildId } = session;
331
- if (!guildId || !userId || command.name === "analyse" || command.parent?.name === "analyse") return;
347
+ if (!guildId || !userId) return;
332
348
  const commandName = command.name;
333
349
  const query = { channelId: guildId, userId, command: commandName };
334
350
  await this.ctx.database.upsert("analyse_cmd", (row) => [{
@@ -395,25 +411,25 @@ var CmdStat = class {
395
411
  const query = {};
396
412
  if (guildId) query.channelId = guildId;
397
413
  if (userId) query.userId = userId;
398
- const records = await this.ctx.database.get("analyse_cmd", query);
399
- if (records.length === 0) return "暂无统计数据";
400
- const totalCount = records.reduce((sum, record) => sum + record.count, 0);
401
- let sortedList;
402
414
  if (userId) {
403
- sortedList = records.map((r) => ({ name: r.command, count: r.count, lastUsed: r.timestamp })).sort((a, b) => b.count - a.count);
404
- } else {
405
- const commandMap = /* @__PURE__ */ new Map();
406
- for (const record of records) {
407
- const existing = commandMap.get(record.command) || { count: 0, lastUsed: /* @__PURE__ */ new Date(0) };
408
- existing.count += record.count;
409
- if (record.timestamp > existing.lastUsed) {
410
- existing.lastUsed = record.timestamp;
411
- }
412
- commandMap.set(record.command, existing);
413
- }
414
- sortedList = Array.from(commandMap.entries()).map(([name2, data]) => ({ name: name2, ...data })).sort((a, b) => b.count - a.count);
415
+ const records = await this.ctx.database.get("analyse_cmd", query);
416
+ if (records.length === 0) return "暂无统计数据";
417
+ const totalCount2 = records.reduce((sum, record) => sum + record.count, 0);
418
+ const sortedList2 = records.map((r) => ({ name: r.command, count: r.count, lastUsed: r.timestamp })).sort((a, b) => b.count - a.count);
419
+ const list2 = sortedList2.map((item) => [item.name, item.count, item.lastUsed]);
420
+ return { list: list2, total: totalCount2 };
415
421
  }
416
- const list = sortedList.map((item) => [item.name, item.count, item.lastUsed]);
422
+ const aggregatedStats = await this.ctx.database.select("analyse_cmd", query).groupBy(
423
+ ["command"],
424
+ {
425
+ count: /* @__PURE__ */ __name((row) => import_koishi2.$.sum(row.count), "count"),
426
+ lastUsed: /* @__PURE__ */ __name((row) => import_koishi2.$.max(row.timestamp), "lastUsed")
427
+ }
428
+ ).execute();
429
+ if (aggregatedStats.length === 0) return "暂无统计数据";
430
+ const totalCount = aggregatedStats.reduce((sum, record) => sum + record.count, 0);
431
+ const sortedList = aggregatedStats.sort((a, b) => b.count - a.count);
432
+ const list = sortedList.map((item) => [item.command, item.count, item.lastUsed]);
417
433
  return { list, total: totalCount };
418
434
  }
419
435
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chat-analyse",
3
3
  "description": "聊天记录分析",
4
- "version": "0.2.2",
4
+ "version": "0.2.3",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],