mirror of
				https://github.com/keanuplayz/TravBot-v3.git
				synced 2024-08-15 02:33:12 +00:00 
			
		
		
		
	Reworked Command.execute and subcommand recursion
This commit is contained in:
		
							parent
							
								
									63441b4aca
								
							
						
					
					
						commit
						6eea068909
					
				
					 3 changed files with 192 additions and 176 deletions
				
			
		|  | @ -1,9 +1,10 @@ | ||||||
| import {parseVars, requireAllCasesHandledFor} from "./lib"; | import {parseVars, requireAllCasesHandledFor} from "./lib"; | ||||||
| import {Collection} from "discord.js"; | import {Collection} from "discord.js"; | ||||||
| import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember} from "discord.js"; | import {Client, Message, TextChannel, DMChannel, NewsChannel, Guild, User, GuildMember, GuildChannel} from "discord.js"; | ||||||
| import {getPrefix} from "../core/structures"; | import {getPrefix} from "../core/structures"; | ||||||
| import {SingleMessageOptions} from "./libd"; | import {SingleMessageOptions} from "./libd"; | ||||||
| import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; | import {hasPermission, getPermissionLevel, getPermissionName} from "./permissions"; | ||||||
|  | import {client} from "../index"; | ||||||
| 
 | 
 | ||||||
| export enum TYPES { | export enum TYPES { | ||||||
|     SUBCOMMAND, |     SUBCOMMAND, | ||||||
|  | @ -13,6 +14,16 @@ export enum TYPES { | ||||||
|     NONE |     NONE | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // RegEx patterns used for identifying/extracting each type from a string argument.
 | ||||||
|  | const patterns = { | ||||||
|  |     channel: /^<#(\d{17,19})>$/, | ||||||
|  |     role: /^<@&(\d{17,19})>$/, | ||||||
|  |     emote: /^<a?:.*?:(\d{17,19})>$/, | ||||||
|  |     message: /(?:\d{17,19}\/(\d{17,19})\/(\d{17,19})$)|(?:^(\d{17,19})-(\d{17,19})$)/, | ||||||
|  |     user: /^<@!?(\d{17,19})>$/, | ||||||
|  |     id: /^(\d{17,19})$/ | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| // Callbacks don't work with discriminated unions:
 | // Callbacks don't work with discriminated unions:
 | ||||||
| // - https://github.com/microsoft/TypeScript/issues/41759
 | // - https://github.com/microsoft/TypeScript/issues/41759
 | ||||||
| // - https://github.com/microsoft/TypeScript/issues/35769
 | // - https://github.com/microsoft/TypeScript/issues/35769
 | ||||||
|  | @ -26,55 +37,58 @@ export enum CHANNEL_TYPE { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface CommandMenu { | interface CommandMenu { | ||||||
|     args: any[]; |     readonly args: any[]; | ||||||
|     client: Client; |     readonly client: Client; | ||||||
|     message: Message; |     readonly message: Message; | ||||||
|     channel: TextChannel | DMChannel | NewsChannel; |     readonly channel: TextChannel | DMChannel | NewsChannel; | ||||||
|     guild: Guild | null; |     readonly guild: Guild | null; | ||||||
|     author: User; |     readonly author: User; | ||||||
|     // According to the documentation, a message can be part of a guild while also not having a
 |     // According to the documentation, a message can be part of a guild while also not having a
 | ||||||
|     // member object for the author. This will happen if the author of a message left the guild.
 |     // member object for the author. This will happen if the author of a message left the guild.
 | ||||||
|     member: GuildMember | null; |     readonly member: GuildMember | null; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface CommandOptionsBase { | interface CommandOptionsBase { | ||||||
|     description?: string; |     readonly description?: string; | ||||||
|     endpoint?: boolean; |     readonly endpoint?: boolean; | ||||||
|     usage?: string; |     readonly usage?: string; | ||||||
|     permission?: number; |     readonly permission?: number; | ||||||
|     nsfw?: boolean; |     readonly nsfw?: boolean; | ||||||
|     channelType?: CHANNEL_TYPE; |     readonly channelType?: CHANNEL_TYPE; | ||||||
|     run?: (($: CommandMenu) => Promise<any>) | string; |     readonly run?: (($: CommandMenu) => Promise<any>) | string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface CommandOptionsEndpoint { | interface CommandOptionsEndpoint { | ||||||
|     endpoint: true; |     readonly endpoint: true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Prevents subcommands from being added by compile-time.
 | // Prevents subcommands from being added by compile-time.
 | ||||||
| interface CommandOptionsNonEndpoint { | interface CommandOptionsNonEndpoint { | ||||||
|     endpoint?: false; |     readonly endpoint?: false; | ||||||
|     subcommands?: {[key: string]: NamedCommand}; |     readonly subcommands?: {[key: string]: NamedCommand}; | ||||||
|     user?: Command; |     readonly user?: Command; | ||||||
|     number?: Command; |     readonly number?: Command; | ||||||
|     any?: Command; |     readonly any?: Command; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type CommandOptions = CommandOptionsBase & (CommandOptionsEndpoint | CommandOptionsNonEndpoint); | type CommandOptions = CommandOptionsBase & (CommandOptionsEndpoint | CommandOptionsNonEndpoint); | ||||||
| type NamedCommandOptions = CommandOptions & {aliases?: string[]}; | type NamedCommandOptions = CommandOptions & {aliases?: string[]}; | ||||||
| 
 | 
 | ||||||
| // RegEx patterns used for identifying/extracting each type from a string argument.
 | interface ExecuteCommandMetadata { | ||||||
| const patterns = { |     readonly header: string; | ||||||
|     //
 |     readonly args: string[]; | ||||||
| }; |     permission: number; | ||||||
|  |     nsfw: boolean; | ||||||
|  |     channelType: CHANNEL_TYPE; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| export class Command { | export class Command { | ||||||
|     public readonly description: string; |     public readonly description: string; | ||||||
|     public readonly endpoint: boolean; |     public readonly endpoint: boolean; | ||||||
|     public readonly usage: string; |     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 permission: number; // -1 (default) indicates to inherit, 0 is the lowest rank, 1 is second lowest rank, and so on.
 | ||||||
|     public readonly nsfw: boolean; |     public readonly nsfw: boolean | null; // null (default) indicates to inherit
 | ||||||
|     public readonly channelType: CHANNEL_TYPE; |     public readonly channelType: CHANNEL_TYPE | null; // null (default) indicates to inherit
 | ||||||
|     protected run: (($: CommandMenu) => Promise<any>) | string; |     protected run: (($: CommandMenu) => Promise<any>) | string; | ||||||
|     protected readonly subcommands: Collection<string, Command>; // This is the final data structure you'll actually use to work with the commands the aliases point to.
 |     protected readonly subcommands: Collection<string, Command>; // This is the final data structure you'll actually use to work with the commands the aliases point to.
 | ||||||
|     protected user: Command | null; |     protected user: Command | null; | ||||||
|  | @ -87,8 +101,8 @@ export class Command { | ||||||
|         this.endpoint = !!options?.endpoint; |         this.endpoint = !!options?.endpoint; | ||||||
|         this.usage = options?.usage ?? ""; |         this.usage = options?.usage ?? ""; | ||||||
|         this.permission = options?.permission ?? -1; |         this.permission = options?.permission ?? -1; | ||||||
|         this.nsfw = !!options?.nsfw; |         this.nsfw = options?.nsfw ?? null; | ||||||
|         this.channelType = options?.channelType ?? CHANNEL_TYPE.ANY; |         this.channelType = options?.channelType ?? null; | ||||||
|         this.run = options?.run || "No action was set on this command!"; |         this.run = options?.run || "No action was set on this command!"; | ||||||
|         this.subcommands = new Collection(); // Populate this collection after setting subcommands.
 |         this.subcommands = new Collection(); // Populate this collection after setting subcommands.
 | ||||||
|         this.user = null; |         this.user = null; | ||||||
|  | @ -129,116 +143,135 @@ export class Command { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public execute($: CommandMenu) { |  | ||||||
|         if (typeof this.run === "string") { |  | ||||||
|             $.channel.send( |  | ||||||
|                 parseVars( |  | ||||||
|                     this.run, |  | ||||||
|                     { |  | ||||||
|                         author: $.author.toString(), |  | ||||||
|                         prefix: getPrefix($.guild) |  | ||||||
|                     }, |  | ||||||
|                     "???" |  | ||||||
|                 ) |  | ||||||
|             ); |  | ||||||
|         } else this.run($).catch(handler.bind($)); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     // Go through the arguments provided and find the right subcommand, then execute with the given arguments.
 |     // Go through the arguments provided and find the right subcommand, then execute with the given arguments.
 | ||||||
|     // Will return null if it successfully executes, SingleMessageOptions if there's an error (to let the user know what it is).
 |     // Will return null if it successfully executes, SingleMessageOptions if there's an error (to let the user know what it is).
 | ||||||
|     public async actualExecute(args: string[], tmp: any): Promise<SingleMessageOptions | null> { |     public async execute( | ||||||
|         // For debug info, use this.originalCommandName?
 |         args: string[], | ||||||
|         // Subcommand Recursion //
 |         menu: CommandMenu, | ||||||
|         let command: Command = new Command(); // = commands.get(header)!;
 |         metadata: ExecuteCommandMetadata | ||||||
|         //resolveSubcommand()
 |     ): Promise<SingleMessageOptions | null> { | ||||||
|         const params: any[] = []; |         const param = args.shift(); | ||||||
|         let isEndpoint = false; |  | ||||||
|         let permLevel = command.permission ?? 0; |  | ||||||
| 
 | 
 | ||||||
|         for (const param of args) { |         // If there are no arguments left, execute the current command. Otherwise, continue on.
 | ||||||
|             const type = command.resolve(param); |         if (!param) { | ||||||
|             command = command.get(param); |             // See if there is anything that'll prevent the user from executing the command.
 | ||||||
|             permLevel = command.permission ?? permLevel; |  | ||||||
| 
 | 
 | ||||||
|             if (type === TYPES.USER) { |             // 1. Does this command specify a required channel type? If so, does the channel type match?
 | ||||||
|                 const id = param.match(/\d+/g)![0]; |             if ( | ||||||
|                 try { |                 metadata.channelType === CHANNEL_TYPE.GUILD && | ||||||
|                     params.push(await message.client.users.fetch(id)); |                 (!(menu.channel instanceof GuildChannel) || menu.guild === null) | ||||||
|                 } catch (error) { |             ) { | ||||||
|                     return message.channel.send(`No user found by the ID \`${id}\`!`); |                 return {content: "This command must be executed in a server."}; | ||||||
|  |             } else if ( | ||||||
|  |                 metadata.channelType === CHANNEL_TYPE.DM && | ||||||
|  |                 (menu.channel.type !== "dm" || menu.guild !== 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") { | ||||||
|  |                     await menu.channel.send( | ||||||
|  |                         parseVars( | ||||||
|  |                             this.run, | ||||||
|  |                             { | ||||||
|  |                                 author: menu.author.toString(), | ||||||
|  |                                 prefix: getPrefix(menu.guild) | ||||||
|  |                             }, | ||||||
|  |                             "???" | ||||||
|  |                         ) | ||||||
|  |                     ); | ||||||
|  |                 } else { | ||||||
|  |                     await this.run(menu); | ||||||
|                 } |                 } | ||||||
|             } else if (type === TYPES.NUMBER) params.push(Number(param)); | 
 | ||||||
|             else if (type !== TYPES.SUBCOMMAND) params.push(param); |                 return null; | ||||||
|  |             } catch (error) { | ||||||
|  |                 const errorMessage = error.stack ?? error; | ||||||
|  |                 console.error(`Command Error: ${metadata.header} (${metadata.args})\n${errorMessage}`); | ||||||
|  | 
 | ||||||
|  |                 return { | ||||||
|  |                     content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (!message.member) |         // If the current command is an endpoint but there are still some arguments left, don't continue.
 | ||||||
|             return console.warn("This command was likely called from a DM channel meaning the member object is null."); |         if (this.endpoint) return {content: "Too many arguments!"}; | ||||||
| 
 | 
 | ||||||
|         if (!hasPermission(message.member, permLevel)) { |         // If the current command's permission level isn't -1 (inherit), then set the permission metadata equal to that.
 | ||||||
|             const userPermLevel = getPermissionLevel(message.member); |         if (this.permission !== -1) metadata.permission = this.permission; | ||||||
|             return message.channel.send( | 
 | ||||||
|                 `You don't have access to this command! Your permission level is \`${getPermissionName( |         // If the current command has an NSFW setting specified, set it.
 | ||||||
|                     userPermLevel |         if (this.nsfw !== null) metadata.nsfw = this.nsfw; | ||||||
|                 )}\` (${userPermLevel}), but this command requires a permission level of \`${getPermissionName( | 
 | ||||||
|                     permLevel |         // If the current command doesn't inherit its channel type, set it.
 | ||||||
|                 )}\` (${permLevel}).` |         if (this.channelType !== null) metadata.channelType = this.channelType; | ||||||
|             ); | 
 | ||||||
|  |         // 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).
 | ||||||
|  |         if (this.subcommands.has(param)) { | ||||||
|  |             return this.subcommands.get(param)!.execute(args, menu, metadata); | ||||||
|  |         } else if (this.user && patterns.user.test(param)) { | ||||||
|  |             const id = patterns.user.exec(param)![1]; | ||||||
|  | 
 | ||||||
|  |             try { | ||||||
|  |                 menu.args.push(await client.users.fetch(id)); | ||||||
|  |                 return this.user.execute(args, menu, metadata); | ||||||
|  |             } catch { | ||||||
|  |                 return { | ||||||
|  |                     content: `No user found by the ID \`${id}\`!` | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|  |         } else if (this.number && !Number.isNaN(Number(param)) && param !== "Infinity" && param !== "-Infinity") { | ||||||
|  |             menu.args.push(Number(param)); | ||||||
|  |             return this.number.execute(args, menu, metadata); | ||||||
|  |         } else if (this.any) { | ||||||
|  |             menu.args.push(param); | ||||||
|  |             return this.any.execute(args, menu, metadata); | ||||||
|  |         } else { | ||||||
|  |             // Continue adding on the rest of the arguments if there's no valid subcommand.
 | ||||||
|  |             menu.args.push(param); | ||||||
|  |             return this.execute(args, menu, metadata); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (isEndpoint) return message.channel.send("Too many arguments!"); |         // Note: Do NOT add a return statement here. In case one of the other sections is missing
 | ||||||
| 
 |         // a return statement, there'll be a compile error to catch that.
 | ||||||
|         command.execute({ |  | ||||||
|             args: params, |  | ||||||
|             author: message.author, |  | ||||||
|             channel: message.channel, |  | ||||||
|             client: message.client, |  | ||||||
|             guild: message.guild, |  | ||||||
|             member: message.member, |  | ||||||
|             message: message |  | ||||||
|         }); |  | ||||||
| 
 |  | ||||||
|         return null; |  | ||||||
|     } |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|     private resolve(param: string): TYPES { | export class NamedCommand extends Command { | ||||||
|         if (this.subcommands.has(param)) return TYPES.SUBCOMMAND; |     public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases.
 | ||||||
|         // Any Discord ID format will automatically format to a user ID.
 |     public originalCommandName: string | null; // If the command is an alias, what's the original name?
 | ||||||
|         else if (this.user && /\d{17,19}/.test(param)) return TYPES.USER; |  | ||||||
|         // Disallow infinity and allow for 0.
 |  | ||||||
|         else if (this.number && (Number(param) || param === "0") && !param.includes("Infinity")) return TYPES.NUMBER; |  | ||||||
|         else if (this.any) return TYPES.ANY; |  | ||||||
|         else return TYPES.NONE; |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     private get(param: string): Command { |     constructor(options?: NamedCommandOptions) { | ||||||
|         const type = this.resolve(param); |         super(options); | ||||||
|         let command: Command; |         this.aliases = options?.aliases || []; | ||||||
| 
 |         this.originalCommandName = null; | ||||||
|         switch (type) { |  | ||||||
|             case TYPES.SUBCOMMAND: |  | ||||||
|                 command = this.subcommands.get(param)!; |  | ||||||
|                 break; |  | ||||||
|             case TYPES.USER: |  | ||||||
|                 command = this.user!; |  | ||||||
|                 break; |  | ||||||
|             case TYPES.NUMBER: |  | ||||||
|                 command = this.number!; |  | ||||||
|                 break; |  | ||||||
|             case TYPES.ANY: |  | ||||||
|                 command = this.any!; |  | ||||||
|                 break; |  | ||||||
|             case TYPES.NONE: |  | ||||||
|                 command = this; |  | ||||||
|                 break; |  | ||||||
|             default: |  | ||||||
|                 requireAllCasesHandledFor(type); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return command; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Returns: [category, command name, command, available subcommands: [type, subcommand]]
 |     // Returns: [category, command name, command, available subcommands: [type, subcommand]]
 | ||||||
|     public async resolveInfo(args: string[]): [string, string, Command, Collection<string, Command>] | null { |     public async resolveInfo(args: string[]): [string, string, Command, Collection<string, Command>] | null { | ||||||
|  |         // For debug info, use this.originalCommandName? (if it exists?)
 | ||||||
|         const commands = await loadableCommands; |         const commands = await loadableCommands; | ||||||
|         let header = args.shift(); |         let header = args.shift(); | ||||||
|         let command = commands.get(header); |         let command = commands.get(header); | ||||||
|  | @ -307,31 +340,3 @@ export class Command { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 |  | ||||||
| export class NamedCommand extends Command { |  | ||||||
|     public readonly aliases: string[]; // This is to keep the array intact for parent Command instances to use. It'll also be used when loading top-level aliases.
 |  | ||||||
|     public originalCommandName: string | null; // If the command is an alias, what's the original name?
 |  | ||||||
| 
 |  | ||||||
|     constructor(options?: NamedCommandOptions) { |  | ||||||
|         super(options); |  | ||||||
|         this.aliases = options?.aliases || []; |  | ||||||
|         this.originalCommandName = null; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| // If you use promises, use this function to display the error in chat.
 |  | ||||||
| // Case #1: await $.channel.send(""); --> Automatically caught by Command.execute().
 |  | ||||||
| // Case #2: $.channel.send("").catch(handler.bind($)); --> Manually caught by the user.
 |  | ||||||
| // TODO: Find a way to catch unhandled rejections automatically, forgoing the need for this.
 |  | ||||||
| export function handler(this: CommandMenu, error: Error) { |  | ||||||
|     if (this) |  | ||||||
|         this.channel.send( |  | ||||||
|             `There was an error while trying to execute that command!\`\`\`${error.stack ?? error}\`\`\`` |  | ||||||
|         ); |  | ||||||
|     else |  | ||||||
|         console.warn( |  | ||||||
|             "No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!" |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|     console.error(error); |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ import {loadableCommands} from "./loader"; | ||||||
| import {Permissions, Message} from "discord.js"; | import {Permissions, Message} from "discord.js"; | ||||||
| import {getPrefix} from "./structures"; | import {getPrefix} from "./structures"; | ||||||
| import {Config} from "./structures"; | import {Config} from "./structures"; | ||||||
|  | import {CHANNEL_TYPE} from "./command"; | ||||||
| 
 | 
 | ||||||
| // For custom message events that want to cancel the command handler on certain conditions.
 | // For custom message events that want to cancel the command handler on certain conditions.
 | ||||||
| const interceptRules: ((message: Message) => boolean)[] = [(message) => message.author.bot]; | const interceptRules: ((message: Message) => boolean)[] = [(message) => message.author.bot]; | ||||||
|  | @ -19,17 +20,16 @@ client.on("message", async (message) => { | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     const {author, channel, content, guild, member} = message; | ||||||
|  | 
 | ||||||
|     // Continue if the bot has permission to send messages in this channel.
 |     // Continue if the bot has permission to send messages in this channel.
 | ||||||
|     if ( |     if (channel.type === "dm" || channel.permissionsFor(client.user!)!.has(Permissions.FLAGS.SEND_MESSAGES)) { | ||||||
|         message.channel.type === "dm" || |         const text = content; | ||||||
|         message.channel.permissionsFor(client.user!)!.has(Permissions.FLAGS.SEND_MESSAGES) |         const prefix = getPrefix(guild); | ||||||
|     ) { |  | ||||||
|         const text = message.content; |  | ||||||
|         const prefix = getPrefix(message.guild); |  | ||||||
| 
 | 
 | ||||||
|         // First, test if the message is just a ping to the bot.
 |         // First, test if the message is just a ping to the bot.
 | ||||||
|         if (new RegExp(`^<@!?${client.user!.id}>$`).test(text)) { |         if (new RegExp(`^<@!?${client.user!.id}>$`).test(text)) { | ||||||
|             message.channel.send(`${message.author}, my prefix on this guild is \`${prefix}\`.`); |             channel.send(`${author}, my prefix on this guild is \`${prefix}\`.`); | ||||||
|         } |         } | ||||||
|         // Then check if it's a normal command.
 |         // Then check if it's a normal command.
 | ||||||
|         else if (text.startsWith(prefix)) { |         else if (text.startsWith(prefix)) { | ||||||
|  | @ -41,25 +41,36 @@ client.on("message", async (message) => { | ||||||
| 
 | 
 | ||||||
|                 // Send the arguments to the command to resolve and execute.
 |                 // Send the arguments to the command to resolve and execute.
 | ||||||
|                 // TMP[MAKE SURE TO REPLACE WITH command.execute WHEN FINISHED]
 |                 // TMP[MAKE SURE TO REPLACE WITH command.execute WHEN FINISHED]
 | ||||||
|                 const result = await command.actualExecute(args, { |                 const result = await command.execute( | ||||||
|                     author: message.author, |                     args, | ||||||
|                     channel: message.channel, |                     { | ||||||
|                     client: message.client, |                         author, | ||||||
|                     guild: message.guild, |                         channel, | ||||||
|                     member: message.member, |                         client, | ||||||
|                     message: message |                         guild, | ||||||
|                 }); |                         member, | ||||||
|  |                         message, | ||||||
|  |                         args: [] | ||||||
|  |                     }, | ||||||
|  |                     { | ||||||
|  |                         header, | ||||||
|  |                         args, | ||||||
|  |                         permission: 0, | ||||||
|  |                         nsfw: false, | ||||||
|  |                         channelType: CHANNEL_TYPE.ANY | ||||||
|  |                     } | ||||||
|  |                 ); | ||||||
| 
 | 
 | ||||||
|                 // If something went wrong, let the user know (like if they don't have permission to use a command).
 |                 // If something went wrong, let the user know (like if they don't have permission to use a command).
 | ||||||
|                 if (result) { |                 if (result) { | ||||||
|                     message.channel.send(result); |                     channel.send(result); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|         message.author.send( |         author.send( | ||||||
|             `I don't have permission to send messages in ${message.channel}. ${ |             `I don't have permission to send messages in ${channel}. ${ | ||||||
|                 message.member!.hasPermission(Permissions.FLAGS.ADMINISTRATOR) |                 member!.hasPermission(Permissions.FLAGS.ADMINISTRATOR) | ||||||
|                     ? "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended." |                     ? "Because you're a server admin, you have the ability to change that channel's permissions to match if that's what you intended." | ||||||
|                     : "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong." |                     : "Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong." | ||||||
|             }` |             }` | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ export const PermissionLevels: PermissionLevel[] = [ | ||||||
|     { |     { | ||||||
|         // MOD //
 |         // MOD //
 | ||||||
|         name: "Moderator", |         name: "Moderator", | ||||||
|         check: (_, member) => |         check: (_user, member) => | ||||||
|             !!member && |             !!member && | ||||||
|             (member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || |             (member.hasPermission(Permissions.FLAGS.MANAGE_ROLES) || | ||||||
|                 member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || |                 member.hasPermission(Permissions.FLAGS.MANAGE_MESSAGES) || | ||||||
|  | @ -25,12 +25,12 @@ export const PermissionLevels: PermissionLevel[] = [ | ||||||
|     { |     { | ||||||
|         // ADMIN //
 |         // ADMIN //
 | ||||||
|         name: "Administrator", |         name: "Administrator", | ||||||
|         check: (_, member) => !!member && member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) |         check: (_user, member) => !!member && member.hasPermission(Permissions.FLAGS.ADMINISTRATOR) | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         // OWNER //
 |         // OWNER //
 | ||||||
|         name: "Server Owner", |         name: "Server Owner", | ||||||
|         check: (_, member) => !!member && member.guild.ownerID === member.id |         check: (_user, member) => !!member && member.guild.ownerID === member.id | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         // BOT_SUPPORT //
 |         // BOT_SUPPORT //
 | ||||||
|  | @ -52,13 +52,13 @@ export const PermissionLevels: PermissionLevel[] = [ | ||||||
| // After checking the lengths of these three objects, use this as the length for consistency.
 | // After checking the lengths of these three objects, use this as the length for consistency.
 | ||||||
| const length = PermissionLevels.length; | const length = PermissionLevels.length; | ||||||
| 
 | 
 | ||||||
| export function hasPermission(member: GuildMember, permission: number): boolean { | export function hasPermission(user: User, member: GuildMember | null, permission: number): boolean { | ||||||
|     for (let i = length - 1; i >= permission; i--) if (PermissionLevels[i].check(member.user, member)) return true; |     for (let i = length - 1; i >= permission; i--) if (PermissionLevels[i].check(user, member)) return true; | ||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export function getPermissionLevel(member: GuildMember): number { | export function getPermissionLevel(user: User, member: GuildMember | null): number { | ||||||
|     for (let i = length - 1; i >= 0; i--) if (PermissionLevels[i].check(member.user, member)) return i; |     for (let i = length - 1; i >= 0; i--) if (PermissionLevels[i].check(user, member)) return i; | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue