mirror of
				https://github.com/keanuplayz/TravBot-v3.git
				synced 2024-08-15 02:33:12 +00:00 
			
		
		
		
	Added rest subcommand type
This commit is contained in:
		
							parent
							
								
									e1e6910b1d
								
							
						
					
					
						commit
						26e0bb5824
					
				
					 6 changed files with 245 additions and 122 deletions
				
			
		|  | @ -9,8 +9,9 @@ | |||
| - `urban`: Bug fixes | ||||
| - Changed `help` to display a paginated embed | ||||
| - Various changes to core | ||||
| 	- Added `guild` subcommand type (only accessible when `id` is set to `guild`) | ||||
| 	- Added `guild` subcommand type (only accessible when `id: "guild"`) | ||||
| 	- Further reduced `channel.send()` to `send()` because it's used in *every, single, command* | ||||
| 	- Added `rest` subcommand type (only available when `endpoint: true`), declaratively states that the following command will do `args.join(" ")`, preventing any other subcommands from being added | ||||
| 
 | ||||
| # 3.2.0 - Internal refactor, more subcommand types, and more command type guards (2021-04-09) | ||||
| - The custom logger changed: `$.log` no longer exists, it's just `console.log`. Now you don't have to do `import $ from "../core/lib"` at the top of every file that uses the custom logger. | ||||
|  |  | |||
|  | @ -33,8 +33,9 @@ export default new NamedCommand({ | |||
|     }, | ||||
|     any: new Command({ | ||||
|         async run({send, message, channel, guild, author, member, client, args}) { | ||||
|             const [result, category] = await getCommandInfo(args); | ||||
|             if (typeof result === "string") return send(result); | ||||
|             const resultingBlob = await getCommandInfo(args); | ||||
|             if (typeof resultingBlob === "string") return send(resultingBlob); | ||||
|             const [result, category] = resultingBlob; | ||||
|             let append = ""; | ||||
|             const command = result.command; | ||||
|             const header = result.args.length > 0 ? `${result.header} ${result.args.join(" ")}` : result.header; | ||||
|  | @ -52,6 +53,7 @@ export default new NamedCommand({ | |||
|                     list.push(`❯ \`${header} ${type}${customUsage}\` - ${subcommand.description}`); | ||||
|                 } | ||||
| 
 | ||||
|                 if (result.hasRestCommand) list.push(`❯ \`${header} <...>\``); | ||||
|                 append = list.length > 0 ? list.join("\n") : "None"; | ||||
|             } else { | ||||
|                 append = `\`${header} ${command.usage}\``; | ||||
|  |  | |||
|  | @ -280,7 +280,7 @@ export default new NamedCommand({ | |||
|                             } | ||||
|                         } else { | ||||
|                             // If it's a unique hour, just search through the tuple list and find the matching entry.
 | ||||
|                             for (const [hourPoint, dayOffset, timezoneOffset] of timezoneTupleList) { | ||||
|                             for (const [hourPoint, _dayOffset, timezoneOffset] of timezoneTupleList) { | ||||
|                                 if (hour === hourPoint) { | ||||
|                                     profile.timezone = timezoneOffset; | ||||
|                                 } | ||||
|  |  | |||
|  | @ -78,11 +78,12 @@ interface CommandOptionsBase { | |||
|     readonly permission?: number; | ||||
|     readonly nsfw?: boolean; | ||||
|     readonly channelType?: CHANNEL_TYPE; | ||||
|     readonly run?: (($: CommandMenu) => Promise<any>) | string; | ||||
| } | ||||
| 
 | ||||
| interface CommandOptionsEndpoint { | ||||
|     readonly endpoint: true; | ||||
|     readonly rest?: RestCommand; | ||||
|     readonly run?: (($: CommandMenu) => Promise<any>) | string; | ||||
| } | ||||
| 
 | ||||
| // Prevents subcommands from being added by compile-time.
 | ||||
|  | @ -100,10 +101,15 @@ interface CommandOptionsNonEndpoint { | |||
|     readonly id?: ID; | ||||
|     readonly number?: Command; | ||||
|     readonly any?: Command; | ||||
|     readonly rest?: undefined; // Redeclare it here as undefined to prevent its use otherwise.
 | ||||
|     readonly run?: (($: CommandMenu) => Promise<any>) | string; | ||||
| } | ||||
| 
 | ||||
| type CommandOptions = CommandOptionsBase & (CommandOptionsEndpoint | CommandOptionsNonEndpoint); | ||||
| type NamedCommandOptions = CommandOptions & {aliases?: string[]}; | ||||
| type NamedCommandOptions = CommandOptions & {aliases?: string[]; nameOverride?: string}; | ||||
| type RestCommandOptions = CommandOptionsBase & { | ||||
|     run?: (($: CommandMenu & {readonly combined: string}) => Promise<any>) | string; | ||||
| }; | ||||
| 
 | ||||
| interface ExecuteCommandMetadata { | ||||
|     readonly header: string; | ||||
|  | @ -116,9 +122,10 @@ interface ExecuteCommandMetadata { | |||
| 
 | ||||
| export interface CommandInfo { | ||||
|     readonly type: "info"; | ||||
|     readonly command: Command; | ||||
|     readonly command: BaseCommand; | ||||
|     readonly subcommandInfo: Collection<string, Command>; | ||||
|     readonly keyedSubcommandInfo: Collection<string, NamedCommand>; | ||||
|     readonly hasRestCommand: boolean; | ||||
|     readonly permission: number; | ||||
|     readonly nsfw: boolean; | ||||
|     readonly channelType: CHANNEL_TYPE; | ||||
|  | @ -141,14 +148,26 @@ interface CommandInfoMetadata { | |||
|     readonly header: string; | ||||
| } | ||||
| 
 | ||||
| // Each Command instance represents a block that links other Command instances under it.
 | ||||
| export class Command { | ||||
| // An isolated command of just the metadata.
 | ||||
| abstract class BaseCommand { | ||||
|     public readonly description: string; | ||||
|     public readonly endpoint: boolean; | ||||
|     public readonly usage: string; | ||||
|     public readonly permission: number; // -1 (default) indicates to inherit, 0 is the lowest rank, 1 is second lowest rank, and so on.
 | ||||
|     public readonly nsfw: boolean | null; // null (default) indicates to inherit
 | ||||
|     public readonly channelType: CHANNEL_TYPE | null; // null (default) indicates to inherit
 | ||||
| 
 | ||||
|     constructor(options?: CommandOptionsBase) { | ||||
|         this.description = options?.description || "No description."; | ||||
|         this.usage = options?.usage ?? ""; | ||||
|         this.permission = options?.permission ?? -1; | ||||
|         this.nsfw = options?.nsfw ?? null; | ||||
|         this.channelType = options?.channelType ?? null; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // Each Command instance represents a block that links other Command instances under it.
 | ||||
| export class Command extends BaseCommand { | ||||
|     public readonly endpoint: boolean; | ||||
|     // The execute and subcommand properties are restricted to the class because subcommand recursion could easily break when manually handled.
 | ||||
|     // The class will handle checking for null fields.
 | ||||
|     private run: (($: CommandMenu) => Promise<any>) | string; | ||||
|  | @ -163,14 +182,11 @@ export class Command { | |||
|     private idType: ID | null; | ||||
|     private number: Command | null; | ||||
|     private any: Command | null; | ||||
|     private rest: RestCommand | null; | ||||
| 
 | ||||
|     constructor(options?: CommandOptions) { | ||||
|         this.description = options?.description || "No description."; | ||||
|         super(options); | ||||
|         this.endpoint = !!options?.endpoint; | ||||
|         this.usage = options?.usage ?? ""; | ||||
|         this.permission = options?.permission ?? -1; | ||||
|         this.nsfw = options?.nsfw ?? null; | ||||
|         this.channelType = options?.channelType ?? null; | ||||
|         this.run = options?.run || "No action was set on this command!"; | ||||
|         this.subcommands = new Collection(); // Populate this collection after setting subcommands.
 | ||||
|         this.channel = null; | ||||
|  | @ -183,44 +199,45 @@ export class Command { | |||
|         this.idType = null; | ||||
|         this.number = null; | ||||
|         this.any = null; | ||||
|         this.rest = null; | ||||
| 
 | ||||
|         if (options && !options.endpoint) { | ||||
|             if (options?.channel) this.channel = options.channel; | ||||
|             if (options?.role) this.role = options.role; | ||||
|             if (options?.emote) this.emote = options.emote; | ||||
|             if (options?.message) this.message = options.message; | ||||
|             if (options?.user) this.user = options.user; | ||||
|             if (options?.guild) this.guild = options.guild; | ||||
|             if (options?.number) this.number = options.number; | ||||
|             if (options?.any) this.any = options.any; | ||||
|             if (options?.id) this.idType = options.id; | ||||
|             if (options.channel) this.channel = options.channel; | ||||
|             if (options.role) this.role = options.role; | ||||
|             if (options.emote) this.emote = options.emote; | ||||
|             if (options.message) this.message = options.message; | ||||
|             if (options.user) this.user = options.user; | ||||
|             if (options.guild) this.guild = options.guild; | ||||
|             if (options.number) this.number = options.number; | ||||
|             if (options.any) this.any = options.any; | ||||
|             if (options.id) this.idType = options.id; | ||||
| 
 | ||||
|             if (options?.id) { | ||||
|                 switch (options.id) { | ||||
|                     case "channel": | ||||
|                         this.id = this.channel; | ||||
|                         break; | ||||
|                     case "role": | ||||
|                         this.id = this.role; | ||||
|                         break; | ||||
|                     case "emote": | ||||
|                         this.id = this.emote; | ||||
|                         break; | ||||
|                     case "message": | ||||
|                         this.id = this.message; | ||||
|                         break; | ||||
|                     case "user": | ||||
|                         this.id = this.user; | ||||
|                         break; | ||||
|                     case "guild": | ||||
|                         this.id = this.guild; | ||||
|                         break; | ||||
|                     default: | ||||
|                         requireAllCasesHandledFor(options.id); | ||||
|                 } | ||||
|             switch (options.id) { | ||||
|                 case "channel": | ||||
|                     this.id = this.channel; | ||||
|                     break; | ||||
|                 case "role": | ||||
|                     this.id = this.role; | ||||
|                     break; | ||||
|                 case "emote": | ||||
|                     this.id = this.emote; | ||||
|                     break; | ||||
|                 case "message": | ||||
|                     this.id = this.message; | ||||
|                     break; | ||||
|                 case "user": | ||||
|                     this.id = this.user; | ||||
|                     break; | ||||
|                 case "guild": | ||||
|                     this.id = this.guild; | ||||
|                     break; | ||||
|                 case undefined: | ||||
|                     break; | ||||
|                 default: | ||||
|                     requireAllCasesHandledFor(options.id); | ||||
|             } | ||||
| 
 | ||||
|             if (options?.subcommands) { | ||||
|             if (options.subcommands) { | ||||
|                 const baseSubcommands = Object.keys(options.subcommands); | ||||
| 
 | ||||
|                 // Loop once to set the base subcommands.
 | ||||
|  | @ -246,6 +263,8 @@ export class Command { | |||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } else if (options && options.endpoint) { | ||||
|             if (options.rest) this.rest = options.rest; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -273,72 +292,49 @@ export class Command { | |||
| 
 | ||||
|         // If there are no arguments left, execute the current command. Otherwise, continue on.
 | ||||
|         if (param === undefined) { | ||||
|             // See if there is anything that'll prevent the user from executing the command.
 | ||||
|             const error = canExecute(menu, metadata); | ||||
|             if (error) return error; | ||||
| 
 | ||||
|             // 1. Does this command specify a required channel type? If so, does the channel type match?
 | ||||
|             if ( | ||||
|                 metadata.channelType === CHANNEL_TYPE.GUILD && | ||||
|                 (!(menu.channel instanceof GuildChannel) || menu.guild === null || menu.member === null) | ||||
|             ) { | ||||
|                 return {content: "This command must be executed in a server."}; | ||||
|             } else if ( | ||||
|                 metadata.channelType === CHANNEL_TYPE.DM && | ||||
|                 (menu.channel.type !== "dm" || menu.guild !== null || menu.member !== null) | ||||
|             ) { | ||||
|                 return {content: "This command must be executed as a direct message."}; | ||||
|             } | ||||
| 
 | ||||
|             // 2. Is this an NSFW command where the channel prevents such use? (DM channels bypass this requirement.)
 | ||||
|             if (metadata.nsfw && menu.channel.type !== "dm" && !menu.channel.nsfw) { | ||||
|                 return {content: "This command must be executed in either an NSFW channel or as a direct message."}; | ||||
|             } | ||||
| 
 | ||||
|             // 3. Does the user have permission to execute the command?
 | ||||
|             if (!hasPermission(menu.author, menu.member, metadata.permission)) { | ||||
|                 const userPermLevel = getPermissionLevel(menu.author, menu.member); | ||||
| 
 | ||||
|                 return { | ||||
|                     content: `You don't have access to this command! Your permission level is \`${getPermissionName( | ||||
|                         userPermLevel | ||||
|                     )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( | ||||
|                         metadata.permission | ||||
|                     )}\` (${metadata.permission}).` | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             // Then capture any potential errors.
 | ||||
|             try { | ||||
|                 if (typeof this.run === "string") { | ||||
|                     // Although I *could* add an option in the launcher to attach arbitrary variables to this var string...
 | ||||
|                     // I'll just leave it like this, because instead of using var strings for user stuff, you could just make "run" a template string.
 | ||||
|                     await menu.send( | ||||
|                         parseVars( | ||||
|                             this.run, | ||||
|                             { | ||||
|                                 author: menu.author.toString(), | ||||
|                                 prefix: getPrefix(menu.guild), | ||||
|                                 command: `${metadata.header} ${metadata.symbolicArgs.join(", ")}` | ||||
|                             }, | ||||
|                             "???" | ||||
|                         ) | ||||
|                     ); | ||||
|                 } else { | ||||
|             if (typeof this.run === "string") { | ||||
|                 // Although I *could* add an option in the launcher to attach arbitrary variables to this var string...
 | ||||
|                 // I'll just leave it like this, because instead of using var strings for user stuff, you could just make "run" a template string.
 | ||||
|                 await menu.send( | ||||
|                     parseVars( | ||||
|                         this.run, | ||||
|                         { | ||||
|                             author: menu.author.toString(), | ||||
|                             prefix: getPrefix(menu.guild), | ||||
|                             command: `${metadata.header} ${metadata.symbolicArgs.join(", ")}` | ||||
|                         }, | ||||
|                         "???" | ||||
|                     ) | ||||
|                 ); | ||||
|             } else { | ||||
|                 // Then capture any potential errors.
 | ||||
|                 try { | ||||
|                     await this.run(menu); | ||||
|                 } catch (error) { | ||||
|                     const errorMessage = error.stack ?? error; | ||||
|                     console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); | ||||
| 
 | ||||
|                     return { | ||||
|                         content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` | ||||
|                     }; | ||||
|                 } | ||||
| 
 | ||||
|                 return null; | ||||
|             } catch (error) { | ||||
|                 const errorMessage = error.stack ?? error; | ||||
|                 console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); | ||||
| 
 | ||||
|                 return { | ||||
|                     content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         // If the current command is an endpoint but there are still some arguments left, don't continue.
 | ||||
|         if (this.endpoint) return {content: "Too many arguments!"}; | ||||
|         // If the current command is an endpoint but there are still some arguments left, don't continue unless there's a RestCommand.
 | ||||
|         if (this.endpoint) { | ||||
|             if (this.rest) { | ||||
|                 args.unshift(param); | ||||
|                 return this.rest.execute(args.join(" "), menu, metadata); | ||||
|             } else { | ||||
|                 return {content: "Too many arguments!"}; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Resolve the value of the current command's argument (adding it to the resolved args),
 | ||||
|         // then pass the thread of execution to whichever subcommand is valid (if any).
 | ||||
|  | @ -532,7 +528,7 @@ export class Command { | |||
|     } | ||||
| 
 | ||||
|     // What this does is resolve the resulting subcommand as well as the inherited properties and the available subcommands.
 | ||||
|     public async resolveInfo(args: string[], header: string): Promise<CommandInfo | CommandInfoError> { | ||||
|     public resolveInfo(args: string[], header: string): CommandInfo | CommandInfoError { | ||||
|         return this.resolveInfoInternal(args, { | ||||
|             permission: 0, | ||||
|             nsfw: false, | ||||
|  | @ -544,10 +540,7 @@ export class Command { | |||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     private async resolveInfoInternal( | ||||
|         args: string[], | ||||
|         metadata: CommandInfoMetadata | ||||
|     ): Promise<CommandInfo | CommandInfoError> { | ||||
|     private resolveInfoInternal(args: string[], metadata: CommandInfoMetadata): CommandInfo | CommandInfoError { | ||||
|         // Update inherited properties if the current command specifies a property.
 | ||||
|         // In case there are no initial arguments, these should go first so that it can register.
 | ||||
|         if (this.permission !== -1) metadata.permission = this.permission; | ||||
|  | @ -586,6 +579,7 @@ export class Command { | |||
|                 command: this, | ||||
|                 keyedSubcommandInfo, | ||||
|                 subcommandInfo, | ||||
|                 hasRestCommand: !!this.rest, | ||||
|                 ...metadata | ||||
|             }; | ||||
|         } | ||||
|  | @ -652,6 +646,13 @@ export class Command { | |||
|             } else { | ||||
|                 return invalidSubcommandGenerator(); | ||||
|             } | ||||
|         } else if (param === "<...>") { | ||||
|             if (this.rest) { | ||||
|                 metadata.args.push("<...>"); | ||||
|                 return this.rest.resolveInfoFinale(metadata); | ||||
|             } else { | ||||
|                 return invalidSubcommandGenerator(); | ||||
|             } | ||||
|         } else if (this.subcommands?.has(param)) { | ||||
|             metadata.args.push(param); | ||||
|             return this.subcommands.get(param)!.resolveInfoInternal(args, metadata); | ||||
|  | @ -668,7 +669,8 @@ export class NamedCommand extends Command { | |||
|     constructor(options?: NamedCommandOptions) { | ||||
|         super(options); | ||||
|         this.aliases = options?.aliases || []; | ||||
|         this.originalCommandName = null; | ||||
|         // The name override exists in case a user wants to bypass filename restrictions.
 | ||||
|         this.originalCommandName = options?.nameOverride ?? null; | ||||
|     } | ||||
| 
 | ||||
|     public get name(): string { | ||||
|  | @ -681,4 +683,119 @@ export class NamedCommand extends Command { | |||
|             throw new Error(`originalCommandName cannot be set twice! Attempted to set the value to "${value}".`); | ||||
|         else this.originalCommandName = value; | ||||
|     } | ||||
| 
 | ||||
|     public isNameSet(): boolean { | ||||
|         return this.originalCommandName !== null; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // RestCommand is a declarative version of the common "any: args.join(' ')" pattern, basically the Command version of a rest parameter.
 | ||||
| // This way, you avoid having extra subcommands when using this pattern.
 | ||||
| // I'm probably not going to add a transformer function (a callback to automatically handle stuff like searching for usernames).
 | ||||
| // I don't think the effort to figure this part out via generics or something is worth it.
 | ||||
| export class RestCommand extends BaseCommand { | ||||
|     private run: (($: CommandMenu & {readonly combined: string}) => Promise<any>) | string; | ||||
| 
 | ||||
|     constructor(options?: RestCommandOptions) { | ||||
|         super(options); | ||||
|         this.run = options?.run || "No action was set on this command!"; | ||||
|     } | ||||
| 
 | ||||
|     public async execute( | ||||
|         combined: string, | ||||
|         menu: CommandMenu, | ||||
|         metadata: ExecuteCommandMetadata | ||||
|     ): Promise<SingleMessageOptions | null> { | ||||
|         // Update inherited properties if the current command specifies a property.
 | ||||
|         // In case there are no initial arguments, these should go first so that it can register.
 | ||||
|         if (this.permission !== -1) metadata.permission = this.permission; | ||||
|         if (this.nsfw !== null) metadata.nsfw = this.nsfw; | ||||
|         if (this.channelType !== null) metadata.channelType = this.channelType; | ||||
| 
 | ||||
|         const error = canExecute(menu, metadata); | ||||
|         if (error) return error; | ||||
| 
 | ||||
|         if (typeof this.run === "string") { | ||||
|             // Although I *could* add an option in the launcher to attach arbitrary variables to this var string...
 | ||||
|             // I'll just leave it like this, because instead of using var strings for user stuff, you could just make "run" a template string.
 | ||||
|             await menu.send( | ||||
|                 parseVars( | ||||
|                     this.run, | ||||
|                     { | ||||
|                         author: menu.author.toString(), | ||||
|                         prefix: getPrefix(menu.guild), | ||||
|                         command: `${metadata.header} ${metadata.symbolicArgs.join(", ")}` | ||||
|                     }, | ||||
|                     "???" | ||||
|                 ) | ||||
|             ); | ||||
|         } else { | ||||
|             // Then capture any potential errors.
 | ||||
|             try { | ||||
|                 await this.run({...menu, combined}); | ||||
|             } catch (error) { | ||||
|                 const errorMessage = error.stack ?? error; | ||||
|                 console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); | ||||
| 
 | ||||
|                 return { | ||||
|                     content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     public resolveInfoFinale(metadata: CommandInfoMetadata): CommandInfo { | ||||
|         if (this.permission !== -1) metadata.permission = this.permission; | ||||
|         if (this.nsfw !== null) metadata.nsfw = this.nsfw; | ||||
|         if (this.channelType !== null) metadata.channelType = this.channelType; | ||||
|         if (this.usage !== "") metadata.usage = this.usage; | ||||
| 
 | ||||
|         return { | ||||
|             type: "info", | ||||
|             command: this, | ||||
|             keyedSubcommandInfo: new Collection<string, NamedCommand>(), | ||||
|             subcommandInfo: new Collection<string, Command>(), | ||||
|             hasRestCommand: false, | ||||
|             ...metadata | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // See if there is anything that'll prevent the user from executing the command.
 | ||||
| // Returns null if successful, otherwise returns a message with the error.
 | ||||
| function canExecute(menu: CommandMenu, metadata: ExecuteCommandMetadata): SingleMessageOptions | null { | ||||
|     // 1. Does this command specify a required channel type? If so, does the channel type match?
 | ||||
|     if ( | ||||
|         metadata.channelType === CHANNEL_TYPE.GUILD && | ||||
|         (!(menu.channel instanceof GuildChannel) || menu.guild === null || menu.member === null) | ||||
|     ) { | ||||
|         return {content: "This command must be executed in a server."}; | ||||
|     } else if ( | ||||
|         metadata.channelType === CHANNEL_TYPE.DM && | ||||
|         (menu.channel.type !== "dm" || menu.guild !== null || menu.member !== null) | ||||
|     ) { | ||||
|         return {content: "This command must be executed as a direct message."}; | ||||
|     } | ||||
| 
 | ||||
|     // 2. Is this an NSFW command where the channel prevents such use? (DM channels bypass this requirement.)
 | ||||
|     if (metadata.nsfw && menu.channel.type !== "dm" && !menu.channel.nsfw) { | ||||
|         return {content: "This command must be executed in either an NSFW channel or as a direct message."}; | ||||
|     } | ||||
| 
 | ||||
|     // 3. Does the user have permission to execute the command?
 | ||||
|     if (!hasPermission(menu.author, menu.member, metadata.permission)) { | ||||
|         const userPermLevel = getPermissionLevel(menu.author, menu.member); | ||||
| 
 | ||||
|         return { | ||||
|             content: `You don't have access to this command! Your permission level is \`${getPermissionName( | ||||
|                 userPermLevel | ||||
|             )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( | ||||
|                 metadata.permission | ||||
|             )}\` (${metadata.permission}).` | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
|     return null; | ||||
| } | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| // Onion Lasers Command Handler //
 | ||||
| export {Command, NamedCommand, CHANNEL_TYPE} from "./command"; | ||||
| export {Command, NamedCommand, RestCommand, CHANNEL_TYPE} from "./command"; | ||||
| export {addInterceptRule} from "./handler"; | ||||
| export {launch} from "./interface"; | ||||
| export * from "./libd"; | ||||
|  |  | |||
|  | @ -43,14 +43,16 @@ export async function loadCommands(commandsDir: string): Promise<Collection<stri | |||
|             const command = (await import(files[i])).default as unknown; | ||||
| 
 | ||||
|             if (command instanceof NamedCommand) { | ||||
|                 command.name = commandName; | ||||
|                 const isNameOverridden = command.isNameSet(); | ||||
|                 if (!isNameOverridden) command.name = commandName; | ||||
|                 const header = command.name; | ||||
| 
 | ||||
|                 if (commands.has(commandName)) { | ||||
|                 if (commands.has(header)) { | ||||
|                     console.warn( | ||||
|                         `Command "${commandName}" already exists! Make sure to make each command uniquely identifiable across categories!` | ||||
|                         `Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!` | ||||
|                     ); | ||||
|                 } else { | ||||
|                     commands.set(commandName, command); | ||||
|                     commands.set(header, command); | ||||
|                 } | ||||
| 
 | ||||
|                 for (const alias of command.aliases) { | ||||
|  | @ -64,9 +66,10 @@ export async function loadCommands(commandsDir: string): Promise<Collection<stri | |||
|                 } | ||||
| 
 | ||||
|                 if (!(category in lists)) lists[category] = []; | ||||
|                 lists[category].push(commandName); | ||||
|                 lists[category].push(header); | ||||
| 
 | ||||
|                 console.log(`Loaded Command: ${commandID}`); | ||||
|                 if (isNameOverridden) console.log(`Loaded Command: "${commandID}" as "${header}"`); | ||||
|                 else console.log(`Loaded Command: ${commandID}`); | ||||
|             } else { | ||||
|                 console.warn(`Command "${commandID}" has no default export which is a NamedCommand instance!`); | ||||
|             } | ||||
|  | @ -139,7 +142,7 @@ export async function getCommandInfo(args: string[]): Promise<[CommandInfo, stri | |||
|     } | ||||
| 
 | ||||
|     // Gather info
 | ||||
|     const result = await command.resolveInfo(args, header); | ||||
|     const result = command.resolveInfo(args, header); | ||||
|     if (result.type === "error") return result.message; | ||||
|     else return [result, category]; | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue