koishi-plugin-chat-analyse 0.6.3 → 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 CHANGED
@@ -19,6 +19,7 @@ export interface CircadianChartData {
19
19
  time: Date;
20
20
  total: string | number;
21
21
  data: number[];
22
+ labels?: string[];
22
23
  }
23
24
  /**
24
25
  * @class Renderer
package/lib/index.d.ts CHANGED
@@ -54,6 +54,7 @@ export declare function generateTitle(ctx: Context, scopeDesc: {
54
54
  main: string;
55
55
  subtype?: string;
56
56
  timeRange?: number;
57
+ timeUnit?: '小时' | '天';
57
58
  }): Promise<string>;
58
59
  /**
59
60
  * @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; visibility: ${maxCount > 50 ? "hidden" : "visible"}; }
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 ${count === maxCount ? "peak" : ""}" style="height: ${count / maxCount * 100}%;"></div>
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 hourlyStats = await this.ctx.database.select("analyse_rank").where({ uid: { $in: scope.uids } }).groupBy(["timestamp"], { count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count") }).execute();
620
- if (hourlyStats.length === 0) return "暂无统计数据";
621
- const hourlyCounts = Array(24).fill(0);
622
- let totalMessages = 0;
623
- hourlyStats.forEach((stat) => {
624
- hourlyCounts[stat.timestamp.getHours()] += stat.count;
625
- totalMessages += stat.count;
626
- });
627
- const title = await generateTitle(this.ctx, scope.scopeDesc, { main: "活跃" });
628
- return this.renderer.renderCircadianChart({ title, time: /* @__PURE__ */ new Date(), total: totalMessages, data: hourlyCounts });
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
  }
@@ -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
- return `${options.timeRange}小时${scopeText}${typeText}${mainText}`;
1045
+ } else {
1046
+ if (userName && guildName) scopeText = `${guildName} ${userName}`;
1047
+ else if (userName) scopeText = userName;
1048
+ else if (guildName) scopeText = guildName;
1005
1049
  }
1006
- if (userName && guildName) scopeText = `${guildName} ${userName}`;
1007
- else if (userName) scopeText = userName;
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) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chat-analyse",
3
3
  "description": "聊天记录分析",
4
- "version": "0.6.3",
4
+ "version": "0.6.4",
5
5
  "contributors": [
6
6
  "Yis_Rime <yis_rime@outlook.com>"
7
7
  ],