Welcome back to another Discord BOT tutorial.
Yeah!
I was so excited about this one.
So where I last left off--
I mean, I couldn't possibly have left off
at a better place, where I have a kitten riding
a unicorn with a rainbow in the background.
[KISSES] But I have a bot that does only two things.
I say "!choochoo" to it, and it replies with a random message,
"Choo choo!" back.
I say "!gif," and maybe I feel like looking at a kitten today.
And I get a nice--
[INAUDIBLE]
So how is this working in the code?
Well, if I look at the code, I have this lovely if statement--
if tokens equals "!choochoo", else if tokens index[0] equals
"!gif".
So this is not a super sustainable method.
I mean, if I'm being honest, this is kind of--
I would be happy to just stay with this method, add a few
more commands, a few more else/ifs.
But a much more sort of conventional way
of building a scalable Discord bot with many-- handling
many, many commands is to use some kind of command handler
framework.
And in fact, if you go to the Discord.js guide,
you'll see an entire page here on command handling.
I'm going to use a slightly different method
than the one described here, although you certainly
could use this one as well.
I'm going to follow the methodology
that I learned when I was a guest on Coding Garden
that CJ showed me.
I think it works well, and it's a nice way to handle commands.
So you could check out this GitHub repo
for another version of the example
that I'm going to build right now.
And the core idea, if I--
looking at the code here that I'm about to write,
is that there's almost nothing here.
There's just, like, creating the Discord client.
It's ready.
And when there's a message, ah, send the message
to this thing called the commandHandler.
And that commandHandler is required from ./commands.
What does that even mean?
So this is where I want to start.
What does it mean to, in your Node program,
in your JavaScript file that you've been writing a node,
that I've been writing a node, that's
only ever been in my entire life until now basically
one JavaScript file--
how can I pull code that I save and manage in multiple files?
This is not unlike what I do very commonly with p5.js.
So here's a p5.js sketch of a particle system.
And the way that I manage that in client-side JavaScript
is have index.html where I reference multiple JavaScript
files-- sketch.js for the sketch, main sketch,
particle.js for the particles, system.js
for the particle system code.
So I want to figure out what is the equivalent
of multiple JavaScript files for a Node project.
And the secret to that is right there in this require function.
Now, in truth, require is perhaps
going to become outdated as time passes.
There's now something called an import module
that's part of JavaScript.
From what I understand, it's an experimental feature
in Node.js, using imports instead of require.
So I'm going to stick with require.
And then someday in the future, I look forward to your comments
that all say about how I should be using import
instead of require.
Oh, it's going to be great.
Pressing forward, step number 1, I
want to take this entire gotMessage function
and just put it in another JavaScript file
because I want to be able to work with it separately
from this main index.js file.
Or it's called bot.js.
I called it bot.js.
Creating a new file, I'll call it commands.js.
And I've pasted it in.
And back in bot.js, you can see there's almost nothing left.
Ah, this replies really goes with those commands.
So I should also move this over.
I mean, ultimately, this might have to go in a different place
entirely.
But let's take it out of here right now.
Then the idea is, could I get this gotMessage function
from the other file?
And let's call this commandHandler, right?
I want that to be commandHandler.
And I want to say, const commandHandler equals require.
What do I put here?
So I'm pretty sure the way that it works is for me to reference
the name of the other JavaScript file without .js.
However, if I do this, it's actually
going to look for a proper Node module that's
been installed as part of my Node modules,
like Discord.js or node.fetch.
So I need to tell it, no, no, no, no.
Don't go look in the Node modules folder.
Look locally.
And the way that that is done is with dot slash.
So I'm done, right?
commandHandler equals require ./commands.
I get everything in that other file.
And now I can just say, client dot on message commandHandler.
No!
No, no, no, no, no, no, no, no!
Stop!
I have to add something else.
I have to explicitly state inside
of this file what I want to make available when it is required.
And the way that I do that is saying, module.exports.
So anything that I put inside of module.exports
will be available.
So what do I want to make available?
This gotMessage function.
And there we go--
module.exports equals the function, gotMessage.
Now, ultimately, I don't need that name of the function
anymore.
It's irrelevant.
Basically, when I say, require ./commands,
whatever is in module.exports will now be saved
in commandHandler.
So the name of the variable that's
holding that function here is commandHandler.
So going back to commands, I can get rid of this.
And I think this is all I need to do.
Let's hope that my bot still works.
Let's try running it again.
Appears to be working--
go back to Discord.
And voila, let's check the GIFs because we
can't have enough rainbow GIFs.
Oh, ah, oh, it's not working!
The GIFs aren't working!
What happened?
The fetch is not defined.
So guess what?
The fetch function is being used here in commands.js.
It is not being used here in bot.js.
So I will want to bring over this require
of the Node module, node-fetch.
And there we go.
Oh, we have this beautiful cat riding a unicorn with a rainbow
GIF again.
Now, there's a lot more to say about module.exports.
I'm just scratching the surface here.
You know, I could set it equal to an object that
has multiple properties and multiple functions or use
classes and all sorts of other kinds of JavaScript things
that I export from a particular JavaScript file.
But I'm going to stick with just this basic idea right now
and, in fact, take it the one step further because still,
look, I have this long if statement.
Yes, I get to put this if statement in a separate file.
But how could I maybe handle these commands more elegantly?
And my goal here is to have a separate JavaScript
file for every single command.
So let's think about-- let's maybe add a folder.
I'll create a folder called "commands."
In that folder, let's add a file called choochoo.js and a file
called gif.js.
Now I can require both of those.
So what goes in those JavaScript files?
(LAUGHING) I want to think about this I'm going.
So in choochoo.js, that's the command
that returns a random reply.
So let's take these two lines of code
and just kind of put them in here for safekeeping right now.
And then also go back to commands.js
and take this whole bit of code and put that into a gif.js.
All right, so I've got the code for sending a GIF.
And I've got the code for sending a random reply
in my separate files.
The other thing that I think would be quite helpful
here is for me to think more clearly about what's
in this variable called tokens.
There's really two things.
There's the command, that !choochoo, !gif,
and then there's the arguments.
What comes after that command?
So the command is the first token.
And it's only a valid command if the first character of it
is an exclamation point.
And actually, a better way for me to do this
would be to actually remove that first token from the array
because I don't want it anymore because I
want that array to the left to be the arguments.
So let's say tokens.shift.
Tokens.shift shifts everything in the array over one spot
and removes the first element.
So that command is there.
If the first character is exclamation point,
then also take out that first character,
which I can do by using the substring function to go
from 1 to the end of the string, but if I leave out
the second argument by default.
So this now-- I mean, it's a little bit convoluted.
I'm sure this could be condensed.
But take out the first token, check
to see if its first character is an exclamation point,
and then remove that exclamation point.
And I've got the valid command.
I can get rid of this stuff now at the bottom.
The only thing-- the things that might
be in the command variable now are choochoo or gif.
OK, what do I do?
What if I have an object--
think about this-- called commands?
And in that object, I have something
called choochoo, which is a function that does something.
And I have something called gif, which is
a function that does something.
And maybe that function takes arguments.
So this object acts as something like a lookup table.
Let me look up the function associated with choochoo.
Let me look up the function associated with gif.
So whatever the command is, I can
look up the function associated with it in that object
and call that function with the tokens, which
are the arguments.
Oh boy, doesn't this look terrifying?
Let's think about it this way.
If I had just said choochoo(tokens) and written
some function like this, this would probably
make sense to you.
I'm calling the function, choochoo,
and passing it the tokens.
What am I doing here?
I am doing exactly the same thing,
only the function isn't just called choochoo.
It's called choochoo inside of an object called commands.
And the term, the lookup term, choochoo, that property name,
is inside of the variable called commands.
So this is the equivalent of saying,
commands.choochoo(tokens).
However, I can't do that because command is a variable.
I've got to pass it in with the square brackets.
So you might have to pause the video
and think about this for a bit.
But hopefully, you're following along with me.
And please ask your questions in the comments
if this is confusing.
So let's build these out a little bit more.
And let's just test to see if this system works.
Whatever the command that comes in is,
I should see a console log in the server.
Remember, ultimately, where I'm going
is I've got to execute the code that's in another JavaScript
file, but I'm not there yet.
I just want to have the command come in,
look up the function associated with it, and run that function.
And this was extra extraneous code
that I need to make sure I remove.
Back to Discord, I shouldn't see anything here now.
But I should see a !gif and !choochoo.
And then if I go back to the server,
we see "gif" and "choochoo."
So those functions are being called correctly.
All right.
And just as a reminder, this extra if statement
up here is just because I want this--
I'm testing this bot only in one particular channel.
So this might not at all be necessary for what
you're doing.
If I wanted my bot to participate
in all the channels of a particular server,
I certainly wouldn't have this here.
OK, we are getting close now to the end of this demonstration.
The big next step it's very exciting.
Instead of defining the function right here,
I want to pull it from that other choochoo.js file.
So first, I need to add module.exports to choochoo.js.
And it's as easy as this.
Now, I'm not using the arguments in this.
It's just a random reply.
And of course, I need to get--
I keep moving this around.
But I need to get these replies.
These are part of this particular command.
I'm noticing another major issue that I forgot about.
The reply is being sent to the channel associated
with the original message.
And I don't have access to that message variable here anymore.
So I think it would be important for the commandHandler
to also pass that in.
So not only send the tokens, the actual tokens which
are the arguments, but send--
also pass along that additional message
object which has all of the metadata associated
with the message, so adding that as another property here.
Let's see.
Let's just, before I go on to the GIF,
let's just see if this works now.
Oh no, it doesn't.
It won't.
Why not?
I need to replace this function here with the function
that I'm getting from choochoo itself.
Now, this is kind of silly, ultimately.
When I have a JavaScript object where
the property and the value have the same name, I can use--
what is that called again, enhanced object literals?
I'm going to leave the gif out right now.
So essentially, this is my list of commands.
The function, choochoo, is right in there.
Let's see if this works
Beautiful.
Who should have my bell?
Ding!
[DING]
Next step, let's add the gif command.
So now the commands object has two properties,
choochoo and gif both coming from two separate JavaScript
files.
I need to add the module.exports stuff
to gif.js and the arguments.
Also, once again, this particular command
is using node-fetch, so that should no longer be here.
Instead, it goes here.
And, ah, OK, so the key words are no longer
coming from the tokens.
They're coming from the arguments.
And I don't need to slice out the first one
because that's already been done by the original commands
function.
So this is much simpler now.
I can just say, args.join with a space.
And then I think we're good here.
Let's try running it.
"Await is only valid in an async function."
Ah, of course.
So I need to make sure that this function is modified
with the async keyword.
Back to Discord, and I hope this works.
We already know !choochoo works.
Let's try !gif, and let's take a look at some puppies today.
Ooh, it's not coming.
There's an error.
"Tokens is not defined," gif.js.
What did I miss?
Aha, I joined the args, but I still
check the length of tokens.
So now I just need to check if there are actual arguments
and if the length is greater than 0
because, again, the command is not part of it anymore.
So that should fix it.
Back to Discord.
Let's see those puppies.
Aw.
[KISSES] There we go.
It couldn't be a more fitting end to this particular video.
So hopefully now, just to recap for a moment,
starting back from bot.js, the only thing bot.js does
is connect Discord and then import a commandHandler
function from another JavaScript file.
In that commandHandler JavaScript file,
that file imports other JavaScript files,
one for each command, stores them in an object,
checks to make sure it's a valid message,
and then executes the proper function
associated with that command.
So now if I wanted to add another command,
let's say we're going to look up some information about a word.
Maybe I'll use the Wordnik API.
I definitely want to do an example of a Discord bot
that connects to an API.
I mean, I am doing that with [INAUDIBLE] API,
but some other API.
So if I were to say, like, define--
there's a define command--
all I would need to do is add that particular define
JavaScript file.
That would have to have exactly the same format
of a single function that handles the command,
does everything it needs.
And I would be done.
And then I could keep adding more commands.
So hopefully, if you watch this particular video,
and you're making your own bot, try this
and try adding some more commands.
I should say, however, there is one small potential issue.
You might be making a bot where you imagine having
a hundred-plus commands in it.
If that's the case, having a hundred require statements
is maybe not so great.
So one of the things you could do
is actually read the file system.
So if you have this particular commands folder with a hundred
JavaScript files in it, you can automatically,
with the Node file system package,
just read what all those files are,
and then you have all of your commands that way.
So actually, the only thing you would
need to do to add a new command is just add
another JavaScript file that's formatted
in the same exact way.
Whoo, oh, this is great.
This is going to make bot-making so much more organized
and neat and tidy and all of the wonderful things that I love.
So this is a little challenging to make this video for you
to watch it, perhaps.
I hope this makes sense.
Ask your questions in the comments.
There are more to come.
So what have I said so far I want to do?
I want to make a-- connect to an API just
to show another command.
I think there were some other things I was thinking of.
I'll think of those again.
But I do want to show how to work with embeds.
Embeds, embeds, embeds.
I will figure out how to say that word.
You've probably seen a Discord bot
that shows this kind of replies with this message that's nicely
formatted.
Here's an example one, the Train bot,
if you issued the command !userinfo,
it gives you this nice, sort of organized table, if you will,
box of information.
Another example of this is, if I issue the command, !help,
I'll get a nice explanation of all the kinds of things this
bot can do and what the commands are and tips for using it.
Oh, that's what I should do!
I'll definitely make-- maybe I should
make a video on how to create an embed with a help command.
So that'll be perfect.
So that'll come maybe in the next video.
Yeah, the next video, maybe the Wordnik API thing.
Leave your suggestions in the comments
if the next video doesn't exist yet
because that'll help me figure out where
this journey is taking me.
At some point, I've got to stop and just,
like, leave the Discord bots alone and see
what the universe brings me as little Discord bot
presents the people are making.
But there are a few more things I'd like to show you.
So stay tuned.
And thanks again for watching.
[KISSES] Goodbye, and see you in a future Coding Train video.
[TOOT TOOT]
Ugh.
[MUSIC PLAYING]