@zumito-team/analytics-module 0.1.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.
Files changed (55) hide show
  1. package/README.md +237 -0
  2. package/commands/AnalyticsConfig.d.ts +12 -0
  3. package/commands/AnalyticsConfig.js +93 -0
  4. package/commands/AnalyticsConfig.ts +118 -0
  5. package/commands/Stats.d.ts +11 -0
  6. package/commands/Stats.js +52 -0
  7. package/commands/Stats.ts +66 -0
  8. package/config.d.ts +11 -0
  9. package/config.js +13 -0
  10. package/config.ts +14 -0
  11. package/events/discord/GuildMemberAdd.d.ts +7 -0
  12. package/events/discord/GuildMemberAdd.js +24 -0
  13. package/events/discord/GuildMemberAdd.ts +23 -0
  14. package/events/discord/GuildMemberRemove.d.ts +7 -0
  15. package/events/discord/GuildMemberRemove.js +24 -0
  16. package/events/discord/GuildMemberRemove.ts +23 -0
  17. package/events/discord/MessageCreate.d.ts +7 -0
  18. package/events/discord/MessageCreate.js +18 -0
  19. package/events/discord/MessageCreate.ts +16 -0
  20. package/events/discord/VoiceStateUpdate.d.ts +7 -0
  21. package/events/discord/VoiceStateUpdate.js +29 -0
  22. package/events/discord/VoiceStateUpdate.ts +31 -0
  23. package/events/framework/CommandExecuted.d.ts +7 -0
  24. package/events/framework/CommandExecuted.js +17 -0
  25. package/events/framework/CommandExecuted.ts +16 -0
  26. package/index.d.ts +15 -0
  27. package/index.js +61 -0
  28. package/index.ts +69 -0
  29. package/models/CommandDailyStats.d.ts +9 -0
  30. package/models/CommandDailyStats.js +34 -0
  31. package/models/CommandDailyStats.ts +25 -0
  32. package/models/GuildAnalyticsConfig.d.ts +12 -0
  33. package/models/GuildAnalyticsConfig.js +43 -0
  34. package/models/GuildAnalyticsConfig.ts +34 -0
  35. package/models/GuildDailyStats.d.ts +11 -0
  36. package/models/GuildDailyStats.js +40 -0
  37. package/models/GuildDailyStats.ts +31 -0
  38. package/models/VoiceChannelDailyStats.d.ts +8 -0
  39. package/models/VoiceChannelDailyStats.js +31 -0
  40. package/models/VoiceChannelDailyStats.ts +22 -0
  41. package/package.json +21 -0
  42. package/routes/AdminAnalytics.d.ts +11 -0
  43. package/routes/AdminAnalytics.js +55 -0
  44. package/routes/AdminAnalytics.ts +69 -0
  45. package/routes/UserPanelAnalytics.d.ts +11 -0
  46. package/routes/UserPanelAnalytics.js +81 -0
  47. package/routes/UserPanelAnalytics.ts +101 -0
  48. package/services/AnalyticsCollector.d.ts +62 -0
  49. package/services/AnalyticsCollector.js +470 -0
  50. package/services/AnalyticsCollector.ts +537 -0
  51. package/translations/en.json +63 -0
  52. package/translations/es.json +63 -0
  53. package/tsconfig.json +18 -0
  54. package/views/admin-analytics.ejs +170 -0
  55. package/views/user-analytics.ejs +209 -0
@@ -0,0 +1,31 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { Collection, Field } from 'zumito-framework';
8
+ let VoiceChannelDailyStats = class VoiceChannelDailyStats {
9
+ };
10
+ __decorate([
11
+ Field({ type: 'string', primary: true, unique: true })
12
+ ], VoiceChannelDailyStats.prototype, "id", void 0);
13
+ __decorate([
14
+ Field({ type: 'string' })
15
+ ], VoiceChannelDailyStats.prototype, "guild_id", void 0);
16
+ __decorate([
17
+ Field({ type: 'string' })
18
+ ], VoiceChannelDailyStats.prototype, "channel_id", void 0);
19
+ __decorate([
20
+ Field({ type: 'string' })
21
+ ], VoiceChannelDailyStats.prototype, "date", void 0);
22
+ __decorate([
23
+ Field({ type: 'number', default: 0 })
24
+ ], VoiceChannelDailyStats.prototype, "total_minutes", void 0);
25
+ __decorate([
26
+ Field({ type: 'number', default: 0 })
27
+ ], VoiceChannelDailyStats.prototype, "unique_users", void 0);
28
+ VoiceChannelDailyStats = __decorate([
29
+ Collection({ name: 'analytics_voice_channel_daily_stats' })
30
+ ], VoiceChannelDailyStats);
31
+ export { VoiceChannelDailyStats };
@@ -0,0 +1,22 @@
1
+ import { Collection, Field } from 'zumito-framework';
2
+
3
+ @Collection({ name: 'analytics_voice_channel_daily_stats' })
4
+ export class VoiceChannelDailyStats {
5
+ @Field({ type: 'string', primary: true, unique: true })
6
+ id!: string;
7
+
8
+ @Field({ type: 'string' })
9
+ guild_id!: string;
10
+
11
+ @Field({ type: 'string' })
12
+ channel_id!: string;
13
+
14
+ @Field({ type: 'string' })
15
+ date!: string;
16
+
17
+ @Field({ type: 'number', default: 0 })
18
+ total_minutes!: number;
19
+
20
+ @Field({ type: 'number', default: 0 })
21
+ unique_users!: number;
22
+ }
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@zumito-team/analytics-module",
3
+ "version": "0.1.0",
4
+ "main": "index.js",
5
+ "types": "index.d.ts",
6
+ "description": "Server analytics module with configurable tracking and optional panel integration",
7
+ "type": "module",
8
+ "scripts": {
9
+ "build": "tsc -p tsconfig.json"
10
+ },
11
+ "dependencies": {
12
+ "@zumito-team/admin-module": "^1.8.0",
13
+ "@zumito-team/user-panel-module": "^0.10.0",
14
+ "ejs": "^3.1.10",
15
+ "zumito-framework": "^1.22.2"
16
+ },
17
+ "devDependencies": {
18
+ "@types/ejs": "^3.1.5",
19
+ "typescript": "^5.9.3"
20
+ }
21
+ }
@@ -0,0 +1,11 @@
1
+ import { Route, RouteMethod } from 'zumito-framework';
2
+ import { Client } from 'zumito-framework/discord';
3
+ import { AnalyticsCollector } from '../services/AnalyticsCollector.js';
4
+ export declare class AdminAnalytics extends Route {
5
+ private collector;
6
+ private client;
7
+ method: RouteMethod;
8
+ path: string;
9
+ constructor(collector?: AnalyticsCollector, client?: Client);
10
+ execute(req: any, res: any): Promise<void>;
11
+ }
@@ -0,0 +1,55 @@
1
+ import { Route, RouteMethod, ServiceContainer } from 'zumito-framework';
2
+ import path, { dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import ejs from 'ejs';
5
+ import { Client } from 'zumito-framework/discord';
6
+ import { AnalyticsCollector } from '../services/AnalyticsCollector.js';
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ export class AdminAnalytics extends Route {
9
+ constructor(collector = ServiceContainer.getService(AnalyticsCollector), client = ServiceContainer.getService(Client)) {
10
+ super();
11
+ this.collector = collector;
12
+ this.client = client;
13
+ this.method = RouteMethod.get;
14
+ this.path = '/admin/analytics';
15
+ }
16
+ async execute(req, res) {
17
+ const { AdminAuthService } = await import('@zumito-team/admin-module/services/AdminAuthService.js');
18
+ const auth = await ServiceContainer.getService(AdminAuthService).isLoginValid(req);
19
+ if (!auth?.isValid)
20
+ return res.redirect('/admin/login');
21
+ const daysBack = parseInt(req.query.days) || 7;
22
+ const summary = await this.collector.getGlobalStatsSummary(daysBack);
23
+ const guildGrowth = await this.collector.getGuildGrowth(daysBack);
24
+ const messagesPerDay = await this.collector.getMessagesPerDay(daysBack);
25
+ const commandsPerDay = await this.collector.getCommandsPerDay(null, daysBack);
26
+ const topCommands = await this.collector.getTopCommands(null, daysBack);
27
+ const slowestCommands = await this.collector.getSlowestCommands(null, daysBack);
28
+ const chartData = {
29
+ guildGrowth,
30
+ messagesPerDay,
31
+ commandsPerDay,
32
+ topCommands,
33
+ slowestCommands,
34
+ };
35
+ const { TranslationManager } = await import('zumito-framework');
36
+ const tm = ServiceContainer.getService(TranslationManager);
37
+ const t = (key, params) => tm.get(key, 'en', params);
38
+ const content = await ejs.renderFile(path.resolve(__dirname, '../views/admin-analytics.ejs'), {
39
+ summary,
40
+ chartData,
41
+ slowestCommands: slowestCommands.length > 0 ? slowestCommands : undefined,
42
+ t,
43
+ daysBack,
44
+ });
45
+ const { AdminViewService } = await import('@zumito-team/admin-module/services/AdminViewService.js');
46
+ const view = ServiceContainer.getService(AdminViewService);
47
+ const html = await view.render({
48
+ title: 'Analiticas',
49
+ content,
50
+ reqPath: this.path,
51
+ user: { name: 'Admin' },
52
+ });
53
+ res.send(html);
54
+ }
55
+ }
@@ -0,0 +1,69 @@
1
+ import { Route, RouteMethod, ServiceContainer } from 'zumito-framework';
2
+ import path, { dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import ejs from 'ejs';
5
+ import { Client } from 'zumito-framework/discord';
6
+ import { AnalyticsCollector } from '../services/AnalyticsCollector.js';
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+
10
+ export class AdminAnalytics extends Route {
11
+ method = RouteMethod.get;
12
+ path = '/admin/analytics';
13
+
14
+ constructor(
15
+ private collector: AnalyticsCollector = ServiceContainer.getService(AnalyticsCollector) as AnalyticsCollector,
16
+ private client: Client = ServiceContainer.getService(Client),
17
+ ) {
18
+ super();
19
+ }
20
+
21
+ async execute(req: any, res: any): Promise<void> {
22
+ const { AdminAuthService } = await import('@zumito-team/admin-module/services/AdminAuthService.js');
23
+ const auth = await ServiceContainer.getService(AdminAuthService).isLoginValid(req);
24
+ if (!auth?.isValid) return res.redirect('/admin/login');
25
+
26
+ const daysBack = parseInt(req.query.days as string) || 7;
27
+
28
+ const summary = await this.collector.getGlobalStatsSummary(daysBack);
29
+ const guildGrowth = await this.collector.getGuildGrowth(daysBack);
30
+ const messagesPerDay = await this.collector.getMessagesPerDay(daysBack);
31
+ const commandsPerDay = await this.collector.getCommandsPerDay(null, daysBack);
32
+ const topCommands = await this.collector.getTopCommands(null, daysBack);
33
+ const slowestCommands = await this.collector.getSlowestCommands(null, daysBack);
34
+
35
+ const chartData = {
36
+ guildGrowth,
37
+ messagesPerDay,
38
+ commandsPerDay,
39
+ topCommands,
40
+ slowestCommands,
41
+ };
42
+
43
+ const { TranslationManager } = await import('zumito-framework');
44
+ const tm = ServiceContainer.getService(TranslationManager) as any;
45
+ const t = (key: string, params?: any) => tm.get(key, 'en', params);
46
+
47
+ const content = await ejs.renderFile(
48
+ path.resolve(__dirname, '../views/admin-analytics.ejs'),
49
+ {
50
+ summary,
51
+ chartData,
52
+ slowestCommands: slowestCommands.length > 0 ? slowestCommands : undefined,
53
+ t,
54
+ daysBack,
55
+ },
56
+ );
57
+
58
+ const { AdminViewService } = await import('@zumito-team/admin-module/services/AdminViewService.js');
59
+ const view = ServiceContainer.getService(AdminViewService);
60
+ const html = await view.render({
61
+ title: 'Analiticas',
62
+ content,
63
+ reqPath: this.path,
64
+ user: { name: 'Admin' },
65
+ });
66
+
67
+ res.send(html);
68
+ }
69
+ }
@@ -0,0 +1,11 @@
1
+ import { Route, RouteMethod } from 'zumito-framework';
2
+ import { Client } from 'zumito-framework/discord';
3
+ import { AnalyticsCollector } from '../services/AnalyticsCollector.js';
4
+ export declare class UserPanelAnalytics extends Route {
5
+ private collector;
6
+ private client;
7
+ method: RouteMethod;
8
+ path: string;
9
+ constructor(collector?: AnalyticsCollector, client?: Client);
10
+ execute(req: any, res: any): Promise<void>;
11
+ }
@@ -0,0 +1,81 @@
1
+ import { Route, RouteMethod, ServiceContainer } from 'zumito-framework';
2
+ import path, { dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import ejs from 'ejs';
5
+ import { Client, PermissionFlagsBits } from 'zumito-framework/discord';
6
+ import { AnalyticsCollector } from '../services/AnalyticsCollector.js';
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ export class UserPanelAnalytics extends Route {
9
+ constructor(collector = ServiceContainer.getService(AnalyticsCollector), client = ServiceContainer.getService(Client)) {
10
+ super();
11
+ this.collector = collector;
12
+ this.client = client;
13
+ this.method = RouteMethod.get;
14
+ this.path = '/panel/:guildId/analytics';
15
+ }
16
+ async execute(req, res) {
17
+ const { UserPanelAuthService } = await import('@zumito-team/user-panel-module/services/UserPanelAuthService');
18
+ const { UserPanelViewService } = await import('@zumito-team/user-panel-module/services/UserPanelViewService');
19
+ const { UserPanelLanguageManager } = await import('@zumito-team/user-panel-module/services/UserPanelLanguageManager');
20
+ const authService = ServiceContainer.getService(UserPanelAuthService);
21
+ const authData = await authService.isLoginValid(req);
22
+ if (!authData.isValid)
23
+ return res.redirect('/panel/login');
24
+ const userId = authData.data.discordUserData.id;
25
+ const guildId = req.params.guildId;
26
+ const guild = this.client.guilds.cache.get(guildId);
27
+ if (!guild)
28
+ return res.status(404).send('Server not found');
29
+ let member = guild.members.cache.get(userId);
30
+ if (!member)
31
+ member = await guild.members.fetch(userId).catch(() => null);
32
+ if (!member || !(member.permissions.has(PermissionFlagsBits.Administrator) ||
33
+ member.permissions.has(PermissionFlagsBits.ManageGuild) ||
34
+ guild.ownerId === userId)) {
35
+ return res.status(403).send('No tienes permisos en este servidor');
36
+ }
37
+ const daysBack = parseInt(req.query.days) || 7;
38
+ const langMgr = ServiceContainer.getService(UserPanelLanguageManager);
39
+ const { t } = langMgr.getLanguageVariables(req, res);
40
+ const stats = await this.collector.getGuildStats(guildId, daysBack);
41
+ let guildSummary = {
42
+ totalMessages: 0, totalJoins: 0, totalLeaves: 0,
43
+ totalVoiceMinutes: 0, totalCommands: 0,
44
+ };
45
+ const joinsPerDay = [];
46
+ const leavesPerDay = [];
47
+ const messagesPerDay = [];
48
+ const voicePerDay = [];
49
+ for (const s of stats) {
50
+ guildSummary.totalMessages += s.message_count;
51
+ guildSummary.totalJoins += s.join_count;
52
+ guildSummary.totalLeaves += s.leave_count;
53
+ guildSummary.totalVoiceMinutes += s.voice_minutes;
54
+ guildSummary.totalCommands += s.command_count;
55
+ messagesPerDay.push({ date: s.date, count: s.message_count });
56
+ joinsPerDay.push({ date: s.date, count: s.join_count });
57
+ leavesPerDay.push({ date: s.date, count: s.leave_count });
58
+ voicePerDay.push({ date: s.date, count: s.voice_minutes });
59
+ }
60
+ const commandsPerDay = await this.collector.getCommandsPerDay(guildId, daysBack);
61
+ const topCommands = await this.collector.getTopCommands(guildId, daysBack);
62
+ const slowestCommands = await this.collector.getSlowestCommands(guildId, daysBack);
63
+ const channelVoice = await this.collector.getVoiceChannelStats(guildId, daysBack);
64
+ const chartData = {
65
+ messagesPerDay, joinsPerDay, leavesPerDay, voicePerDay,
66
+ commandsPerDay, topCommands, slowestCommands, channelVoice,
67
+ };
68
+ const config = await this.collector.getConfig(guildId);
69
+ const content = await ejs.renderFile(path.resolve(__dirname, '../views/user-analytics.ejs'), {
70
+ guildSummary,
71
+ chartData,
72
+ slowestCommands: (config.track_command_performance && slowestCommands.length > 0) ? slowestCommands : undefined,
73
+ channelVoice: (config.track_per_channel_voice && channelVoice.length > 0) ? channelVoice : undefined,
74
+ t,
75
+ daysBack,
76
+ });
77
+ const view = ServiceContainer.getService(UserPanelViewService);
78
+ const html = await view.render({ content, reqPath: req.path, req, res });
79
+ res.send(html);
80
+ }
81
+ }
@@ -0,0 +1,101 @@
1
+ import { Route, RouteMethod, ServiceContainer } from 'zumito-framework';
2
+ import path, { dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import ejs from 'ejs';
5
+ import { Client, PermissionFlagsBits } from 'zumito-framework/discord';
6
+ import { AnalyticsCollector } from '../services/AnalyticsCollector.js';
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+
10
+ export class UserPanelAnalytics extends Route {
11
+ method = RouteMethod.get;
12
+ path = '/panel/:guildId/analytics';
13
+
14
+ constructor(
15
+ private collector: AnalyticsCollector = ServiceContainer.getService(AnalyticsCollector) as AnalyticsCollector,
16
+ private client: Client = ServiceContainer.getService(Client) as Client,
17
+ ) {
18
+ super();
19
+ }
20
+
21
+ async execute(req: any, res: any): Promise<void> {
22
+ const { UserPanelAuthService } = await import('@zumito-team/user-panel-module/services/UserPanelAuthService');
23
+ const { UserPanelViewService } = await import('@zumito-team/user-panel-module/services/UserPanelViewService');
24
+ const { UserPanelLanguageManager } = await import('@zumito-team/user-panel-module/services/UserPanelLanguageManager');
25
+
26
+ const authService = ServiceContainer.getService(UserPanelAuthService);
27
+ const authData = await authService.isLoginValid(req);
28
+ if (!authData.isValid) return res.redirect('/panel/login');
29
+
30
+ const userId = authData.data.discordUserData.id;
31
+ const guildId = req.params.guildId as string;
32
+ const guild = this.client.guilds.cache.get(guildId);
33
+ if (!guild) return res.status(404).send('Server not found');
34
+
35
+ let member = guild.members.cache.get(userId);
36
+ if (!member) member = await guild.members.fetch(userId).catch(() => null);
37
+ if (!member || !(
38
+ member.permissions.has(PermissionFlagsBits.Administrator) ||
39
+ member.permissions.has(PermissionFlagsBits.ManageGuild) ||
40
+ guild.ownerId === userId
41
+ )) {
42
+ return res.status(403).send('No tienes permisos en este servidor');
43
+ }
44
+
45
+ const daysBack = parseInt(req.query.days as string) || 7;
46
+ const langMgr = ServiceContainer.getService(UserPanelLanguageManager);
47
+ const { t } = langMgr.getLanguageVariables(req, res);
48
+
49
+ const stats = await this.collector.getGuildStats(guildId, daysBack);
50
+
51
+ let guildSummary = {
52
+ totalMessages: 0, totalJoins: 0, totalLeaves: 0,
53
+ totalVoiceMinutes: 0, totalCommands: 0,
54
+ };
55
+ const joinsPerDay: { date: string; count: number }[] = [];
56
+ const leavesPerDay: { date: string; count: number }[] = [];
57
+ const messagesPerDay: { date: string; count: number }[] = [];
58
+ const voicePerDay: { date: string; count: number }[] = [];
59
+
60
+ for (const s of stats) {
61
+ guildSummary.totalMessages += s.message_count;
62
+ guildSummary.totalJoins += s.join_count;
63
+ guildSummary.totalLeaves += s.leave_count;
64
+ guildSummary.totalVoiceMinutes += s.voice_minutes;
65
+ guildSummary.totalCommands += s.command_count;
66
+
67
+ messagesPerDay.push({ date: s.date, count: s.message_count });
68
+ joinsPerDay.push({ date: s.date, count: s.join_count });
69
+ leavesPerDay.push({ date: s.date, count: s.leave_count });
70
+ voicePerDay.push({ date: s.date, count: s.voice_minutes });
71
+ }
72
+
73
+ const commandsPerDay = await this.collector.getCommandsPerDay(guildId, daysBack);
74
+ const topCommands = await this.collector.getTopCommands(guildId, daysBack);
75
+ const slowestCommands = await this.collector.getSlowestCommands(guildId, daysBack);
76
+ const channelVoice = await this.collector.getVoiceChannelStats(guildId, daysBack);
77
+
78
+ const chartData = {
79
+ messagesPerDay, joinsPerDay, leavesPerDay, voicePerDay,
80
+ commandsPerDay, topCommands, slowestCommands, channelVoice,
81
+ };
82
+
83
+ const config = await this.collector.getConfig(guildId);
84
+
85
+ const content = await ejs.renderFile(
86
+ path.resolve(__dirname, '../views/user-analytics.ejs'),
87
+ {
88
+ guildSummary,
89
+ chartData,
90
+ slowestCommands: (config.track_command_performance && slowestCommands.length > 0) ? slowestCommands : undefined,
91
+ channelVoice: (config.track_per_channel_voice && channelVoice.length > 0) ? channelVoice : undefined,
92
+ t,
93
+ daysBack,
94
+ },
95
+ );
96
+
97
+ const view = ServiceContainer.getService(UserPanelViewService);
98
+ const html = await view.render({ content, reqPath: req.path, req, res });
99
+ res.send(html);
100
+ }
101
+ }
@@ -0,0 +1,62 @@
1
+ import { GuildDailyStats } from '../models/GuildDailyStats.js';
2
+ import { CommandDailyStats } from '../models/CommandDailyStats.js';
3
+ import { VoiceChannelDailyStats } from '../models/VoiceChannelDailyStats.js';
4
+ import { GuildAnalyticsConfig } from '../models/GuildAnalyticsConfig.js';
5
+ export interface CommandExecutedPayload {
6
+ guildId: string;
7
+ commandName: string;
8
+ type: 'prefix' | 'slash';
9
+ executionTimeMs: number;
10
+ success: boolean;
11
+ }
12
+ export declare class AnalyticsCollector {
13
+ private db;
14
+ private voiceSessions;
15
+ private dailyVoiceUsers;
16
+ private cleanupTimer;
17
+ constructor(db?: any);
18
+ private repo;
19
+ private ensuredConfig;
20
+ recordMessage(guildId: string): Promise<void>;
21
+ recordMemberJoin(guildId: string): Promise<void>;
22
+ recordMemberLeave(guildId: string): Promise<void>;
23
+ recordVoiceJoin(guildId: string, channelId: string, userId: string): Promise<void>;
24
+ recordVoiceLeave(guildId: string, channelId: string, userId: string): Promise<void>;
25
+ recordVoiceMinutes(guildId: string, channelId: string, minutes: number, userIds: Set<string>): Promise<void>;
26
+ recordCommand(payload: CommandExecutedPayload): Promise<void>;
27
+ recordMemberCount(guildId: string, memberCount: number): Promise<void>;
28
+ private upsertGuildStats;
29
+ getGuildStats(guildId: string, daysBack: number): Promise<GuildDailyStats[]>;
30
+ getGlobalStatsSummary(daysBack: number): Promise<{
31
+ totalGuilds: number;
32
+ totalMessages: number;
33
+ totalCommands: number;
34
+ totalVoiceMinutes: number;
35
+ totalJoins: number;
36
+ totalLeaves: number;
37
+ }>;
38
+ getGuildGrowth(daysBack: number): Promise<{
39
+ date: string;
40
+ guildCount: number;
41
+ }[]>;
42
+ getMessagesPerDay(daysBack: number): Promise<{
43
+ date: string;
44
+ count: number;
45
+ }[]>;
46
+ getCommandsPerDay(guildId: string | null, daysBack: number): Promise<{
47
+ date: string;
48
+ count: number;
49
+ }[]>;
50
+ getTopCommands(guildId: string | null, daysBack: number, limit?: number): Promise<CommandDailyStats[]>;
51
+ getSlowestCommands(guildId: string | null, daysBack: number, limit?: number): Promise<(CommandDailyStats & {
52
+ avgExecutionTimeMs: number;
53
+ })[]>;
54
+ getVoiceChannelStats(guildId: string, daysBack: number): Promise<VoiceChannelDailyStats[]>;
55
+ getConfig(guildId: string): Promise<GuildAnalyticsConfig>;
56
+ updateConfig(guildId: string, partial: Partial<GuildAnalyticsConfig>): Promise<void>;
57
+ runCleanup(): Promise<void>;
58
+ startCleanupScheduler(): void;
59
+ stopCleanupScheduler(): void;
60
+ clearVoiceSessions(): void;
61
+ private dateDaysAgo;
62
+ }