Discord JS Advanced Command Handler (2020) [Episode #22]

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everyone alex here from wardoffkeys.com and in this video i'm going to show you how you can create a more advanced command handler compared to what we have been using throughout the series so if you have been following along you'll be familiar with this command.js file which is a very basic command handler however when it comes to more advanced commands this type of command handler requires us to write a lot of duplicate code and that's not a very good practice so in this video i'm going to show you how you can make a much more user-friendly command handler they'll do a lot more heavy lifting for you especially in those very complicated commands so to get started i'm going to make a new folder here we'll call this commands all of our commands will be located inside of here i'm also going to make a new file called command-based.js inside of here we'll hold most of our logics for actually executing the commands however this file will be ignored whenever we're trying to import all of our commands which we will be doing through this commands folder we'll also be able to add multiple different subfolders inside of here such as moderation folders or other things to organize our command files and all of those files excluding command base will be imported and executed as commands automatically to get started i'm going to import my prefix my command prefix and this will be something that is from the config.json file so constant prefix equals require from config.json i'm then going to export a function so pass in a client and command options which i'll show you what those are in a few moments here so now that we have this i'm going to make a new file this will be called add.js as an example for this video we're going to be creating an addition command and so users be able to pass in two numbers and it'll return the sum so inside of here i'm going to export a json document the first thing will be commands and this can be a string or it can be an array so you can include your command and then additional aliases such as edition after that we're going to add in expected args and this is going to be a string so whenever the user specifies the syntax incorrectly for the command this is going to help us create a correct syntax command so if we say add five but we're expecting add num1 num2 we're then going to go play into the user because they only provided one number and so the string here of expected args is going to be both of these into one string right here and typically i like to wrap my arguments in less than and greater than signs and so you'll see this in action later on for now don't worry about it if it doesn't make too much sense to you next we're going to add a permission error and so this is what's going to happen if a user runs a command but they don't have access to that command so in this exact example we can say you need admin permissions to run this command obviously for an add command that's not necessary but this is just for an example afterwards we're going to have the minimum number of arguments which will be two the maximum number of arguments which will also be two so if the user answers one arguments or four arguments or something that isn't just two it's then going to complain to the user telling them the correct number of arguments to use afterwards we're going to create a callback function and this callback function is going to be ran whenever the command is correctly ran so callback will have three different arguments the first one is the message object the second one is the arguments array and the third one is going to be text which is all of the arguments inside of its own string that's not gonna be too helpful for this exact use case however if you were ever going to create like a ticketing system so like let's say we have tickets and then some message here that could be any number of different arguments because they're inserting just free form text and so in that case we'll probably want to remove the maximum arguments and it'll be very useful to have all that in a string and we will automatically within here however with an ad we don't have to worry about that for now i'm just going to say to do add the numbers i'm not going to actually worry about the functionality now there's a couple more things we want to add that aren't going to be too relevant for this but i just want to let you know that they exist so we can say required roles and this will be an array let's say we have uh there's a math rule that you have to have in order to actually run this command we can also say permissions which would be official discord permissions as an array so both of these are going to be blank arrays we don't actually need anything inside of them because obviously we don't really care what type of permissions or roles you have for an add command however if you were to create moderation commands such as banning or kicking you would want to use these now we can go back to our command base and this object here that we just created this is going to be the command options that is passed in right here now we need to import some of that data i'm actually going to open this to my right over here i'm going to minimize this so we get more room i'm going to actually destructure some of this information into our command base and then actually parse through and run the command we can say let equals command options now inside this object we can say commands we can say expected args and by default this is going to be an empty string permission error by default this is going to say you do not have permission to run this command and so these are the defaults where if you exclude this property from this object or you include an empty string or something like that we're going to assume that you want a permission error and so you can change this to whatever you want the default to be afterwards we're going to have the minimum number of arguments and by default this is going to be zero we're gonna have the maximum arguments by default it's going to be null and i'll explain why in a few minutes once we get to that part we're then going to have permissions there'll be an empty array by default and then required roles which will also be an empty way by default obviously we have a lot of default things here that way if you don't necessarily need a certain feature inside of your options you don't have to add it and so it makes it much easier for us as developers to make new commands in the future finally we want our callback and now we have all the options we want so we need to now go through these options we need to check for if the usage is correct and to make sure that it is ready to be used for actually listening for our commands so how we do that starting off with the actual commands right here is i want this to be able to be passed in as an array like this or as an actual string so i just just add however it needs to be converted into an array if it is a string so we can say ensure the command and aliases are in an array so we can say if type of commands is exactly equal to string we could then say commands equals an array with the only element being commands and so that'll convert that into an array for us after that we need to ensure the permissions are in an array and are all valid so i'm actually going to go above our exported function here and i'm going to create a function called validate permissions this is going to take a permissions object here as a parameter i'm actually going to copy and paste in a array here which is every single valid discord permission as of recording this video you can find this in the video description if you want to copy and paste this also make sure this is permissions and not permission i'm going to copy this and we now want to loop through this array and make sure that every single element in this array actually exists in this array as well so we can say for constant permission of permissions we can then say if not validpermissions.includes will pass in the permission we then want to throw a new error and we'll say with a template literal here unknown permission node and then we'll paste it into some strings so we'll just pass in permission so now if we insert something that doesn't exist like let's say test then it's going to throw an error and that's going to help us catch some errors when it comes to having typos or something like that inside of our permission nodes however if i were to paste in let's say manage emojis into the permissions array it would still work as normal so scrolling down now we need to actually use that and so we also need to make sure that if we pass in a single string here that it gets converted to an array so if we only have one specific permission we can pass in a string that's fine but ultimately it will become an array and that just makes it more convenient for us and so we can say if permissions dot length and this will work for it being a string or an array and so this is essentially just making sure that it exists so if that does exist we want to say if type of permissions is exactly equal to a string now if this is the case we'll do the same exact thing we did before so we can say permissions equals an array with the only element being permissions and then we also want to call validate permissions and pass in this array and this will call the function we made above and make sure that all the permissions we passed in in our options file are actual valid discord permissions now at this point we want to listen for messages so we can say client.on message we have a callback which contains the message and inside we want to destructure a couple properties from the message object so we're going to get access to the member the content and the guild from message we're going to need these throughout some of the functionality here after that we want to loop through every single alias and see if we should handle the command so in this exact case we're going to loop through the commands array and see if the main command and also additional aliases are actually here and so we can say four const alias of commands now here i want to say if content.2 lowercase that starts with here we're going to use a template literal we're going to add in the prefix and then directly afterwards without a space we're going to add an alias.two lowercase so if this is the case a command has been ran there we go need to actually call that correctly so the reason why i'm using two lower case is it allows us to capitalize these if we prefer or if we accidentally do it's not going to break things but more importantly it allows the users to be able to run exclamation point add or exponential point add with different capitals and it's not going to actually break anything it's not going to expect the user to always use lowercase because some users might not whether it's on purpose or accidentally and so we're going to convert both the message to the user sent and also our own command to lower case just for the comparison and so if both of those are the same then that means that we're now running a command before we forget we actually want to return we're going to add some code above this but we want to make sure that we return because there's no need to loop through the rest of the commands because we already know we're supposed to handle this command so now the first couple things we want to do is ensure we have the required permissions and required roles so we can say ensure the user has the required permissions we can actually loop through all the permissions so for constant permission of permissions now inside of here we can say if not member which we destructed above right here so if not member dot has permission and we'll pass in the permission string now if this is the case we want to say message.reply permission error now this is the string that we have right here inside of our options and so you can change this to whatever you want per command however in this exact case we're just going to say you need admin permissions to run this command now because of that i'm actually going to scroll all the way up and we see administrator right here i'm actually going to add that as a required permission now i can do this by adding it inside of the array or i can just pass in the string either way the reason for that is we just wrote the code to make sure that all the permissions get added into an array if they are a string now with that said the permission error makes a little bit more sense and we also want to make sure that we return because we don't want to continue and actually execute the function moving forward we want to ensure the user has the required roles so here we're going to loop through the required roles so for constant required role of required roles and then we're going to say constant role we need to gain access to the role so how i do this is we have to use the guild which we just structured previously right here so we could say guild.roles.cash.find and this is going to give us a callback and this will be ran for every single role until we return true and so we're going to gain access to each role while it's looping through them so if we get a role we can then return true if role.name is exactly equal to a required role so now we want to make sure that the user has this role but we also want to make sure that this rule actually exists if either of those are false then we want to complain to the user so let's say that i were to add a math rule here but i actually forgot to make that rule that would still complain to the user this way it forces the functionality to require that role and so the owner of the discord can then go and make that role so we can say if not role or member dot roles dot cash dot has role dot id you want to say if not role or not this and so this code right here will say if the role doesn't exist or if the member does not have the rule we then want to say message.reply i'll pass in a template literal we'll say you must have the add in quotes here pass in the required role the required role role to use this command then we can return now after this we know that they have all the permissions they need and they have access to all the roles they need in order to use this command next up we want to split our command into an array based off of different spaces so if we have the command add 5 and 10 this will become an array with add 5 and 10. the way we can do this let's add a comment here saying split on any number of spaces the way we can do this is constant arguments equals content dot split and you might think we can just add a space here however if the user adds a bunch of spaces this is going to add a bunch of empty objects here in our element and our array i mean that we don't want so to fix this and to make sure that no matter how many spaces they add we can split on a regular expression to do that we're gonna use two forward slashes inside we're gonna have two square brackets we're gonna have a space and after the square brackets we're gonna add a plus so now this will make it so no matter how many spaces there are in the actual command it'll still always be these three elements in the array now we don't actually care about the exclamation point add and so we can remove the command which is the first index to do that we could say arguments.shift which is a javascript function to remove the first element of an array now we can ensure we have the correct number of arguments so here we can say if arguments.length is less than minimum arcs which is the number we defined right here then we want to explain to the user but we also want to complain to the user if there's too many arguments in this case if there are three or more numbers passed in now in some cases you may not care about a maximum number of arguments let's say that the user is creating a is submitting a ticket using a custom ticketing command then there might send an entire message that might contain 80 arguments and so you might not care about that so that is why we actually have null as a default from max args because zero could be technically the maximum number of arguments in something like a ping command or something like that so we want to make sure that the maximum of arguments is not exactly equal to null and arguments dot length is greater than max rx if this is true or if this is true we're going to complain to the user because i either have too few or too many arguments so here we can say message.reply and add in a template literal say incorrect syntax use and then we're going to add in our prefix directly afterwards we're going to add in the alias and then after that we're going to add a space and we're going to add expected arcs and we can return and so what this is doing is let's say i add let's say i ran add 5 10 20. this is going to say incorrect syntax see here incorrect syntax use add num1 num2 and this is a string that we see on line three of expected args now it's important that we're using the alias here the reason for that is if i were to use the other option which is the addition command it would then say use addition so it's going to match the exact command that they use to not cause confusion so this is just making sure we have the correct number of arguments and finally we can say handle a custom command code and so we can call our callback pass on our message our arguments and now the last argument here is text and that's going to be all of them within a single string and to do that we can say arguments.join with a space so if we had an array that said 5 and 10 this would then combine that into a single string with a space in between each element and so in this exact case that's not too useful we'd rather have access to arguments but in the previous example i gave a few seconds ago of a ticketing system that'll make a lot more sense to have access to this text right here because then you have access to an entire message which then you can store to a database or whatever you want to do with that we can now save command base we should be done with that we can now save add.js and now we want to actually import all the commands within this file right here within this commands folder so we can go into our main file and we need to import two different packages for this these are things that should be included inside of node.js automatically so you don't have to install them we're going to have path we can require the path module and then we're going to have fs so we can require the fs module now inside of here i want to create a variable that will hold the name of our command-based file so constant base file equals command dash base dot js you'll see why we're using this in a few seconds we then want to import command base so const command base equals require we're going to add in a template literal so we're going to import from the commands folder and then we're going to insert in the base file name this is one reason why we have this variable is we're going to use this variable in two different places and that's why we have a variable so we don't have to write this out multiple times now we're going to create a function called read commands this will take in a directory as an argument and we're going to call this function passing in the directory or folder name for your commands mine is just commands so that's what i'm going to pass in here now inside of here the goal is to first get all the files in here and then we're going to see if there's any folders and as we come across folders we're then going to recursively call this function and then try and find all the files inside of those folders the end results of this will be every single file inside of the commands directory whether they're inside of a subdirectory or not will be found and actually read as a command and so we don't have to import any of these manually they'll be all automatically added and so as soon as you make a new command you don't have to worry about running it it will automatically be imported and set up correctly without you having to do anything manually so to get started we can create a files object i set this equal to fs which is one of the packages we imported at the top dot read directory sync this will synchronously read all of the files inside of a directory we have to then pass in the directory to use and so we're going to use path.join this takes in two arguments the first one is der name which is going to be the absolute path if we look at my console here we see the c drive users alex desktop all this stuff here that's what the dirt name is going to be so no matter where your bot is ran that's what it'll use we then have to pass in the directory that we want to target and this will combine the two into whatever the result is and then that'll be passed into the read directory synchronous file or function rather and that is going to return all the files within the directory so in this exact case it'll return add.js and command base.js now let's say that i had a new folder in here let's call this misc and inside i wanted to make a very basic ping command so real quick i'm going to make a new file called ping.js i'm going to make one single command called ping there will not be any expected arguments no permissions minimum arguments with two maximum arguments will be zero the callback will just simply say message.reply pong and we're not going to have permissions or required roles now we just created an entire command just by editing a couple configuration things right here and so this is the advantage to using this command-based system is we don't have to go through and do a lot of the heavy lifting because it's done automatically for us for every command now obviously more advanced commands you will require more things to write in your callback but that's up to your own individual implementation on that command so i'm only making this so you see that the ping command even though it's inside of a subdirectory is still imported as well going back here we can now loop through all of these files so constant file of files now we want to see if the file that we're looping through at the moment is going to be a directory because this is going to return misc add.js and command based.js and so misc is a directory now in order to get that we have to get something called stats so constant stat equals fs.l stat sync we had to pass in path.join passing in the directory name the actual directory and then the file name now we have access to some information this has a function called is directory so if stat dot is directory then we know that this is a directory and this will be true in the case of whenever we're looping through misc so now we want to actually call this function again and so like i mentioned this is going to be a recursive function where it keeps calling itself until it finds all the commands you can pass in read commands pass in file.join dir and file and that's all we need to do it'll then go through and try and find all the files inside of there now we need an else statement and this is going to handle things that are actual files so in this case ping.js.js and command-based.js however we want to make sure that we don't actually care about command-based.js and this is the reason why we made the base file variable so else if file is not exactly equal to base file so now we're only going to care about ping.js and add.js and here we can actually import the options or the command options which will be these this json object inside so we can say const option equals require we can use path.join pass in the door name the directory and then the file we could then console log the options to see what we have but i also want to console the file name so we know which options is for what i can then run the bot with node index.js we see ping.js has this basic json object right here and then scrolling up we see add.js has this and they have their own individual properties as we expected so now i can actually call command base and if we look at the actual implementation of this function with the pass in a client and the command options so i can pass in the client which i already have access to and then the options or option is the name of the variable and so now we can save this and inside the command base i actually want to gain access to the first command here i want to add a console.log saying registering command with quotes let's actually make this a template literal and then inside i want to pass in commands index 0. now if i save this and i restart my bots okay so we get an error saying that this is not a function we need module.exports so if we save that we can then restart we then see registering command add registering command ping and if i go into my testing channel i can try out ping and it replies with pong the reason for that is our callback right here however ad won't do anything and so we can very quickly console log the arguments now if i restart this we are going to see add it's going to complain saying oh i must have the math role so i'm actually going to get rid of that because i don't want to actually go through the process of adding that role so now i can try add and it's saying that i need num1 and num2 so i can try that add 5 and 10. and then it says in the console here an array 5 and 10 and they're add strings so we can actually gain access to these so const num1 equals plus arguments index zero this is one way in javascript to actually convert a string to a number you can say constant num2 equals plus arguments index one and then we can say message.reply with a backtick here we can say the sum is and then we're going to pass in num1 plus num2 so if we save this and we restart the bots we can then actually add those so add 5 and 10 and it says the sum is 15. now if i try to add 5 or 4 8 9 it'll then say incorrect syntax because we're forcing it to only have two arguments and so we can very quickly and easily set up the basics of the commands that way the only thing we have to worry about when actually creating a command is the logic behind the command itself everything else is done for us we only have to write it once now for very simple commands like this like ping and add this doesn't seem like the hugest benefit in the world but when it comes to commands that require permissions or roles or very detailed syntax then this is going to save us a bunch of time thanks for watching this discord.js tutorial if you want to learn more about discord.js consider clicking on the playlist you see on your screen now if you need help feel free to leave a comment or ask in the worn off keys discord which can be found in the video description
Info
Channel: Worn Off Keys
Views: 33,746
Rating: undefined out of 5
Keywords: command handler, folders, files, discord js, javascript, discord, discord bot, creating a discord bot, v12
Id: lbpUc17InkM
Channel Id: undefined
Length: 26min 12sec (1572 seconds)
Published: Sat Jul 18 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.