koishi-plugin-chat-analyse 0.2.4 → 0.2.6

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,71 +1,96 @@
1
1
  import { Context } from 'koishi';
2
+ import { Config } from './index';
2
3
  declare module 'koishi' {
3
4
  interface Tables {
4
- analyse_msg: {
5
- id: number;
5
+ analyse_user: {
6
+ uid: number;
6
7
  channelId: string;
7
8
  userId: string;
9
+ channelName: string;
10
+ userName: string;
11
+ };
12
+ analyse_cmd: {
13
+ uid: number;
14
+ command: string;
15
+ count: number;
16
+ timestamp: Date;
17
+ };
18
+ analyse_msg: {
19
+ uid: number;
8
20
  type: string;
9
- content: string;
21
+ count: number;
10
22
  timestamp: Date;
11
23
  };
12
- analyse_name: {
24
+ analyse_cache: {
25
+ id: number;
13
26
  channelId: string;
14
- channelName: string;
15
27
  userId: string;
16
- userName: string;
28
+ content: string;
29
+ timestamp: Date;
17
30
  };
18
31
  }
19
32
  }
20
33
  /**
21
34
  * @class Collector
22
- * @description 负责收集、缓冲并持久化消息数据,同时高效缓存用户与群组的名称信息。
35
+ * @description 核心数据收集器。根据插件配置,高效地监听、收集、缓冲并持久化聊天数据。
23
36
  */
24
37
  export declare class Collector {
25
38
  private ctx;
39
+ private config;
40
+ /** @const {number} FLUSH_INTERVAL - 内存缓存区自动刷新到数据库的时间间隔。 */
26
41
  private static readonly FLUSH_INTERVAL;
42
+ /** @const {number} BUFFER_THRESHOLD - 内存缓存区触发自动刷新的消息数量阈值。 */
27
43
  private static readonly BUFFER_THRESHOLD;
28
- private msgBuffer;
29
- private nameCache;
30
- private pendingNameRequests;
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;
31
50
  private flushInterval;
32
51
  /**
33
52
  * @constructor
34
- * @param ctx {Context} Koishi 上下文,用于访问框架核心功能。
35
- */
36
- constructor(ctx: Context);
37
- /**
38
- * 核心消息处理器,对消息进行格式化并存入缓冲区。
39
- * @param session {Session} 消息会话对象。
53
+ * @param {Context} ctx - Koishi 的插件上下文。
54
+ * @param {Config} config - 插件的配置对象。
40
55
  */
41
- private handleMessage;
56
+ constructor(ctx: Context, config: Config);
42
57
  /**
43
- * 汇总消息元素的类型,生成紧凑的类型字符串。
44
- * @param elements {Element[]} 消息元素数组。
45
- * @returns {string} 类型汇总字符串,如 `[text][img]`。
58
+ * @private
59
+ * @method defineModels
60
+ * @description 定义插件所需的所有数据表模型。
46
61
  */
47
- private summarizeElementTypes;
62
+ private defineModels;
48
63
  /**
49
- * 清理并格式化消息内容,提取关键信息。
50
- * @param elements {Element[]} 消息元素数组。
51
- * @returns {string} 处理后的内容字符串。
64
+ * @private
65
+ * @async
66
+ * @method handleMessage
67
+ * @description 统一的消息和命令处理器。它会解析收到的消息,提取关键信息并更新相应的统计数据。
68
+ * @param {Session} session - Koishi 的会话对象,包含消息的全部信息。
52
69
  */
53
- private sanitizeContent;
70
+ private handleMessage;
54
71
  /**
55
- * 将内存缓冲区的消息批量写入数据库,并处理写入失败的情况。
72
+ * @private
73
+ * @async
74
+ * @method getOrCreateUser
75
+ * @description 高效地获取或创建用户的中央记录 (`analyse_user`)。
76
+ * @param {Session} session - Koishi 会话对象,用于获取用户信息和 Bot 实例。
77
+ * @param {string} channelId - 消息所在的频道或群组 ID。
78
+ * @returns {Promise<number | null>} 返回用户的唯一 `uid`,如果操作失败则返回 `null`。
56
79
  */
57
- private flushBuffer;
80
+ private getOrCreateUser;
58
81
  /**
59
- * 检查用户和群组名称是否需要更新,利用缓存和请求锁机制避免重复调用。
60
- * @param session {Session} 消息会话对象。
61
- * @param effectiveId {string} 有效的频道/群组ID。
82
+ * @private
83
+ * @method sanitizeContent
84
+ * @description Koishi 的消息元素 (Element) 数组净化为纯文本字符串,以便存储和分析。
85
+ * @param {Element[]} elements - 消息元素的数组。
86
+ * @returns {string} 净化后的纯文本字符串。
62
87
  */
63
- private updateNameIfNeeded;
88
+ private sanitizeContent;
64
89
  /**
65
- * 异步获取用户和群组的最新名称,并更新到数据库和内存缓存。
66
- * @param session {Session} 消息会话对象。
67
- * @param effectiveId {string} 频道/群组ID。
68
- * @param cacheKey {string} 用于缓存的键。
90
+ * @private
91
+ * @async
92
+ * @method flushCacheBuffer
93
+ * @description 将内存中的消息缓存 (`cacheBuffer`) 批量写入数据库。
69
94
  */
70
- private fetchAndUpdateNames;
95
+ private flushCacheBuffer;
71
96
  }
package/lib/Renderer.d.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { Context } from 'koishi';
2
2
  /**
3
- * 定义渲染列表中的单行数据格式。
4
- * @example ['ping', 150, new Date()]
3
+ * @typedef {Array<string | number | Date>} RenderListItem
4
+ * @description 定义了统计列表中单行数据的格式,它是一个由字符串、数字或日期组成的元组。
5
5
  */
6
6
  export type RenderListItem = (string | number | Date)[];
7
7
  /**
8
- * 定义渲染图片所需的数据结构。
8
+ * @interface ListRenderData
9
+ * @description 定义了调用渲染器生成列表图片时所需的完整数据结构。
9
10
  */
10
11
  export interface ListRenderData {
11
12
  title: string;
@@ -15,33 +16,41 @@ export interface ListRenderData {
15
16
  }
16
17
  /**
17
18
  * @class Renderer
18
- * @description 通用列表渲染器,通过 Puppeteer 将数据渲染为包含精美表格的图片。
19
+ * @description 一个通用的列表渲染器。它使用 Koishi 的 Puppeteer 服务将结构化的 `ListRenderData` 数据
20
+ * 渲染为一张包含精美表格的图片。
19
21
  */
20
22
  export declare class Renderer {
21
23
  private ctx;
22
24
  /**
23
25
  * @constructor
24
- * @param ctx {Context} Koishi 上下文,用于访问 puppeteer 服务。
26
+ * @param {Context} ctx - Koishi 的插件上下文,用于访问 puppeteer 服务。
25
27
  */
26
28
  constructor(ctx: Context);
27
29
  /**
28
- * 将列表数据渲染为图片。
29
- * @param data {ListRenderData} 待渲染的列表数据。
30
- * @param headers {string[]} (可选) 表头文案数组,若不提供则不渲染表头。
31
- * @returns {Promise<string | Buffer>} 成功时返回图片 Buffer,无数据时返回提示文本。
30
+ * @public
31
+ * @async
32
+ * @method renderList
33
+ * @description 将列表数据渲染为图片。
34
+ * @param {ListRenderData} data - 待渲染的完整列表数据。
35
+ * @param {string[]} [headers] - (可选) 表头文案数组。如果提供,将会在表格顶部渲染表头。
36
+ * @returns {Promise<string | Buffer>} 渲染成功时返回图片的 Buffer 数据;如果输入数据为空,则返回提示文本。
32
37
  */
33
38
  renderList(data: ListRenderData, headers?: string[]): Promise<string | Buffer>;
34
39
  /**
35
- * 智能格式化日期,提供相对时间(如“刚刚”,“x分钟前”)和绝对日期。
36
- * @param date {Date} 待格式化的日期对象。
40
+ * @private
41
+ * @method formatDate
42
+ * @description 智能格式化日期。对于近期的时间,提供更人性化的相对时间描述(如“刚刚”,“x 分钟前”);对于较早的时间,则显示标准的“年-月-日”格式。
43
+ * @param {Date} date - 待格式化的 Date 对象。
37
44
  * @returns {string} 格式化后的日期字符串。
38
45
  */
39
46
  private formatDate;
40
47
  /**
41
- * 根据数据动态生成渲染图片所需的完整 HTML 字符串。
42
- * @param data {ListRenderData} 列表数据。
43
- * @param headers {string[]} (可选) 表头数组。
44
- * @returns {string | null} 生成的 HTML 字符串,若无数据则返回 null。
48
+ * @private
49
+ * @method generateListHtml
50
+ * @description 根据传入的结构化数据和表头,动态生成用于 Puppeteer 渲染的完整 HTML 字符串。
51
+ * @param {ListRenderData} data - 列表数据对象。
52
+ * @param {string[]} [headers] - (可选) 表头数组。
53
+ * @returns {string | null} 返回生成的 HTML 字符串。如果列表数据为空,则返回 `null`。
45
54
  */
46
55
  private generateListHtml;
47
56
  }
package/lib/Stat.d.ts ADDED
@@ -0,0 +1,56 @@
1
+ import { Context, Command } from 'koishi';
2
+ import { Renderer } from './Renderer';
3
+ import { Config } from './index';
4
+ /**
5
+ * @class Stat
6
+ * @description 提供统一的统计查询服务。它负责注册查询命令,根据用户输入从数据库中获取数据,并调用渲染器生成统计图表。
7
+ */
8
+ export declare class Stat {
9
+ private ctx;
10
+ private config;
11
+ renderer: Renderer;
12
+ /**
13
+ * @constructor
14
+ * @param {Context} ctx - Koishi 的插件上下文。
15
+ * @param {Config} config - 插件的配置对象。
16
+ */
17
+ constructor(ctx: Context, config: Config);
18
+ /**
19
+ * @method registerCommands
20
+ * @description 根据插件配置,动态地将 `.command` 和 `.message` 子命令注册到主 `analyse` 命令下。
21
+ * @param {Command} analyse - 主 `analyse` 命令实例。
22
+ */
23
+ registerCommands(analyse: Command): void;
24
+ /**
25
+ * @private
26
+ * @async
27
+ * @method _generateTitle
28
+ * @description 通用的标题生成器。根据查询参数 (guildId, userId) 和统计类型动态生成易于理解的图片标题。
29
+ * @param {Session} session - 当前会话,备用。
30
+ * @param {string} [guildId] - (可选) 查询的群组 ID。
31
+ * @param {string} [userId] - (可选) 查询的用户 ID。
32
+ * @param {'命令' | '消息'} type - 统计类型,用于嵌入标题文本中。
33
+ * @returns {Promise<string>} 生成的标题字符串。
34
+ */
35
+ private _generateTitle;
36
+ /**
37
+ * @private
38
+ * @async
39
+ * @method getCommandStats
40
+ * @description 从数据库中获取并聚合命令使用统计数据。
41
+ * @param {string} [guildId] - (可选) 若提供,则将范围限制在此群组。
42
+ * @param {string} [userId] - (可选) 若提供,则将范围限制在此用户。
43
+ * @returns {Promise<{ list: RenderListItem[], total: number } | string>} 返回一个包含列表和总数的对象,或在无数据时返回提示字符串。
44
+ */
45
+ private getCommandStats;
46
+ /**
47
+ * @private
48
+ * @async
49
+ * @method getMessageStats
50
+ * @description 从数据库中获取并聚合消息类型统计数据。
51
+ * @param {string} [guildId] - (可选) 若提供,则将范围限制在此群组。
52
+ * @param {string} [userId] - (可选) 若提供,则将范围限制在此用户。
53
+ * @returns {Promise<{ list: RenderListItem[], total: number } | string>} 返回一个包含列表和总数的对象,或在无数据时返回提示字符串。
54
+ */
55
+ private getMessageStats;
56
+ }
package/lib/index.d.ts CHANGED
@@ -1,12 +1,30 @@
1
1
  import { Context, Schema } from 'koishi';
2
+ /**
3
+ * @name 插件使用说明
4
+ * @description 在 Koishi 控制台中显示的插件介绍和帮助信息。
5
+ */
2
6
  export declare const usage = "\n<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);\">\n <h2 style=\"margin-top: 0; color: #4a6ee0;\">\uD83D\uDCCC \u63D2\u4EF6\u8BF4\u660E</h2>\n <p>\uD83D\uDCD6 <strong>\u4F7F\u7528\u6587\u6863</strong>\uFF1A\u8BF7\u70B9\u51FB\u5DE6\u4E0A\u89D2\u7684 <strong>\u63D2\u4EF6\u4E3B\u9875</strong> \u67E5\u770B\u63D2\u4EF6\u4F7F\u7528\u6587\u6863</p>\n <p>\uD83D\uDD0D <strong>\u66F4\u591A\u63D2\u4EF6</strong>\uFF1A\u53EF\u8BBF\u95EE <a href=\"https://github.com/YisRime\" style=\"color:#4a6ee0;text-decoration:none;\">\u82E1\u6DDE\u7684 GitHub</a> \u67E5\u770B\u672C\u4EBA\u7684\u6240\u6709\u63D2\u4EF6</p>\n</div>\n<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);\">\n <h2 style=\"margin-top: 0; color: #e0574a;\">\u2764\uFE0F \u652F\u6301\u4E0E\u53CD\u9988</h2>\n <p>\uD83C\uDF1F \u559C\u6B22\u8FD9\u4E2A\u63D2\u4EF6\uFF1F\u8BF7\u5728 <a href=\"https://github.com/YisRime\" style=\"color:#e0574a;text-decoration:none;\">GitHub</a> \u4E0A\u7ED9\u6211\u4E00\u4E2A Star\uFF01</p>\n <p>\uD83D\uDC1B \u9047\u5230\u95EE\u9898\uFF1F\u8BF7\u901A\u8FC7 <strong>Issues</strong> \u63D0\u4EA4\u53CD\u9988\uFF0C\u6216\u52A0\u5165 QQ \u7FA4 <a href=\"https://qm.qq.com/q/PdLMx9Jowq\" style=\"color:#e0574a;text-decoration:none;\"><strong>855571375</strong></a> \u8FDB\u884C\u4EA4\u6D41</p>\n</div>\n";
3
7
  export declare const name = "chat-analyse";
4
8
  export declare const using: string[];
9
+ /**
10
+ * @interface Config
11
+ * @description 定义插件的配置项结构。
12
+ */
5
13
  export interface Config {
14
+ enableListener: boolean;
15
+ enableCmdStat: boolean;
16
+ enableMsgStat: boolean;
17
+ enableAdvanced: boolean;
6
18
  }
19
+ /**
20
+ * @const {Schema<Config>} Config
21
+ * @description 使用 Koishi 的 `Schema` 来定义配置项的类型、默认值和在控制台中的交互界面。
22
+ */
7
23
  export declare const Config: Schema<Config>;
8
24
  /**
9
- * Koishi 插件主入口函数。
10
- * @param ctx {Context} Koishi 上下文,用于访问和扩展框架功能。
25
+ * @function apply
26
+ * @description Koishi 插件的主入口函数。
27
+ * @param {Context} ctx - Koishi 的插件上下文,提供了访问核心 API 的能力。
28
+ * @param {Config} config - 用户在 `koishi.config.js` 或控制台中配置的对象。
11
29
  */
12
- export declare function apply(ctx: Context): void;
30
+ export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js CHANGED
@@ -27,171 +27,213 @@ __export(src_exports, {
27
27
  using: () => using
28
28
  });
29
29
  module.exports = __toCommonJS(src_exports);
30
- var import_koishi3 = require("koishi");
30
+ var import_koishi4 = require("koishi");
31
31
 
32
32
  // src/Collector.ts
33
+ var import_koishi = require("koishi");
33
34
  var Collector = class _Collector {
34
35
  /**
35
36
  * @constructor
36
- * @param ctx {Context} Koishi 上下文,用于访问框架核心功能。
37
+ * @param {Context} ctx - Koishi 的插件上下文。
38
+ * @param {Config} config - 插件的配置对象。
37
39
  */
38
- constructor(ctx) {
40
+ constructor(ctx, config) {
39
41
  this.ctx = ctx;
40
- ctx.model.extend("analyse_msg", {
41
- id: "unsigned",
42
- channelId: "string",
43
- userId: "string",
44
- type: "string",
45
- content: "text",
46
- timestamp: "timestamp"
47
- }, { primary: "id", autoInc: true, indexes: ["timestamp", "channelId", "userId", "type"] });
48
- ctx.model.extend("analyse_name", {
49
- channelId: "string",
50
- channelName: "string",
51
- userId: "string",
52
- userName: "string"
53
- }, { primary: ["channelId", "userId"] });
42
+ this.config = config;
43
+ this.defineModels();
54
44
  ctx.on("message", (session) => this.handleMessage(session));
55
- this.flushInterval = setInterval(() => this.flushBuffer(), _Collector.FLUSH_INTERVAL);
56
- ctx.on("dispose", () => {
57
- clearInterval(this.flushInterval);
58
- this.flushBuffer();
59
- });
45
+ if (this.config.enableAdvanced) {
46
+ this.flushInterval = setInterval(() => this.flushCacheBuffer(), _Collector.FLUSH_INTERVAL);
47
+ ctx.on("dispose", () => {
48
+ clearInterval(this.flushInterval);
49
+ this.flushCacheBuffer();
50
+ });
51
+ }
60
52
  }
61
53
  static {
62
54
  __name(this, "Collector");
63
55
  }
64
- // 数据刷新配置
56
+ /** @const {number} FLUSH_INTERVAL - 内存缓存区自动刷新到数据库的时间间隔。 */
65
57
  static FLUSH_INTERVAL = 60 * 1e3;
66
- // 每分钟刷新一次
58
+ /** @const {number} BUFFER_THRESHOLD - 内存缓存区触发自动刷新的消息数量阈值。 */
67
59
  static BUFFER_THRESHOLD = 100;
68
- // 缓冲区达到100条消息时刷新
69
- // 消息和名称缓存
70
- msgBuffer = [];
71
- nameCache = /* @__PURE__ */ new Map();
72
- pendingNameRequests = /* @__PURE__ */ new Map();
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();
73
66
  flushInterval;
74
67
  /**
75
- * 核心消息处理器,对消息进行格式化并存入缓冲区。
76
- * @param session {Session} 消息会话对象。
68
+ * @private
69
+ * @method defineModels
70
+ * @description 定义插件所需的所有数据表模型。
77
71
  */
78
- async handleMessage(session) {
79
- const { userId, channelId, guildId, content, timestamp, argv, elements } = session;
80
- const effectiveId = channelId || guildId;
81
- if (!effectiveId || !userId || !timestamp || !content?.trim()) return;
82
- this.updateNameIfNeeded(session, effectiveId);
83
- const isCommand = !!argv?.command;
84
- const type = isCommand ? argv.command.name : this.summarizeElementTypes(elements);
85
- const finalContent = isCommand ? content : this.sanitizeContent(elements);
86
- this.msgBuffer.push({
87
- channelId: effectiveId,
88
- userId,
89
- type,
90
- content: finalContent,
91
- timestamp: new Date(timestamp)
92
- });
93
- if (this.msgBuffer.length >= _Collector.BUFFER_THRESHOLD) await this.flushBuffer();
72
+ defineModels() {
73
+ this.ctx.model.extend("analyse_user", {
74
+ uid: "unsigned",
75
+ channelId: "string",
76
+ userId: "string",
77
+ channelName: "string",
78
+ userName: "string"
79
+ }, { primary: "uid", autoInc: true, indexes: ["channelId", "userId"] });
80
+ this.ctx.model.extend("analyse_cmd", {
81
+ uid: "unsigned",
82
+ command: "string",
83
+ count: "unsigned",
84
+ timestamp: "timestamp"
85
+ }, { primary: ["uid", "command"] });
86
+ this.ctx.model.extend("analyse_msg", {
87
+ uid: "unsigned",
88
+ type: "string",
89
+ count: "unsigned",
90
+ timestamp: "timestamp"
91
+ }, { primary: ["uid", "type"] });
92
+ if (this.config.enableAdvanced) {
93
+ this.ctx.model.extend("analyse_cache", {
94
+ id: "unsigned",
95
+ channelId: "string",
96
+ userId: "string",
97
+ content: "text",
98
+ timestamp: "timestamp"
99
+ }, { primary: "id", autoInc: true });
100
+ }
94
101
  }
95
102
  /**
96
- * 汇总消息元素的类型,生成紧凑的类型字符串。
97
- * @param elements {Element[]} 消息元素数组。
98
- * @returns {string} 类型汇总字符串,如 `[text][img]`。
103
+ * @private
104
+ * @async
105
+ * @method handleMessage
106
+ * @description 统一的消息和命令处理器。它会解析收到的消息,提取关键信息并更新相应的统计数据。
107
+ * @param {Session} session - Koishi 的会话对象,包含消息的全部信息。
99
108
  */
100
- summarizeElementTypes(elements) {
101
- const types = new Set(elements.map((e) => `[${e.type}]`));
102
- return Array.from(types).join("");
109
+ async handleMessage(session) {
110
+ try {
111
+ const { userId, channelId, guildId, content, timestamp, argv, elements } = session;
112
+ const effectiveId = channelId || guildId;
113
+ if (!effectiveId || !userId || !timestamp || !content?.trim()) return;
114
+ const uid = await this.getOrCreateUser(session, effectiveId);
115
+ if (!uid) return;
116
+ const now = /* @__PURE__ */ new Date();
117
+ if (argv?.command) {
118
+ await this.ctx.database.upsert("analyse_cmd", (row) => [{
119
+ uid,
120
+ command: argv.command.name,
121
+ count: import_koishi.$.add(import_koishi.$.ifNull(row.count, import_koishi.$.literal(0)), 1),
122
+ timestamp: now
123
+ }]);
124
+ }
125
+ const uniqueElementTypes = new Set(elements.map((e) => e.type));
126
+ for (const type of uniqueElementTypes) {
127
+ await this.ctx.database.upsert("analyse_msg", (row) => [{
128
+ uid,
129
+ type,
130
+ count: import_koishi.$.add(import_koishi.$.ifNull(row.count, import_koishi.$.literal(0)), 1),
131
+ timestamp: now
132
+ }]);
133
+ }
134
+ if (this.config.enableAdvanced) {
135
+ this.cacheBuffer.push({
136
+ channelId: effectiveId,
137
+ userId,
138
+ content: this.sanitizeContent(elements),
139
+ timestamp: new Date(timestamp)
140
+ });
141
+ if (this.cacheBuffer.length >= _Collector.BUFFER_THRESHOLD) await this.flushCacheBuffer();
142
+ }
143
+ } catch (error) {
144
+ this.ctx.logger.warn("消息处理出错:", error);
145
+ }
103
146
  }
104
147
  /**
105
- * 清理并格式化消息内容,提取关键信息。
106
- * @param elements {Element[]} 消息元素数组。
107
- * @returns {string} 处理后的内容字符串。
148
+ * @private
149
+ * @async
150
+ * @method getOrCreateUser
151
+ * @description 高效地获取或创建用户的中央记录 (`analyse_user`)。
152
+ * @param {Session} session - Koishi 会话对象,用于获取用户信息和 Bot 实例。
153
+ * @param {string} channelId - 消息所在的频道或群组 ID。
154
+ * @returns {Promise<number | null>} 返回用户的唯一 `uid`,如果操作失败则返回 `null`。
108
155
  */
109
- sanitizeContent(elements) {
110
- return elements.map((e) => {
111
- switch (e.type) {
112
- case "text":
113
- return e.attrs.content;
114
- case "img":
115
- return e.attrs.summary === "[动画表情]" ? "[gif]" : "[img]";
116
- case "at":
117
- return `[at:${e.attrs.id}]`;
118
- default:
119
- return `[${e.type}]`;
156
+ async getOrCreateUser(session, channelId) {
157
+ const { userId, bot, guildId } = session;
158
+ const cacheKey = `${channelId}:${userId}`;
159
+ if (this.uidCache.has(cacheKey)) return this.uidCache.get(cacheKey);
160
+ if (this.pendingUidRequests.has(cacheKey)) return this.pendingUidRequests.get(cacheKey);
161
+ const promise = (async () => {
162
+ try {
163
+ const existing = await this.ctx.database.get("analyse_user", { channelId, userId }, ["uid"]);
164
+ if (existing.length > 0) {
165
+ this.uidCache.set(cacheKey, existing[0].uid);
166
+ return existing[0].uid;
167
+ }
168
+ const [guild, member] = await Promise.all([
169
+ guildId ? bot.getGuild(guildId).catch(() => null) : Promise.resolve(null),
170
+ guildId ? bot.getGuildMember(guildId, userId).catch(() => null) : Promise.resolve(null)
171
+ ]);
172
+ const user = !member ? await bot.getUser(userId).catch(() => null) : null;
173
+ const newUser = await this.ctx.database.create("analyse_user", {
174
+ channelId,
175
+ userId,
176
+ channelName: guild?.name || channelId,
177
+ userName: member?.nick || member?.name || user?.name || userId
178
+ });
179
+ this.uidCache.set(cacheKey, newUser.uid);
180
+ return newUser.uid;
181
+ } catch (error) {
182
+ this.ctx.logger.error(`创建或获取用户(${cacheKey}) UID 失败:`, error);
183
+ return null;
184
+ } finally {
185
+ this.pendingUidRequests.delete(cacheKey);
120
186
  }
121
- }).join("");
187
+ })();
188
+ this.pendingUidRequests.set(cacheKey, promise);
189
+ return promise;
122
190
  }
123
191
  /**
124
- * 将内存缓冲区的消息批量写入数据库,并处理写入失败的情况。
192
+ * @private
193
+ * @method sanitizeContent
194
+ * @description 将 Koishi 的消息元素 (Element) 数组净化为纯文本字符串,以便存储和分析。
195
+ * @param {Element[]} elements - 消息元素的数组。
196
+ * @returns {string} 净化后的纯文本字符串。
125
197
  */
126
- async flushBuffer() {
127
- if (this.msgBuffer.length === 0) return;
128
- const bufferToFlush = this.msgBuffer;
129
- this.msgBuffer = [];
130
- try {
131
- await this.ctx.database.upsert("analyse_msg", bufferToFlush);
132
- } catch (error) {
133
- this.ctx.logger.error("数据写入失败:", error);
134
- this.msgBuffer.unshift(...bufferToFlush);
198
+ sanitizeContent = /* @__PURE__ */ __name((elements) => elements.map((e) => {
199
+ switch (e.type) {
200
+ case "text":
201
+ return e.attrs.content;
202
+ case "img":
203
+ return e.attrs.summary === "[动画表情]" ? "[gif]" : "[img]";
204
+ case "at":
205
+ return `[at:${e.attrs.id}]`;
206
+ default:
207
+ return `[${e.type}]`;
135
208
  }
136
- }
209
+ }).join(""), "sanitizeContent");
137
210
  /**
138
- * 检查用户和群组名称是否需要更新,利用缓存和请求锁机制避免重复调用。
139
- * @param session {Session} 消息会话对象。
140
- * @param effectiveId {string} 有效的频道/群组ID。
211
+ * @private
212
+ * @async
213
+ * @method flushCacheBuffer
214
+ * @description 将内存中的消息缓存 (`cacheBuffer`) 批量写入数据库。
141
215
  */
142
- async updateNameIfNeeded(session, effectiveId) {
143
- const { userId } = session;
144
- if (!userId) return;
145
- const cacheKey = `${effectiveId}:${userId}`;
146
- const CACHE_EXPIRATION = 24 * 60 * 60 * 1e3;
147
- if (this.pendingNameRequests.has(cacheKey)) return this.pendingNameRequests.get(cacheKey);
148
- const cached = this.nameCache.get(cacheKey);
149
- if (cached && Date.now() - cached.timestamp < CACHE_EXPIRATION) return;
150
- const promise = this.fetchAndUpdateNames(session, effectiveId, cacheKey);
151
- this.pendingNameRequests.set(cacheKey, promise);
152
- promise.finally(() => this.pendingNameRequests.delete(cacheKey));
153
- }
154
- /**
155
- * 异步获取用户和群组的最新名称,并更新到数据库和内存缓存。
156
- * @param session {Session} 消息会话对象。
157
- * @param effectiveId {string} 频道/群组ID。
158
- * @param cacheKey {string} 用于缓存的键。
159
- */
160
- async fetchAndUpdateNames(session, effectiveId, cacheKey) {
216
+ async flushCacheBuffer() {
217
+ if (this.cacheBuffer.length === 0) return;
218
+ const bufferToFlush = this.cacheBuffer;
219
+ this.cacheBuffer = [];
161
220
  try {
162
- const { userId, guildId, bot } = session;
163
- const [guild, member] = await Promise.all([
164
- guildId ? bot.getGuild(guildId).catch(() => null) : Promise.resolve(null),
165
- guildId && userId ? bot.getGuildMember(guildId, userId).catch(() => null) : Promise.resolve(null)
166
- ]);
167
- const channelName = guild?.name;
168
- const userName = member?.nick || member?.name;
169
- if (!channelName || !userName) {
170
- this.nameCache.set(cacheKey, { name: null, timestamp: Date.now() });
171
- return;
172
- }
173
- await this.ctx.database.upsert("analyse_name", [{
174
- channelId: effectiveId,
175
- userId,
176
- channelName,
177
- userName
178
- }]);
179
- this.nameCache.set(cacheKey, { name: userName, timestamp: Date.now() });
221
+ await this.ctx.database.upsert("analyse_cache", bufferToFlush);
180
222
  } catch (error) {
181
- this.nameCache.set(cacheKey, { name: null, timestamp: Date.now() });
223
+ this.ctx.logger.error("写入缓存出错:", error);
182
224
  }
183
225
  }
184
226
  };
185
227
 
186
- // src/CmdStat.ts
187
- var import_koishi2 = require("koishi");
228
+ // src/Stat.ts
229
+ var import_koishi3 = require("koishi");
188
230
 
189
231
  // src/Renderer.ts
190
- var import_koishi = require("koishi");
232
+ var import_koishi2 = require("koishi");
191
233
  var Renderer = class {
192
234
  /**
193
235
  * @constructor
194
- * @param ctx {Context} Koishi 上下文,用于访问 puppeteer 服务。
236
+ * @param {Context} ctx - Koishi 的插件上下文,用于访问 puppeteer 服务。
195
237
  */
196
238
  constructor(ctx) {
197
239
  this.ctx = ctx;
@@ -200,10 +242,13 @@ var Renderer = class {
200
242
  __name(this, "Renderer");
201
243
  }
202
244
  /**
203
- * 将列表数据渲染为图片。
204
- * @param data {ListRenderData} 待渲染的列表数据。
205
- * @param headers {string[]} (可选) 表头文案数组,若不提供则不渲染表头。
206
- * @returns {Promise<string | Buffer>} 成功时返回图片 Buffer,无数据时返回提示文本。
245
+ * @public
246
+ * @async
247
+ * @method renderList
248
+ * @description 将列表数据渲染为图片。
249
+ * @param {ListRenderData} data - 待渲染的完整列表数据。
250
+ * @param {string[]} [headers] - (可选) 表头文案数组。如果提供,将会在表格顶部渲染表头。
251
+ * @returns {Promise<string | Buffer>} 渲染成功时返回图片的 Buffer 数据;如果输入数据为空,则返回提示文本。
207
252
  */
208
253
  async renderList(data, headers) {
209
254
  const htmlContent = this.generateListHtml(data, headers);
@@ -211,23 +256,27 @@ var Renderer = class {
211
256
  return this.ctx.puppeteer.render(htmlContent);
212
257
  }
213
258
  /**
214
- * 智能格式化日期,提供相对时间(如“刚刚”,“x分钟前”)和绝对日期。
215
- * @param date {Date} 待格式化的日期对象。
259
+ * @private
260
+ * @method formatDate
261
+ * @description 智能格式化日期。对于近期的时间,提供更人性化的相对时间描述(如“刚刚”,“x 分钟前”);对于较早的时间,则显示标准的“年-月-日”格式。
262
+ * @param {Date} date - 待格式化的 Date 对象。
216
263
  * @returns {string} 格式化后的日期字符串。
217
264
  */
218
265
  formatDate(date) {
219
266
  if (!date) return "未知";
220
267
  const diff = Date.now() - date.getTime();
221
- if (diff < import_koishi.Time.minute) return "刚刚";
222
- if (diff < import_koishi.Time.hour) return `${Math.floor(diff / import_koishi.Time.minute)} 分钟前`;
223
- if (diff < import_koishi.Time.day) return `${Math.floor(diff / import_koishi.Time.hour)} 小时前`;
268
+ if (diff < import_koishi2.Time.minute) return "刚刚";
269
+ if (diff < import_koishi2.Time.hour) return `${Math.floor(diff / import_koishi2.Time.minute)} 分钟前`;
270
+ if (diff < import_koishi2.Time.day) return `${Math.floor(diff / import_koishi2.Time.hour)} 小时前`;
224
271
  return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
225
272
  }
226
273
  /**
227
- * 根据数据动态生成渲染图片所需的完整 HTML 字符串。
228
- * @param data {ListRenderData} 列表数据。
229
- * @param headers {string[]} (可选) 表头数组。
230
- * @returns {string | null} 生成的 HTML 字符串,若无数据则返回 null。
274
+ * @private
275
+ * @method generateListHtml
276
+ * @description 根据传入的结构化数据和表头,动态生成用于 Puppeteer 渲染的完整 HTML 字符串。
277
+ * @param {ListRenderData} data - 列表数据对象。
278
+ * @param {string[]} [headers] - (可选) 表头数组。
279
+ * @returns {string | null} 返回生成的 HTML 字符串。如果列表数据为空,则返回 `null`。
231
280
  */
232
281
  generateListHtml(data, headers) {
233
282
  const { title, time, total, list } = data;
@@ -292,102 +341,164 @@ var Renderer = class {
292
341
  }
293
342
  };
294
343
 
295
- // src/CmdStat.ts
296
- var CmdStat = class {
297
- constructor(ctx) {
344
+ // src/Stat.ts
345
+ var Stat = class {
346
+ /**
347
+ * @constructor
348
+ * @param {Context} ctx - Koishi 的插件上下文。
349
+ * @param {Config} config - 插件的配置对象。
350
+ */
351
+ constructor(ctx, config) {
298
352
  this.ctx = ctx;
353
+ this.config = config;
299
354
  this.renderer = new Renderer(ctx);
300
- this.ctx.model.extend("analyse_cmd", {
301
- channelId: "string",
302
- userId: "string",
303
- command: "string",
304
- count: "unsigned",
305
- timestamp: "timestamp"
306
- }, { primary: ["channelId", "userId", "command"] });
307
- this.ctx.on("command/before-execute", async ({ command, session }) => {
308
- const { userId, guildId } = session;
309
- if (!guildId || !userId) return;
310
- const query = { channelId: guildId, userId, command: command.name };
311
- await this.ctx.database.upsert("analyse_cmd", (row) => [{
312
- ...query,
313
- count: import_koishi2.$.add(import_koishi2.$.ifNull(row.count, 0), 1),
314
- timestamp: /* @__PURE__ */ new Date()
315
- }]);
316
- });
317
355
  }
318
356
  static {
319
- __name(this, "CmdStat");
357
+ __name(this, "Stat");
320
358
  }
321
359
  renderer;
322
360
  /**
323
- * 注册所有相关的子命令到主 `analyse` 命令下。
324
- * @param analyse {Command} `analyse` 命令实例。
361
+ * @method registerCommands
362
+ * @description 根据插件配置,动态地将 `.command` `.message` 子命令注册到主 `analyse` 命令下。
363
+ * @param {Command} analyse - 主 `analyse` 命令实例。
325
364
  */
326
365
  registerCommands(analyse) {
327
- analyse.subcommand(".command", "命令使用统计").option("user", "-u [user:user] 查看指定用户的统计").option("guild", "-g [guildId:string] 查看指定群组的统计 (默认当前群)").usage("查询命令使用统计。支持按用户、按群组或组合查询。").action(async ({ session, options }) => {
328
- const userId = options.user ? import_koishi2.h.select(options.user, "user")[0]?.attrs.id : void 0;
329
- let guildId = options.guild;
330
- if (options.guild === "" && !options.user) {
331
- if (!session.guildId) return "私聊中请使用 -g <群组ID> 指定群组。";
332
- guildId = session.guildId;
333
- }
334
- try {
335
- const stats = await this.getCommandStats(guildId, userId);
336
- if (typeof stats === "string") return stats;
337
- const title = await this.generateTitle(session, guildId, userId);
338
- const renderData = {
339
- title,
340
- time: /* @__PURE__ */ new Date(),
341
- total: stats.total,
342
- list: stats.list
343
- };
344
- const headers = ["命令", "次数", "上次使用"];
345
- const result = await this.renderer.renderList(renderData, headers);
346
- return Buffer.isBuffer(result) ? import_koishi2.Element.image(result, "image/png") : result;
347
- } catch (error) {
348
- this.ctx.logger.error("渲染统计图片失败:", error);
349
- return "渲染统计图片失败";
350
- }
351
- });
366
+ if (this.config.enableCmdStat) {
367
+ analyse.subcommand(".command", "命令使用统计").option("user", "-u [user:user] 查看指定用户的统计").option("guild", "-g [guildId:string] 查看指定群组的统计 (默认当前群)").usage("查询命令使用统计。支持按用户、按群组或组合查询。").action(async ({ session, options }) => {
368
+ const userId = options.user ? import_koishi3.h.select(options.user, "user")[0]?.attrs.id : void 0;
369
+ let guildId = options.guild;
370
+ if (options.guild === "" && !options.user) {
371
+ if (!session.guildId) return "私聊中请使用 -g <群组ID> 指定群组。";
372
+ guildId = session.guildId;
373
+ }
374
+ try {
375
+ const stats = await this.getCommandStats(guildId, userId);
376
+ if (typeof stats === "string") return stats;
377
+ const title = await this._generateTitle(session, guildId, userId, "命令");
378
+ const renderData = {
379
+ title,
380
+ time: /* @__PURE__ */ new Date(),
381
+ total: stats.total,
382
+ list: stats.list
383
+ };
384
+ const headers = ["命令", "次数", "上次使用"];
385
+ const result = await this.renderer.renderList(renderData, headers);
386
+ return Buffer.isBuffer(result) ? import_koishi3.Element.image(result, "image/png") : result;
387
+ } catch (error) {
388
+ this.ctx.logger.error("渲染命令统计图片失败:", error);
389
+ return "渲染命令统计图片失败";
390
+ }
391
+ });
392
+ }
393
+ if (this.config.enableMsgStat) {
394
+ analyse.subcommand(".message", "消息类型统计").option("user", "-u [user:user] 查看指定用户的统计").option("guild", "-g [guildId:string] 查看指定群组的统计 (默认当前群)").usage("查询消息类型统计。支持按用户、按群组或组合查询。").action(async ({ session, options }) => {
395
+ const userId = options.user ? import_koishi3.h.select(options.user, "user")[0]?.attrs.id : void 0;
396
+ let guildId = options.guild;
397
+ if (options.guild === "" && !options.user) {
398
+ if (!session.guildId) return "私聊中请使用 -g <群组ID> 指定群组。";
399
+ guildId = session.guildId;
400
+ }
401
+ try {
402
+ const stats = await this.getMessageStats(guildId, userId);
403
+ if (typeof stats === "string") return stats;
404
+ const title = await this._generateTitle(session, guildId, userId, "消息");
405
+ const renderData = {
406
+ title,
407
+ time: /* @__PURE__ */ new Date(),
408
+ total: stats.total,
409
+ list: stats.list
410
+ };
411
+ const headers = ["消息类型", "条数", "上次发送"];
412
+ const result = await this.renderer.renderList(renderData, headers);
413
+ return Buffer.isBuffer(result) ? import_koishi3.Element.image(result, "image/png") : result;
414
+ } catch (error) {
415
+ this.ctx.logger.error("渲染消息统计图片失败:", error);
416
+ return "渲染消息统计图片失败";
417
+ }
418
+ });
419
+ }
352
420
  }
353
421
  /**
354
- * 根据查询参数动态生成图片标题。
422
+ * @private
423
+ * @async
424
+ * @method _generateTitle
425
+ * @description 通用的标题生成器。根据查询参数 (guildId, userId) 和统计类型动态生成易于理解的图片标题。
426
+ * @param {Session} session - 当前会话,备用。
427
+ * @param {string} [guildId] - (可选) 查询的群组 ID。
428
+ * @param {string} [userId] - (可选) 查询的用户 ID。
429
+ * @param {'命令' | '消息'} type - 统计类型,用于嵌入标题文本中。
430
+ * @returns {Promise<string>} 生成的标题字符串。
355
431
  */
356
- async generateTitle(session, guildId, userId) {
432
+ async _generateTitle(session, guildId, userId, type) {
357
433
  if (userId && guildId) {
358
- const userName = (await session.bot.getUser(userId).catch(() => null))?.name || userId;
359
- const guildName = (await session.bot.getGuild(guildId).catch(() => null))?.name || guildId;
360
- return `${userName} ${guildName} 的命令统计`;
434
+ const user = await this.ctx.database.get("analyse_user", { channelId: guildId, userId }, ["userName"]);
435
+ const guild = await this.ctx.database.get("analyse_user", { channelId: guildId }, ["channelName"]);
436
+ const userName = user[0]?.userName || userId;
437
+ const guildName = guild[0]?.channelName || guildId;
438
+ return `${userName} 在 ${guildName} 的${type}统计`;
361
439
  }
362
440
  if (userId) {
363
- const userName = (await session.bot.getUser(userId).catch(() => null))?.name || userId;
364
- return `${userName} 的全局命令统计`;
441
+ const user = await this.ctx.database.get("analyse_user", { userId }, ["userName"]);
442
+ const userName = user[0]?.userName || userId;
443
+ return `${userName} 的全局${type}统计`;
365
444
  }
366
445
  if (guildId) {
367
- const guildName = (await session.bot.getGuild(guildId).catch(() => null))?.name || guildId;
368
- return `${guildName} 的命令统计`;
446
+ const guild = await this.ctx.database.get("analyse_user", { channelId: guildId }, ["channelName"]);
447
+ const guildName = guild[0]?.channelName || guildId;
448
+ return `${guildName} 的${type}统计`;
369
449
  }
370
- return "全局命令统计";
450
+ return `全局${type}统计`;
371
451
  }
372
452
  /**
373
- * 从数据库获取并聚合命令统计数据。
374
- * @param guildId {string} (可选) 群组ID。
375
- * @param userId {string} (可选) 用户ID。
376
- * @returns {Promise<{ list: RenderListItem[], total: number } | string>} 包含结果列表和总数的对象,或错误/提示信息。
453
+ * @private
454
+ * @async
455
+ * @method getCommandStats
456
+ * @description 从数据库中获取并聚合命令使用统计数据。
457
+ * @param {string} [guildId] - (可选) 若提供,则将范围限制在此群组。
458
+ * @param {string} [userId] - (可选) 若提供,则将范围限制在此用户。
459
+ * @returns {Promise<{ list: RenderListItem[], total: number } | string>} 返回一个包含列表和总数的对象,或在无数据时返回提示字符串。
377
460
  */
378
461
  async getCommandStats(guildId, userId) {
379
- const query = {};
380
- if (guildId) query.channelId = guildId;
381
- if (userId) query.userId = userId;
382
- const aggregatedStats = await this.ctx.database.select("analyse_cmd", query).groupBy(["command"], {
383
- count: /* @__PURE__ */ __name((row) => import_koishi2.$.sum(row.count), "count"),
384
- lastUsed: /* @__PURE__ */ __name((row) => import_koishi2.$.max(row.timestamp), "lastUsed")
462
+ const userQuery = {};
463
+ if (guildId) userQuery.channelId = guildId;
464
+ if (userId) userQuery.userId = userId;
465
+ const users = await this.ctx.database.get("analyse_user", userQuery, ["uid"]);
466
+ if (users.length === 0) return "暂无目标用户的统计数据";
467
+ const uids = users.map((u) => u.uid);
468
+ const aggregatedStats = await this.ctx.database.select("analyse_cmd").where({ uid: { $in: uids } }).groupBy(["command"], {
469
+ count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count"),
470
+ lastUsed: /* @__PURE__ */ __name((row) => import_koishi3.$.max(row.timestamp), "lastUsed")
385
471
  }).orderBy("count", "desc").execute();
386
472
  if (aggregatedStats.length === 0) return "暂无统计数据";
387
473
  const totalCount = aggregatedStats.reduce((sum, record) => sum + record.count, 0);
388
474
  const list = aggregatedStats.map((item) => [item.command, item.count, item.lastUsed]);
389
475
  return { list, total: totalCount };
390
476
  }
477
+ /**
478
+ * @private
479
+ * @async
480
+ * @method getMessageStats
481
+ * @description 从数据库中获取并聚合消息类型统计数据。
482
+ * @param {string} [guildId] - (可选) 若提供,则将范围限制在此群组。
483
+ * @param {string} [userId] - (可选) 若提供,则将范围限制在此用户。
484
+ * @returns {Promise<{ list: RenderListItem[], total: number } | string>} 返回一个包含列表和总数的对象,或在无数据时返回提示字符串。
485
+ */
486
+ async getMessageStats(guildId, userId) {
487
+ const userQuery = {};
488
+ if (guildId) userQuery.channelId = guildId;
489
+ if (userId) userQuery.userId = userId;
490
+ const users = await this.ctx.database.get("analyse_user", userQuery, ["uid"]);
491
+ if (users.length === 0) return "暂无目标用户的统计数据";
492
+ const uids = users.map((u) => u.uid);
493
+ const aggregatedStats = await this.ctx.database.select("analyse_msg").where({ uid: { $in: uids } }).groupBy(["type"], {
494
+ count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count"),
495
+ lastUsed: /* @__PURE__ */ __name((row) => import_koishi3.$.max(row.timestamp), "lastUsed")
496
+ }).orderBy("count", "desc").execute();
497
+ if (aggregatedStats.length === 0) return "暂无统计数据";
498
+ const totalCount = aggregatedStats.reduce((sum, record) => sum + record.count, 0);
499
+ const list = aggregatedStats.map((item) => [item.type, item.count, item.lastUsed]);
500
+ return { list, total: totalCount };
501
+ }
391
502
  };
392
503
 
393
504
  // src/index.ts
@@ -405,12 +516,20 @@ var usage = `
405
516
  `;
406
517
  var name = "chat-analyse";
407
518
  var using = ["database", "puppeteer"];
408
- var Config = import_koishi3.Schema.object({});
409
- function apply(ctx) {
410
- new Collector(ctx);
411
- const cmd = new CmdStat(ctx);
519
+ var Config = import_koishi4.Schema.intersect([
520
+ import_koishi4.Schema.object({
521
+ enableListener: import_koishi4.Schema.boolean().default(true).description("开启监听")
522
+ }).description("基本设置"),
523
+ import_koishi4.Schema.object({
524
+ enableCmdStat: import_koishi4.Schema.boolean().default(true).description("启用命令统计"),
525
+ enableMsgStat: import_koishi4.Schema.boolean().default(true).description("启用消息统计"),
526
+ enableAdvanced: import_koishi4.Schema.boolean().default(true).description("启用原始记录")
527
+ }).description("功能开关")
528
+ ]);
529
+ function apply(ctx, config) {
530
+ if (config.enableListener) new Collector(ctx, config);
412
531
  const analyse = ctx.command("analyse", "聊天记录分析");
413
- cmd.registerCommands(analyse);
532
+ new Stat(ctx, config).registerCommands(analyse);
414
533
  }
415
534
  __name(apply, "apply");
416
535
  // 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.2.4",
4
+ "version": "0.2.6",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],
package/lib/CmdStat.d.ts DELETED
@@ -1,38 +0,0 @@
1
- import { Context, Command } from 'koishi';
2
- import { Renderer } from './Renderer';
3
- declare module 'koishi' {
4
- interface Tables {
5
- analyse_cmd: {
6
- channelId: string;
7
- userId: string;
8
- command: string;
9
- count: number;
10
- timestamp: Date;
11
- };
12
- }
13
- }
14
- /**
15
- * @class CmdStat
16
- * @description 提供命令统计服务,处理用户查询并渲染结果。
17
- */
18
- export declare class CmdStat {
19
- private ctx;
20
- renderer: Renderer;
21
- constructor(ctx: Context);
22
- /**
23
- * 注册所有相关的子命令到主 `analyse` 命令下。
24
- * @param analyse {Command} 主 `analyse` 命令实例。
25
- */
26
- registerCommands(analyse: Command): void;
27
- /**
28
- * 根据查询参数动态生成图片标题。
29
- */
30
- private generateTitle;
31
- /**
32
- * 从数据库获取并聚合命令统计数据。
33
- * @param guildId {string} (可选) 群组ID。
34
- * @param userId {string} (可选) 用户ID。
35
- * @returns {Promise<{ list: RenderListItem[], total: number } | string>} 包含结果列表和总数的对象,或错误/提示信息。
36
- */
37
- private getCommandStats;
38
- }