mirror of
				https://github.com/keanuplayz/TravBot-v3.git
				synced 2024-08-15 02:33:12 +00:00 
			
		
		
		
	Removed lenient command handling
This commit is contained in:
		
							parent
							
								
									15012c7d17
								
							
						
					
					
						commit
						3798c27df9
					
				
					 14 changed files with 228 additions and 201 deletions
				
			
		|  | @ -11,7 +11,8 @@ | |||
| - Various changes to core | ||||
| 	- 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 | ||||
| 	- Added a `RestCommand` type, declaratively states that the following command will do `args.join(" ")`, preventing any other subcommands from being added | ||||
| 	- Is no longer lenient to arguments when no proper subcommand fits (now it doesn't silently fail anymore), you now have to explicitly declare a `RestCommand` to get an arbitrary number of arguments | ||||
| 
 | ||||
| # 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. | ||||
|  |  | |||
|  | @ -26,7 +26,6 @@ const responses = [ | |||
| 
 | ||||
| export default new NamedCommand({ | ||||
|     description: "Answers your question in an 8-ball manner.", | ||||
|     endpoint: false, | ||||
|     usage: "<question>", | ||||
|     run: "Please provide a question.", | ||||
|     any: new Command({ | ||||
|  |  | |||
|  | @ -62,7 +62,7 @@ export const BetCommand = new NamedCommand({ | |||
| 
 | ||||
|                         // handle invalid target
 | ||||
|                         if (target.id == author.id) return send("You can't bet Mons with yourself!"); | ||||
|                         else if (target.bot && process.argv[2] !== "dev") return send("You can't bet Mons with a bot!"); | ||||
|                         else if (target.bot && !IS_DEV_MODE) return send("You can't bet Mons with a bot!"); | ||||
| 
 | ||||
|                         // handle invalid amount
 | ||||
|                         if (amount <= 0) return send("You must bet at least one Mon!"); | ||||
|  |  | |||
|  | @ -128,7 +128,7 @@ export const PayCommand = new NamedCommand({ | |||
|                     else if (sender.money < amount) | ||||
|                         return send("You don't have enough Mons for that.", getMoneyEmbed(author)); | ||||
|                     else if (target.id === author.id) return send("You can't send Mons to yourself!"); | ||||
|                     else if (target.bot && process.argv[2] !== "dev") return send("You can't send Mons to a bot!"); | ||||
|                     else if (target.bot && !IS_DEV_MODE) return send("You can't send Mons to a bot!"); | ||||
| 
 | ||||
|                     sender.money -= amount; | ||||
|                     receiver.money += amount; | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import {Command, NamedCommand} from "../../core"; | ||||
| import {Command, NamedCommand, RestCommand} from "../../core"; | ||||
| 
 | ||||
| const letters: {[letter: string]: string[]} = { | ||||
|     a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""), | ||||
|  | @ -35,7 +35,6 @@ export default new NamedCommand({ | |||
|     description: "Transforms your text into vietnamese.", | ||||
|     usage: "thonk ([text])", | ||||
|     async run({send, message, channel, guild, author, member, client, args}) { | ||||
|         if (args.length > 0) phrase = args.join(" "); | ||||
|         const msg = await send(transform(phrase)); | ||||
|         msg.createReactionCollector( | ||||
|             (reaction, user) => { | ||||
|  | @ -44,5 +43,17 @@ export default new NamedCommand({ | |||
|             }, | ||||
|             {time: 60000} | ||||
|         ); | ||||
|     } | ||||
|     }, | ||||
|     any: new RestCommand({ | ||||
|         async run({send, message, channel, guild, author, member, client, args, combined}) { | ||||
|             const msg = await send(transform(combined)); | ||||
|             msg.createReactionCollector( | ||||
|                 (reaction, user) => { | ||||
|                     if (user.id === author.id && reaction.emoji.name === "❌") msg.delete(); | ||||
|                     return false; | ||||
|                 }, | ||||
|                 {time: 60000} | ||||
|             ); | ||||
|         } | ||||
|     }) | ||||
| }); | ||||
|  |  | |||
|  | @ -293,7 +293,7 @@ export default new NamedCommand({ | |||
|                 }); | ||||
|                 send("Activity set to default."); | ||||
|             }, | ||||
|             any: new Command({ | ||||
|             any: new RestCommand({ | ||||
|                 description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, | ||||
|                 async run({send, message, channel, guild, author, member, client, args}) { | ||||
|                     const type = args[0]; | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import {Command, NamedCommand} from "../core"; | ||||
| import {Command, NamedCommand, RestCommand} from "../core"; | ||||
| 
 | ||||
| export default new NamedCommand({ | ||||
|     async run({send, message, channel, guild, author, member, client, args}) { | ||||
|  |  | |||
|  | @ -1,11 +1,11 @@ | |||
| import {Command, NamedCommand} from "../../core"; | ||||
| import {Command, NamedCommand, RestCommand} from "../../core"; | ||||
| import {processEmoteQueryFormatted} from "./modules/emote-utils"; | ||||
| 
 | ||||
| export default new NamedCommand({ | ||||
|     description: | ||||
|         "Send the specified emote list. Enter + to move an emote list to the next line, - to add a space, and _ to add a zero-width space.", | ||||
|     run: "Please provide a list of emotes.", | ||||
|     any: new Command({ | ||||
|     any: new RestCommand({ | ||||
|         description: "The emote(s) to send.", | ||||
|         usage: "<emotes...>", | ||||
|         async run({send, guild, channel, message, args}) { | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| import {GuildEmoji, MessageEmbed, User} from "discord.js"; | ||||
| import {Command, NamedCommand, paginate, SendFunction} from "../../core"; | ||||
| import {Command, NamedCommand, RestCommand, paginate, SendFunction} from "../../core"; | ||||
| import {split} from "../../lib"; | ||||
| import vm from "vm"; | ||||
| 
 | ||||
|  | @ -11,7 +11,7 @@ export default new NamedCommand({ | |||
|     async run({send, message, channel, guild, author, member, client, args}) { | ||||
|         displayEmoteList(client.emojis.cache.array(), send, author); | ||||
|     }, | ||||
|     any: new Command({ | ||||
|     any: new RestCommand({ | ||||
|         description: | ||||
|             "Filters emotes by via a regular expression. Flags can be added by adding a dash at the end. For example, to do a case-insensitive search, do %prefix%lsemotes somepattern -i", | ||||
|         async run({send, message, channel, guild, author, member, client, args}) { | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import {Command, NamedCommand} from "../../core"; | ||||
| import {Command, NamedCommand, RestCommand} from "../../core"; | ||||
| import {Message, Channel, TextChannel} from "discord.js"; | ||||
| import {processEmoteQueryArray} from "./modules/emote-utils"; | ||||
| 
 | ||||
|  | @ -6,109 +6,112 @@ export default new NamedCommand({ | |||
|     description: | ||||
|         "Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.", | ||||
|     usage: 'react <emotes...> (<distance / message ID / "Copy ID" / "Copy Message Link">)', | ||||
|     async run({send, message, channel, guild, author, member, client, args}) { | ||||
|         let target: Message | undefined; | ||||
|         let distance = 1; | ||||
|     run: "You need to enter some emotes first.", | ||||
|     any: new RestCommand({ | ||||
|         async run({send, message, channel, guild, author, member, client, args}) { | ||||
|             let target: Message | undefined; | ||||
|             let distance = 1; | ||||
| 
 | ||||
|         if (message.reference) { | ||||
|             // If the command message is a reply to another message, use that as the react target.
 | ||||
|             target = await channel.messages.fetch(message.reference.messageID!); | ||||
|         } | ||||
|         // handles reacts by message id/distance
 | ||||
|         else if (args.length >= 2) { | ||||
|             const last = args[args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator.
 | ||||
|             const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,}))$/; | ||||
|             const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/; | ||||
|             if (message.reference) { | ||||
|                 // If the command message is a reply to another message, use that as the react target.
 | ||||
|                 target = await channel.messages.fetch(message.reference.messageID!); | ||||
|             } | ||||
|             // handles reacts by message id/distance
 | ||||
|             else if (args.length >= 2) { | ||||
|                 const last = args[args.length - 1]; // Because this is optional, do not .pop() unless you're sure it's a message link indicator.
 | ||||
|                 const URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,}))$/; | ||||
|                 const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/; | ||||
| 
 | ||||
|             // https://discord.com/channels/<Guild ID>/<Channel ID>/<Message ID> ("Copy Message Link" Button)
 | ||||
|             if (URLPattern.test(last)) { | ||||
|                 const match = URLPattern.exec(last)!; | ||||
|                 const guildID = match[1]; | ||||
|                 const channelID = match[2]; | ||||
|                 const messageID = match[3]; | ||||
|                 let tmpChannel: Channel | undefined = channel; | ||||
|                 // https://discord.com/channels/<Guild ID>/<Channel ID>/<Message ID> ("Copy Message Link" Button)
 | ||||
|                 if (URLPattern.test(last)) { | ||||
|                     const match = URLPattern.exec(last)!; | ||||
|                     const guildID = match[1]; | ||||
|                     const channelID = match[2]; | ||||
|                     const messageID = match[3]; | ||||
|                     let tmpChannel: Channel | undefined = channel; | ||||
| 
 | ||||
|                 if (guild?.id !== guildID) { | ||||
|                     try { | ||||
|                         guild = await client.guilds.fetch(guildID); | ||||
|                     } catch { | ||||
|                         return send(`\`${guildID}\` is an invalid guild ID!`); | ||||
|                     if (guild?.id !== guildID) { | ||||
|                         try { | ||||
|                             guild = await client.guilds.fetch(guildID); | ||||
|                         } catch { | ||||
|                             return send(`\`${guildID}\` is an invalid guild ID!`); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
| 
 | ||||
|                 if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID); | ||||
|                 if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); | ||||
|                     if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID); | ||||
|                     if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); | ||||
| 
 | ||||
|                 if (message.id !== messageID) { | ||||
|                     try { | ||||
|                         target = await (tmpChannel as TextChannel).messages.fetch(messageID); | ||||
|                     } catch { | ||||
|                         return send(`\`${messageID}\` is an invalid message ID!`); | ||||
|                     if (message.id !== messageID) { | ||||
|                         try { | ||||
|                             target = await (tmpChannel as TextChannel).messages.fetch(messageID); | ||||
|                         } catch { | ||||
|                             return send(`\`${messageID}\` is an invalid message ID!`); | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     args.pop(); | ||||
|                 } | ||||
|                 // <Channel ID>-<Message ID> ("Copy ID" Button)
 | ||||
|                 else if (copyIDPattern.test(last)) { | ||||
|                     const match = copyIDPattern.exec(last)!; | ||||
|                     const channelID = match[1]; | ||||
|                     const messageID = match[2]; | ||||
|                     let tmpChannel: Channel | undefined = channel; | ||||
| 
 | ||||
|                 args.pop(); | ||||
|             } | ||||
|             // <Channel ID>-<Message ID> ("Copy ID" Button)
 | ||||
|             else if (copyIDPattern.test(last)) { | ||||
|                 const match = copyIDPattern.exec(last)!; | ||||
|                 const channelID = match[1]; | ||||
|                 const messageID = match[2]; | ||||
|                 let tmpChannel: Channel | undefined = channel; | ||||
|                     if (tmpChannel.id !== channelID) tmpChannel = guild?.channels.cache.get(channelID); | ||||
|                     if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); | ||||
| 
 | ||||
|                 if (tmpChannel.id !== channelID) tmpChannel = guild?.channels.cache.get(channelID); | ||||
|                 if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); | ||||
| 
 | ||||
|                 if (message.id !== messageID) { | ||||
|                     try { | ||||
|                         target = await (tmpChannel as TextChannel).messages.fetch(messageID); | ||||
|                     } catch { | ||||
|                         return send(`\`${messageID}\` is an invalid message ID!`); | ||||
|                     if (message.id !== messageID) { | ||||
|                         try { | ||||
|                             target = await (tmpChannel as TextChannel).messages.fetch(messageID); | ||||
|                         } catch { | ||||
|                             return send(`\`${messageID}\` is an invalid message ID!`); | ||||
|                         } | ||||
|                     } | ||||
| 
 | ||||
|                     args.pop(); | ||||
|                 } | ||||
|                 // <Message ID>
 | ||||
|                 else if (/^\d{17,}$/.test(last)) { | ||||
|                     try { | ||||
|                         target = await channel.messages.fetch(last); | ||||
|                     } catch { | ||||
|                         return send(`No valid message found by the ID \`${last}\`!`); | ||||
|                     } | ||||
| 
 | ||||
|                 args.pop(); | ||||
|             } | ||||
|             // <Message ID>
 | ||||
|             else if (/^\d{17,}$/.test(last)) { | ||||
|                 try { | ||||
|                     target = await channel.messages.fetch(last); | ||||
|                 } catch { | ||||
|                     return send(`No valid message found by the ID \`${last}\`!`); | ||||
|                     args.pop(); | ||||
|                 } | ||||
|                 // The entire string has to be a number for this to match. Prevents leaCheeseAmerican1 from triggering this.
 | ||||
|                 else if (/^\d+$/.test(last)) { | ||||
|                     distance = parseInt(last); | ||||
| 
 | ||||
|                 args.pop(); | ||||
|                     if (distance >= 0 && distance <= 99) args.pop(); | ||||
|                     else return send("Your distance must be between 0 and 99!"); | ||||
|                 } | ||||
|             } | ||||
|             // The entire string has to be a number for this to match. Prevents leaCheeseAmerican1 from triggering this.
 | ||||
|             else if (/^\d+$/.test(last)) { | ||||
|                 distance = parseInt(last); | ||||
| 
 | ||||
|                 if (distance >= 0 && distance <= 99) args.pop(); | ||||
|                 else return send("Your distance must be between 0 and 99!"); | ||||
|             if (!target) { | ||||
|                 // Messages are ordered from latest to earliest.
 | ||||
|                 // You also have to add 1 as well because fetchMessages includes your own message.
 | ||||
|                 target = ( | ||||
|                     await message.channel.messages.fetch({ | ||||
|                         limit: distance + 1 | ||||
|                     }) | ||||
|                 ).last(); | ||||
|             } | ||||
| 
 | ||||
|             for (const emote of processEmoteQueryArray(args)) { | ||||
|                 // Even though the bot will always grab *some* emote, the user can choose not to keep that emote there if it isn't what they want
 | ||||
|                 const reaction = await target!.react(emote); | ||||
| 
 | ||||
|                 // This part is called with a promise because you don't want to wait 5 seconds between each reaction.
 | ||||
|                 setTimeout(() => { | ||||
|                     // This reason for this null assertion is that by the time you use this command, the client is going to be loaded.
 | ||||
|                     reaction.users.remove(client.user!); | ||||
|                 }, 5000); | ||||
|             } | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (!target) { | ||||
|             // Messages are ordered from latest to earliest.
 | ||||
|             // You also have to add 1 as well because fetchMessages includes your own message.
 | ||||
|             target = ( | ||||
|                 await message.channel.messages.fetch({ | ||||
|                     limit: distance + 1 | ||||
|                 }) | ||||
|             ).last(); | ||||
|         } | ||||
| 
 | ||||
|         for (const emote of processEmoteQueryArray(args)) { | ||||
|             // Even though the bot will always grab *some* emote, the user can choose not to keep that emote there if it isn't what they want
 | ||||
|             const reaction = await target!.react(emote); | ||||
| 
 | ||||
|             // This part is called with a promise because you don't want to wait 5 seconds between each reaction.
 | ||||
|             setTimeout(() => { | ||||
|                 // This reason for this null assertion is that by the time you use this command, the client is going to be loaded.
 | ||||
|                 reaction.users.remove(client.user!); | ||||
|             }, 5000); | ||||
|         } | ||||
| 
 | ||||
|         return; | ||||
|     } | ||||
|     }) | ||||
| }); | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import {Command, NamedCommand} from "../../core"; | ||||
| import {Command, NamedCommand, RestCommand} from "../../core"; | ||||
| import {streamList} from "../../modules/streamNotifications"; | ||||
| 
 | ||||
| export default new NamedCommand({ | ||||
|  | @ -8,12 +8,11 @@ export default new NamedCommand({ | |||
| 
 | ||||
|         if (streamList.has(userID)) { | ||||
|             const stream = streamList.get(userID)!; | ||||
|             const description = args.join(" ") || "No description set."; | ||||
|             stream.description = description; | ||||
|             stream.description = "No description set."; | ||||
|             stream.update(); | ||||
|             send(`Successfully set the stream description to:`, { | ||||
|                 embed: { | ||||
|                     description, | ||||
|                     description: "No description set.", | ||||
|                     color: member!.displayColor | ||||
|                 } | ||||
|             }); | ||||
|  | @ -21,5 +20,25 @@ export default new NamedCommand({ | |||
|             // Alternatively, I could make descriptions last outside of just one stream.
 | ||||
|             send("You can only use this command when streaming."); | ||||
|         } | ||||
|     } | ||||
|     }, | ||||
|     any: new RestCommand({ | ||||
|         async run({send, message, channel, guild, author, member, client, args, combined}) { | ||||
|             const userID = author.id; | ||||
| 
 | ||||
|             if (streamList.has(userID)) { | ||||
|                 const stream = streamList.get(userID)!; | ||||
|                 stream.description = combined; | ||||
|                 stream.update(); | ||||
|                 send(`Successfully set the stream description to:`, { | ||||
|                     embed: { | ||||
|                         description: stream.description, | ||||
|                         color: member!.displayColor | ||||
|                     } | ||||
|                 }); | ||||
|             } else { | ||||
|                 // Alternatively, I could make descriptions last outside of just one stream.
 | ||||
|                 send("You can only use this command when streaming."); | ||||
|             } | ||||
|         } | ||||
|     }) | ||||
| }); | ||||
|  |  | |||
|  | @ -1,35 +1,43 @@ | |||
| import {Command, NamedCommand} from "../../core"; | ||||
| import {Command, NamedCommand, RestCommand} from "../../core"; | ||||
| import translate from "translate-google"; | ||||
| 
 | ||||
| export default new NamedCommand({ | ||||
|     description: "Translates your input.", | ||||
|     usage: "<lang ID> <input>", | ||||
|     async run({send, message, channel, guild, author, member, client, args}) { | ||||
|         const lang = args[0]; | ||||
|         const input = args.slice(1).join(" "); | ||||
|         translate(input, { | ||||
|             to: lang | ||||
|         }) | ||||
|             .then((res) => { | ||||
|                 send({ | ||||
|                     embed: { | ||||
|                         title: "Translation", | ||||
|                         fields: [ | ||||
|                             { | ||||
|                                 name: "Input", | ||||
|                                 value: `\`\`\`${input}\`\`\`` | ||||
|                             }, | ||||
|                             { | ||||
|                                 name: "Output", | ||||
|                                 value: `\`\`\`${res}\`\`\`` | ||||
|     run: "You need to specify a language to translate to.", | ||||
|     any: new Command({ | ||||
|         run: "You need to enter some text to translate.", | ||||
|         any: new RestCommand({ | ||||
|             async run({send, message, channel, guild, author, member, client, args}) { | ||||
|                 const lang = args[0]; | ||||
|                 const input = args.slice(1).join(" "); | ||||
|                 translate(input, { | ||||
|                     to: lang | ||||
|                 }) | ||||
|                     .then((res) => { | ||||
|                         send({ | ||||
|                             embed: { | ||||
|                                 title: "Translation", | ||||
|                                 fields: [ | ||||
|                                     { | ||||
|                                         name: "Input", | ||||
|                                         value: `\`\`\`${input}\`\`\`` | ||||
|                                     }, | ||||
|                                     { | ||||
|                                         name: "Output", | ||||
|                                         value: `\`\`\`${res}\`\`\`` | ||||
|                                     } | ||||
|                                 ] | ||||
|                             } | ||||
|                         ] | ||||
|                     } | ||||
|                 }); | ||||
|             }) | ||||
|             .catch((error) => { | ||||
|                 console.error(error); | ||||
|                 send(`${error}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes`); | ||||
|             }); | ||||
|     } | ||||
|                         }); | ||||
|                     }) | ||||
|                     .catch((error) => { | ||||
|                         console.error(error); | ||||
|                         send( | ||||
|                             `${error}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes` | ||||
|                         ); | ||||
|                     }); | ||||
|             } | ||||
|         }) | ||||
|     }) | ||||
| }); | ||||
|  |  | |||
|  | @ -73,23 +73,15 @@ interface CommandMenu { | |||
| 
 | ||||
| interface CommandOptionsBase { | ||||
|     readonly description?: string; | ||||
|     readonly endpoint?: boolean; | ||||
|     readonly usage?: string; | ||||
|     readonly permission?: number; | ||||
|     readonly nsfw?: boolean; | ||||
|     readonly channelType?: CHANNEL_TYPE; | ||||
| } | ||||
| 
 | ||||
| interface CommandOptionsEndpoint { | ||||
|     readonly endpoint: true; | ||||
|     readonly run?: (($: CommandMenu) => Promise<any>) | string; | ||||
| } | ||||
| 
 | ||||
| // Prevents subcommands from being added by compile-time.
 | ||||
| // Also, contrary to what you might think, channel pings do still work in DM channels.
 | ||||
| // Role pings, maybe not, but it's not a big deal.
 | ||||
| interface CommandOptionsNonEndpoint { | ||||
|     readonly endpoint?: false; | ||||
| interface CommandOptions extends CommandOptionsBase { | ||||
|     readonly run?: (($: CommandMenu) => Promise<any>) | string; | ||||
|     readonly subcommands?: {[key: string]: NamedCommand}; | ||||
|     readonly channel?: Command; | ||||
|  | @ -103,11 +95,14 @@ interface CommandOptionsNonEndpoint { | |||
|     readonly any?: Command | RestCommand; | ||||
| } | ||||
| 
 | ||||
| type CommandOptions = CommandOptionsBase & (CommandOptionsEndpoint | CommandOptionsNonEndpoint); | ||||
| type NamedCommandOptions = CommandOptions & {aliases?: string[]; nameOverride?: string}; | ||||
| type RestCommandOptions = CommandOptionsBase & { | ||||
|     run?: (($: CommandMenu & {readonly combined: string}) => Promise<any>) | string; | ||||
| }; | ||||
| interface NamedCommandOptions extends CommandOptions { | ||||
|     readonly aliases?: string[]; | ||||
|     readonly nameOverride?: string; | ||||
| } | ||||
| 
 | ||||
| interface RestCommandOptions extends CommandOptionsBase { | ||||
|     readonly run?: (($: CommandMenu & {readonly combined: string}) => Promise<any>) | string; | ||||
| } | ||||
| 
 | ||||
| interface ExecuteCommandMetadata { | ||||
|     readonly header: string; | ||||
|  | @ -164,7 +159,6 @@ abstract class BaseCommand { | |||
| 
 | ||||
| // 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; | ||||
|  | @ -182,31 +176,20 @@ export class Command extends BaseCommand { | |||
| 
 | ||||
|     constructor(options?: CommandOptions) { | ||||
|         super(options); | ||||
|         this.endpoint = !!options?.endpoint; | ||||
|         this.run = options?.run || "No action was set on this command!"; | ||||
|         this.subcommands = new Collection(); // Populate this collection after setting subcommands.
 | ||||
|         this.channel = null; | ||||
|         this.role = null; | ||||
|         this.emote = null; | ||||
|         this.message = null; | ||||
|         this.user = null; | ||||
|         this.guild = null; | ||||
|         this.channel = options?.channel || null; | ||||
|         this.role = options?.role || null; | ||||
|         this.emote = options?.emote || null; | ||||
|         this.message = options?.message || null; | ||||
|         this.user = options?.user || null; | ||||
|         this.guild = options?.guild || null; | ||||
|         this.id = null; | ||||
|         this.idType = null; | ||||
|         this.number = null; | ||||
|         this.any = 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; | ||||
|         this.idType = options?.id || null; | ||||
|         this.number = options?.number || null; | ||||
|         this.any = options?.any || null; | ||||
| 
 | ||||
|         if (options) | ||||
|             switch (options.id) { | ||||
|                 case "channel": | ||||
|                     this.id = this.channel; | ||||
|  | @ -232,30 +215,29 @@ export class Command extends BaseCommand { | |||
|                     requireAllCasesHandledFor(options.id); | ||||
|             } | ||||
| 
 | ||||
|             if (options.subcommands) { | ||||
|                 const baseSubcommands = Object.keys(options.subcommands); | ||||
|         if (options?.subcommands) { | ||||
|             const baseSubcommands = Object.keys(options.subcommands); | ||||
| 
 | ||||
|                 // Loop once to set the base subcommands.
 | ||||
|                 for (const name in options.subcommands) this.subcommands.set(name, options.subcommands[name]); | ||||
|             // Loop once to set the base subcommands.
 | ||||
|             for (const name in options.subcommands) this.subcommands.set(name, options.subcommands[name]); | ||||
| 
 | ||||
|                 // Then loop again to make aliases point to the base subcommands and warn if something's not right.
 | ||||
|                 // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object.
 | ||||
|                 for (const name in options.subcommands) { | ||||
|                     const subcmd = options.subcommands[name]; | ||||
|                     subcmd.name = name; | ||||
|                     const aliases = subcmd.aliases; | ||||
|             // Then loop again to make aliases point to the base subcommands and warn if something's not right.
 | ||||
|             // This shouldn't be a problem because I'm hoping that JS stores these as references that point to the same object.
 | ||||
|             for (const name in options.subcommands) { | ||||
|                 const subcmd = options.subcommands[name]; | ||||
|                 subcmd.name = name; | ||||
|                 const aliases = subcmd.aliases; | ||||
| 
 | ||||
|                     for (const alias of aliases) { | ||||
|                         if (baseSubcommands.includes(alias)) | ||||
|                             console.warn( | ||||
|                                 `"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)` | ||||
|                             ); | ||||
|                         else if (this.subcommands.has(alias)) | ||||
|                             console.warn( | ||||
|                                 `Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)` | ||||
|                             ); | ||||
|                         else this.subcommands.set(alias, subcmd); | ||||
|                     } | ||||
|                 for (const alias of aliases) { | ||||
|                     if (baseSubcommands.includes(alias)) | ||||
|                         console.warn( | ||||
|                             `"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)` | ||||
|                         ); | ||||
|                     else if (this.subcommands.has(alias)) | ||||
|                         console.warn( | ||||
|                             `Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)` | ||||
|                         ); | ||||
|                     else this.subcommands.set(alias, subcmd); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | @ -319,9 +301,6 @@ export class Command extends BaseCommand { | |||
|             return null; | ||||
|         } | ||||
| 
 | ||||
|         // 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) 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).
 | ||||
|         const isMessageLink = patterns.messageLink.test(param); | ||||
|  | @ -506,11 +485,15 @@ export class Command extends BaseCommand { | |||
|         } else if (this.any instanceof RestCommand) { | ||||
|             metadata.symbolicArgs.push("<...>"); | ||||
|             args.unshift(param); | ||||
|             menu.args.push(...args); | ||||
|             return this.any.execute(args.join(" "), 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); | ||||
|             metadata.symbolicArgs.push(`"${param}"`); | ||||
|             return { | ||||
|                 content: `No valid command sequence matching \`${metadata.header} ${metadata.symbolicArgs.join( | ||||
|                     " " | ||||
|                 )}\` found.` | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         // Note: Do NOT add a return statement here. In case one of the other sections is missing
 | ||||
|  | @ -726,7 +709,9 @@ export class RestCommand extends BaseCommand { | |||
|         } else { | ||||
|             // Then capture any potential errors.
 | ||||
|             try { | ||||
|                 await this.run({...menu, combined}); | ||||
|                 // Args will still be kept intact. A common pattern is popping some parameters off the end then doing some branching.
 | ||||
|                 // That way, you can still declaratively mark an argument list as continuing while also handling the individual args.
 | ||||
|                 await this.run({...menu, args: menu.args, combined}); | ||||
|             } catch (error) { | ||||
|                 const errorMessage = error.stack ?? error; | ||||
|                 console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); | ||||
|  |  | |||
|  | @ -21,8 +21,7 @@ const lastCommandInfo: { | |||
| const defaultMetadata = { | ||||
|     permission: 0, | ||||
|     nsfw: false, | ||||
|     channelType: 0, // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet
 | ||||
|     symbolicArgs: [] | ||||
|     channelType: 0 // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet
 | ||||
| }; | ||||
| 
 | ||||
| // Note: client.user is only undefined before the bot logs in, so by this point, client.user cannot be undefined.
 | ||||
|  | @ -67,7 +66,8 @@ export function attachMessageHandlerToClient(client: Client) { | |||
|                 const result = await command.execute(args, menu, { | ||||
|                     header, | ||||
|                     args: [...args], | ||||
|                     ...defaultMetadata | ||||
|                     ...defaultMetadata, | ||||
|                     symbolicArgs: [] | ||||
|                 }); | ||||
| 
 | ||||
|                 // If something went wrong, let the user know (like if they don't have permission to use a command).
 | ||||
|  | @ -104,7 +104,8 @@ export function attachMessageHandlerToClient(client: Client) { | |||
|                     const result = await command.execute(args, menu, { | ||||
|                         header, | ||||
|                         args: [...args], | ||||
|                         ...defaultMetadata | ||||
|                         ...defaultMetadata, | ||||
|                         symbolicArgs: [] | ||||
|                     }); | ||||
| 
 | ||||
|                     // If something went wrong, let the user know (like if they don't have permission to use a command).
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue