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 | - Various changes to core | ||||||
| 	- Added `guild` subcommand type (only accessible when `id: "guild"`) | 	- Added `guild` subcommand type (only accessible when `id: "guild"`) | ||||||
| 	- Further reduced `channel.send()` to `send()` because it's used in *every, single, command* | 	- 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) | # 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. | - 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({ | export default new NamedCommand({ | ||||||
|     description: "Answers your question in an 8-ball manner.", |     description: "Answers your question in an 8-ball manner.", | ||||||
|     endpoint: false, |  | ||||||
|     usage: "<question>", |     usage: "<question>", | ||||||
|     run: "Please provide a question.", |     run: "Please provide a question.", | ||||||
|     any: new Command({ |     any: new Command({ | ||||||
|  |  | ||||||
|  | @ -62,7 +62,7 @@ export const BetCommand = new NamedCommand({ | ||||||
| 
 | 
 | ||||||
|                         // handle invalid target
 |                         // handle invalid target
 | ||||||
|                         if (target.id == author.id) return send("You can't bet Mons with yourself!"); |                         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
 |                         // handle invalid amount
 | ||||||
|                         if (amount <= 0) return send("You must bet at least one Mon!"); |                         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) |                     else if (sender.money < amount) | ||||||
|                         return send("You don't have enough Mons for that.", getMoneyEmbed(author)); |                         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.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; |                     sender.money -= amount; | ||||||
|                     receiver.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[]} = { | const letters: {[letter: string]: string[]} = { | ||||||
|     a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""), |     a: "aáàảãạâấầẩẫậăắằẳẵặ".split(""), | ||||||
|  | @ -35,7 +35,6 @@ export default new NamedCommand({ | ||||||
|     description: "Transforms your text into vietnamese.", |     description: "Transforms your text into vietnamese.", | ||||||
|     usage: "thonk ([text])", |     usage: "thonk ([text])", | ||||||
|     async run({send, message, channel, guild, author, member, client, args}) { |     async run({send, message, channel, guild, author, member, client, args}) { | ||||||
|         if (args.length > 0) phrase = args.join(" "); |  | ||||||
|         const msg = await send(transform(phrase)); |         const msg = await send(transform(phrase)); | ||||||
|         msg.createReactionCollector( |         msg.createReactionCollector( | ||||||
|             (reaction, user) => { |             (reaction, user) => { | ||||||
|  | @ -44,5 +43,17 @@ export default new NamedCommand({ | ||||||
|             }, |             }, | ||||||
|             {time: 60000} |             {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."); |                 send("Activity set to default."); | ||||||
|             }, |             }, | ||||||
|             any: new Command({ |             any: new RestCommand({ | ||||||
|                 description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, |                 description: `Select an activity type to set. Available levels: \`[${activities.join(", ")}]\``, | ||||||
|                 async run({send, message, channel, guild, author, member, client, args}) { |                 async run({send, message, channel, guild, author, member, client, args}) { | ||||||
|                     const type = args[0]; |                     const type = args[0]; | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| import {Command, NamedCommand} from "../core"; | import {Command, NamedCommand, RestCommand} from "../core"; | ||||||
| 
 | 
 | ||||||
| export default new NamedCommand({ | export default new NamedCommand({ | ||||||
|     async run({send, message, channel, guild, author, member, client, args}) { |     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"; | import {processEmoteQueryFormatted} from "./modules/emote-utils"; | ||||||
| 
 | 
 | ||||||
| export default new NamedCommand({ | export default new NamedCommand({ | ||||||
|     description: |     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.", |         "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.", |     run: "Please provide a list of emotes.", | ||||||
|     any: new Command({ |     any: new RestCommand({ | ||||||
|         description: "The emote(s) to send.", |         description: "The emote(s) to send.", | ||||||
|         usage: "<emotes...>", |         usage: "<emotes...>", | ||||||
|         async run({send, guild, channel, message, args}) { |         async run({send, guild, channel, message, args}) { | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| import {GuildEmoji, MessageEmbed, User} from "discord.js"; | 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 {split} from "../../lib"; | ||||||
| import vm from "vm"; | import vm from "vm"; | ||||||
| 
 | 
 | ||||||
|  | @ -11,7 +11,7 @@ export default new NamedCommand({ | ||||||
|     async run({send, message, channel, guild, author, member, client, args}) { |     async run({send, message, channel, guild, author, member, client, args}) { | ||||||
|         displayEmoteList(client.emojis.cache.array(), send, author); |         displayEmoteList(client.emojis.cache.array(), send, author); | ||||||
|     }, |     }, | ||||||
|     any: new Command({ |     any: new RestCommand({ | ||||||
|         description: |         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", |             "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}) { |         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 {Message, Channel, TextChannel} from "discord.js"; | ||||||
| import {processEmoteQueryArray} from "./modules/emote-utils"; | import {processEmoteQueryArray} from "./modules/emote-utils"; | ||||||
| 
 | 
 | ||||||
|  | @ -6,109 +6,112 @@ export default new NamedCommand({ | ||||||
|     description: |     description: | ||||||
|         "Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.", |         "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">)', |     usage: 'react <emotes...> (<distance / message ID / "Copy ID" / "Copy Message Link">)', | ||||||
|     async run({send, message, channel, guild, author, member, client, args}) { |     run: "You need to enter some emotes first.", | ||||||
|         let target: Message | undefined; |     any: new RestCommand({ | ||||||
|         let distance = 1; |         async run({send, message, channel, guild, author, member, client, args}) { | ||||||
|  |             let target: Message | undefined; | ||||||
|  |             let distance = 1; | ||||||
| 
 | 
 | ||||||
|         if (message.reference) { |             if (message.reference) { | ||||||
|             // If the command message is a reply to another message, use that as the react target.
 |                 // If the command message is a reply to another message, use that as the react target.
 | ||||||
|             target = await channel.messages.fetch(message.reference.messageID!); |                 target = await channel.messages.fetch(message.reference.messageID!); | ||||||
|         } |             } | ||||||
|         // handles reacts by message id/distance
 |             // handles reacts by message id/distance
 | ||||||
|         else if (args.length >= 2) { |             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 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 URLPattern = /^(?:https:\/\/discord.com\/channels\/(\d{17,})\/(\d{17,})\/(\d{17,}))$/; | ||||||
|             const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/; |                 const copyIDPattern = /^(?:(\d{17,})-(\d{17,}))$/; | ||||||
| 
 | 
 | ||||||
|             // https://discord.com/channels/<Guild ID>/<Channel ID>/<Message ID> ("Copy Message Link" Button)
 |                 // https://discord.com/channels/<Guild ID>/<Channel ID>/<Message ID> ("Copy Message Link" Button)
 | ||||||
|             if (URLPattern.test(last)) { |                 if (URLPattern.test(last)) { | ||||||
|                 const match = URLPattern.exec(last)!; |                     const match = URLPattern.exec(last)!; | ||||||
|                 const guildID = match[1]; |                     const guildID = match[1]; | ||||||
|                 const channelID = match[2]; |                     const channelID = match[2]; | ||||||
|                 const messageID = match[3]; |                     const messageID = match[3]; | ||||||
|                 let tmpChannel: Channel | undefined = channel; |                     let tmpChannel: Channel | undefined = channel; | ||||||
| 
 | 
 | ||||||
|                 if (guild?.id !== guildID) { |                     if (guild?.id !== guildID) { | ||||||
|                     try { |                         try { | ||||||
|                         guild = await client.guilds.fetch(guildID); |                             guild = await client.guilds.fetch(guildID); | ||||||
|                     } catch { |                         } catch { | ||||||
|                         return send(`\`${guildID}\` is an invalid guild ID!`); |                             return send(`\`${guildID}\` is an invalid guild ID!`); | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 } |  | ||||||
| 
 | 
 | ||||||
|                 if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID); |                     if (tmpChannel.id !== channelID) tmpChannel = guild.channels.cache.get(channelID); | ||||||
|                 if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); |                     if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); | ||||||
| 
 | 
 | ||||||
|                 if (message.id !== messageID) { |                     if (message.id !== messageID) { | ||||||
|                     try { |                         try { | ||||||
|                         target = await (tmpChannel as TextChannel).messages.fetch(messageID); |                             target = await (tmpChannel as TextChannel).messages.fetch(messageID); | ||||||
|                     } catch { |                         } catch { | ||||||
|                         return send(`\`${messageID}\` is an invalid message ID!`); |                             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(); |                     if (tmpChannel.id !== channelID) tmpChannel = guild?.channels.cache.get(channelID); | ||||||
|             } |                     if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); | ||||||
|             // <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 (message.id !== messageID) { | ||||||
|                 if (!tmpChannel) return send(`\`${channelID}\` is an invalid channel ID!`); |                         try { | ||||||
| 
 |                             target = await (tmpChannel as TextChannel).messages.fetch(messageID); | ||||||
|                 if (message.id !== messageID) { |                         } catch { | ||||||
|                     try { |                             return send(`\`${messageID}\` is an invalid message ID!`); | ||||||
|                         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(); |                     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}\`!`); |  | ||||||
|                 } |                 } | ||||||
|  |                 // 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(); |             if (!target) { | ||||||
|                 else return send("Your distance must be between 0 and 99!"); |                 // 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"; | import {streamList} from "../../modules/streamNotifications"; | ||||||
| 
 | 
 | ||||||
| export default new NamedCommand({ | export default new NamedCommand({ | ||||||
|  | @ -8,12 +8,11 @@ export default new NamedCommand({ | ||||||
| 
 | 
 | ||||||
|         if (streamList.has(userID)) { |         if (streamList.has(userID)) { | ||||||
|             const stream = streamList.get(userID)!; |             const stream = streamList.get(userID)!; | ||||||
|             const description = args.join(" ") || "No description set."; |             stream.description = "No description set."; | ||||||
|             stream.description = description; |  | ||||||
|             stream.update(); |             stream.update(); | ||||||
|             send(`Successfully set the stream description to:`, { |             send(`Successfully set the stream description to:`, { | ||||||
|                 embed: { |                 embed: { | ||||||
|                     description, |                     description: "No description set.", | ||||||
|                     color: member!.displayColor |                     color: member!.displayColor | ||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
|  | @ -21,5 +20,25 @@ export default new NamedCommand({ | ||||||
|             // Alternatively, I could make descriptions last outside of just one stream.
 |             // Alternatively, I could make descriptions last outside of just one stream.
 | ||||||
|             send("You can only use this command when streaming."); |             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"; | import translate from "translate-google"; | ||||||
| 
 | 
 | ||||||
| export default new NamedCommand({ | export default new NamedCommand({ | ||||||
|     description: "Translates your input.", |     description: "Translates your input.", | ||||||
|     usage: "<lang ID> <input>", |     usage: "<lang ID> <input>", | ||||||
|     async run({send, message, channel, guild, author, member, client, args}) { |     run: "You need to specify a language to translate to.", | ||||||
|         const lang = args[0]; |     any: new Command({ | ||||||
|         const input = args.slice(1).join(" "); |         run: "You need to enter some text to translate.", | ||||||
|         translate(input, { |         any: new RestCommand({ | ||||||
|             to: lang |             async run({send, message, channel, guild, author, member, client, args}) { | ||||||
|         }) |                 const lang = args[0]; | ||||||
|             .then((res) => { |                 const input = args.slice(1).join(" "); | ||||||
|                 send({ |                 translate(input, { | ||||||
|                     embed: { |                     to: lang | ||||||
|                         title: "Translation", |                 }) | ||||||
|                         fields: [ |                     .then((res) => { | ||||||
|                             { |                         send({ | ||||||
|                                 name: "Input", |                             embed: { | ||||||
|                                 value: `\`\`\`${input}\`\`\`` |                                 title: "Translation", | ||||||
|                             }, |                                 fields: [ | ||||||
|                             { |                                     { | ||||||
|                                 name: "Output", |                                         name: "Input", | ||||||
|                                 value: `\`\`\`${res}\`\`\`` |                                         value: `\`\`\`${input}\`\`\`` | ||||||
|  |                                     }, | ||||||
|  |                                     { | ||||||
|  |                                         name: "Output", | ||||||
|  |                                         value: `\`\`\`${res}\`\`\`` | ||||||
|  |                                     } | ||||||
|  |                                 ] | ||||||
|                             } |                             } | ||||||
|                         ] |                         }); | ||||||
|                     } |                     }) | ||||||
|                 }); |                     .catch((error) => { | ||||||
|             }) |                         console.error(error); | ||||||
|             .catch((error) => { |                         send( | ||||||
|                 console.error(error); |                             `${error}\nPlease use the following list: https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes` | ||||||
|                 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 { | interface CommandOptionsBase { | ||||||
|     readonly description?: string; |     readonly description?: string; | ||||||
|     readonly endpoint?: boolean; |  | ||||||
|     readonly usage?: string; |     readonly usage?: string; | ||||||
|     readonly permission?: number; |     readonly permission?: number; | ||||||
|     readonly nsfw?: boolean; |     readonly nsfw?: boolean; | ||||||
|     readonly channelType?: CHANNEL_TYPE; |     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.
 | // 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.
 | // Role pings, maybe not, but it's not a big deal.
 | ||||||
| interface CommandOptionsNonEndpoint { | interface CommandOptions extends CommandOptionsBase { | ||||||
|     readonly endpoint?: false; |  | ||||||
|     readonly run?: (($: CommandMenu) => Promise<any>) | string; |     readonly run?: (($: CommandMenu) => Promise<any>) | string; | ||||||
|     readonly subcommands?: {[key: string]: NamedCommand}; |     readonly subcommands?: {[key: string]: NamedCommand}; | ||||||
|     readonly channel?: Command; |     readonly channel?: Command; | ||||||
|  | @ -103,11 +95,14 @@ interface CommandOptionsNonEndpoint { | ||||||
|     readonly any?: Command | RestCommand; |     readonly any?: Command | RestCommand; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type CommandOptions = CommandOptionsBase & (CommandOptionsEndpoint | CommandOptionsNonEndpoint); | interface NamedCommandOptions extends CommandOptions { | ||||||
| type NamedCommandOptions = CommandOptions & {aliases?: string[]; nameOverride?: string}; |     readonly aliases?: string[]; | ||||||
| type RestCommandOptions = CommandOptionsBase & { |     readonly nameOverride?: string; | ||||||
|     run?: (($: CommandMenu & {readonly combined: string}) => Promise<any>) | string; | } | ||||||
| }; | 
 | ||||||
|  | interface RestCommandOptions extends CommandOptionsBase { | ||||||
|  |     readonly run?: (($: CommandMenu & {readonly combined: string}) => Promise<any>) | string; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| interface ExecuteCommandMetadata { | interface ExecuteCommandMetadata { | ||||||
|     readonly header: string; |     readonly header: string; | ||||||
|  | @ -164,7 +159,6 @@ abstract class BaseCommand { | ||||||
| 
 | 
 | ||||||
| // Each Command instance represents a block that links other Command instances under it.
 | // Each Command instance represents a block that links other Command instances under it.
 | ||||||
| export class Command extends BaseCommand { | 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 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.
 |     // The class will handle checking for null fields.
 | ||||||
|     private run: (($: CommandMenu) => Promise<any>) | string; |     private run: (($: CommandMenu) => Promise<any>) | string; | ||||||
|  | @ -182,31 +176,20 @@ export class Command extends BaseCommand { | ||||||
| 
 | 
 | ||||||
|     constructor(options?: CommandOptions) { |     constructor(options?: CommandOptions) { | ||||||
|         super(options); |         super(options); | ||||||
|         this.endpoint = !!options?.endpoint; |  | ||||||
|         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.channel = null; |         this.channel = options?.channel || null; | ||||||
|         this.role = null; |         this.role = options?.role || null; | ||||||
|         this.emote = null; |         this.emote = options?.emote || null; | ||||||
|         this.message = null; |         this.message = options?.message || null; | ||||||
|         this.user = null; |         this.user = options?.user || null; | ||||||
|         this.guild = null; |         this.guild = options?.guild || null; | ||||||
|         this.id = null; |         this.id = null; | ||||||
|         this.idType = null; |         this.idType = options?.id || null; | ||||||
|         this.number = null; |         this.number = options?.number || null; | ||||||
|         this.any = null; |         this.any = options?.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; |  | ||||||
| 
 | 
 | ||||||
|  |         if (options) | ||||||
|             switch (options.id) { |             switch (options.id) { | ||||||
|                 case "channel": |                 case "channel": | ||||||
|                     this.id = this.channel; |                     this.id = this.channel; | ||||||
|  | @ -232,30 +215,29 @@ export class Command extends BaseCommand { | ||||||
|                     requireAllCasesHandledFor(options.id); |                     requireAllCasesHandledFor(options.id); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (options.subcommands) { |         if (options?.subcommands) { | ||||||
|                 const baseSubcommands = Object.keys(options.subcommands); |             const baseSubcommands = Object.keys(options.subcommands); | ||||||
| 
 | 
 | ||||||
|                 // Loop once to set the base subcommands.
 |             // Loop once to set the base subcommands.
 | ||||||
|                 for (const name in options.subcommands) this.subcommands.set(name, options.subcommands[name]); |             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.
 |             // 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.
 |             // 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) { |             for (const name in options.subcommands) { | ||||||
|                     const subcmd = options.subcommands[name]; |                 const subcmd = options.subcommands[name]; | ||||||
|                     subcmd.name = name; |                 subcmd.name = name; | ||||||
|                     const aliases = subcmd.aliases; |                 const aliases = subcmd.aliases; | ||||||
| 
 | 
 | ||||||
|                     for (const alias of aliases) { |                 for (const alias of aliases) { | ||||||
|                         if (baseSubcommands.includes(alias)) |                     if (baseSubcommands.includes(alias)) | ||||||
|                             console.warn( |                         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.)` |                             `"${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)) |                     else if (this.subcommands.has(alias)) | ||||||
|                             console.warn( |                         console.warn( | ||||||
|                                 `Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)` |                             `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); |                     else this.subcommands.set(alias, subcmd); | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  | @ -319,9 +301,6 @@ export class Command extends BaseCommand { | ||||||
|             return null; |             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),
 |         // 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).
 | ||||||
|         const isMessageLink = patterns.messageLink.test(param); |         const isMessageLink = patterns.messageLink.test(param); | ||||||
|  | @ -506,11 +485,15 @@ export class Command extends BaseCommand { | ||||||
|         } else if (this.any instanceof RestCommand) { |         } else if (this.any instanceof RestCommand) { | ||||||
|             metadata.symbolicArgs.push("<...>"); |             metadata.symbolicArgs.push("<...>"); | ||||||
|             args.unshift(param); |             args.unshift(param); | ||||||
|  |             menu.args.push(...args); | ||||||
|             return this.any.execute(args.join(" "), menu, metadata); |             return this.any.execute(args.join(" "), menu, metadata); | ||||||
|         } else { |         } else { | ||||||
|             // Continue adding on the rest of the arguments if there's no valid subcommand.
 |             metadata.symbolicArgs.push(`"${param}"`); | ||||||
|             menu.args.push(param); |             return { | ||||||
|             return this.execute(args, menu, metadata); |                 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
 |         // 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 { |         } else { | ||||||
|             // Then capture any potential errors.
 |             // Then capture any potential errors.
 | ||||||
|             try { |             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) { |             } catch (error) { | ||||||
|                 const errorMessage = error.stack ?? error; |                 const errorMessage = error.stack ?? error; | ||||||
|                 console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); |                 console.error(`Command Error: ${metadata.header} (${metadata.args.join(", ")})\n${errorMessage}`); | ||||||
|  |  | ||||||
|  | @ -21,8 +21,7 @@ const lastCommandInfo: { | ||||||
| const defaultMetadata = { | const defaultMetadata = { | ||||||
|     permission: 0, |     permission: 0, | ||||||
|     nsfw: false, |     nsfw: false, | ||||||
|     channelType: 0, // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet
 |     channelType: 0 // CHANNEL_TYPE.ANY, apparently isn't initialized at this point yet
 | ||||||
|     symbolicArgs: [] |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // 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.
 | ||||||
|  | @ -67,7 +66,8 @@ export function attachMessageHandlerToClient(client: Client) { | ||||||
|                 const result = await command.execute(args, menu, { |                 const result = await command.execute(args, menu, { | ||||||
|                     header, |                     header, | ||||||
|                     args: [...args], |                     args: [...args], | ||||||
|                     ...defaultMetadata |                     ...defaultMetadata, | ||||||
|  |                     symbolicArgs: [] | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|                 // 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).
 | ||||||
|  | @ -104,7 +104,8 @@ export function attachMessageHandlerToClient(client: Client) { | ||||||
|                     const result = await command.execute(args, menu, { |                     const result = await command.execute(args, menu, { | ||||||
|                         header, |                         header, | ||||||
|                         args: [...args], |                         args: [...args], | ||||||
|                         ...defaultMetadata |                         ...defaultMetadata, | ||||||
|  |                         symbolicArgs: [] | ||||||
|                     }); |                     }); | ||||||
| 
 | 
 | ||||||
|                     // 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).
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue