11import { SlashCommandBuilder , EmbedBuilder , PermissionsBitField } from 'discord.js' ;
2+ import { promisify } from 'node:util' ;
23
34const VERIFIED_ROLE_ID = process . env . VERIFIED_ROLE_ID || 'YOUR_VERIFIED_ROLE_ID_HERE' ;
45
@@ -12,60 +13,42 @@ export async function execute(interaction) {
1213 return interaction . reply ( { content : 'This command can only be used in a server.' , ephemeral : true } ) ;
1314 }
1415
16+ if ( interaction . replied || interaction . deferred ) {
17+ console . warn ( `[myStats] Interaction ${ interaction . id } already acknowledged. Skipping.` ) ;
18+ return ;
19+ }
20+
21+ await interaction . deferReply ( ) ;
22+
1523 const userId = interaction . user . id ;
1624 const guildId = interaction . guild . id ;
1725 const db = interaction . client . db ;
18- const isModerator = interaction . member . permissions . has ( PermissionsBitField . Flags . KickMembers || PermissionsBitField . Flags . BanMembers ) ;
19- const hasVerifiedRole = interaction . member . roles . cache . has ( VERIFIED_ROLE_ID ) ;
20-
21- await interaction . deferReply ( ) ;
2226
23- try {
24- const statsRow = await new Promise ( ( resolve , reject ) => {
25- db . get ( `SELECT messages_sent, voice_time_minutes FROM user_stats WHERE user_id = ? AND guild_id = ?` ,
26- [ userId , guildId ] , ( err , row ) => {
27- if ( err ) reject ( err ) ;
28- else resolve ( row ) ;
29- } ) ;
30- } ) ;
27+ const dbGet = promisify ( db . get ) . bind ( db ) ;
28+ const dbAll = promisify ( db . all ) . bind ( db ) ;
3129
32- const warnRows = await new Promise ( ( resolve , reject ) => {
33- db . all ( `SELECT reason, timestamp FROM warnings WHERE userId = ? AND guildId = ? ORDER BY timestamp DESC LIMIT 3` ,
34- [ userId , guildId ] , ( err , rows ) => { // Fetch last 3 warn reasons
35- if ( err ) reject ( err ) ;
36- else resolve ( rows ) ;
37- } ) ;
38- } ) ;
39- const warnCount = warnRows . length ;
30+ const isModerator = interaction . member . permissions . has (
31+ PermissionsBitField . Flags . KickMembers || PermissionsBitField . Flags . BanMembers
32+ ) ;
4033
41- const reputationRow = await new Promise ( ( resolve , reject ) => {
42- db . get ( `SELECT reputation_points FROM reputation WHERE user_id = ? AND guild_id = ?` ,
43- [ userId , guildId ] , ( err , row ) => {
44- if ( err ) reject ( err ) ;
45- else resolve ( row ) ;
46- } ) ;
47- } ) ;
34+ const hasVerifiedRole = interaction . member . roles . cache . has ( VERIFIED_ROLE_ID ) ;
4835
49- let verifiedUserRow = null ;
50- let birthdayRow = null ;
51- if ( hasVerifiedRole ) {
52- verifiedUserRow = await new Promise ( ( resolve , reject ) => {
53- db . get ( `SELECT real_name, email FROM verified_users WHERE user_id = ? AND guild_id = ?` ,
54- [ userId , guildId ] , ( err , row ) => {
55- if ( err ) reject ( err ) ;
56- else resolve ( row ) ;
57- } ) ;
58- } ) ;
59-
60- birthdayRow = await new Promise ( ( resolve , reject ) => {
61- db . get ( `SELECT month, day, year FROM birthdays WHERE user_id = ? AND guild_id = ?` ,
62- [ userId , guildId ] , ( err , row ) => {
63- if ( err ) reject ( err ) ;
64- else resolve ( row ) ;
65- } ) ;
66- } ) ;
67- }
36+ try {
37+ const statsRow = await dbGet (
38+ `SELECT messages_sent, voice_time_minutes FROM user_stats WHERE user_id = ? AND guild_id = ?` ,
39+ [ userId , guildId ]
40+ ) ;
41+
42+ const warnRows = await dbAll (
43+ `SELECT reason, timestamp FROM warnings WHERE userId = ? AND guildId = ? ORDER BY timestamp DESC LIMIT 3` ,
44+ [ userId , guildId ]
45+ ) ;
46+ const warnCount = warnRows . length ;
6847
48+ const reputationRow = await dbGet (
49+ `SELECT reputation_points FROM reputation WHERE user_id = ? AND guild_id = ?` ,
50+ [ userId , guildId ]
51+ ) ;
6952
7053 const messagesSent = statsRow ? statsRow . messages_sent : 0 ;
7154 const voiceTimeMinutes = statsRow ? statsRow . voice_time_minutes : 0 ;
@@ -85,64 +68,73 @@ export async function execute(interaction) {
8568 statusColor = '#FF0000' ;
8669 }
8770
88- const embed = new EmbedBuilder ( )
71+ const publicEmbed = new EmbedBuilder ( )
8972 . setColor ( statusColor )
90- . setTitle ( `📊 ${ interaction . user . tag } 's Stats` )
91- . setDescription ( 'Your activity and standing in this server:' )
73+ . setTitle ( `📊 ${ interaction . user . tag } 's Server Stats` )
74+ . setDescription ( 'Here are some general statistics about your activity and standing in this server:' )
9275 . setThumbnail ( interaction . user . displayAvatarURL ( { dynamic : true } ) )
9376 . setTimestamp ( )
9477 . addFields (
9578 { name : 'Messages Sent' , value : messagesSent . toLocaleString ( ) , inline : true } ,
9679 { name : 'Voice Time' , value : `${ voiceTimeMinutes } minutes` , inline : true } ,
9780 { name : 'Reputation' , value : reputationPoints . toLocaleString ( ) , inline : true } ,
98- { name : 'Warn Status' , value : `${ warnStatus } (${ warnCount } warns )` , inline : true }
81+ { name : 'Warn Status' , value : `${ warnCount } warns (${ warnStatus } )` , inline : true }
9982 ) ;
10083
10184 if ( warnCount > 0 ) {
10285 const warnReasons = warnRows . map ( ( warn , index ) =>
10386 `${ index + 1 } . ${ warn . reason } (<t:${ Math . floor ( warn . timestamp / 1000 ) } :d>)`
10487 ) . join ( '\n' ) ;
105- embed . addFields (
88+ publicEmbed . addFields (
10689 { name : 'Recent Warn Reasons' , value : warnReasons }
10790 ) ;
10891 }
10992
110- if ( hasVerifiedRole && verifiedUserRow ) {
111- embed . addFields (
112- { name : '\u200b' , value : '**Verified Information:**' , inline : false } , // Spacer
113- { name : 'Full Name' , value : verifiedUserRow . real_name , inline : true } ,
114- { name : 'Email' , value : verifiedUserRow . email , inline : true }
93+ await interaction . editReply ( { embeds : [ publicEmbed ] } ) ;
94+
95+ const privateEmbed = new EmbedBuilder ( )
96+ . setColor ( '#0099FF' )
97+ . setTitle ( `🔒 ${ interaction . user . tag } 's Private Stats` )
98+ . setDescription ( 'This message contains sensitive or personal information.' )
99+ . setTimestamp ( ) ;
100+ console . log ( VERIFIED_ROLE_ID ) ;
101+ console . log ( hasVerifiedRole ) ;
102+ if ( hasVerifiedRole ) {
103+ const verifiedUserRow = await dbGet (
104+ `SELECT real_name, email FROM verified_users WHERE user_id = ? AND guild_id = ?` ,
105+ [ userId , guildId ]
106+ ) ;
107+ const birthdayRow = await dbGet (
108+ `SELECT month, day, year FROM birthdays WHERE user_id = ? AND guild_id = ?` ,
109+ [ userId , guildId ]
115110 ) ;
116111
117- if ( birthdayRow ) {
118- let dob = `${ birthdayRow . month } /${ birthdayRow . day } ` ;
119- if ( birthdayRow . year ) {
120- dob += `/${ birthdayRow . year } ` ;
121- }
122- embed . addFields (
123- { name : 'Date of Birth' , value : dob , inline : true }
112+ if ( verifiedUserRow ) {
113+ privateEmbed . addFields (
114+ { name : '\u200b' , value : '**Verified Information:**' , inline : false } ,
115+ { name : 'Full Name' , value : verifiedUserRow . real_name , inline : true } ,
116+ { name : 'Email' , value : verifiedUserRow . email , inline : true }
124117 ) ;
118+
119+ if ( birthdayRow ) {
120+ let dob = `${ birthdayRow . month } /${ birthdayRow . day } ` ;
121+ if ( birthdayRow . year ) {
122+ dob += `/${ birthdayRow . year } ` ;
123+ }
124+ privateEmbed . addFields (
125+ { name : 'Date of Birth' , value : dob , inline : true }
126+ ) ;
127+ }
125128 }
126129 }
127130
128-
129131 if ( isModerator ) {
130- const modActions = await new Promise ( ( resolve , reject ) => {
131- db . all ( `SELECT action_type, COUNT(*) as count FROM moderation_actions WHERE moderator_id = ? AND guild_id = ? GROUP BY action_type` ,
132- [ userId , guildId ] , ( err , rows ) => {
133- if ( err ) reject ( err ) ;
134- else resolve ( rows ) ;
135- } ) ;
136- } ) ;
137-
138- const modStats = {
139- kicks : 0 ,
140- bans : 0 ,
141- timeouts : 0 ,
142- mutes : 0 ,
143- deafens : 0 ,
144- } ;
132+ const modActions = await dbAll (
133+ `SELECT action_type, COUNT(*) as count FROM moderation_actions WHERE moderator_id = ? AND guild_id = ? GROUP BY action_type` ,
134+ [ userId , guildId ]
135+ ) ;
145136
137+ const modStats = { kicks : 0 , bans : 0 , timeouts : 0 , mutes : 0 , deafens : 0 } ;
146138 for ( const action of modActions ) {
147139 switch ( action . action_type ) {
148140 case 'kick' : modStats . kicks = action . count ; break ;
@@ -153,8 +145,8 @@ export async function execute(interaction) {
153145 }
154146 }
155147
156- embed . addFields (
157- { name : '\u200b' , value : '**Moderation Actions Issued:**' , inline : false } , // Spacer
148+ privateEmbed . addFields (
149+ { name : '\u200b' , value : '**Moderation Actions Issued:**' , inline : false } ,
158150 { name : 'Kicks' , value : modStats . kicks . toLocaleString ( ) , inline : true } ,
159151 { name : 'Bans' , value : modStats . bans . toLocaleString ( ) , inline : true } ,
160152 { name : 'Timeouts' , value : modStats . timeouts . toLocaleString ( ) , inline : true } ,
@@ -163,10 +155,24 @@ export async function execute(interaction) {
163155 ) ;
164156 }
165157
166- await interaction . editReply ( { embeds : [ embed ] } ) ;
158+ if ( privateEmbed . data . fields && privateEmbed . data . fields . length > 0 ) {
159+ try {
160+ await interaction . user . send ( { embeds : [ privateEmbed ] } ) ;
161+ } catch ( dmError ) {
162+ console . error ( `Error sending DM to ${ interaction . user . tag } :` , dmError ) ;
163+ await interaction . followUp ( {
164+ content : 'I could not send your private stats to your DMs. Please check your privacy settings to allow DMs from server members.' ,
165+ ephemeral : true
166+ } ) ;
167+ }
168+ }
167169
168170 } catch ( err ) {
169171 console . error ( 'Error fetching stats:' , err . message ) ;
170- await interaction . editReply ( { embeds : [ new EmbedBuilder ( ) . setColor ( '#FF0000' ) . setDescription ( `❌ An error occurred while fetching your stats: ${ err . message } ` ) ] , ephemeral : true } ) ;
172+ const replyMethod = interaction . deferred || interaction . replied ? 'followUp' : 'editReply' ;
173+ await interaction [ replyMethod ] ( {
174+ embeds : [ new EmbedBuilder ( ) . setColor ( '#FF0000' ) . setDescription ( `❌ An error occurred while fetching your stats: ${ err . message } ` ) ] ,
175+ ephemeral : true
176+ } ) ;
171177 }
172- }
178+ }
0 commit comments