koishi-plugin-chat-analyse 0.6.3 → 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/Renderer.d.ts +1 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +108 -110
- 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/Renderer.d.ts
CHANGED
package/lib/index.d.ts
CHANGED
|
@@ -19,7 +19,6 @@ export interface Config {
|
|
|
19
19
|
atRetentionDays: number;
|
|
20
20
|
rankRetentionDays: number;
|
|
21
21
|
enableWordCloud: boolean;
|
|
22
|
-
enableVocabulary: boolean;
|
|
23
22
|
}
|
|
24
23
|
/** @description 插件的配置项定义 */
|
|
25
24
|
export declare const Config: Schema<Config>;
|
|
@@ -54,6 +53,7 @@ export declare function generateTitle(ctx: Context, scopeDesc: {
|
|
|
54
53
|
main: string;
|
|
55
54
|
subtype?: string;
|
|
56
55
|
timeRange?: number;
|
|
56
|
+
timeUnit?: '小时' | '天';
|
|
57
57
|
}): Promise<string>;
|
|
58
58
|
/**
|
|
59
59
|
* @function apply
|
package/lib/index.js
CHANGED
|
@@ -425,16 +425,15 @@ var Renderer = class {
|
|
|
425
425
|
* @returns {Promise<string | Buffer[]>} - 成功时返回包含图片 Buffer 的数组,失败或无数据时返回提示字符串。
|
|
426
426
|
*/
|
|
427
427
|
async renderCircadianChart(data) {
|
|
428
|
-
const { title, time, total, data: hourlyCounts } = data;
|
|
428
|
+
const { title, time, total, data: hourlyCounts, labels } = data;
|
|
429
429
|
if (!hourlyCounts || hourlyCounts.every((c) => c === 0)) return "暂无数据可供渲染";
|
|
430
430
|
const maxCount = Math.max(...hourlyCounts, 1);
|
|
431
431
|
const chartStyles = `
|
|
432
432
|
.chart-container { display: flex; align-items: flex-end; gap: 4px; height: 180px; padding: 30px 15px 10px; }
|
|
433
433
|
.bar-wrapper { flex: 1; text-align: center; display: flex; flex-direction: column; justify-content: flex-end; height: 100%; }
|
|
434
|
-
.bar-value { font-size: 11px; color: var(--sub-text-color); height: 16px; line-height: 16px; font-weight: 500;
|
|
434
|
+
.bar-value { font-size: 11px; color: var(--sub-text-color); height: 16px; line-height: 16px; font-weight: 500; }
|
|
435
435
|
.bar-container { flex-grow: 1; display: flex; align-items: flex-end; width: 100%; }
|
|
436
436
|
.bar { width: 100%; background-color: var(--accent-color); opacity: .7; border-radius: 3px 3px 0 0; transition: height .3s ease-out; }
|
|
437
|
-
.bar.peak { opacity: 1; background-color: var(--gold); }
|
|
438
437
|
.bar-label { font-size: 10px; color: var(--sub-text-color); margin-top: 4px; height: 12px; }
|
|
439
438
|
`;
|
|
440
439
|
const cardHtml = `
|
|
@@ -449,9 +448,9 @@ var Renderer = class {
|
|
|
449
448
|
<div class="bar-wrapper">
|
|
450
449
|
<div class="bar-value">${count > 0 ? count : ""}</div>
|
|
451
450
|
<div class="bar-container">
|
|
452
|
-
<div class="bar
|
|
451
|
+
<div class="bar" style="height: ${count / maxCount * 100}%;"></div>
|
|
453
452
|
</div>
|
|
454
|
-
<div class="bar-label">${hour}</div>
|
|
453
|
+
<div class="bar-label">${labels ? labels[hour] : hour}</div>
|
|
455
454
|
</div>`).join("")}
|
|
456
455
|
</div>
|
|
457
456
|
</div>`;
|
|
@@ -554,7 +553,7 @@ var Stat = class {
|
|
|
554
553
|
};
|
|
555
554
|
}, "createHandler");
|
|
556
555
|
if (this.config.enableCmdStat) {
|
|
557
|
-
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) => {
|
|
558
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();
|
|
559
558
|
if (stats.length === 0) return "暂无统计数据";
|
|
560
559
|
let processedStats;
|
|
@@ -582,7 +581,7 @@ var Stat = class {
|
|
|
582
581
|
}));
|
|
583
582
|
}
|
|
584
583
|
if (this.config.enableMsgStat) {
|
|
585
|
-
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) => {
|
|
586
585
|
const { type } = options;
|
|
587
586
|
const query = { uid: { $in: scope.uids } };
|
|
588
587
|
if (type) query.type = type;
|
|
@@ -598,7 +597,7 @@ var Stat = class {
|
|
|
598
597
|
}));
|
|
599
598
|
}
|
|
600
599
|
if (this.config.enableRankStat) {
|
|
601
|
-
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) => {
|
|
602
601
|
const { hours, type } = options;
|
|
603
602
|
const since = new Date(Date.now() - hours * import_koishi3.Time.hour);
|
|
604
603
|
const query = { uid: { $in: scope.uids }, timestamp: { $gte: since } };
|
|
@@ -615,17 +614,58 @@ var Stat = class {
|
|
|
615
614
|
}));
|
|
616
615
|
}
|
|
617
616
|
if (this.config.enableActivity) {
|
|
618
|
-
cmd.subcommand("activity", "活跃统计").option("user", "-u <user:string> 指定用户").option("guild", "-g <guildId:string> 指定群组").option("all", "-a 全局").action(createHandler(async (scope) => {
|
|
619
|
-
const
|
|
620
|
-
if (
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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
|
+
const { days, hours } = options;
|
|
619
|
+
if (days) {
|
|
620
|
+
const timeRangeInDays = 24;
|
|
621
|
+
const since = new Date(Date.now() - timeRangeInDays * import_koishi3.Time.day);
|
|
622
|
+
const stats = await this.ctx.database.select("analyse_rank").where({ uid: { $in: scope.uids }, timestamp: { $gte: since } }).project(["timestamp", "count"]).execute();
|
|
623
|
+
if (stats.length === 0) return "暂无统计数据";
|
|
624
|
+
const startOfToday = /* @__PURE__ */ new Date();
|
|
625
|
+
startOfToday.setHours(0, 0, 0, 0);
|
|
626
|
+
const dailyCounts = Array(timeRangeInDays).fill(0);
|
|
627
|
+
const dayLabels = Array(timeRangeInDays).fill("");
|
|
628
|
+
for (let i = 0; i < timeRangeInDays; i++) {
|
|
629
|
+
const d = new Date(startOfToday.getTime() - i * import_koishi3.Time.day);
|
|
630
|
+
dayLabels[timeRangeInDays - 1 - i] = String(d.getDate());
|
|
631
|
+
}
|
|
632
|
+
stats.forEach((stat) => {
|
|
633
|
+
const statDayStart = new Date(stat.timestamp);
|
|
634
|
+
statDayStart.setHours(0, 0, 0, 0);
|
|
635
|
+
const daysAgo = Math.round((startOfToday.getTime() - statDayStart.getTime()) / import_koishi3.Time.day);
|
|
636
|
+
if (daysAgo >= 0 && daysAgo < timeRangeInDays) {
|
|
637
|
+
const index = timeRangeInDays - 1 - daysAgo;
|
|
638
|
+
dailyCounts[index] += stat.count;
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
const totalMessages = dailyCounts.reduce((a, b) => a + b, 0);
|
|
642
|
+
const title = await generateTitle(this.ctx, scope.scopeDesc, { main: "活跃", timeRange: timeRangeInDays, timeUnit: "天" });
|
|
643
|
+
return this.renderer.renderCircadianChart({ title, time: /* @__PURE__ */ new Date(), total: totalMessages, data: dailyCounts, labels: dayLabels });
|
|
644
|
+
} else {
|
|
645
|
+
const timeWindowHours = 24;
|
|
646
|
+
const offsetHours = typeof hours === "number" ? hours : 0;
|
|
647
|
+
const now = /* @__PURE__ */ new Date();
|
|
648
|
+
const until = new Date(now.getTime() - offsetHours * import_koishi3.Time.hour);
|
|
649
|
+
const since = new Date(until.getTime() - timeWindowHours * import_koishi3.Time.hour);
|
|
650
|
+
const hourlyStats = await this.ctx.database.select("analyse_rank").where({ uid: { $in: scope.uids }, timestamp: { $gte: since, $lt: until } }).groupBy("timestamp", { count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count") }).execute();
|
|
651
|
+
if (hourlyStats.length === 0) return "暂无统计数据";
|
|
652
|
+
const processedCounts = Array(timeWindowHours).fill(0);
|
|
653
|
+
const hourLabels = Array(timeWindowHours).fill("");
|
|
654
|
+
for (let i = 0; i < timeWindowHours; i++) {
|
|
655
|
+
const d = new Date(until.getTime() - (i + 1) * import_koishi3.Time.hour);
|
|
656
|
+
hourLabels[timeWindowHours - 1 - i] = String(d.getHours());
|
|
657
|
+
}
|
|
658
|
+
hourlyStats.forEach((stat) => {
|
|
659
|
+
const hoursBeforeUntil = Math.floor((until.getTime() - stat.timestamp.getTime()) / import_koishi3.Time.hour);
|
|
660
|
+
if (hoursBeforeUntil >= 0 && hoursBeforeUntil < timeWindowHours) {
|
|
661
|
+
const index = timeWindowHours - 1 - hoursBeforeUntil;
|
|
662
|
+
processedCounts[index] += stat.count;
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
const totalMessages = processedCounts.reduce((a, b) => a + b, 0);
|
|
666
|
+
const title = await generateTitle(this.ctx, scope.scopeDesc, { main: "活跃", timeRange: timeWindowHours, timeUnit: "小时" });
|
|
667
|
+
return this.renderer.renderCircadianChart({ title, time: /* @__PURE__ */ new Date(), total: totalMessages, data: processedCounts, labels: hourLabels });
|
|
668
|
+
}
|
|
629
669
|
}));
|
|
630
670
|
}
|
|
631
671
|
}
|
|
@@ -758,7 +798,7 @@ var Data = class {
|
|
|
758
798
|
return "数据恢复失败";
|
|
759
799
|
}
|
|
760
800
|
});
|
|
761
|
-
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 }) => {
|
|
762
802
|
if (Object.keys(options).length === 0) return "请指定清除条件";
|
|
763
803
|
if (options.table && !ALL_TABLES.includes(options.table)) return `表名 ${options.table} 无效`;
|
|
764
804
|
try {
|
|
@@ -831,107 +871,64 @@ ${commandOutput}`;
|
|
|
831
871
|
|
|
832
872
|
// src/Analyse.ts
|
|
833
873
|
var import_koishi6 = require("koishi");
|
|
834
|
-
var
|
|
835
|
-
var
|
|
874
|
+
var import_jieba = require("@node-rs/jieba");
|
|
875
|
+
var import_dict = require("@node-rs/jieba/dict");
|
|
836
876
|
var Analyse = class {
|
|
837
877
|
constructor(ctx, config) {
|
|
838
878
|
this.ctx = ctx;
|
|
839
879
|
this.config = config;
|
|
840
880
|
this.renderer = new Renderer(ctx);
|
|
841
|
-
this.
|
|
842
|
-
this.nlp.settings.autoSave = false;
|
|
843
|
-
this.nlp.container.register("extract-lang-zh", new import_lang_zh.LangZh());
|
|
844
|
-
this.initializeNlp().catch((err) => {
|
|
845
|
-
this.ctx.logger.error("NLP 语言模型加载失败:", err);
|
|
846
|
-
});
|
|
881
|
+
if (config.enableWordCloud) this.jieba = import_jieba.Jieba.withDict(import_dict.dict);
|
|
847
882
|
}
|
|
848
883
|
static {
|
|
849
884
|
__name(this, "Analyse");
|
|
850
885
|
}
|
|
851
886
|
renderer;
|
|
852
|
-
|
|
853
|
-
isNlpReady = false;
|
|
887
|
+
jieba = null;
|
|
854
888
|
/**
|
|
855
|
-
* @
|
|
856
|
-
* @
|
|
857
|
-
* @
|
|
889
|
+
* @public @method registerCommands
|
|
890
|
+
* @description 在主命令下注册子命令。
|
|
891
|
+
* @param cmd - 主命令实例。
|
|
858
892
|
*/
|
|
859
|
-
async initializeNlp() {
|
|
860
|
-
await this.nlp.train();
|
|
861
|
-
this.isNlpReady = true;
|
|
862
|
-
}
|
|
863
893
|
registerCommands(cmd) {
|
|
864
894
|
if (this.config.enableWordCloud) {
|
|
865
|
-
cmd.subcommand("
|
|
866
|
-
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 分词服务未就绪";
|
|
867
897
|
const scope = await parseQueryScope(this.ctx, session, options);
|
|
868
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 "暂无用户数据";
|
|
869
901
|
const since = new Date(Date.now() - options.hours * import_koishi6.Time.hour);
|
|
870
|
-
const records = await this.ctx.database.
|
|
871
|
-
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 "暂无统计数据";
|
|
872
904
|
const allText = records.map((r) => r.content).join(" ");
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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());
|
|
880
926
|
const wordList = Array.from(wordCounts.entries()).sort((a, b) => b[1] - a[1]).slice(0, 150);
|
|
881
927
|
const title = await generateTitle(this.ctx, scope.scopeDesc, { main: "词云" });
|
|
882
|
-
const
|
|
883
|
-
if (typeof
|
|
884
|
-
if (Array.isArray(
|
|
885
|
-
for (const buffer of
|
|
886
|
-
}
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
if (this.config.enableVocabulary) {
|
|
890
|
-
cmd.subcommand(".vocabulary", "词汇排行").usage("根据不重复词汇量占总词汇量的比例进行排行。").option("guild", "-g <guildId:string> 指定群组").option("all", "-a 全局").option("hours", "-h <hours:number> 指定时长", { fallback: 24 }).action(async ({ session, options }) => {
|
|
891
|
-
if (!this.isNlpReady) return "文本分析尚未就绪,请稍后再试";
|
|
892
|
-
const scope = await parseQueryScope(this.ctx, session, options);
|
|
893
|
-
if (scope.error) return scope.error;
|
|
894
|
-
const users = await this.ctx.database.get("analyse_user", { uid: { $in: scope.uids } }, ["uid", "userName"]);
|
|
895
|
-
const userNameMap = new Map(users.map((u) => [u.uid, u.userName]));
|
|
896
|
-
const since = new Date(Date.now() - options.hours * import_koishi6.Time.hour);
|
|
897
|
-
const allRecords = await this.ctx.database.get("analyse_cache", { uid: { $in: scope.uids }, timestamp: { $gte: since } }, ["uid", "content"]);
|
|
898
|
-
if (allRecords.length === 0) return "暂无统计数据";
|
|
899
|
-
const messagesByUid = /* @__PURE__ */ new Map();
|
|
900
|
-
for (const record of allRecords) {
|
|
901
|
-
if (!messagesByUid.has(record.uid)) messagesByUid.set(record.uid, []);
|
|
902
|
-
messagesByUid.get(record.uid).push(record.content);
|
|
903
|
-
}
|
|
904
|
-
const richnessData = [];
|
|
905
|
-
for (const [uid, messages] of messagesByUid.entries()) {
|
|
906
|
-
const allText = messages.join(" ");
|
|
907
|
-
const result = await this.nlp.process("zh", allText);
|
|
908
|
-
const words = (result.stems || []).filter((stem) => stem.length > 1);
|
|
909
|
-
if (words.length < 50) continue;
|
|
910
|
-
const uniqueWords = new Set(words);
|
|
911
|
-
const richness = uniqueWords.size / words.length;
|
|
912
|
-
richnessData.push({
|
|
913
|
-
name: userNameMap.get(uid) || `UID ${uid}`,
|
|
914
|
-
total: words.length,
|
|
915
|
-
unique: uniqueWords.size,
|
|
916
|
-
richness
|
|
917
|
-
});
|
|
918
|
-
}
|
|
919
|
-
if (richnessData.length === 0) return "暂无有效词语";
|
|
920
|
-
richnessData.sort((a, b) => b.richness - a.richness);
|
|
921
|
-
const list = richnessData.map((item) => [
|
|
922
|
-
item.name,
|
|
923
|
-
item.unique,
|
|
924
|
-
item.total,
|
|
925
|
-
`${(item.richness * 100).toFixed(2)}%`
|
|
926
|
-
]);
|
|
927
|
-
const title = await generateTitle(this.ctx, scope.scopeDesc, { main: "词汇排行" });
|
|
928
|
-
const renderResult = await this.renderer.renderList(
|
|
929
|
-
{ title, time: /* @__PURE__ */ new Date(), total: richnessData.length, list },
|
|
930
|
-
["用户", "不重复词数", "总词数", "丰富度"]
|
|
931
|
-
);
|
|
932
|
-
if (typeof renderResult === "string") return renderResult;
|
|
933
|
-
if (Array.isArray(renderResult) && renderResult.length > 0) {
|
|
934
|
-
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"));
|
|
935
932
|
}
|
|
936
933
|
});
|
|
937
934
|
}
|
|
@@ -969,8 +966,7 @@ var Config3 = import_koishi7.Schema.intersect([
|
|
|
969
966
|
}).description("基础分析配置"),
|
|
970
967
|
import_koishi7.Schema.object({
|
|
971
968
|
enableOriRecord: import_koishi7.Schema.boolean().default(true).description("启用原始记录"),
|
|
972
|
-
enableWordCloud: import_koishi7.Schema.boolean().default(true).description("启用词云生成")
|
|
973
|
-
enableVocabulary: import_koishi7.Schema.boolean().default(true).description("启用词汇排行")
|
|
969
|
+
enableWordCloud: import_koishi7.Schema.boolean().default(true).description("启用词云生成")
|
|
974
970
|
}).description("高级分析配置")
|
|
975
971
|
]);
|
|
976
972
|
async function parseQueryScope(ctx, session, options) {
|
|
@@ -997,16 +993,18 @@ async function generateTitle(ctx, scopeDesc, options) {
|
|
|
997
993
|
const [user] = await ctx.database.get("analyse_user", { userId: scopeDesc.userId }, ["userName"]);
|
|
998
994
|
userName = user?.userName || scopeDesc.userId;
|
|
999
995
|
}
|
|
996
|
+
const timeText = options.timeRange ? `${options.timeRange}${options.timeUnit || "小时"}` : "";
|
|
1000
997
|
const typeText = options.subtype ? `“${options.subtype}”` : "";
|
|
1001
998
|
const mainText = options.main;
|
|
1002
|
-
if (mainText.includes("排行")) {
|
|
999
|
+
if (mainText.includes("排行") || mainText.includes("活跃")) {
|
|
1003
1000
|
scopeText = guildName || "全局";
|
|
1004
|
-
|
|
1001
|
+
} else {
|
|
1002
|
+
if (userName && guildName) scopeText = `${guildName} ${userName}`;
|
|
1003
|
+
else if (userName) scopeText = userName;
|
|
1004
|
+
else if (guildName) scopeText = guildName;
|
|
1005
1005
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
else if (guildName) scopeText = guildName;
|
|
1009
|
-
return `${scopeText}${typeText}${mainText}统计`;
|
|
1006
|
+
const suffix = mainText.includes("排行") ? "" : "统计";
|
|
1007
|
+
return `${timeText}${scopeText}${typeText}${mainText}${suffix}`;
|
|
1010
1008
|
}
|
|
1011
1009
|
__name(generateTitle, "generateTitle");
|
|
1012
1010
|
function apply(ctx, config) {
|
|
@@ -1015,7 +1013,7 @@ function apply(ctx, config) {
|
|
|
1015
1013
|
new Stat(ctx, config).registerCommands(analyse);
|
|
1016
1014
|
if (config.enableWhoAt) new WhoAt(ctx, config).registerCommand(analyse);
|
|
1017
1015
|
if (config.enableDataIO) new Data(ctx).registerCommands(analyse);
|
|
1018
|
-
if (config.enableOriRecord &&
|
|
1016
|
+
if (config.enableOriRecord && config.enableWordCloud) new Analyse(ctx, config).registerCommands(analyse);
|
|
1019
1017
|
}
|
|
1020
1018
|
__name(apply, "apply");
|
|
1021
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
|
}
|