@zumito-team/analytics-module 0.4.0 → 0.6.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 (52) hide show
  1. package/README.md +2 -2
  2. package/{services → dist/services}/AnalyticsCollector.js +1 -1
  3. package/package.json +5 -15
  4. package/commands/AnalyticsConfig.ts +0 -118
  5. package/commands/Stats.ts +0 -66
  6. package/events/discord/GuildMemberAdd.ts +0 -23
  7. package/events/discord/GuildMemberRemove.ts +0 -23
  8. package/events/discord/MessageCreate.ts +0 -16
  9. package/events/discord/VoiceStateUpdate.ts +0 -31
  10. package/events/framework/CommandExecuted.ts +0 -16
  11. package/models/CommandDailyStats.ts +0 -25
  12. package/models/GuildAnalyticsConfig.ts +0 -34
  13. package/models/GuildDailyStats.ts +0 -31
  14. package/models/VoiceChannelDailyStats.ts +0 -22
  15. package/routes/AdminAnalytics.ts +0 -69
  16. package/routes/UserPanelAnalytics.ts +0 -101
  17. package/services/AnalyticsCollector.ts +0 -537
  18. /package/{commands → dist/commands}/AnalyticsConfig.d.ts +0 -0
  19. /package/{commands → dist/commands}/AnalyticsConfig.js +0 -0
  20. /package/{commands → dist/commands}/Stats.d.ts +0 -0
  21. /package/{commands → dist/commands}/Stats.js +0 -0
  22. /package/{config.d.ts → dist/config.d.ts} +0 -0
  23. /package/{config.js → dist/config.js} +0 -0
  24. /package/{events → dist/events}/discord/GuildMemberAdd.d.ts +0 -0
  25. /package/{events → dist/events}/discord/GuildMemberAdd.js +0 -0
  26. /package/{events → dist/events}/discord/GuildMemberRemove.d.ts +0 -0
  27. /package/{events → dist/events}/discord/GuildMemberRemove.js +0 -0
  28. /package/{events → dist/events}/discord/MessageCreate.d.ts +0 -0
  29. /package/{events → dist/events}/discord/MessageCreate.js +0 -0
  30. /package/{events → dist/events}/discord/VoiceStateUpdate.d.ts +0 -0
  31. /package/{events → dist/events}/discord/VoiceStateUpdate.js +0 -0
  32. /package/{events → dist/events}/framework/CommandExecuted.d.ts +0 -0
  33. /package/{events → dist/events}/framework/CommandExecuted.js +0 -0
  34. /package/{index.d.ts → dist/index.d.ts} +0 -0
  35. /package/{index.js → dist/index.js} +0 -0
  36. /package/{models → dist/models}/CommandDailyStats.d.ts +0 -0
  37. /package/{models → dist/models}/CommandDailyStats.js +0 -0
  38. /package/{models → dist/models}/GuildAnalyticsConfig.d.ts +0 -0
  39. /package/{models → dist/models}/GuildAnalyticsConfig.js +0 -0
  40. /package/{models → dist/models}/GuildDailyStats.d.ts +0 -0
  41. /package/{models → dist/models}/GuildDailyStats.js +0 -0
  42. /package/{models → dist/models}/VoiceChannelDailyStats.d.ts +0 -0
  43. /package/{models → dist/models}/VoiceChannelDailyStats.js +0 -0
  44. /package/{routes → dist/routes}/AdminAnalytics.d.ts +0 -0
  45. /package/{routes → dist/routes}/AdminAnalytics.js +0 -0
  46. /package/{routes → dist/routes}/UserPanelAnalytics.d.ts +0 -0
  47. /package/{routes → dist/routes}/UserPanelAnalytics.js +0 -0
  48. /package/{services → dist/services}/AnalyticsCollector.d.ts +0 -0
  49. /package/{translations → dist/translations}/en.json +0 -0
  50. /package/{translations → dist/translations}/es.json +0 -0
  51. /package/{views → dist/views}/admin-analytics.ejs +0 -0
  52. /package/{views → dist/views}/user-analytics.ejs +0 -0
package/README.md CHANGED
@@ -8,11 +8,11 @@
8
8
  npm install @zumito-team/analytics-module
9
9
  ```
10
10
 
11
- Add to your `zumito.config.ts`:
11
+ Add to your `zumito.config.ts` (note the `/dist` suffix):
12
12
 
13
13
  ```ts
14
14
  {
15
- path: path.join(__dirname, "node_modules", "@zumito-team", "analytics-module"),
15
+ path: path.join(__dirname, "node_modules", "@zumito-team", "analytics-module", "dist"),
16
16
  }
17
17
  ```
18
18
 
@@ -15,7 +15,7 @@ export class AnalyticsCollector {
15
15
  this.cleanupTimer = null;
16
16
  }
17
17
  repo(name) {
18
- return this.repo(name);
18
+ return this.db.getRepository(name);
19
19
  }
20
20
  // ── Event recording ──────────────────────────────────────────
21
21
  async ensuredConfig(guildId) {
package/package.json CHANGED
@@ -1,26 +1,16 @@
1
1
  {
2
2
  "name": "@zumito-team/analytics-module",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Server analytics module with configurable tracking and optional panel integration",
5
5
  "type": "module",
6
- "main": "index.js",
7
- "types": "index.d.ts",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
8
  "files": [
9
- "commands",
10
- "events",
11
- "models",
12
- "services",
13
- "routes",
14
- "translations",
15
- "views",
16
- "config.js",
17
- "config.d.ts",
18
- "index.js",
19
- "index.d.ts",
9
+ "dist",
20
10
  "README.md"
21
11
  ],
22
12
  "scripts": {
23
- "build": "tsc -p tsconfig.json"
13
+ "build": "tsc -p tsconfig.json && cp -r translations dist/ && cp -r views dist/"
24
14
  },
25
15
  "dependencies": {
26
16
  "@zumito-team/admin-module": "^1.8.0",
@@ -1,118 +0,0 @@
1
- import { Command, CommandParameters, CommandType, ServiceContainer } from 'zumito-framework';
2
- import { EmbedBuilder, PermissionFlagsBits } from 'zumito-framework/discord';
3
- import { AnalyticsCollector } from '../services/AnalyticsCollector.js';
4
-
5
- export class AnalyticsConfig extends Command {
6
- name = 'analytics-config';
7
- categories = ['analytics', 'admin'];
8
- type = CommandType.any;
9
- dm = false;
10
- adminOnly = true;
11
-
12
- constructor(
13
- private collector: AnalyticsCollector = ServiceContainer.getService(AnalyticsCollector) as AnalyticsCollector,
14
- ) {
15
- super();
16
- }
17
-
18
- async execute({ message, interaction, trans, args }: CommandParameters): Promise<void> {
19
- const target = message || interaction;
20
- if (!target) return;
21
-
22
- const guildId = message?.guildId || interaction?.guildId;
23
- if (!guildId) {
24
- await target.reply({ content: trans('noGuild') } as any);
25
- return;
26
- }
27
-
28
- const config = await this.collector.getConfig(guildId);
29
-
30
- const subcommand = args instanceof Map ? args.get('action') : (typeof args === 'object' && 'action' in args ? args['action'] : undefined);
31
-
32
- if (!subcommand) {
33
- const embed = new EmbedBuilder()
34
- .setTitle(trans('configTitle'))
35
- .setDescription(trans('configDescription'))
36
- .addFields(
37
- { name: trans('enabled'), value: config.enabled ? '✅ Yes' : '❌ No', inline: true },
38
- { name: trans('trackMessages'), value: config.track_messages ? '✅' : '❌', inline: true },
39
- { name: trans('trackVoice'), value: config.track_voice ? '✅' : '❌', inline: true },
40
- { name: trans('trackMembers'), value: config.track_members ? '✅' : '❌', inline: true },
41
- { name: trans('trackCommands'), value: config.track_commands ? '✅' : '❌', inline: true },
42
- { name: trans('trackPerformance'), value: config.track_command_performance ? '✅' : '❌', inline: true },
43
- { name: trans('trackPerChannelVoice'), value: config.track_per_channel_voice ? '✅' : '❌', inline: true },
44
- { name: trans('retentionDays'), value: (config.retention_days || '90').toString(), inline: true },
45
- { name: trans('publicStats'), value: config.public_stats_page ? '✅' : '❌', inline: true },
46
- )
47
- .setColor(0x5865F2);
48
-
49
- if (message) {
50
- await message.reply({ embeds: [embed] });
51
- } else if (interaction) {
52
- if (interaction.replied || interaction.deferred) {
53
- await interaction.editReply({ embeds: [embed] });
54
- } else {
55
- await interaction.reply({ embeds: [embed], ephemeral: true });
56
- }
57
- }
58
- return;
59
- }
60
-
61
- switch (subcommand) {
62
- case 'enable':
63
- await this.collector.updateConfig(guildId, { enabled: true });
64
- break;
65
- case 'disable':
66
- await this.collector.updateConfig(guildId, { enabled: false });
67
- break;
68
- case 'messages':
69
- await this.collector.updateConfig(guildId, { track_messages: !config.track_messages });
70
- break;
71
- case 'voice':
72
- await this.collector.updateConfig(guildId, { track_voice: !config.track_voice });
73
- break;
74
- case 'members':
75
- await this.collector.updateConfig(guildId, { track_members: !config.track_members });
76
- break;
77
- case 'commands':
78
- await this.collector.updateConfig(guildId, { track_commands: !config.track_commands });
79
- break;
80
- case 'performance':
81
- await this.collector.updateConfig(guildId, { track_command_performance: !config.track_command_performance });
82
- break;
83
- case 'retention': {
84
- const days = args instanceof Map ? args.get('days') : (typeof args === 'object' && 'days' in args ? args['days'] : null);
85
- if (days && typeof days === 'number') {
86
- await this.collector.updateConfig(guildId, { retention_days: days });
87
- }
88
- break;
89
- }
90
- default:
91
- break;
92
- }
93
-
94
- const updated = await this.collector.getConfig(guildId);
95
- const embed = new EmbedBuilder()
96
- .setTitle(trans('configUpdated'))
97
- .addFields(
98
- { name: trans('enabled'), value: updated.enabled ? '✅ Yes' : '❌ No', inline: true },
99
- { name: trans('trackMessages'), value: updated.track_messages ? '✅' : '❌', inline: true },
100
- { name: trans('trackVoice'), value: updated.track_voice ? '✅' : '❌', inline: true },
101
- { name: trans('trackMembers'), value: updated.track_members ? '✅' : '❌', inline: true },
102
- { name: trans('trackCommands'), value: updated.track_commands ? '✅' : '❌', inline: true },
103
- { name: trans('trackPerformance'), value: updated.track_command_performance ? '✅' : '❌', inline: true },
104
- { name: trans('retentionDays'), value: (updated.retention_days || '90').toString(), inline: true },
105
- )
106
- .setColor(0x57F287);
107
-
108
- if (message) {
109
- await message.reply({ embeds: [embed] });
110
- } else if (interaction) {
111
- if (interaction.replied || interaction.deferred) {
112
- await interaction.editReply({ embeds: [embed] });
113
- } else {
114
- await interaction.reply({ embeds: [embed], ephemeral: true });
115
- }
116
- }
117
- }
118
- }
package/commands/Stats.ts DELETED
@@ -1,66 +0,0 @@
1
- import { Command, CommandParameters, CommandType, ServiceContainer } from 'zumito-framework';
2
- import { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } from 'zumito-framework/discord';
3
- import { AnalyticsCollector } from '../services/AnalyticsCollector.js';
4
-
5
- export class Stats extends Command {
6
- name = 'stats';
7
- categories = ['analytics', 'utility'];
8
- type = CommandType.any;
9
- dm = false;
10
-
11
- constructor(
12
- private collector: AnalyticsCollector = ServiceContainer.getService(AnalyticsCollector) as AnalyticsCollector,
13
- ) {
14
- super();
15
- }
16
-
17
- async execute({ message, interaction, framework, trans }: CommandParameters): Promise<void> {
18
- const target = message || interaction;
19
- if (!target) return;
20
-
21
- const guildId = message?.guildId || interaction?.guildId;
22
- if (!guildId) {
23
- await target.reply({ content: trans('noGuild'), ephemeral: true } as any);
24
- return;
25
- }
26
-
27
- const daysBack = 7;
28
- const stats = await this.collector.getGuildStats(guildId, daysBack);
29
-
30
- let totalMessages = 0, totalJoins = 0, totalLeaves = 0;
31
- let totalVoice = 0, totalCommands = 0;
32
-
33
- for (const s of stats) {
34
- totalMessages += s.message_count;
35
- totalJoins += s.join_count;
36
- totalLeaves += s.leave_count;
37
- totalVoice += s.voice_minutes;
38
- totalCommands += s.command_count;
39
- }
40
-
41
- const embed = new EmbedBuilder()
42
- .setTitle(trans('title'))
43
- .setDescription(trans('description', { days: daysBack }))
44
- .addFields(
45
- { name: trans('messages'), value: totalMessages.toString(), inline: true },
46
- { name: trans('commands'), value: totalCommands.toString(), inline: true },
47
- { name: trans('joins'), value: totalJoins.toString(), inline: true },
48
- { name: trans('leaves'), value: totalLeaves.toString(), inline: true },
49
- { name: trans('voice'), value: `${Math.round(totalVoice)} min`, inline: true },
50
- )
51
- .setColor(0x5865F2)
52
- .setTimestamp();
53
-
54
- const replyPayload = { embeds: [embed] } as any;
55
-
56
- if (message) {
57
- await message.reply(replyPayload);
58
- } else if (interaction) {
59
- if (interaction.replied || interaction.deferred) {
60
- await interaction.editReply(replyPayload);
61
- } else {
62
- await interaction.reply({ ...replyPayload, ephemeral: true });
63
- }
64
- }
65
- }
66
- }
@@ -1,23 +0,0 @@
1
- import { FrameworkEvent } from 'zumito-framework';
2
- import { ServiceContainer } from 'zumito-framework';
3
- import { AnalyticsCollector } from '../../services/AnalyticsCollector.js';
4
-
5
- export class GuildMemberAdd extends FrameworkEvent {
6
- once = false;
7
- source = 'discord';
8
-
9
- private collector = ServiceContainer.getService(AnalyticsCollector) as AnalyticsCollector;
10
-
11
- async execute({ guildmember }: any): Promise<void> {
12
- if (!guildmember?.guild?.id) return;
13
- const guildId = guildmember.guild.id;
14
- await this.collector.recordMemberJoin(guildId);
15
-
16
- try {
17
- const memberCount = guildmember.guild.memberCount;
18
- if (memberCount) {
19
- await this.collector.recordMemberCount(guildId, memberCount);
20
- }
21
- } catch (_) { /* ignore */ }
22
- }
23
- }
@@ -1,23 +0,0 @@
1
- import { FrameworkEvent } from 'zumito-framework';
2
- import { ServiceContainer } from 'zumito-framework';
3
- import { AnalyticsCollector } from '../../services/AnalyticsCollector.js';
4
-
5
- export class GuildMemberRemove extends FrameworkEvent {
6
- once = false;
7
- source = 'discord';
8
-
9
- private collector = ServiceContainer.getService(AnalyticsCollector) as AnalyticsCollector;
10
-
11
- async execute({ guildmember }: any): Promise<void> {
12
- if (!guildmember?.guild?.id) return;
13
- const guildId = guildmember.guild.id;
14
- await this.collector.recordMemberLeave(guildId);
15
-
16
- try {
17
- const memberCount = guildmember.guild.memberCount;
18
- if (memberCount) {
19
- await this.collector.recordMemberCount(guildId, memberCount);
20
- }
21
- } catch (_) { /* ignore */ }
22
- }
23
- }
@@ -1,16 +0,0 @@
1
- import { FrameworkEvent } from 'zumito-framework';
2
- import { ServiceContainer } from 'zumito-framework';
3
- import { AnalyticsCollector } from '../../services/AnalyticsCollector.js';
4
-
5
- export class MessageCreate extends FrameworkEvent {
6
- once = false;
7
- source = 'discord';
8
-
9
- private collector = ServiceContainer.getService(AnalyticsCollector) as AnalyticsCollector;
10
-
11
- async execute({ message }: any): Promise<void> {
12
- if (!message.guildId) return;
13
- if (message.author?.bot) return;
14
- await this.collector.recordMessage(message.guildId);
15
- }
16
- }
@@ -1,31 +0,0 @@
1
- import { FrameworkEvent } from 'zumito-framework';
2
- import { ServiceContainer } from 'zumito-framework';
3
- import { AnalyticsCollector } from '../../services/AnalyticsCollector.js';
4
-
5
- export class VoiceStateUpdate extends FrameworkEvent {
6
- once = false;
7
- source = 'discord';
8
-
9
- private collector = ServiceContainer.getService(AnalyticsCollector) as AnalyticsCollector;
10
-
11
- async execute({ oldState, newState }: any): Promise<void> {
12
- const guildId = newState?.guild?.id || oldState?.guild?.id;
13
- if (!guildId) return;
14
-
15
- const userId = newState?.id || oldState?.id;
16
- if (!userId) return;
17
-
18
- const oldChannelId = oldState?.channelId || null;
19
- const newChannelId = newState?.channelId || null;
20
-
21
- if (oldChannelId === newChannelId) return;
22
-
23
- if (oldChannelId) {
24
- await this.collector.recordVoiceLeave(guildId, oldChannelId, userId);
25
- }
26
-
27
- if (newChannelId) {
28
- await this.collector.recordVoiceJoin(guildId, newChannelId, userId);
29
- }
30
- }
31
- }
@@ -1,16 +0,0 @@
1
- import { FrameworkEvent } from 'zumito-framework';
2
- import { ServiceContainer } from 'zumito-framework';
3
- import { AnalyticsCollector } from '../../services/AnalyticsCollector.js';
4
-
5
- export class CommandExecuted extends FrameworkEvent {
6
- once = false;
7
- source = 'framework';
8
-
9
- private collector = ServiceContainer.getService(AnalyticsCollector) as AnalyticsCollector;
10
-
11
- async execute(args: any): Promise<void> {
12
- const payload = args.object || args;
13
- if (!payload.guildId) return;
14
- await this.collector.recordCommand(payload);
15
- }
16
- }
@@ -1,25 +0,0 @@
1
- import { Collection, Field } from 'zumito-framework';
2
-
3
- @Collection({ name: 'analytics_command_daily_stats' })
4
- export class CommandDailyStats {
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
- command_name!: string;
13
-
14
- @Field({ type: 'string' })
15
- date!: string;
16
-
17
- @Field({ type: 'number', default: 0 })
18
- usage_count!: number;
19
-
20
- @Field({ type: 'number', default: 0 })
21
- total_execution_time_ms!: number;
22
-
23
- @Field({ type: 'number', default: 0 })
24
- error_count!: number;
25
- }
@@ -1,34 +0,0 @@
1
- import { Collection, Field } from 'zumito-framework';
2
-
3
- @Collection({ name: 'analytics_guild_config' })
4
- export class GuildAnalyticsConfig {
5
- @Field({ type: 'string', primary: true, unique: true })
6
- guild_id!: string;
7
-
8
- @Field({ type: 'boolean', default: true })
9
- enabled!: boolean;
10
-
11
- @Field({ type: 'boolean', default: true })
12
- track_messages!: boolean;
13
-
14
- @Field({ type: 'boolean', default: true })
15
- track_voice!: boolean;
16
-
17
- @Field({ type: 'boolean', default: true })
18
- track_members!: boolean;
19
-
20
- @Field({ type: 'boolean', default: true })
21
- track_commands!: boolean;
22
-
23
- @Field({ type: 'boolean', default: false })
24
- track_command_performance!: boolean;
25
-
26
- @Field({ type: 'boolean', default: false })
27
- track_per_channel_voice!: boolean;
28
-
29
- @Field({ type: 'number' })
30
- retention_days!: number;
31
-
32
- @Field({ type: 'boolean', default: false })
33
- public_stats_page!: boolean;
34
- }
@@ -1,31 +0,0 @@
1
- import { Collection, Field } from 'zumito-framework';
2
-
3
- @Collection({ name: 'analytics_guild_daily_stats' })
4
- export class GuildDailyStats {
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
- date!: string;
13
-
14
- @Field({ type: 'number', default: 0 })
15
- message_count!: number;
16
-
17
- @Field({ type: 'number', default: 0 })
18
- join_count!: number;
19
-
20
- @Field({ type: 'number', default: 0 })
21
- leave_count!: number;
22
-
23
- @Field({ type: 'number', default: 0 })
24
- voice_minutes!: number;
25
-
26
- @Field({ type: 'number', default: 0 })
27
- command_count!: number;
28
-
29
- @Field({ type: 'number', default: 0 })
30
- member_count!: number;
31
- }
@@ -1,22 +0,0 @@
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
- }
@@ -1,69 +0,0 @@
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
- }
@@ -1,101 +0,0 @@
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
- }