Mallory - The Discord Bot
Discord is a community based instant messaging platform. It features servers which are synonymous to groups and a server can have multiple channels where users can send text messages (text-channels) or communicate via voice (voice-channels).
Discord supports creation of bots which users in a server can interact with by sending messages in the text channels.
Mallory is a Discord bot which treats messages begining with the .
prefix as commands. For instance .help
is a command which when sent on a text channel, Mallory will respond with a list of commands that it can respond to.
Disclaimer: This is not a tutorial on how you can make a Discord bot but rather a high-level perspective of how Discord bots work. Links to tutorials I used are included at the end of the post.
Event-Driven Programming
Discord provides a library which enables developers to create bots. The bots are simply console applications/scripts running in the cloud (or even locally) and can be written in Python or Javascript.
Via Discord’s developer portal, you can create a bot which will act as a new user and then you can add it to your server.
Each message sent on a text channel is viewed as an event and the bot listens for such events.
To listen to an events the following function signature is used. When an “event” is emmited the callback function is called. More on callbacks can be found on my previous post
client.on("event",function(){
// do something
})
const { Client } = require('discord.js');
const client = new Client();
client.on('message',(message)=>{
let content = message.content;
let authorID = message.author.id;
})
The client
variable represents the library provided by Discord which enables you to interact with your bot. When a chat is sent on a text channel the message
event is emmited and using the code above you can handle such an event.
The content
variable represents the message sent in the chat and authorID
is the user who sent the message.
Commands
To be able to differentiate conversations with bot commands, it is best to use a prefix for your commands. In this case, I have used the .
prefix. All messages begining with a .
will be treated as a command for the bot to respond to.
client.on('message',(message)=>{
let content = message.content;
// check is message sent was a command or not
if(content.startsWith(".")){
let cmd = content.slice(1,content.length)
switch(cmd){
case "help":
message.channel.send("")
case "..":
default :
message.channel.send("Command not found")
}
}
})
The above snippet takes the message sent and checks if it begins with the .
prefix. The command is then extracted using the slice()
method. slice()
is used to extract a section of a string by specifying a starting point(inclusive) and an end point (exclusive).
.|help|
So .help
becomes help
.
Using the switch
statement, commands can be mapped to a certain action. For instance the help
command can trigger the bot to reply with a list of commands it can respond to.
The bot sends a reply using the message.channel.send()
function.
Mallory Commands
I worked on Mallory during the lock-down period and at the time classes had moved online. Therefore, a number of the commands were centered on notifying me when class was about to start and when the next class was.
The .next
command would trigger Mallory to respond with details about the next class of the day.
Discord bots can also be used to make a server interactive by allowing users to play simple chat games for instance a riddle game.
The Hangman Game
Due to the complexity of this command, I implemented it using Object Oriented Programming. In hangman, players guess letters randomly until they either guess the correct word or run out of chances. Some key attributes of the game are:
- The word being guessed
- Correctly guessed letters
- How many chances players have
- The currently guessed letters
class Hangman{
constructor(word,channel){
this.word = word; // represents word being guessed
this.channel = channel; // text-channel the game is being played on
this.wrongCount = 0; // number of failed attempts so far
this.guesses = []; // letters guessed so far
this.wordProgress = []; // portion of word guessed
this.isFinished = false; // game status
}
Initializing the game
[...this.word].forEach(c => {
this.wordProgress.push(`šµ`)
});
To initialize the game the following code is also included within the constructor.
The word being guessed for instance “dog” is broken into its respective letters and then each letter is replaced with “šµ” to represent a blank space. So “dog” becomes “šµšµšµ” and this is stored in the this.wordProgress
array as individual characters.
Guessing letters
When a player guesses a letter the guess()
method is called and the letter is passed to it as an argument.
guess(letter){
// Check is letter has already been guessed
if(this.guesses.includes(letter)){
this.channel.send(`already guessed letter \`${letter}\``);
return;
// Check if guessed letter is part of the word
}else if(!this.word.includes(letter)){
this.wrongCount++;
}
// Check if players have run out of guessing chances
if(this.wrongCount >= 6){
this.isFinished = true;
this.channel.send(`Game over. Word was: ${this.word}`);
return;
}
}
If the guessed letter is part of the word, we need to figure out which position the letter is in the word. For instance if the word is apple and someone guesses p, we need to update “šµšµšµšµšµ” to “šµ p pšµšµ”.
for (let i = 0; i < word.length; i++) {
if(this.word[i] == letter){
this.wordProgress[i] = `${letter.toUpperCase()}`;
}
}
Looping through the word e.g apple
, we compare each letter with the guessed letter and if they match, replace the “šµ” character at that index of this.wordProgress
array with the guessed letter.
this.wordProgress
array before guessing p
[šµ,šµ,šµ,šµ,šµ]
this.wordProgress
array after guessing p
[šµ, p , p ,šµ,šµ]
Game completion
Finally, to check if the game is complete, we need to compare the word being guessed and the correct letters guessed so far (stored in the this.wordProgress
array as characters). This should be done with each guess.
If the word progress is equal to the word being guessed the game is won.
if(this.wordProgress.toString()== this.word.toUpperCase()){
this.channel.send("Game won")
}
array.toString()
method converts our array of letters into a single string. [a,p,p,l,e]
becomes apple
Playing the game
To start a game the .hangman
command can be used and .guess [letter]
command for guessing
client.on('message',(message)=>{
let content = message.content;
if(content.startsWith(".")){
let cmd = content.slice(1,content.length)
// split "guess a" to an array: ["guess","a"]
// Then extract the guessed letter at index 1 of the array
let guess = cmd.split(" ")[1]
// OR
let guess = cmd.split(" ")
guess = guess[1]
switch(cmd){
case "hangman":
// pass the word to be guessed and channel where the bot will be responding to
// (same channel the command was typed in for this case)
let game = new Hangman("apple",message.channel)
case "guess":
game.guess(guess)
}
}
})
Other commands
Mallory could also join Voice channels and play music from Youtube which was made possible using discord-youtube-api and node-ytdl-core libraries.
Below are the commands which can be used to interact with the music player
Adding music to the queue and showing what tracks are currently in the queue.
Dicord bots are a good addition to any server since they provide users with fun activities making a server more active.
Cheers.
Links
The source code can be found on my Github Repository
The tutorial I used in learning how to create a Discord bot can be found here
NodeJS discord bots data-structures javascript
4fbf3c8 @ 2021-12-08