koishi-plugin-chat-analyse 0.2.6 → 0.3.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 +2 -2
- package/lib/Stat.d.ts +27 -7
- package/lib/index.js +159 -42
- package/package.json +1 -1
package/lib/Collector.d.ts
CHANGED
|
@@ -18,13 +18,13 @@ declare module 'koishi' {
|
|
|
18
18
|
analyse_msg: {
|
|
19
19
|
uid: number;
|
|
20
20
|
type: string;
|
|
21
|
+
hour: Date;
|
|
21
22
|
count: number;
|
|
22
23
|
timestamp: Date;
|
|
23
24
|
};
|
|
24
25
|
analyse_cache: {
|
|
25
26
|
id: number;
|
|
26
|
-
|
|
27
|
-
userId: string;
|
|
27
|
+
uid: number;
|
|
28
28
|
content: string;
|
|
29
29
|
timestamp: Date;
|
|
30
30
|
};
|
package/lib/Stat.d.ts
CHANGED
|
@@ -17,22 +17,21 @@ export declare class Stat {
|
|
|
17
17
|
constructor(ctx: Context, config: Config);
|
|
18
18
|
/**
|
|
19
19
|
* @method registerCommands
|
|
20
|
-
* @description 根据插件配置,动态地将 `.command
|
|
20
|
+
* @description 根据插件配置,动态地将 `.command`, `.message`, `.rank` 子命令注册到主 `analyse` 命令下。
|
|
21
21
|
* @param {Command} analyse - 主 `analyse` 命令实例。
|
|
22
22
|
*/
|
|
23
23
|
registerCommands(analyse: Command): void;
|
|
24
24
|
/**
|
|
25
25
|
* @private
|
|
26
26
|
* @async
|
|
27
|
-
* @method
|
|
28
|
-
* @description
|
|
29
|
-
* @param {Session} session - 当前会话,备用。
|
|
27
|
+
* @method generateTitle
|
|
28
|
+
* @description 通用的标题生成器。根据查询参数和类型选项动态生成易于理解的图片标题。
|
|
30
29
|
* @param {string} [guildId] - (可选) 查询的群组 ID。
|
|
31
30
|
* @param {string} [userId] - (可选) 查询的用户 ID。
|
|
32
|
-
* @param {
|
|
31
|
+
* @param {TitleOptions} options - 标题的配置选项。
|
|
33
32
|
* @returns {Promise<string>} 生成的标题字符串。
|
|
34
33
|
*/
|
|
35
|
-
private
|
|
34
|
+
private generateTitle;
|
|
36
35
|
/**
|
|
37
36
|
* @private
|
|
38
37
|
* @async
|
|
@@ -47,10 +46,31 @@ export declare class Stat {
|
|
|
47
46
|
* @private
|
|
48
47
|
* @async
|
|
49
48
|
* @method getMessageStats
|
|
50
|
-
* @description
|
|
49
|
+
* @description 从数据库中获取并聚合所有消息类型的统计数据。
|
|
51
50
|
* @param {string} [guildId] - (可选) 若提供,则将范围限制在此群组。
|
|
52
51
|
* @param {string} [userId] - (可选) 若提供,则将范围限制在此用户。
|
|
53
52
|
* @returns {Promise<{ list: RenderListItem[], total: number } | string>} 返回一个包含列表和总数的对象,或在无数据时返回提示字符串。
|
|
54
53
|
*/
|
|
55
54
|
private getMessageStats;
|
|
55
|
+
/**
|
|
56
|
+
* @private
|
|
57
|
+
* @async
|
|
58
|
+
* @method getMessageStatsByType
|
|
59
|
+
* @description 按指定消息类型,从数据库中获取并聚合用户排行数据。
|
|
60
|
+
* @param {string} type - 要查询的消息类型。
|
|
61
|
+
* @param {string} [guildId] - (可选) 若提供,则将范围限制在此群组。
|
|
62
|
+
* @param {string} [userId] - (可选) 若提供,则将范围限制在此用户。
|
|
63
|
+
* @returns {Promise<{ list: RenderListItem[], total: number } | string>}
|
|
64
|
+
*/
|
|
65
|
+
private getMessageStatsByType;
|
|
66
|
+
/**
|
|
67
|
+
* @private
|
|
68
|
+
* @async
|
|
69
|
+
* @method getActiveUserStats
|
|
70
|
+
* @description 从数据库中获取并聚合活跃用户排行数据。
|
|
71
|
+
* @param {string} guildId - 要查询的群组 ID。
|
|
72
|
+
* @param {number} hours - 查询过去的小时数。
|
|
73
|
+
* @returns {Promise<{ list: RenderListItem[], total: number } | string>}
|
|
74
|
+
*/
|
|
75
|
+
private getActiveUserStats;
|
|
56
76
|
}
|
package/lib/index.js
CHANGED
|
@@ -86,17 +86,17 @@ var Collector = class _Collector {
|
|
|
86
86
|
this.ctx.model.extend("analyse_msg", {
|
|
87
87
|
uid: "unsigned",
|
|
88
88
|
type: "string",
|
|
89
|
+
hour: "timestamp",
|
|
89
90
|
count: "unsigned",
|
|
90
91
|
timestamp: "timestamp"
|
|
91
|
-
}, { primary: ["uid", "type"] });
|
|
92
|
+
}, { primary: ["uid", "type", "hour"] });
|
|
92
93
|
if (this.config.enableAdvanced) {
|
|
93
94
|
this.ctx.model.extend("analyse_cache", {
|
|
94
95
|
id: "unsigned",
|
|
95
|
-
|
|
96
|
-
userId: "string",
|
|
96
|
+
uid: "unsigned",
|
|
97
97
|
content: "text",
|
|
98
98
|
timestamp: "timestamp"
|
|
99
|
-
}, { primary: "id", autoInc: true });
|
|
99
|
+
}, { primary: "id", autoInc: true, indexes: ["uid", "timestamp"] });
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
@@ -113,28 +113,29 @@ var Collector = class _Collector {
|
|
|
113
113
|
if (!effectiveId || !userId || !timestamp || !content?.trim()) return;
|
|
114
114
|
const uid = await this.getOrCreateUser(session, effectiveId);
|
|
115
115
|
if (!uid) return;
|
|
116
|
-
const now = /* @__PURE__ */ new Date();
|
|
117
116
|
if (argv?.command) {
|
|
118
117
|
await this.ctx.database.upsert("analyse_cmd", (row) => [{
|
|
119
118
|
uid,
|
|
120
119
|
command: argv.command.name,
|
|
121
120
|
count: import_koishi.$.add(import_koishi.$.ifNull(row.count, import_koishi.$.literal(0)), 1),
|
|
122
|
-
timestamp:
|
|
121
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
123
122
|
}]);
|
|
124
123
|
}
|
|
124
|
+
const messageTime = new Date(timestamp);
|
|
125
|
+
const hourStart = new Date(messageTime.getFullYear(), messageTime.getMonth(), messageTime.getDate(), messageTime.getHours());
|
|
125
126
|
const uniqueElementTypes = new Set(elements.map((e) => e.type));
|
|
126
127
|
for (const type of uniqueElementTypes) {
|
|
127
128
|
await this.ctx.database.upsert("analyse_msg", (row) => [{
|
|
128
129
|
uid,
|
|
129
130
|
type,
|
|
130
|
-
|
|
131
|
-
|
|
131
|
+
hour: hourStart,
|
|
132
|
+
count: import_koishi.$.add(import_koishi.$.ifNull(row.count, 0), 1),
|
|
133
|
+
timestamp: messageTime
|
|
132
134
|
}]);
|
|
133
135
|
}
|
|
134
136
|
if (this.config.enableAdvanced) {
|
|
135
137
|
this.cacheBuffer.push({
|
|
136
|
-
|
|
137
|
-
userId,
|
|
138
|
+
uid,
|
|
138
139
|
content: this.sanitizeContent(elements),
|
|
139
140
|
timestamp: new Date(timestamp)
|
|
140
141
|
});
|
|
@@ -359,29 +360,30 @@ var Stat = class {
|
|
|
359
360
|
renderer;
|
|
360
361
|
/**
|
|
361
362
|
* @method registerCommands
|
|
362
|
-
* @description 根据插件配置,动态地将 `.command
|
|
363
|
+
* @description 根据插件配置,动态地将 `.command`, `.message`, `.rank` 子命令注册到主 `analyse` 命令下。
|
|
363
364
|
* @param {Command} analyse - 主 `analyse` 命令实例。
|
|
364
365
|
*/
|
|
365
366
|
registerCommands(analyse) {
|
|
366
367
|
if (this.config.enableCmdStat) {
|
|
367
|
-
analyse.subcommand(".command", "命令使用统计").option("user", "-u [user:user]
|
|
368
|
+
analyse.subcommand(".command", "命令使用统计").option("user", "-u [user:user] 指定用户").option("guild", "-g [guildId:string] 指定群组").usage("查询用户或群组的命令使用统计,默认展示全局统计。").action(async ({ session, options }) => {
|
|
368
369
|
const userId = options.user ? import_koishi3.h.select(options.user, "user")[0]?.attrs.id : void 0;
|
|
369
370
|
let guildId = options.guild;
|
|
370
|
-
if (
|
|
371
|
-
if (!session.guildId) return "私聊中请使用 -g <群组ID> 指定群组。";
|
|
371
|
+
if (!userId && !guildId && session.guildId) {
|
|
372
372
|
guildId = session.guildId;
|
|
373
|
+
} else if (!userId && !guildId && !session.guildId) {
|
|
374
|
+
return "请指定查询范围";
|
|
373
375
|
}
|
|
374
376
|
try {
|
|
375
377
|
const stats = await this.getCommandStats(guildId, userId);
|
|
376
378
|
if (typeof stats === "string") return stats;
|
|
377
|
-
const title = await this.
|
|
379
|
+
const title = await this.generateTitle(guildId, userId, { main: "命令" });
|
|
378
380
|
const renderData = {
|
|
379
381
|
title,
|
|
380
382
|
time: /* @__PURE__ */ new Date(),
|
|
381
383
|
total: stats.total,
|
|
382
384
|
list: stats.list
|
|
383
385
|
};
|
|
384
|
-
const headers = ["命令", "次数", "
|
|
386
|
+
const headers = ["命令", "次数", "最后使用"];
|
|
385
387
|
const result = await this.renderer.renderList(renderData, headers);
|
|
386
388
|
return Buffer.isBuffer(result) ? import_koishi3.Element.image(result, "image/png") : result;
|
|
387
389
|
} catch (error) {
|
|
@@ -391,29 +393,72 @@ var Stat = class {
|
|
|
391
393
|
});
|
|
392
394
|
}
|
|
393
395
|
if (this.config.enableMsgStat) {
|
|
394
|
-
analyse.subcommand(".message", "
|
|
396
|
+
analyse.subcommand(".message", "消息发送统计").option("user", "-u [user:user] 指定用户").option("guild", "-g [guildId:string] 指定群组").option("type", "-t <type:string> 指定类型").usage("查询用户或群组的消息发送统计,默认展示全局统计。").action(async ({ session, options }) => {
|
|
395
397
|
const userId = options.user ? import_koishi3.h.select(options.user, "user")[0]?.attrs.id : void 0;
|
|
396
398
|
let guildId = options.guild;
|
|
397
|
-
if (
|
|
398
|
-
if (!session.guildId) return "私聊中请使用 -g <群组ID> 指定群组。";
|
|
399
|
+
if (!userId && !guildId && !options.type && session.guildId) {
|
|
399
400
|
guildId = session.guildId;
|
|
401
|
+
} else if (!userId && !guildId && !session.guildId) {
|
|
402
|
+
return "请指定查询范围";
|
|
400
403
|
}
|
|
401
404
|
try {
|
|
402
|
-
|
|
405
|
+
if (options.type) {
|
|
406
|
+
const stats = await this.getMessageStatsByType(options.type, guildId, userId);
|
|
407
|
+
if (typeof stats === "string") return stats;
|
|
408
|
+
const title = await this.generateTitle(guildId, void 0, { main: "消息", subtype: options.type });
|
|
409
|
+
const renderData = {
|
|
410
|
+
title,
|
|
411
|
+
time: /* @__PURE__ */ new Date(),
|
|
412
|
+
total: stats.total,
|
|
413
|
+
list: stats.list
|
|
414
|
+
};
|
|
415
|
+
const headers = ["用户", "条数", "最后发言"];
|
|
416
|
+
const result = await this.renderer.renderList(renderData, headers);
|
|
417
|
+
return Buffer.isBuffer(result) ? import_koishi3.Element.image(result, "image/png") : result;
|
|
418
|
+
} else {
|
|
419
|
+
const stats = await this.getMessageStats(guildId, userId);
|
|
420
|
+
if (typeof stats === "string") return stats;
|
|
421
|
+
const title = await this.generateTitle(guildId, userId, { main: "消息" });
|
|
422
|
+
const renderData = {
|
|
423
|
+
title,
|
|
424
|
+
time: /* @__PURE__ */ new Date(),
|
|
425
|
+
total: stats.total,
|
|
426
|
+
list: stats.list
|
|
427
|
+
};
|
|
428
|
+
const headers = ["类型", "条数", "最后发言"];
|
|
429
|
+
const result = await this.renderer.renderList(renderData, headers);
|
|
430
|
+
return Buffer.isBuffer(result) ? import_koishi3.Element.image(result, "image/png") : result;
|
|
431
|
+
}
|
|
432
|
+
} catch (error) {
|
|
433
|
+
this.ctx.logger.error("渲染消息统计图片失败:", error);
|
|
434
|
+
return "渲染消息统计图片失败";
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
analyse.subcommand(".rank", "用户发言排行").option("guild", "-g [guildId:string] 指定群组").option("hours", "-h <hours:number> 查看过去 N 小时的排行", { fallback: 24 }).usage("查询用户或群组的用户发言排行,默认展示全局统计。").action(async ({ session, options }) => {
|
|
438
|
+
let guildId = options.guild;
|
|
439
|
+
if (!guildId && session.guildId) guildId = session.guildId;
|
|
440
|
+
if (!guildId) return "请指定查询范围";
|
|
441
|
+
try {
|
|
442
|
+
const stats = await this.getActiveUserStats(guildId, options.hours);
|
|
403
443
|
if (typeof stats === "string") return stats;
|
|
404
|
-
const
|
|
444
|
+
const listWithPercentage = stats.list.map((row) => {
|
|
445
|
+
const count = row[1];
|
|
446
|
+
const percentage = stats.total > 0 ? `${(count / stats.total * 100).toFixed(2)}%` : "0.00%";
|
|
447
|
+
return [...row, percentage];
|
|
448
|
+
});
|
|
449
|
+
const title = await this.generateTitle(guildId, void 0, { main: "排行", timeRange: options.hours });
|
|
405
450
|
const renderData = {
|
|
406
451
|
title,
|
|
407
452
|
time: /* @__PURE__ */ new Date(),
|
|
408
453
|
total: stats.total,
|
|
409
|
-
list:
|
|
454
|
+
list: listWithPercentage
|
|
410
455
|
};
|
|
411
|
-
const headers = ["
|
|
456
|
+
const headers = ["用户", "总计发言", "占比"];
|
|
412
457
|
const result = await this.renderer.renderList(renderData, headers);
|
|
413
458
|
return Buffer.isBuffer(result) ? import_koishi3.Element.image(result, "image/png") : result;
|
|
414
459
|
} catch (error) {
|
|
415
|
-
this.ctx.logger.error("
|
|
416
|
-
return "
|
|
460
|
+
this.ctx.logger.error("渲染发言排行图片失败:", error);
|
|
461
|
+
return "渲染发言排行图片失败";
|
|
417
462
|
}
|
|
418
463
|
});
|
|
419
464
|
}
|
|
@@ -421,33 +466,42 @@ var Stat = class {
|
|
|
421
466
|
/**
|
|
422
467
|
* @private
|
|
423
468
|
* @async
|
|
424
|
-
* @method
|
|
425
|
-
* @description
|
|
426
|
-
* @param {Session} session - 当前会话,备用。
|
|
469
|
+
* @method generateTitle
|
|
470
|
+
* @description 通用的标题生成器。根据查询参数和类型选项动态生成易于理解的图片标题。
|
|
427
471
|
* @param {string} [guildId] - (可选) 查询的群组 ID。
|
|
428
472
|
* @param {string} [userId] - (可选) 查询的用户 ID。
|
|
429
|
-
* @param {
|
|
473
|
+
* @param {TitleOptions} options - 标题的配置选项。
|
|
430
474
|
* @returns {Promise<string>} 生成的标题字符串。
|
|
431
475
|
*/
|
|
432
|
-
async
|
|
476
|
+
async generateTitle(guildId, userId, options) {
|
|
477
|
+
let scopeText;
|
|
433
478
|
if (userId && guildId) {
|
|
434
479
|
const user = await this.ctx.database.get("analyse_user", { channelId: guildId, userId }, ["userName"]);
|
|
435
480
|
const guild = await this.ctx.database.get("analyse_user", { channelId: guildId }, ["channelName"]);
|
|
436
481
|
const userName = user[0]?.userName || userId;
|
|
437
482
|
const guildName = guild[0]?.channelName || guildId;
|
|
438
|
-
|
|
439
|
-
}
|
|
440
|
-
if (userId) {
|
|
483
|
+
scopeText = `${userName} 在 ${guildName}`;
|
|
484
|
+
} else if (userId) {
|
|
441
485
|
const user = await this.ctx.database.get("analyse_user", { userId }, ["userName"]);
|
|
442
486
|
const userName = user[0]?.userName || userId;
|
|
443
|
-
|
|
444
|
-
}
|
|
445
|
-
if (guildId) {
|
|
487
|
+
scopeText = `${userName}的全局`;
|
|
488
|
+
} else if (guildId) {
|
|
446
489
|
const guild = await this.ctx.database.get("analyse_user", { channelId: guildId }, ["channelName"]);
|
|
447
|
-
|
|
448
|
-
|
|
490
|
+
scopeText = guild[0]?.channelName || guildId;
|
|
491
|
+
} else {
|
|
492
|
+
scopeText = "全局";
|
|
493
|
+
}
|
|
494
|
+
switch (options.main) {
|
|
495
|
+
case "命令":
|
|
496
|
+
return `${scopeText}的命令统计`;
|
|
497
|
+
case "消息":
|
|
498
|
+
if (options.subtype) return `${scopeText}的"${options.subtype}"消息统计`;
|
|
499
|
+
return `${scopeText}的消息统计`;
|
|
500
|
+
case "排行":
|
|
501
|
+
return `${scopeText}的${options.timeRange}小时消息排行`;
|
|
502
|
+
default:
|
|
503
|
+
return scopeText;
|
|
449
504
|
}
|
|
450
|
-
return `全局${type}统计`;
|
|
451
505
|
}
|
|
452
506
|
/**
|
|
453
507
|
* @private
|
|
@@ -463,7 +517,7 @@ var Stat = class {
|
|
|
463
517
|
if (guildId) userQuery.channelId = guildId;
|
|
464
518
|
if (userId) userQuery.userId = userId;
|
|
465
519
|
const users = await this.ctx.database.get("analyse_user", userQuery, ["uid"]);
|
|
466
|
-
if (users.length === 0) return "
|
|
520
|
+
if (users.length === 0) return "暂无目标用户统计数据";
|
|
467
521
|
const uids = users.map((u) => u.uid);
|
|
468
522
|
const aggregatedStats = await this.ctx.database.select("analyse_cmd").where({ uid: { $in: uids } }).groupBy(["command"], {
|
|
469
523
|
count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count"),
|
|
@@ -478,7 +532,7 @@ var Stat = class {
|
|
|
478
532
|
* @private
|
|
479
533
|
* @async
|
|
480
534
|
* @method getMessageStats
|
|
481
|
-
* @description
|
|
535
|
+
* @description 从数据库中获取并聚合所有消息类型的统计数据。
|
|
482
536
|
* @param {string} [guildId] - (可选) 若提供,则将范围限制在此群组。
|
|
483
537
|
* @param {string} [userId] - (可选) 若提供,则将范围限制在此用户。
|
|
484
538
|
* @returns {Promise<{ list: RenderListItem[], total: number } | string>} 返回一个包含列表和总数的对象,或在无数据时返回提示字符串。
|
|
@@ -488,7 +542,7 @@ var Stat = class {
|
|
|
488
542
|
if (guildId) userQuery.channelId = guildId;
|
|
489
543
|
if (userId) userQuery.userId = userId;
|
|
490
544
|
const users = await this.ctx.database.get("analyse_user", userQuery, ["uid"]);
|
|
491
|
-
if (users.length === 0) return "
|
|
545
|
+
if (users.length === 0) return "暂无目标用户统计数据";
|
|
492
546
|
const uids = users.map((u) => u.uid);
|
|
493
547
|
const aggregatedStats = await this.ctx.database.select("analyse_msg").where({ uid: { $in: uids } }).groupBy(["type"], {
|
|
494
548
|
count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count"),
|
|
@@ -499,6 +553,69 @@ var Stat = class {
|
|
|
499
553
|
const list = aggregatedStats.map((item) => [item.type, item.count, item.lastUsed]);
|
|
500
554
|
return { list, total: totalCount };
|
|
501
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* @private
|
|
558
|
+
* @async
|
|
559
|
+
* @method getMessageStatsByType
|
|
560
|
+
* @description 按指定消息类型,从数据库中获取并聚合用户排行数据。
|
|
561
|
+
* @param {string} type - 要查询的消息类型。
|
|
562
|
+
* @param {string} [guildId] - (可选) 若提供,则将范围限制在此群组。
|
|
563
|
+
* @param {string} [userId] - (可选) 若提供,则将范围限制在此用户。
|
|
564
|
+
* @returns {Promise<{ list: RenderListItem[], total: number } | string>}
|
|
565
|
+
*/
|
|
566
|
+
async getMessageStatsByType(type, guildId, userId) {
|
|
567
|
+
const userQuery = {};
|
|
568
|
+
if (guildId) userQuery.channelId = guildId;
|
|
569
|
+
if (userId) userQuery.userId = userId;
|
|
570
|
+
const users = await this.ctx.database.get("analyse_user", userQuery, ["uid", "userName"]);
|
|
571
|
+
if (users.length === 0) return "暂无目标用户统计数据";
|
|
572
|
+
const uids = users.map((u) => u.uid);
|
|
573
|
+
const userNameMap = new Map(users.map((u) => [u.uid, u.userName]));
|
|
574
|
+
const aggregatedStats = await this.ctx.database.select("analyse_msg").where({
|
|
575
|
+
uid: { $in: uids },
|
|
576
|
+
type
|
|
577
|
+
}).groupBy(["uid"], {
|
|
578
|
+
count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count"),
|
|
579
|
+
lastUsed: /* @__PURE__ */ __name((row) => import_koishi3.$.max(row.timestamp), "lastUsed")
|
|
580
|
+
}).orderBy("count", "desc").execute();
|
|
581
|
+
if (aggregatedStats.length === 0) return `暂无统计数据`;
|
|
582
|
+
const totalCount = aggregatedStats.reduce((sum, record) => sum + record.count, 0);
|
|
583
|
+
const list = aggregatedStats.map((item) => [
|
|
584
|
+
userNameMap.get(item.uid) || `UID ${item.uid}`,
|
|
585
|
+
item.count,
|
|
586
|
+
item.lastUsed
|
|
587
|
+
]);
|
|
588
|
+
return { list, total: totalCount };
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* @private
|
|
592
|
+
* @async
|
|
593
|
+
* @method getActiveUserStats
|
|
594
|
+
* @description 从数据库中获取并聚合活跃用户排行数据。
|
|
595
|
+
* @param {string} guildId - 要查询的群组 ID。
|
|
596
|
+
* @param {number} hours - 查询过去的小时数。
|
|
597
|
+
* @returns {Promise<{ list: RenderListItem[], total: number } | string>}
|
|
598
|
+
*/
|
|
599
|
+
async getActiveUserStats(guildId, hours) {
|
|
600
|
+
const since = new Date(Date.now() - hours * 3600 * 1e3);
|
|
601
|
+
const usersInGuild = await this.ctx.database.get("analyse_user", { channelId: guildId }, ["uid", "userId", "userName"]);
|
|
602
|
+
if (usersInGuild.length === 0) return "暂无用户统计数据";
|
|
603
|
+
const uids = usersInGuild.map((u) => u.uid);
|
|
604
|
+
const userNameMap = new Map(usersInGuild.map((u) => [u.uid, u.userName]));
|
|
605
|
+
const aggregatedStats = await this.ctx.database.select("analyse_msg").where({
|
|
606
|
+
uid: { $in: uids },
|
|
607
|
+
hour: { $gte: since }
|
|
608
|
+
}).groupBy(["uid"], {
|
|
609
|
+
count: /* @__PURE__ */ __name((row) => import_koishi3.$.sum(row.count), "count")
|
|
610
|
+
}).orderBy("count", "desc").limit(100).execute();
|
|
611
|
+
if (aggregatedStats.length === 0) return "暂无统计数据";
|
|
612
|
+
const totalCount = aggregatedStats.reduce((sum, record) => sum + record.count, 0);
|
|
613
|
+
const list = aggregatedStats.map((item) => [
|
|
614
|
+
userNameMap.get(item.uid) || `UID ${item.uid}`,
|
|
615
|
+
item.count
|
|
616
|
+
]);
|
|
617
|
+
return { list, total: totalCount };
|
|
618
|
+
}
|
|
502
619
|
};
|
|
503
620
|
|
|
504
621
|
// src/index.ts
|