11import {
2- ApplicationCommandType ,
32 AutocompleteInteraction ,
43 Awaitable ,
54 Collection ,
@@ -13,7 +12,11 @@ import {
1312import type { CommandKit } from '../../commandkit' ;
1413import { AsyncFunction , GenericFunction } from '../../context/async-context' ;
1514import { Logger } from '../../logger/Logger' ;
16- import type { CommandData } from '../../types' ;
15+ import type {
16+ CommandData ,
17+ CommandMetadata ,
18+ CommandMetadataFunction ,
19+ } from '../../types' ;
1720import colors from '../../utils/colors' ;
1821import { COMMANDKIT_IS_DEV } from '../../utils/constants' ;
1922import { CommandKitErrorCodes , isErrorType } from '../../utils/error-codes' ;
@@ -25,6 +28,14 @@ import { MessageCommandParser } from '../commands/MessageCommandParser';
2528import { CommandRegistrar } from '../register/CommandRegistrar' ;
2629import { Command , Middleware } from '../router' ;
2730import { getConfig } from '../../config/config' ;
31+ import { beforeExecute , middlewareId } from '../middlewares/permissions' ;
32+
33+ const KNOWN_NON_HANDLER_KEYS = [
34+ 'command' ,
35+ 'generateMetadata' ,
36+ 'metadata' ,
37+ 'aiConfig' ,
38+ ] ;
2839
2940/**
3041 * Function type for wrapping command execution with custom logic.
@@ -38,6 +49,8 @@ export type RunCommand = <T extends AsyncFunction>(fn: T) => T;
3849 */
3950export interface AppCommandNative {
4051 command : CommandData | Record < string , any > ;
52+ generateMetadata ?: CommandMetadataFunction ;
53+ metadata ?: CommandMetadata ;
4154 chatInput ?: ( ctx : Context ) => Awaitable < unknown > ;
4255 autocomplete ?: ( ctx : Context ) => Awaitable < unknown > ;
4356 message ?: ( ctx : Context ) => Awaitable < unknown > ;
@@ -73,8 +86,8 @@ interface AppCommandMiddleware {
7386 */
7487export interface LoadedCommand {
7588 command : Command ;
89+ metadata : CommandMetadata ;
7690 data : AppCommand ;
77- guilds ?: string [ ] ;
7891}
7992
8093/**
@@ -129,8 +142,22 @@ const commandDataSchema = {
129142 userContextMenu : ( c : unknown ) => typeof c === 'function' ,
130143} ;
131144
145+ /**
146+ * @private
147+ * @internal
148+ */
132149export type CommandDataSchema = typeof commandDataSchema ;
150+
151+ /**
152+ * @private
153+ * @internal
154+ */
133155export type CommandDataSchemaKey = keyof CommandDataSchema ;
156+
157+ /**
158+ * @private
159+ * @internal
160+ */
134161export type CommandDataSchemaValue = CommandDataSchema [ CommandDataSchemaKey ] ;
135162
136163/**
@@ -444,8 +471,8 @@ export class AppCommandHandler {
444471
445472 if (
446473 source . guildId &&
447- loadedCommand . guilds ?. length &&
448- ! loadedCommand . guilds . includes ( source . guildId ! )
474+ loadedCommand . metadata ?. guilds ?. length &&
475+ ! loadedCommand . metadata ?. guilds . includes ( source . guildId ! )
449476 ) {
450477 return null ;
451478 }
@@ -499,8 +526,8 @@ export class AppCommandHandler {
499526 ( source instanceof CommandInteraction ||
500527 source instanceof AutocompleteInteraction ) &&
501528 source . guildId &&
502- loadedCommand . guilds ?. length &&
503- ! loadedCommand . guilds . includes ( source . guildId )
529+ loadedCommand . metadata ?. guilds ?. length &&
530+ ! loadedCommand . metadata ?. guilds . includes ( source . guildId )
504531 ) {
505532 return null ;
506533 }
@@ -516,6 +543,24 @@ export class AppCommandHandler {
516543 }
517544 }
518545
546+ if ( ! getConfig ( ) . disablePermissionsMiddleware ) {
547+ middlewares . push ( {
548+ data : {
549+ // @ts -ignore
550+ beforeExecute,
551+ } ,
552+ middleware : {
553+ command : null ,
554+ global : true ,
555+ id : middlewareId ,
556+ name : 'permissions' ,
557+ parentPath : '' ,
558+ path : '' ,
559+ relativePath : '' ,
560+ } ,
561+ } ) ;
562+ }
563+
519564 // No middleware for subcommands since they inherit from parent command
520565 return {
521566 command : loadedCommand ,
@@ -536,7 +581,7 @@ export class AppCommandHandler {
536581 }
537582
538583 // Check aliases for prefix commands
539- const aliases = loadedCommand . data . command . aliases ;
584+ const aliases = loadedCommand . data . metadata ? .aliases ;
540585 if ( aliases && Array . isArray ( aliases ) && aliases . includes ( name ) ) {
541586 return loadedCommand ;
542587 }
@@ -640,7 +685,7 @@ export class AppCommandHandler {
640685 ( v ) => v . data . command . name ,
641686 ) ;
642687 const aliases = Array . from ( this . loadedCommands . values ( ) ) . flatMap (
643- ( v ) => v . data . command . aliases || [ ] ,
688+ ( v ) => v . metadata . aliases || [ ] ,
644689 ) ;
645690
646691 const allNames = [ ...commandNames , ...aliases ] ;
@@ -698,6 +743,12 @@ export class AppCommandHandler {
698743 if ( command . path === null ) {
699744 this . loadedCommands . set ( id , {
700745 command,
746+ metadata : {
747+ guilds : [ ] ,
748+ aliases : [ ] ,
749+ userPermissions : [ ] ,
750+ botPermissions : [ ] ,
751+ } ,
701752 data : {
702753 command : {
703754 name : command . name ,
@@ -719,6 +770,22 @@ export class AppCommandHandler {
719770 ) ;
720771 }
721772
773+ const metadataFunc = commandFileData . generateMetadata ;
774+ const metadataObj = commandFileData . metadata ;
775+
776+ if ( metadataFunc && metadataObj ) {
777+ throw new Error (
778+ 'A command may only export either `generateMetadata` or `metadata`, not both' ,
779+ ) ;
780+ }
781+
782+ const metadata = ( metadataFunc ? await metadataFunc ( ) : metadataObj ) ?? {
783+ aliases : [ ] ,
784+ guilds : [ ] ,
785+ userPermissions : [ ] ,
786+ botPermissions : [ ] ,
787+ } ;
788+
722789 // Apply the specified logic for name and description
723790 const commandName = commandFileData . command . name || command . name ;
724791 const commandDescription =
@@ -729,7 +796,6 @@ export class AppCommandHandler {
729796 ...commandFileData . command ,
730797 name : commandName ,
731798 description : commandDescription ,
732- aliases : commandFileData . command . aliases ,
733799 } as CommandData ;
734800
735801 let handlerCount = 0 ;
@@ -747,7 +813,7 @@ export class AppCommandHandler {
747813 ) ;
748814 }
749815
750- if ( key !== 'command' ) {
816+ if ( ! KNOWN_NON_HANDLER_KEYS . includes ( key ) ) {
751817 // command file includes a handler function (chatInput, message, etc)
752818 handlerCount ++ ;
753819 }
@@ -770,19 +836,53 @@ export class AppCommandHandler {
770836 }
771837 } ) ;
772838
839+ const commandJson =
840+ 'toJSON' in lastUpdated && typeof lastUpdated . toJSON === 'function'
841+ ? lastUpdated . toJSON ( )
842+ : lastUpdated ;
843+
844+ if ( 'guilds' in commandJson || 'aliases' in commandJson ) {
845+ Logger . warn (
846+ `Command \`${ command . name } \` uses deprecated metadata properties. Please update to use the new \`metadata\` object or \`generateMetadata\` function.` ,
847+ ) ;
848+ }
849+
773850 this . loadedCommands . set ( id , {
774851 command,
775- guilds : commandFileData . command . guilds ,
852+ metadata : {
853+ guilds : commandJson . guilds ,
854+ aliases : commandJson . aliases ,
855+ ...metadata ,
856+ } ,
776857 data : {
777858 ...commandFileData ,
778- command :
779- 'toJSON' in lastUpdated && typeof lastUpdated . toJSON === 'function'
780- ? lastUpdated . toJSON ( )
781- : lastUpdated ,
859+ metadata : {
860+ guilds : commandJson . guilds ,
861+ aliases : commandJson . aliases ,
862+ ...metadata ,
863+ } ,
864+ command : commandJson ,
782865 } ,
783866 } ) ;
784867 } catch ( error ) {
785868 Logger . error ( `Failed to load command ${ command . name } (${ id } )` , error ) ;
786869 }
787870 }
871+
872+ /**
873+ * Gets the metadata for a command.
874+ * @param command - The command name to get metadata for
875+ * @returns The command metadata or null if not found
876+ */
877+ public getMetadataFor ( command : string ) : CommandMetadata | null {
878+ const loadedCommand = this . findCommandByName ( command ) ;
879+ if ( ! loadedCommand ) return null ;
880+
881+ return ( loadedCommand . metadata ??= {
882+ aliases : [ ] ,
883+ guilds : [ ] ,
884+ userPermissions : [ ] ,
885+ botPermissions : [ ] ,
886+ } ) ;
887+ }
788888}
0 commit comments