koishi-plugin-chat-analyse 0.6.2 → 0.6.4
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/Renderer.d.ts +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +66 -24
- package/package.json +1 -1
package/lib/Renderer.d.ts
CHANGED
package/lib/index.d.ts
CHANGED
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>`;
|
|
@@ -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", "活跃统计").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
|
}
|
|
@@ -871,7 +911,7 @@ var Analyse = class {
|
|
|
871
911
|
if (records.length === 0) return "暂无统计数据";
|
|
872
912
|
const allText = records.map((r) => r.content).join(" ");
|
|
873
913
|
const result = await this.nlp.process("zh", allText);
|
|
874
|
-
const words = result.stems.filter((stem) => stem.length > 1);
|
|
914
|
+
const words = (result.stems || []).filter((stem) => stem.length > 1);
|
|
875
915
|
const wordCounts = words.reduce((map, word) => {
|
|
876
916
|
map.set(word, (map.get(word) || 0) + 1);
|
|
877
917
|
return map;
|
|
@@ -905,7 +945,7 @@ var Analyse = class {
|
|
|
905
945
|
for (const [uid, messages] of messagesByUid.entries()) {
|
|
906
946
|
const allText = messages.join(" ");
|
|
907
947
|
const result = await this.nlp.process("zh", allText);
|
|
908
|
-
const words = result.stems.filter((stem) => stem.length > 1);
|
|
948
|
+
const words = (result.stems || []).filter((stem) => stem.length > 1);
|
|
909
949
|
if (words.length < 50) continue;
|
|
910
950
|
const uniqueWords = new Set(words);
|
|
911
951
|
const richness = uniqueWords.size / words.length;
|
|
@@ -997,16 +1037,18 @@ async function generateTitle(ctx, scopeDesc, options) {
|
|
|
997
1037
|
const [user] = await ctx.database.get("analyse_user", { userId: scopeDesc.userId }, ["userName"]);
|
|
998
1038
|
userName = user?.userName || scopeDesc.userId;
|
|
999
1039
|
}
|
|
1040
|
+
const timeText = options.timeRange ? `${options.timeRange}${options.timeUnit || "小时"}` : "";
|
|
1000
1041
|
const typeText = options.subtype ? `“${options.subtype}”` : "";
|
|
1001
1042
|
const mainText = options.main;
|
|
1002
|
-
if (mainText.includes("排行")) {
|
|
1043
|
+
if (mainText.includes("排行") || mainText.includes("活跃")) {
|
|
1003
1044
|
scopeText = guildName || "全局";
|
|
1004
|
-
|
|
1045
|
+
} else {
|
|
1046
|
+
if (userName && guildName) scopeText = `${guildName} ${userName}`;
|
|
1047
|
+
else if (userName) scopeText = userName;
|
|
1048
|
+
else if (guildName) scopeText = guildName;
|
|
1005
1049
|
}
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
else if (guildName) scopeText = guildName;
|
|
1009
|
-
return `${scopeText}${typeText}${mainText}统计`;
|
|
1050
|
+
const suffix = mainText.includes("排行") ? "" : "统计";
|
|
1051
|
+
return `${timeText}${scopeText}${typeText}${mainText}${suffix}`;
|
|
1010
1052
|
}
|
|
1011
1053
|
__name(generateTitle, "generateTitle");
|
|
1012
1054
|
function apply(ctx, config) {
|