koishi-plugin-chat-analyse 0.6.4 → 0.7.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/Analyse.d.ts +8 -6
- package/lib/index.d.ts +0 -1
- package/lib/index.js +45 -89
- package/package.json +2 -3
package/lib/Analyse.d.ts
CHANGED
|
@@ -5,18 +5,20 @@ export interface WordCloudData {
|
|
|
5
5
|
time: Date;
|
|
6
6
|
words: [string, number][];
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* @class Analyse
|
|
10
|
+
* @description 提供文本分析功能,如生成词云。
|
|
11
|
+
*/
|
|
8
12
|
export declare class Analyse {
|
|
9
13
|
private ctx;
|
|
10
14
|
private config;
|
|
11
15
|
private renderer;
|
|
12
|
-
private
|
|
13
|
-
private isNlpReady;
|
|
16
|
+
private readonly jieba;
|
|
14
17
|
constructor(ctx: Context, config: Config);
|
|
15
18
|
/**
|
|
16
|
-
* @
|
|
17
|
-
* @
|
|
18
|
-
* @
|
|
19
|
+
* @public @method registerCommands
|
|
20
|
+
* @description 在主命令下注册子命令。
|
|
21
|
+
* @param cmd - 主命令实例。
|
|
19
22
|
*/
|
|
20
|
-
private initializeNlp;
|
|
21
23
|
registerCommands(cmd: Command): void;
|
|
22
24
|
}
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -553,7 +553,7 @@ var Stat = class {
|
|
|
553
553
|
};
|
|
554
554
|
}, "createHandler");
|
|
555
555
|
if (this.config.enableCmdStat) {
|
|
556
|
-
cmd.subcommand("cmdstat", "命令统计").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("separate", "-h 分离展示").option("all", "-a 全局").action(createHandler(async (scope, options) => {
|
|
556
|
+
cmd.subcommand("cmdstat", "命令统计").usage("查询命令统计,可指定查询范围,默认当前群组。").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("separate", "-h 分离展示").option("all", "-a 全局").action(createHandler(async (scope, options) => {
|
|
557
557
|
const stats = await this.ctx.database.select("analyse_cmd").where({ uid: { $in: scope.uids } }).groupBy("command", { count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count"), lastUsed: /* @__PURE__ */ __name((row) => import_koishi3.$.max(row.timestamp), "lastUsed") }).orderBy("count", "desc").execute();
|
|
558
558
|
if (stats.length === 0) return "暂无统计数据";
|
|
559
559
|
let processedStats;
|
|
@@ -581,7 +581,7 @@ var Stat = class {
|
|
|
581
581
|
}));
|
|
582
582
|
}
|
|
583
583
|
if (this.config.enableMsgStat) {
|
|
584
|
-
cmd.subcommand("msgstat", "发言统计").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("type", "-t <type:string> 指定类型").option("all", "-a 全局").action(createHandler(async (scope, options) => {
|
|
584
|
+
cmd.subcommand("msgstat", "发言统计").usage("查询发言统计,可指定查询范围,默认当前群组。").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("type", "-t <type:string> 指定类型").option("all", "-a 全局").action(createHandler(async (scope, options) => {
|
|
585
585
|
const { type } = options;
|
|
586
586
|
const query = { uid: { $in: scope.uids } };
|
|
587
587
|
if (type) query.type = type;
|
|
@@ -597,7 +597,7 @@ var Stat = class {
|
|
|
597
597
|
}));
|
|
598
598
|
}
|
|
599
599
|
if (this.config.enableRankStat) {
|
|
600
|
-
cmd.subcommand("rankstat", "发言排行").option("guild", "-g <guildId:string> 指定群组").option("type", "-t <type:string> 指定类型").option("hours", "-h <hours:number> 指定时长", { fallback: 24 }).option("all", "-a 全局").action(createHandler(async (scope, options) => {
|
|
600
|
+
cmd.subcommand("rankstat", "发言排行").usage("查询发言排行,可指定查询范围,默认当前群组。").option("guild", "-g <guildId:string> 指定群组").option("type", "-t <type:string> 指定类型").option("hours", "-h <hours:number> 指定时长", { fallback: 24 }).option("all", "-a 全局").action(createHandler(async (scope, options) => {
|
|
601
601
|
const { hours, type } = options;
|
|
602
602
|
const since = new Date(Date.now() - hours * import_koishi3.Time.hour);
|
|
603
603
|
const query = { uid: { $in: scope.uids }, timestamp: { $gte: since } };
|
|
@@ -614,7 +614,7 @@ var Stat = class {
|
|
|
614
614
|
}));
|
|
615
615
|
}
|
|
616
616
|
if (this.config.enableActivity) {
|
|
617
|
-
cmd.subcommand("activity", "活跃统计").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("hours", "-h <hours:number> 指定偏移时长").option("all", "-a 全局").option("days", "-d 切换至天数").action(createHandler(async (scope, options) => {
|
|
617
|
+
cmd.subcommand("activity", "活跃统计").usage("查询活跃统计,可指定查询范围,默认当前群组。").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("hours", "-h <hours:number> 指定偏移时长").option("all", "-a 全局").option("days", "-d 切换至天数").action(createHandler(async (scope, options) => {
|
|
618
618
|
const { days, hours } = options;
|
|
619
619
|
if (days) {
|
|
620
620
|
const timeRangeInDays = 24;
|
|
@@ -798,7 +798,7 @@ var Data = class {
|
|
|
798
798
|
return "数据恢复失败";
|
|
799
799
|
}
|
|
800
800
|
});
|
|
801
|
-
cmd.subcommand(".clear", "清除数据", { authority: 4 }).option("table", "-t <table:string> 指定表名").option("guild", "-g <guildId:string> 指定群组").option("user", "-u <user:string> 指定用户").option("days", "-d <days:number> 指定天数").option("command", "-c <command:string> 指定命令").option("all", "-a 清除全部").
|
|
801
|
+
cmd.subcommand(".clear", "清除数据", { authority: 4 }).usage(`清除指定统计数据,可精确控制清除范围。`).option("table", "-t <table:string> 指定表名").option("guild", "-g <guildId:string> 指定群组").option("user", "-u <user:string> 指定用户").option("days", "-d <days:number> 指定天数").option("command", "-c <command:string> 指定命令").option("all", "-a 清除全部").action(async ({ options }) => {
|
|
802
802
|
if (Object.keys(options).length === 0) return "请指定清除条件";
|
|
803
803
|
if (options.table && !ALL_TABLES.includes(options.table)) return `表名 ${options.table} 无效`;
|
|
804
804
|
try {
|
|
@@ -871,107 +871,64 @@ ${commandOutput}`;
|
|
|
871
871
|
|
|
872
872
|
// src/Analyse.ts
|
|
873
873
|
var import_koishi6 = require("koishi");
|
|
874
|
-
var
|
|
875
|
-
var
|
|
874
|
+
var import_jieba = require("@node-rs/jieba");
|
|
875
|
+
var import_dict = require("@node-rs/jieba/dict");
|
|
876
876
|
var Analyse = class {
|
|
877
877
|
constructor(ctx, config) {
|
|
878
878
|
this.ctx = ctx;
|
|
879
879
|
this.config = config;
|
|
880
880
|
this.renderer = new Renderer(ctx);
|
|
881
|
-
this.
|
|
882
|
-
this.nlp.settings.autoSave = false;
|
|
883
|
-
this.nlp.container.register("extract-lang-zh", new import_lang_zh.LangZh());
|
|
884
|
-
this.initializeNlp().catch((err) => {
|
|
885
|
-
this.ctx.logger.error("NLP 语言模型加载失败:", err);
|
|
886
|
-
});
|
|
881
|
+
if (config.enableWordCloud) this.jieba = import_jieba.Jieba.withDict(import_dict.dict);
|
|
887
882
|
}
|
|
888
883
|
static {
|
|
889
884
|
__name(this, "Analyse");
|
|
890
885
|
}
|
|
891
886
|
renderer;
|
|
892
|
-
|
|
893
|
-
isNlpReady = false;
|
|
887
|
+
jieba = null;
|
|
894
888
|
/**
|
|
895
|
-
* @
|
|
896
|
-
* @
|
|
897
|
-
* @
|
|
889
|
+
* @public @method registerCommands
|
|
890
|
+
* @description 在主命令下注册子命令。
|
|
891
|
+
* @param cmd - 主命令实例。
|
|
898
892
|
*/
|
|
899
|
-
async initializeNlp() {
|
|
900
|
-
await this.nlp.train();
|
|
901
|
-
this.isNlpReady = true;
|
|
902
|
-
}
|
|
903
893
|
registerCommands(cmd) {
|
|
904
894
|
if (this.config.enableWordCloud) {
|
|
905
|
-
cmd.subcommand("
|
|
906
|
-
if (!this.
|
|
895
|
+
cmd.subcommand("wordcloud", "生成词云").usage("基于聊天记录生成词云图,可指定范围,默认当前群组。").option("guild", "-g <guildId:string> 指定群组").option("user", "-u <user:string> 指定用户").option("hours", "-h <hours:number> 指定时长", { fallback: 24 }).option("all", "-a 全局").action(async ({ session, options }) => {
|
|
896
|
+
if (!this.jieba) return "Jieba 分词服务未就绪";
|
|
907
897
|
const scope = await parseQueryScope(this.ctx, session, options);
|
|
908
898
|
if (scope.error) return scope.error;
|
|
899
|
+
scope.uids ??= (await this.ctx.database.get("analyse_user", {}, ["uid"])).map((u) => u.uid);
|
|
900
|
+
if (!scope.uids?.length) return "暂无用户数据";
|
|
909
901
|
const since = new Date(Date.now() - options.hours * import_koishi6.Time.hour);
|
|
910
|
-
const records = await this.ctx.database.
|
|
911
|
-
if (records.length
|
|
902
|
+
const records = await this.ctx.database.get("analyse_cache", { uid: { $in: scope.uids }, timestamp: { $gte: since } }, ["content"]);
|
|
903
|
+
if (!records.length) return "暂无统计数据";
|
|
912
904
|
const allText = records.map((r) => r.content).join(" ");
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
905
|
+
const exclusionSet = /* @__PURE__ */ new Set([
|
|
906
|
+
"[face]",
|
|
907
|
+
"[file]",
|
|
908
|
+
"[forward]",
|
|
909
|
+
"[img]",
|
|
910
|
+
"[audio]",
|
|
911
|
+
"[video]",
|
|
912
|
+
"[json]",
|
|
913
|
+
"[rps]",
|
|
914
|
+
"[markdown]",
|
|
915
|
+
"[dice]"
|
|
916
|
+
]);
|
|
917
|
+
const words = this.jieba.cut(allText).filter((w) => {
|
|
918
|
+
if (w.length <= 1) return false;
|
|
919
|
+
if (/^\d+$/.test(w)) return false;
|
|
920
|
+
if (exclusionSet.has(w)) return false;
|
|
921
|
+
if (/^\[at:.*?\]$/.test(w)) return false;
|
|
922
|
+
return true;
|
|
923
|
+
});
|
|
924
|
+
if (!words.length) return "暂无有效词语";
|
|
925
|
+
const wordCounts = words.reduce((map, word) => map.set(word, (map.get(word) || 0) + 1), /* @__PURE__ */ new Map());
|
|
920
926
|
const wordList = Array.from(wordCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 150);
|
|
921
927
|
const title = await generateTitle(this.ctx, scope.scopeDesc, { main: "词云" });
|
|
922
|
-
const
|
|
923
|
-
if (typeof
|
|
924
|
-
if (Array.isArray(
|
|
925
|
-
for (const buffer of
|
|
926
|
-
}
|
|
927
|
-
});
|
|
928
|
-
}
|
|
929
|
-
if (this.config.enableVocabulary) {
|
|
930
|
-
cmd.subcommand(".vocabulary", "词汇排行").usage("根据不重复词汇量占总词汇量的比例进行排行。").option("guild", "-g <guildId:string> 指定群组").option("all", "-a 全局").option("hours", "-h <hours:number> 指定时长", { fallback: 24 }).action(async ({ session, options }) => {
|
|
931
|
-
if (!this.isNlpReady) return "文本分析尚未就绪,请稍后再试";
|
|
932
|
-
const scope = await parseQueryScope(this.ctx, session, options);
|
|
933
|
-
if (scope.error) return scope.error;
|
|
934
|
-
const users = await this.ctx.database.get("analyse_user", { uid: { $in: scope.uids } }, ["uid", "userName"]);
|
|
935
|
-
const userNameMap = new Map(users.map((u) => [u.uid, u.userName]));
|
|
936
|
-
const since = new Date(Date.now() - options.hours * import_koishi6.Time.hour);
|
|
937
|
-
const allRecords = await this.ctx.database.get("analyse_cache", { uid: { $in: scope.uids }, timestamp: { $gte: since } }, ["uid", "content"]);
|
|
938
|
-
if (allRecords.length === 0) return "暂无统计数据";
|
|
939
|
-
const messagesByUid = /* @__PURE__ */ new Map();
|
|
940
|
-
for (const record of allRecords) {
|
|
941
|
-
if (!messagesByUid.has(record.uid)) messagesByUid.set(record.uid, []);
|
|
942
|
-
messagesByUid.get(record.uid).push(record.content);
|
|
943
|
-
}
|
|
944
|
-
const richnessData = [];
|
|
945
|
-
for (const [uid, messages] of messagesByUid.entries()) {
|
|
946
|
-
const allText = messages.join(" ");
|
|
947
|
-
const result = await this.nlp.process("zh", allText);
|
|
948
|
-
const words = (result.stems || []).filter((stem) => stem.length > 1);
|
|
949
|
-
if (words.length < 50) continue;
|
|
950
|
-
const uniqueWords = new Set(words);
|
|
951
|
-
const richness = uniqueWords.size / words.length;
|
|
952
|
-
richnessData.push({
|
|
953
|
-
name: userNameMap.get(uid) || `UID ${uid}`,
|
|
954
|
-
total: words.length,
|
|
955
|
-
unique: uniqueWords.size,
|
|
956
|
-
richness
|
|
957
|
-
});
|
|
958
|
-
}
|
|
959
|
-
if (richnessData.length === 0) return "暂无有效词语";
|
|
960
|
-
richnessData.sort((a, b) => b.richness - a.richness);
|
|
961
|
-
const list = richnessData.map((item) => [
|
|
962
|
-
item.name,
|
|
963
|
-
item.unique,
|
|
964
|
-
item.total,
|
|
965
|
-
`${(item.richness * 100).toFixed(2)}%`
|
|
966
|
-
]);
|
|
967
|
-
const title = await generateTitle(this.ctx, scope.scopeDesc, { main: "词汇排行" });
|
|
968
|
-
const renderResult = await this.renderer.renderList(
|
|
969
|
-
{ title, time: /* @__PURE__ */ new Date(), total: richnessData.length, list },
|
|
970
|
-
["用户", "不重复词数", "总词数", "丰富度"]
|
|
971
|
-
);
|
|
972
|
-
if (typeof renderResult === "string") return renderResult;
|
|
973
|
-
if (Array.isArray(renderResult) && renderResult.length > 0) {
|
|
974
|
-
for (const buffer of renderResult) await session.sendQueued(import_koishi6.h.image(buffer, "image/png"));
|
|
928
|
+
const result = await this.renderer.renderWordCloud({ title, time: /* @__PURE__ */ new Date(), words: wordList });
|
|
929
|
+
if (typeof result === "string") return result;
|
|
930
|
+
if (Array.isArray(result) && result.length > 0) {
|
|
931
|
+
for (const buffer of result) await session.sendQueued(import_koishi6.h.image(buffer, "image/png"));
|
|
975
932
|
}
|
|
976
933
|
});
|
|
977
934
|
}
|
|
@@ -1009,8 +966,7 @@ var Config3 = import_koishi7.Schema.intersect([
|
|
|
1009
966
|
}).description("基础分析配置"),
|
|
1010
967
|
import_koishi7.Schema.object({
|
|
1011
968
|
enableOriRecord: import_koishi7.Schema.boolean().default(true).description("启用原始记录"),
|
|
1012
|
-
enableWordCloud: import_koishi7.Schema.boolean().default(true).description("启用词云生成")
|
|
1013
|
-
enableVocabulary: import_koishi7.Schema.boolean().default(true).description("启用词汇排行")
|
|
969
|
+
enableWordCloud: import_koishi7.Schema.boolean().default(true).description("启用词云生成")
|
|
1014
970
|
}).description("高级分析配置")
|
|
1015
971
|
]);
|
|
1016
972
|
async function parseQueryScope(ctx, session, options) {
|
|
@@ -1057,7 +1013,7 @@ function apply(ctx, config) {
|
|
|
1057
1013
|
new Stat(ctx, config).registerCommands(analyse);
|
|
1058
1014
|
if (config.enableWhoAt) new WhoAt(ctx, config).registerCommand(analyse);
|
|
1059
1015
|
if (config.enableDataIO) new Data(ctx).registerCommands(analyse);
|
|
1060
|
-
if (config.enableOriRecord &&
|
|
1016
|
+
if (config.enableOriRecord && config.enableWordCloud) new Analyse(ctx, config).registerCommands(analyse);
|
|
1061
1017
|
}
|
|
1062
1018
|
__name(apply, "apply");
|
|
1063
1019
|
// 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.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"Yis_Rime <yis_rime@outlook.com>"
|
|
7
7
|
],
|
|
@@ -39,7 +39,6 @@
|
|
|
39
39
|
"koishi": "4.18.8"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@
|
|
43
|
-
"@nlpjs/lang-zh": "^4.26.1"
|
|
42
|
+
"@node-rs/jieba": "^2.0.1"
|
|
44
43
|
}
|
|
45
44
|
}
|