mirror of
				https://github.com/keanuplayz/TravBot-v3.git
				synced 2024-08-15 02:33:12 +00:00 
			
		
		
		
	Fixed some bugs and added proper event handler
This commit is contained in:
		
							parent
							
								
									5c3896c2db
								
							
						
					
					
						commit
						44cae5c0cb
					
				
					 5 changed files with 99 additions and 21 deletions
				
			
		|  | @ -1,5 +1,5 @@ | ||||||
| import {Command, NamedCommand, loadableCommands, categories, getPermissionName} from "../../core"; | import {Command, NamedCommand, loadableCommands, categories, getPermissionName, CHANNEL_TYPE} from "../../core"; | ||||||
| import {toTitleCase} from "../../lib"; | import {toTitleCase, requireAllCasesHandledFor} from "../../lib"; | ||||||
| 
 | 
 | ||||||
| export default new NamedCommand({ | export default new NamedCommand({ | ||||||
|     description: "Lists all commands. If a command is specified, their arguments are listed as well.", |     description: "Lists all commands. If a command is specified, their arguments are listed as well.", | ||||||
|  | @ -10,14 +10,19 @@ export default new NamedCommand({ | ||||||
|         let output = `Legend: \`<type>\`, \`[list/of/stuff]\`, \`(optional)\`, \`(<optional type>)\`, \`([optional/list/...])\``; |         let output = `Legend: \`<type>\`, \`[list/of/stuff]\`, \`(optional)\`, \`(<optional type>)\`, \`([optional/list/...])\``; | ||||||
| 
 | 
 | ||||||
|         for (const [category, headers] of categories) { |         for (const [category, headers] of categories) { | ||||||
|             output += `\n\n===[ ${toTitleCase(category)} ]===`; |             let tmp = `\n\n===[ ${toTitleCase(category)} ]===`; | ||||||
|  |             // Ignore empty categories, including ["test"].
 | ||||||
|  |             let hasActualCommands = false; | ||||||
| 
 | 
 | ||||||
|             for (const header of headers) { |             for (const header of headers) { | ||||||
|                 if (header !== "test") { |                 if (header !== "test") { | ||||||
|                     const command = commands.get(header)!; |                     const command = commands.get(header)!; | ||||||
|                     output += `\n- \`${header}\`: ${command.description}`; |                     tmp += `\n- \`${header}\`: ${command.description}`; | ||||||
|  |                     hasActualCommands = true; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             if (hasActualCommands) output += tmp; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         channel.send(output, {split: true}); |         channel.send(output, {split: true}); | ||||||
|  | @ -50,6 +55,8 @@ export default new NamedCommand({ | ||||||
|             let append = ""; |             let append = ""; | ||||||
|             command = result.command; |             command = result.command; | ||||||
| 
 | 
 | ||||||
|  |             if (result.args.length > 0) header += " " + result.args.join(" "); | ||||||
|  | 
 | ||||||
|             if (command.usage === "") { |             if (command.usage === "") { | ||||||
|                 const list: string[] = []; |                 const list: string[] = []; | ||||||
| 
 | 
 | ||||||
|  | @ -80,9 +87,24 @@ export default new NamedCommand({ | ||||||
|             return channel.send( |             return channel.send( | ||||||
|                 `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${category}\`\nPermission Required: \`${getPermissionName( |                 `Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${category}\`\nPermission Required: \`${getPermissionName( | ||||||
|                     result.permission |                     result.permission | ||||||
|                 )}\` (${result.permission})\nDescription: ${command.description}\n${append}`, |                 )}\` (${result.permission})\nChannel Type: ${getChannelTypeName(result.channelType)}\nNSFW Only: ${ | ||||||
|  |                     result.nsfw ? "Yes" : "No" | ||||||
|  |                 }\nDescription: ${command.description}\n${append}`,
 | ||||||
|                 {split: true} |                 {split: true} | ||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
|     }) |     }) | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | function getChannelTypeName(type: CHANNEL_TYPE): string { | ||||||
|  |     switch (type) { | ||||||
|  |         case CHANNEL_TYPE.ANY: | ||||||
|  |             return "Any"; | ||||||
|  |         case CHANNEL_TYPE.GUILD: | ||||||
|  |             return "Guild Only"; | ||||||
|  |         case CHANNEL_TYPE.DM: | ||||||
|  |             return "DM Only"; | ||||||
|  |         default: | ||||||
|  |             requireAllCasesHandledFor(type); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -32,7 +32,7 @@ const patterns = { | ||||||
| // Therefore, there won't by any type narrowing on channel or guild of CommandMenu until this is fixed.
 | // Therefore, there won't by any type narrowing on channel or guild of CommandMenu until this is fixed.
 | ||||||
| // Otherwise, you'd have to define channelType for every single subcommand, which would get very tedious.
 | // Otherwise, you'd have to define channelType for every single subcommand, which would get very tedious.
 | ||||||
| // Just use type assertions when you specify a channel type.
 | // Just use type assertions when you specify a channel type.
 | ||||||
| enum CHANNEL_TYPE { | export enum CHANNEL_TYPE { | ||||||
|     ANY, |     ANY, | ||||||
|     GUILD, |     GUILD, | ||||||
|     DM |     DM | ||||||
|  | @ -126,7 +126,6 @@ export class Command { | ||||||
|     protected user: Command | null; |     protected user: Command | null; | ||||||
|     protected number: Command | null; |     protected number: Command | null; | ||||||
|     protected any: Command | null; |     protected any: Command | null; | ||||||
|     public static readonly CHANNEL_TYPE = CHANNEL_TYPE; |  | ||||||
| 
 | 
 | ||||||
|     constructor(options?: CommandOptions) { |     constructor(options?: CommandOptions) { | ||||||
|         this.description = options?.description || "No description."; |         this.description = options?.description || "No description."; | ||||||
|  | @ -182,6 +181,13 @@ export class Command { | ||||||
|         menu: CommandMenu, |         menu: CommandMenu, | ||||||
|         metadata: ExecuteCommandMetadata |         metadata: ExecuteCommandMetadata | ||||||
|     ): Promise<SingleMessageOptions | null> { |     ): 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; | ||||||
|  | 
 | ||||||
|  |         // Take off the leftmost argument from the list.
 | ||||||
|         const param = args.shift(); |         const param = args.shift(); | ||||||
| 
 | 
 | ||||||
|         // If there are no arguments left, execute the current command. Otherwise, continue on.
 |         // If there are no arguments left, execute the current command. Otherwise, continue on.
 | ||||||
|  | @ -239,7 +245,7 @@ export class Command { | ||||||
|                 return null; |                 return null; | ||||||
|             } catch (error) { |             } catch (error) { | ||||||
|                 const errorMessage = error.stack ?? error; |                 const errorMessage = error.stack ?? error; | ||||||
|                 console.error(`Command Error: ${metadata.header} (${metadata.args})\n${errorMessage}`); |                 console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); | ||||||
| 
 | 
 | ||||||
|                 return { |                 return { | ||||||
|                     content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` |                     content: `There was an error while trying to execute that command!\`\`\`${errorMessage}\`\`\`` | ||||||
|  | @ -250,11 +256,6 @@ export class Command { | ||||||
|         // If the current command is an endpoint but there are still some arguments left, don't continue.
 |         // 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 (this.endpoint) return {content: "Too many arguments!"}; | ||||||
| 
 | 
 | ||||||
|         // Update inherited properties if the current command specifies a property.
 |  | ||||||
|         if (this.permission !== -1) metadata.permission = this.permission; |  | ||||||
|         if (this.nsfw !== null) metadata.nsfw = this.nsfw; |  | ||||||
|         if (this.channelType !== null) metadata.channelType = this.channelType; |  | ||||||
| 
 |  | ||||||
|         // Resolve the value of the current command's argument (adding it to the resolved args),
 |         // 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).
 |         // then pass the thread of execution to whichever subcommand is valid (if any).
 | ||||||
|         if (this.subcommands.has(param)) { |         if (this.subcommands.has(param)) { | ||||||
|  | @ -295,6 +296,14 @@ export class Command { | ||||||
|         args: string[], |         args: string[], | ||||||
|         metadata: CommandInfoMetadata |         metadata: CommandInfoMetadata | ||||||
|     ): Promise<CommandInfo | CommandInfoError> { |     ): Promise<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; | ||||||
|  |         if (this.nsfw !== null) metadata.nsfw = this.nsfw; | ||||||
|  |         if (this.channelType !== null) metadata.channelType = this.channelType; | ||||||
|  |         if (this.usage !== "") metadata.usage = this.usage; | ||||||
|  | 
 | ||||||
|  |         // Take off the leftmost argument from the list.
 | ||||||
|         const param = args.shift(); |         const param = args.shift(); | ||||||
| 
 | 
 | ||||||
|         // If there are no arguments left, return the data or an error message.
 |         // If there are no arguments left, return the data or an error message.
 | ||||||
|  | @ -324,12 +333,6 @@ export class Command { | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // Update inherited properties if the current command specifies a property.
 |  | ||||||
|         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; |  | ||||||
| 
 |  | ||||||
|         // Then test if anything fits any hardcoded values, otherwise check if it's a valid keyed subcommand.
 |         // Then test if anything fits any hardcoded values, otherwise check if it's a valid keyed subcommand.
 | ||||||
|         if (param === "<user>") { |         if (param === "<user>") { | ||||||
|             if (this.user) { |             if (this.user) { | ||||||
|  |  | ||||||
|  | @ -1,6 +1,6 @@ | ||||||
| import {client} from "../index"; | import {client} from "../index"; | ||||||
| import {loadableCommands} from "./loader"; | import {loadableCommands} from "./loader"; | ||||||
| import {Permissions, Message} from "discord.js"; | import {Permissions, Message, TextChannel, DMChannel, NewsChannel} from "discord.js"; | ||||||
| import {getPrefix} from "../structures"; | import {getPrefix} from "../structures"; | ||||||
| import {defaultMetadata} from "./command"; | import {defaultMetadata} from "./command"; | ||||||
| 
 | 
 | ||||||
|  | @ -11,6 +11,16 @@ export function addInterceptRule(handler: (message: Message) => boolean) { | ||||||
|     interceptRules.push(handler); |     interceptRules.push(handler); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | const lastCommandInfo: { | ||||||
|  |     header: string; | ||||||
|  |     args: string[]; | ||||||
|  |     channel: TextChannel | DMChannel | NewsChannel | null; | ||||||
|  | } = { | ||||||
|  |     header: "N/A", | ||||||
|  |     args: [], | ||||||
|  |     channel: null | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| // Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined.
 | // Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined.
 | ||||||
| // Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild.
 | // Note: guild.available will never need to be checked because the message starts in either a DM channel or an already-available guild.
 | ||||||
| client.on("message", async (message) => { | client.on("message", async (message) => { | ||||||
|  | @ -41,6 +51,11 @@ client.on("message", async (message) => { | ||||||
|         if (commands.has(header)) { |         if (commands.has(header)) { | ||||||
|             const command = commands.get(header)!; |             const command = commands.get(header)!; | ||||||
| 
 | 
 | ||||||
|  |             // Set last command info in case of unhandled rejections.
 | ||||||
|  |             lastCommandInfo.header = header; | ||||||
|  |             lastCommandInfo.args = [...args]; | ||||||
|  |             lastCommandInfo.channel = channel; | ||||||
|  | 
 | ||||||
|             // Send the arguments to the command to resolve and execute.
 |             // Send the arguments to the command to resolve and execute.
 | ||||||
|             const result = await command.execute(args, menu, { |             const result = await command.execute(args, menu, { | ||||||
|                 header, |                 header, | ||||||
|  | @ -73,6 +88,11 @@ client.on("message", async (message) => { | ||||||
|             if (commands.has(header)) { |             if (commands.has(header)) { | ||||||
|                 const command = commands.get(header)!; |                 const command = commands.get(header)!; | ||||||
| 
 | 
 | ||||||
|  |                 // Set last command info in case of unhandled rejections.
 | ||||||
|  |                 lastCommandInfo.header = header; | ||||||
|  |                 lastCommandInfo.args = [...args]; | ||||||
|  |                 lastCommandInfo.channel = channel; | ||||||
|  | 
 | ||||||
|                 // Send the arguments to the command to resolve and execute.
 |                 // Send the arguments to the command to resolve and execute.
 | ||||||
|                 const result = await command.execute(args, menu, { |                 const result = await command.execute(args, menu, { | ||||||
|                     header, |                     header, | ||||||
|  | @ -98,3 +118,12 @@ client.on("message", async (message) => { | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | process.on("unhandledRejection", (reason: any) => { | ||||||
|  |     if (reason?.name === "DiscordAPIError") { | ||||||
|  |         console.error(`Command Error: ${lastCommandInfo.header} (${lastCommandInfo.args.join(", ")})\n${reason.stack}`); | ||||||
|  |         lastCommandInfo.channel?.send( | ||||||
|  |             `There was an error while trying to execute that command!\`\`\`${reason.stack}\`\`\`` | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| export {Command, NamedCommand} from "./command"; | export {Command, NamedCommand, CHANNEL_TYPE} from "./command"; | ||||||
| export {addInterceptRule} from "./handler"; | export {addInterceptRule} from "./handler"; | ||||||
| export { | export { | ||||||
|     SingleMessageOptions, |     SingleMessageOptions, | ||||||
|  |  | ||||||
|  | @ -22,3 +22,27 @@ attachClientToLavalink(client, { | ||||||
|     helpCmd: "mhelp", |     helpCmd: "mhelp", | ||||||
|     admins: ["717352467280691331"] |     admins: ["717352467280691331"] | ||||||
| }); | }); | ||||||
|  | 
 | ||||||
|  | // Disable the unhandledRejection listener by Lavalink because it captures every single unhandled
 | ||||||
|  | // rejection and adds its message with it. Then replace it with a better, more selective error handler.
 | ||||||
|  | for (const listener of process.listeners("unhandledRejection")) { | ||||||
|  |     if (listener.toString().includes("discord.js-lavalink-musicbot")) { | ||||||
|  |         process.off("unhandledRejection", listener); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | process.on("unhandledRejection", (reason: any) => { | ||||||
|  |     if (reason?.code === "ECONNREFUSED") { | ||||||
|  |         console.error( | ||||||
|  |             `[discord.js-lavalink-musicbot] Caught unhandled rejection: ${reason.stack}\nIf this is causing issues, head to the support server at https://discord.gg/dNN4azK` | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | // It's unsafe to process uncaughtException because after an uncaught exception, the system
 | ||||||
|  | // becomes corrupted. So disable Lavalink from adding a hook to it.
 | ||||||
|  | for (const listener of process.listeners("uncaughtException")) { | ||||||
|  |     if (listener.toString().includes("discord.js-lavalink-musicbot")) { | ||||||
|  |         process.off("uncaughtException", listener); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue