koishi-plugin-chat-analyse 0.1.1 → 0.1.2

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.
@@ -1,42 +1,63 @@
1
1
  import { Context } from 'koishi';
2
2
  /**
3
3
  * @file collector.ts
4
- * @description 通过独立的事件监听器,分别持久化存储收到的普通消息和已确认的命令,并高效地维护ID与名称的映射。
4
+ * @description 通过统一的事件监听器,持久化存储所有收到的消息和命令,并高效地维护用户/群组ID与名称的映射。
5
5
  */
6
6
  declare module 'koishi' {
7
7
  interface Tables {
8
- analyse_origin_msg: {
8
+ analyse_msg: {
9
9
  id: number;
10
10
  channelId: string;
11
11
  userId: string;
12
+ type: string;
12
13
  content: string;
13
14
  timestamp: Date;
14
15
  };
15
- analyse_origin_cmd: {
16
- id: number;
17
- channelId: string;
18
- userId: string;
19
- command: string;
20
- content: string;
21
- timestamp: Date;
22
- };
23
- analyse_name_map: {
16
+ analyse_name: {
24
17
  id: number;
25
18
  channelId: string;
26
19
  channelName: string;
27
20
  userId: string;
28
21
  userName: string;
29
- timestamp: Date;
30
22
  };
31
23
  }
32
24
  }
33
25
  /**
34
26
  * @class Collector
35
- * @description 使用双监听器,精确捕获并存储消息和命令,并维护一个ID到名称的映射表。
27
+ * @description 核心收集器类。负责初始化数据库、监听消息、分类处理并持久化数据。
36
28
  */
37
29
  export declare class Collector {
38
30
  private ctx;
31
+ /**
32
+ * @property {Map<string, { name: string, timestamp: number }>} nameCache
33
+ * @description 用户名缓存,键为“频道ID:用户ID”,值为名称和时间戳。
34
+ */
39
35
  private nameCache;
36
+ /**
37
+ * @constructor
38
+ * @param {Context} ctx Koishi 的上下文对象
39
+ */
40
40
  constructor(ctx: Context);
41
- private updateName;
41
+ /**
42
+ * @private
43
+ * @method defineModels
44
+ * @description 初始化插件所需的两个数据表模型。
45
+ */
46
+ private defineModels;
47
+ /**
48
+ * @private
49
+ * @method sanitizeContent
50
+ * @description 将消息元素数组转换并清理为用于存储的字符串。
51
+ * @param {Element[]} elements 消息元素数组
52
+ * @returns {string} 清理后的消息内容
53
+ */
54
+ private sanitizeContent;
55
+ /**
56
+ * @private
57
+ * @method updateNameIfNeeded
58
+ * @description 检查并按需更新用户和频道的名称。
59
+ * @param {Session} session 当前会话对象
60
+ * @param {string} effectiveId 当前生效的频道/群组ID
61
+ */
62
+ private updateNameIfNeeded;
42
63
  }
package/lib/index.js CHANGED
@@ -31,108 +31,113 @@ var import_koishi = require("koishi");
31
31
 
32
32
  // src/collector.ts
33
33
  var Collector = class {
34
+ /**
35
+ * @constructor
36
+ * @param {Context} ctx Koishi 的上下文对象
37
+ */
34
38
  constructor(ctx) {
35
39
  this.ctx = ctx;
36
- ctx.model.extend("analyse_origin_msg", {
37
- id: "unsigned",
38
- channelId: "string",
39
- userId: "string",
40
- content: "text",
41
- timestamp: "timestamp"
42
- }, {
43
- autoInc: true,
44
- indexes: ["channelId", "userId", "timestamp"]
40
+ this.defineModels();
41
+ ctx.on("message", async (session) => {
42
+ const { userId, channelId, guildId, content, timestamp, argv, elements } = session;
43
+ const effectiveId = channelId || guildId;
44
+ if (!effectiveId || !userId) return;
45
+ this.updateNameIfNeeded(session, effectiveId);
46
+ const isCommand = !!argv?.command;
47
+ const type = isCommand ? argv.command.name : [...new Set(elements.map((e) => `[${e.type}]`))].join("");
48
+ const finalContent = isCommand ? content : this.sanitizeContent(elements);
49
+ if (!finalContent?.trim()) return;
50
+ await ctx.database.create("analyse_msg", {
51
+ channelId: effectiveId,
52
+ userId,
53
+ type,
54
+ content: finalContent,
55
+ timestamp: new Date(timestamp)
56
+ });
45
57
  });
46
- ctx.model.extend("analyse_origin_cmd", {
58
+ }
59
+ static {
60
+ __name(this, "Collector");
61
+ }
62
+ /**
63
+ * @property {Map<string, { name: string, timestamp: number }>} nameCache
64
+ * @description 用户名缓存,键为“频道ID:用户ID”,值为名称和时间戳。
65
+ */
66
+ nameCache = /* @__PURE__ */ new Map();
67
+ /**
68
+ * @private
69
+ * @method defineModels
70
+ * @description 初始化插件所需的两个数据表模型。
71
+ */
72
+ defineModels() {
73
+ this.ctx.model.extend("analyse_msg", {
47
74
  id: "unsigned",
48
75
  channelId: "string",
49
76
  userId: "string",
50
- command: "string",
77
+ type: "string",
51
78
  content: "text",
52
79
  timestamp: "timestamp"
53
- }, {
54
- autoInc: true,
55
- indexes: ["channelId", "userId", "command", "timestamp"]
56
- });
57
- ctx.model.extend("analyse_name_map", {
80
+ }, { autoInc: true, indexes: ["channelId", "userId", "type", "timestamp"] });
81
+ this.ctx.model.extend("analyse_name", {
58
82
  id: "unsigned",
59
83
  channelId: "string",
60
84
  channelName: "string",
61
85
  userId: "string",
62
- userName: "string",
63
- timestamp: "timestamp"
64
- }, {
65
- autoInc: true,
66
- unique: [["channelId", "userId"]]
67
- });
68
- ctx.on("command/before-execute", async (argv) => {
69
- const effectiveId = argv.session.channelId || argv.session.guildId;
70
- if (!effectiveId) return;
71
- await ctx.database.create("analyse_origin_cmd", {
72
- channelId: effectiveId,
73
- userId: argv.session.userId,
74
- command: argv.command.name,
75
- content: argv.session.content,
76
- timestamp: new Date(argv.session.timestamp)
77
- });
78
- });
79
- ctx.on("message", async (session) => {
80
- const { userId, author } = session;
81
- const effectiveId = session.channelId || session.guildId;
82
- const { channelName, guildName } = session;
83
- const effectiveName = channelName || guildName;
84
- const currentName = author?.name || author?.nick;
85
- if (currentName && effectiveName && effectiveId && userId) {
86
- const cacheKey = `${effectiveId}:${userId}`;
87
- const cachedName = this.nameCache.get(cacheKey);
88
- if (cachedName && currentName !== cachedName) await this.updateName(effectiveId, effectiveName, userId, currentName);
89
- else if (!cachedName) {
90
- const dbRecord = await ctx.database.get("analyse_name_map", {
91
- channelId: effectiveId,
92
- userId
93
- });
94
- const dbName = dbRecord[0]?.userName;
95
- if (!dbName || currentName !== dbName) await this.updateName(effectiveId, effectiveName, userId, currentName);
96
- else this.nameCache.set(cacheKey, currentName);
97
- }
98
- }
99
- if (session.argv?.command) return;
100
- if (!effectiveId) return;
101
- const content = session.elements.map((element) => {
102
- switch (element.type) {
103
- case "text":
104
- return element.attrs.content;
105
- case "img":
106
- return element.attrs.summary === "[动画表情]" ? "[gif]" : `[${element.type}]`;
107
- case "at":
108
- return `[at:${element.attrs.id}]`;
109
- default:
110
- return `[${element.type}]`;
111
- }
112
- }).join("");
113
- const sanitizedContent = content.trim();
114
- if (!sanitizedContent) return;
115
- await ctx.database.create("analyse_origin_msg", {
116
- channelId: effectiveId,
117
- userId: session.userId,
118
- content: sanitizedContent,
119
- timestamp: new Date(session.timestamp)
120
- });
121
- });
86
+ userName: "string"
87
+ }, { autoInc: true, unique: [["channelId", "userId"]] });
122
88
  }
123
- static {
124
- __name(this, "Collector");
89
+ /**
90
+ * @private
91
+ * @method sanitizeContent
92
+ * @description 将消息元素数组转换并清理为用于存储的字符串。
93
+ * @param {Element[]} elements 消息元素数组
94
+ * @returns {string} 清理后的消息内容
95
+ */
96
+ sanitizeContent(elements) {
97
+ return elements.map((element) => {
98
+ switch (element.type) {
99
+ case "text":
100
+ return element.attrs.content;
101
+ case "img":
102
+ return element.attrs.summary === "[动画表情]" ? "[gif]" : `[img]`;
103
+ case "at":
104
+ return `[at:${element.attrs.id}]`;
105
+ default:
106
+ return `[${element.type}]`;
107
+ }
108
+ }).join("");
125
109
  }
126
- nameCache = /* @__PURE__ */ new Map();
127
- async updateName(channelId, channelName, userId, userName) {
128
- await this.ctx.database.upsert("analyse_name_map", [{
129
- channelId,
110
+ /**
111
+ * @private
112
+ * @method updateNameIfNeeded
113
+ * @description 检查并按需更新用户和频道的名称。
114
+ * @param {Session} session 当前会话对象
115
+ * @param {string} effectiveId 当前生效的频道/群组ID
116
+ */
117
+ async updateNameIfNeeded(session, effectiveId) {
118
+ const { userId, guildId, bot } = session;
119
+ const cacheKey = `${effectiveId}:${userId}`;
120
+ const cached = this.nameCache.get(cacheKey);
121
+ if (cached && Date.now() - cached.timestamp < 864e5) return;
122
+ const [guild, member] = await Promise.all([
123
+ bot.getGuild(guildId),
124
+ bot.getGuildMember(guildId, userId)
125
+ ]);
126
+ const channelName = guild?.name;
127
+ const userName = member?.nick || member?.name;
128
+ if (!channelName || !userName) return;
129
+ const [record] = await this.ctx.database.get("analyse_name", { channelId: effectiveId, userId });
130
+ if (record?.userName === userName && record?.channelName === channelName) {
131
+ this.nameCache.set(cacheKey, { name: userName, timestamp: Date.now() });
132
+ return;
133
+ }
134
+ await this.ctx.database.upsert("analyse_name", [{
135
+ channelId: effectiveId,
130
136
  channelName,
131
137
  userId,
132
- userName,
133
- timestamp: /* @__PURE__ */ new Date()
138
+ userName
134
139
  }]);
135
- this.nameCache.set(`${channelId}:${userId}`, userName);
140
+ this.nameCache.set(cacheKey, { name: userName, timestamp: Date.now() });
136
141
  }
137
142
  };
138
143
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chat-analyse",
3
3
  "description": "聊天记录分析",
4
- "version": "0.1.1",
4
+ "version": "0.1.2",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],