koishi-plugin-chat-analyse 0.3.6 → 0.4.0

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.
@@ -28,6 +28,12 @@ declare module 'koishi' {
28
28
  content: string;
29
29
  timestamp: Date;
30
30
  };
31
+ analyse_at: {
32
+ uid: number;
33
+ target: string;
34
+ content: string;
35
+ timestamp: Date;
36
+ };
31
37
  }
32
38
  }
33
39
  /**
@@ -44,6 +50,7 @@ export declare class Collector {
44
50
  private msgStatBuffer;
45
51
  private cmdStatBuffer;
46
52
  private oriCacheBuffer;
53
+ private whoAtBuffer;
47
54
  private userCache;
48
55
  private pendingUserRequests;
49
56
  private flushInterval;
package/lib/WhoAt.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { Context, Command } from 'koishi';
2
+ import { Config } from './index';
3
+ /**
4
+ * @class WhoAt
5
+ * @description
6
+ * 负责处理与“谁@我”相关的功能。
7
+ * 该类会注册一个 'whoatme' 子命令,允许用户查询在何时被谁提及。
8
+ * 查询结果将以合并转发的形式发送给用户。
9
+ * 此外,该类还包含一个定时任务,用于定期清理数据库中旧的@记录。
10
+ */
11
+ export declare class WhoAt {
12
+ private ctx;
13
+ private config;
14
+ /**
15
+ * WhoAt 类的构造函数。
16
+ * @param {Context} ctx - Koishi 的插件上下文,用于访问框架核心功能和数据库等服务。
17
+ * @param {Config} config - 插件的配置对象,包含如记录保留天数等设置。
18
+ */
19
+ constructor(ctx: Context, config: Config);
20
+ /**
21
+ * @private
22
+ * @method setupCleanupTask
23
+ * @description 设置一个定时清理任务。
24
+ * 此任务会根据配置中的 `retentionDays` 定期删除过期的@记录,以防止数据库膨胀。
25
+ */
26
+ private setupCleanupTask;
27
+ /**
28
+ * @public
29
+ * @method registerCommand
30
+ * @description 在主 `analyse` 命令下注册 `whoatme` 子命令。
31
+ * @param {Command} analyse - 用户传入的主 `analyse` 命令实例,`whoatme` 将作为其子命令。
32
+ */
33
+ registerCommand(analyse: Command): void;
34
+ }
package/lib/index.d.ts CHANGED
@@ -16,6 +16,8 @@ export interface Config {
16
16
  enableMsgStat: boolean;
17
17
  enableRankStat: boolean;
18
18
  enableOriRecord: boolean;
19
+ enableWhoAt: boolean;
20
+ retentionDays: number;
19
21
  }
20
22
  /**
21
23
  * @const {Schema<Config>} Config
package/lib/index.js CHANGED
@@ -27,7 +27,7 @@ __export(src_exports, {
27
27
  using: () => using
28
28
  });
29
29
  module.exports = __toCommonJS(src_exports);
30
- var import_koishi4 = require("koishi");
30
+ var import_koishi5 = require("koishi");
31
31
 
32
32
  // src/Collector.ts
33
33
  var import_koishi = require("koishi");
@@ -59,6 +59,7 @@ var Collector = class _Collector {
59
59
  msgStatBuffer = /* @__PURE__ */ new Map();
60
60
  cmdStatBuffer = /* @__PURE__ */ new Map();
61
61
  oriCacheBuffer = [];
62
+ whoAtBuffer = [];
62
63
  // 用户缓存
63
64
  userCache = /* @__PURE__ */ new Map();
64
65
  pendingUserRequests = /* @__PURE__ */ new Map();
@@ -97,6 +98,14 @@ var Collector = class _Collector {
97
98
  timestamp: "timestamp"
98
99
  }, { primary: "id", autoInc: true, indexes: ["uid", "timestamp"] });
99
100
  }
101
+ if (this.config.enableWhoAt) {
102
+ this.ctx.model.extend("analyse_at", {
103
+ uid: "unsigned",
104
+ target: "string",
105
+ content: "text",
106
+ timestamp: "timestamp"
107
+ }, { indexes: ["target", "uid"] });
108
+ }
100
109
  }
101
110
  /**
102
111
  * @private
@@ -136,6 +145,16 @@ var Collector = class _Collector {
136
145
  this.msgStatBuffer.set(key, { uid, type, hour: hourStart, count: 1, timestamp: messageTime });
137
146
  }
138
147
  }
148
+ if (this.config.enableWhoAt) {
149
+ const atElements = elements.filter((e) => e.type === "at");
150
+ if (atElements.length > 0) {
151
+ const sanitizedContent = this.sanitizeContent(elements);
152
+ for (const atElement of atElements) {
153
+ const targetId = atElement.attrs.id;
154
+ if (targetId && targetId !== userId) this.whoAtBuffer.push({ uid, target: targetId, content: sanitizedContent, timestamp: messageTime });
155
+ }
156
+ }
157
+ }
139
158
  if (this.config.enableOriRecord) {
140
159
  this.oriCacheBuffer.push({
141
160
  uid,
@@ -225,10 +244,12 @@ var Collector = class _Collector {
225
244
  async flushBuffers() {
226
245
  const cmdBufferToFlush = Array.from(this.cmdStatBuffer.values());
227
246
  const msgBufferToFlush = Array.from(this.msgStatBuffer.values());
228
- const advancedBufferToFlush = this.oriCacheBuffer;
247
+ const oriCacheBufferToFlush = this.oriCacheBuffer;
248
+ const whoAtBufferToFlush = this.whoAtBuffer;
229
249
  this.cmdStatBuffer.clear();
230
250
  this.msgStatBuffer.clear();
231
251
  this.oriCacheBuffer = [];
252
+ this.whoAtBuffer = [];
232
253
  try {
233
254
  if (cmdBufferToFlush.length > 0) {
234
255
  await this.ctx.database.upsert(
@@ -253,7 +274,8 @@ var Collector = class _Collector {
253
274
  }))
254
275
  );
255
276
  }
256
- if (advancedBufferToFlush.length > 0) await this.ctx.database.upsert("analyse_cache", advancedBufferToFlush);
277
+ if (whoAtBufferToFlush.length > 0) await this.ctx.database.upsert("analyse_at", whoAtBufferToFlush);
278
+ if (oriCacheBufferToFlush.length > 0) await this.ctx.database.upsert("analyse_cache", oriCacheBufferToFlush);
257
279
  } catch (error) {
258
280
  this.ctx.logger.error("写入数据出错:", error);
259
281
  }
@@ -423,16 +445,16 @@ var Renderer = class {
423
445
  const { title, time, list } = data;
424
446
  if (!list?.length) return "暂无数据可供渲染";
425
447
  let totalValueForPercent = 0;
426
- const countHeaderIndex = headers?.findIndex((h2) => ["总计发言", "条数", "次数", "数量"].includes(h2));
448
+ const countHeaderIndex = headers?.findIndex((h3) => ["总计发言", "条数", "次数", "数量"].includes(h3));
427
449
  if (countHeaderIndex > -1) {
428
450
  totalValueForPercent = list.reduce((sum, row) => sum + (Number(row[countHeaderIndex]) || 0), 0);
429
451
  }
430
452
  const totalCount = data.total || totalValueForPercent;
431
- const tableHeadHtml = headers?.length > 0 ? `<thead><tr><th class="rank-cell">#</th>${headers.map((h2, i) => {
453
+ const tableHeadHtml = headers?.length > 0 ? `<thead><tr><th class="rank-cell">#</th>${headers.map((h3, i) => {
432
454
  const firstCell = list[0]?.[i];
433
- const isRightAlign = typeof firstCell === "number" || firstCell instanceof Date || h2.includes("占比");
455
+ const isRightAlign = typeof firstCell === "number" || firstCell instanceof Date || h3.includes("占比");
434
456
  const alignClass = isRightAlign ? "header-right-align" : "name-header";
435
- return `<th class="${alignClass}">${h2}</th>`;
457
+ return `<th class="${alignClass}">${h3}</th>`;
436
458
  }).join("")}</tr></thead>` : "";
437
459
  const tableRowsHtml = list.map((row, index) => {
438
460
  const rank = index + 1;
@@ -758,6 +780,73 @@ var Stat = class {
758
780
  }
759
781
  };
760
782
 
783
+ // src/WhoAt.ts
784
+ var import_koishi4 = require("koishi");
785
+ var WhoAt = class {
786
+ /**
787
+ * WhoAt 类的构造函数。
788
+ * @param {Context} ctx - Koishi 的插件上下文,用于访问框架核心功能和数据库等服务。
789
+ * @param {Config} config - 插件的配置对象,包含如记录保留天数等设置。
790
+ */
791
+ constructor(ctx, config) {
792
+ this.ctx = ctx;
793
+ this.config = config;
794
+ this.setupCleanupTask();
795
+ }
796
+ static {
797
+ __name(this, "WhoAt");
798
+ }
799
+ /**
800
+ * @private
801
+ * @method setupCleanupTask
802
+ * @description 设置一个定时清理任务。
803
+ * 此任务会根据配置中的 `retentionDays` 定期删除过期的@记录,以防止数据库膨胀。
804
+ */
805
+ setupCleanupTask() {
806
+ if (this.config.retentionDays > 0) {
807
+ this.ctx.cron("0 0 * * *", async () => {
808
+ try {
809
+ const cutoffDate = new Date(Date.now() - this.config.retentionDays * import_koishi4.Time.day);
810
+ await this.ctx.database.remove("analyse_at", { timestamp: { $lt: cutoffDate } });
811
+ } catch (error) {
812
+ this.ctx.logger.error("清理 @ 历史记录出错:", error);
813
+ }
814
+ });
815
+ }
816
+ }
817
+ /**
818
+ * @public
819
+ * @method registerCommand
820
+ * @description 在主 `analyse` 命令下注册 `whoatme` 子命令。
821
+ * @param {Command} analyse - 用户传入的主 `analyse` 命令实例,`whoatme` 将作为其子命令。
822
+ */
823
+ registerCommand(analyse) {
824
+ analyse.subcommand("whoatme", "谁 @ 我").action(async ({ session }) => {
825
+ if (!session.userId) return "无法获取用户信息";
826
+ try {
827
+ const records = await this.ctx.database.select("analyse_at").where({ target: session.userId }).orderBy("timestamp", "asc").limit(100).execute();
828
+ if (records.length === 0) return "暂无 @ 记录";
829
+ const uids = [...new Set(records.map((r) => r.uid))];
830
+ const users = await this.ctx.database.select("analyse_user", { uid: { $in: uids } }).project(["uid", "userName", "userId"]).execute();
831
+ const userInfoMap = new Map(users.map((u) => [u.uid, { name: u.userName, id: u.userId }]));
832
+ const messageElements = records.map((record) => {
833
+ const senderInfo = userInfoMap.get(record.uid);
834
+ const userId = senderInfo?.id;
835
+ const authorElement = (0, import_koishi4.h)("author", { userId, name: userId });
836
+ const contentElement = import_koishi4.h.text(record.content);
837
+ return (0, import_koishi4.h)("message", {}, [authorElement, contentElement]);
838
+ });
839
+ if (messageElements.length === 0) return "暂无有效 @ 记录";
840
+ const forwardMessage = (0, import_koishi4.h)("message", { forward: true }, messageElements);
841
+ await session.send(forwardMessage);
842
+ } catch (error) {
843
+ this.ctx.logger.error("查询 @ 记录时失败:", error);
844
+ return "查询失败,请稍后再试";
845
+ }
846
+ });
847
+ }
848
+ };
849
+
761
850
  // src/index.ts
762
851
  var usage = `
763
852
  <div style="border-radius: 10px; border: 1px solid #ddd; padding: 16px; margin-bottom: 20px; box-shadow: 0 2px 5px rgba(0,0,0,0.1);">
@@ -772,22 +861,27 @@ var usage = `
772
861
  </div>
773
862
  `;
774
863
  var name = "chat-analyse";
775
- var using = ["database", "puppeteer"];
776
- var Config = import_koishi4.Schema.intersect([
777
- import_koishi4.Schema.object({
778
- enableListener: import_koishi4.Schema.boolean().default(true).description("启用消息监听"),
779
- enableOriRecord: import_koishi4.Schema.boolean().default(true).description("启用原始记录")
864
+ var using = ["database", "puppeteer", "cron"];
865
+ var Config = import_koishi5.Schema.intersect([
866
+ import_koishi5.Schema.object({
867
+ enableListener: import_koishi5.Schema.boolean().default(true).description("启用消息监听"),
868
+ enableOriRecord: import_koishi5.Schema.boolean().default(true).description("启用原始记录")
780
869
  }).description("监听配置"),
781
- import_koishi4.Schema.object({
782
- enableCmdStat: import_koishi4.Schema.boolean().default(true).description("启用命令统计"),
783
- enableMsgStat: import_koishi4.Schema.boolean().default(true).description("启用消息统计"),
784
- enableRankStat: import_koishi4.Schema.boolean().default(true).description("启用发言排行")
785
- }).description("命令配置")
870
+ import_koishi5.Schema.object({
871
+ enableCmdStat: import_koishi5.Schema.boolean().default(true).description("启用命令统计"),
872
+ enableMsgStat: import_koishi5.Schema.boolean().default(true).description("启用消息统计"),
873
+ enableRankStat: import_koishi5.Schema.boolean().default(true).description("启用发言排行")
874
+ }).description("命令配置"),
875
+ import_koishi5.Schema.object({
876
+ enableWhoAt: import_koishi5.Schema.boolean().default(true).description("启用 @ 记录"),
877
+ retentionDays: import_koishi5.Schema.number().min(0).default(7).description("保留天数")
878
+ }).description("@ 记录配置")
786
879
  ]);
787
880
  function apply(ctx, config) {
788
881
  if (config.enableListener) new Collector(ctx, config);
789
882
  const analyse = ctx.command("analyse", "聊天记录分析");
790
883
  new Stat(ctx, config).registerCommands(analyse);
884
+ if (config.enableWhoAt) new WhoAt(ctx, config).registerCommand(analyse);
791
885
  }
792
886
  __name(apply, "apply");
793
887
  // Annotate the CommonJS export names for ESM import in node:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chat-analyse",
3
3
  "description": "聊天记录分析",
4
- "version": "0.3.6",
4
+ "version": "0.4.0",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],