1+ import {
2+ ChatInputCommandInteraction ,
3+ MessageFlags ,
4+ PermissionFlagsBits ,
5+ SlashCommandBuilder ,
6+ SlashCommandOptionsOnlyBuilder ,
7+ SlashCommandSubcommandsOnlyBuilder ,
8+ } from 'discord.js' ;
9+ import { logger } from '../../libs/logger.js' ;
10+
11+ type DiscordSlashCommandBuilder =
12+ | SlashCommandBuilder
13+ | SlashCommandOptionsOnlyBuilder
14+ | Omit < SlashCommandBuilder , 'addSubcommand' | 'addSubcommandGroup' >
15+ | SlashCommandSubcommandsOnlyBuilder ;
16+
17+ interface CommandOptions {
18+ info : DiscordSlashCommandBuilder ;
19+ execute : ( interaction : ChatInputCommandInteraction ) => Promise < void > ;
20+ guildOnly ?: boolean ;
21+ ownerOnly ?: boolean ;
22+ requiredPermissions ?: ( keyof typeof PermissionFlagsBits ) [ ] ;
23+ cooldown ?: number ;
24+ }
25+
26+ export class Command {
27+ data : DiscordSlashCommandBuilder ;
28+ execute : ( interaction : ChatInputCommandInteraction ) => Promise < void > ;
29+ guildOnly : boolean ;
30+ ownerOnly : boolean ;
31+ requiredPermissions : ( keyof typeof PermissionFlagsBits ) [ ] ;
32+ cooldown : number ;
33+ private _cooldowns : Map < string , number > ;
34+
35+ constructor ( { info, execute, guildOnly = false , ownerOnly = false , requiredPermissions = [ ] , cooldown = 0 } : CommandOptions ) {
36+ this . data = info ;
37+ this . execute = execute ;
38+ this . guildOnly = guildOnly ;
39+ this . ownerOnly = ownerOnly ;
40+ this . requiredPermissions = requiredPermissions ;
41+ this . cooldown = cooldown ;
42+ this . _cooldowns = new Map ( ) ;
43+ }
44+
45+ get name ( ) : string {
46+ return this . data . name ;
47+ }
48+
49+ async run ( interaction : ChatInputCommandInteraction , ownerId ?: string ) : Promise < void > {
50+ if ( this . guildOnly && ! interaction . guild ) {
51+ await interaction . reply ( { content : 'This command is limited to servers' , flags : MessageFlags . Ephemeral } ) ;
52+ return ;
53+ }
54+
55+ if ( this . ownerOnly && interaction . user . id !== ownerId ) {
56+ await interaction . reply ( { content : 'Only the owner can run this command' , flags : MessageFlags . Ephemeral } ) ;
57+ return ;
58+ }
59+
60+ if ( this . requiredPermissions . length && interaction . guild ) {
61+ const missing = this . requiredPermissions . filter (
62+ p => ! interaction . memberPermissions ?. has ( PermissionFlagsBits [ p ] )
63+ ) ;
64+ if ( missing . length ) {
65+ await interaction . reply ( {
66+ content : `You're lacking of the necessary permissions: \`${ missing . join ( ', ' ) } \`` ,
67+ flags : MessageFlags . Ephemeral ,
68+ } ) ;
69+ return ;
70+ }
71+ }
72+
73+ if ( this . cooldown > 0 ) {
74+ const now = Date . now ( ) ;
75+ const expiry = this . _cooldowns . get ( interaction . user . id ) ;
76+ if ( expiry && now < expiry ) {
77+ const unixExpiry = Math . floor ( expiry / 1000 ) ;
78+ await interaction . reply ( {
79+ content : `You can use \`/${ this . name } \` again, please wait <t:${ unixExpiry } :R>.` ,
80+ flags : MessageFlags . Ephemeral ,
81+ } ) ;
82+ return ;
83+ }
84+ this . _cooldowns . set ( interaction . user . id , now + this . cooldown * 1000 ) ;
85+ setTimeout ( ( ) => this . _cooldowns . delete ( interaction . user . id ) , this . cooldown * 1000 ) ;
86+ }
87+
88+ try {
89+ await this . execute ( interaction ) ;
90+ } catch ( err ) {
91+ logger . fatal ( `[Command: ${ this . name } ]` , err ) ;
92+ const msg = { content : 'Something went wrong.' , ephemeral : true } ;
93+ if ( interaction . replied || interaction . deferred ) {
94+ await interaction . followUp ( msg ) ;
95+ } else {
96+ await interaction . reply ( msg ) ;
97+ }
98+ }
99+ }
100+ }
0 commit comments