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.
- package/lib/Collector.d.ts +7 -0
- package/lib/WhoAt.d.ts +34 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +111 -17
- package/package.json +1 -1
package/lib/Collector.d.ts
CHANGED
|
@@ -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
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
|
|
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
|
|
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 (
|
|
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((
|
|
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((
|
|
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 ||
|
|
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}">${
|
|
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 =
|
|
777
|
-
|
|
778
|
-
enableListener:
|
|
779
|
-
enableOriRecord:
|
|
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
|
-
|
|
782
|
-
enableCmdStat:
|
|
783
|
-
enableMsgStat:
|
|
784
|
-
enableRankStat:
|
|
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:
|