My wife is a dedicated Clash of Clans player. From her, I recently learnt about Discord, which is a “All-in-one voice and text chat for gamers”. The gamers seem to have built thriving communities on the app, and my wife is an active member in one of them. She was having a chat with one of her fellow community members about getting a bot going which would auto-wish members on their birthdays. Sounded like a nice li’l weekend project for /me!
A bit of Googling revealed that there are a number of nice libraries to build a bot for Discord, and quite a few for Node.js, the weapon of my choice! I liked discord.io, “a small, single-file library for creating DiscordApp clients from Node.js”. Coupled with a node scheduler library (I like Agenda), I figured the bot can be built quite easily.
Before Starting to Code
You’ll need to register yourself on Discord & login, after which they allow you to create a new “App” inside the Developers Section. Once done, you will be given the option to Create a Bot User for this app. Do that and you will have your bot. You need to note down two things here: the Client ID and the App Bot User Token (click to reveal them). Further, you may tick the Public Bot checkbox. You can also add a cool icon here, which will be the face of your bot.
Now that you have your app and your bot (a pretty boring one though, since it cannot do a thing yet!), it is a good idea to set up a new Discord Server where you can play around with the bot. The process is as easy as clicking on a + button after logging into Discord. Choose a cool name for your server (mine is called Botyard; ain’t that geeky?)
Next, you need to add your bot to your server. Google says there are a number of ways to do this, but the simplest method for me was visiting https://discordapp.com/oauth2/authorize?client_id=CLIENT_ID&scope=bot&permissions=0 in a new tab while you are logged in and choosing your Server name in the following screen. Remember to replace CLIENT_ID with the one you had jotted down previously.
Finally, we need to have a place where we can store our data which is used by Agenda for persisting its schedules. A local running instance of MongoDB will be fine for testing. You’ll need to note down the mongo connection string which is of the format mongodb://:@:
And that’s about all the bootstrapping you’d need to do before you can start infusing life into your bot, viz. the coding.
Coding
First things first: make a new directory to hold your code, create an empty index.js
file (all our code will be written in this) and run npm init
within it. After answering (or skipping) the stock questions, you can go on to install the required dependencies using npm i -S moment agenda discord.io
Now let’s just put in the usual skeleton code to load the libraries and all in the index.js
file (you have it opened in your favorite editor, right?):
const moment = require('moment')
const Agenda = require('agenda')
const agenda = new Agenda({
db: {address: process.env.MONGO_CONNECTION_STRING},
processEvery: '30 minutes'
});
const Discord = require('discord.io');
const bot = new Discord.Client({
autorun: true,
token: process.env.BOT_TOKEN
});
agenda.on('ready', function() {
agenda.start();
});
function graceful() {
agenda.stop(function() {
process.exit(0);
});
}
process.on('SIGTERM', graceful);
process.on('SIGINT' , graceful);
Pretty self-explanatory, right? Note that, we will be passing our secrets (MONGO_CONNECTION_STRING & BOT_TOKEN) to the code using environmental variables at runtime. Also, using the graceful() function is the official Agenda way of ensuring that job processing is resumed post server shutdown/restarts.
Before writing any more code, let us take a step back and strategize a bit. That way, (and if you peruse the code comments), little to no explanation would be required when you see the actual code.
Here’s what we would like our code to achieve:
- Define an Agenda job for sending a birthday wish to a particular user as a message from the Bot
- Setup the bot to listen to messages, and parse the ones in a pre-defined format as a “command” to get a user’s handle and her birthday
- Use the above info to set up an Agenda schedule to wish the particular use on her birthday, and repeat it every year
The first and the third points can be handled easily using the following functions (Modularization is cool, and so is separation of concerns!):
let sendBirthdayMsg = function(user, channelID) {
bot.sendMessage({
to: channelID,
message: `@${user} Happy Birthday to You!!!`
});
}
agenda.define('send birthday wish', function(job, done) {
let data = job.attrs.data;
sendBirthdayMsg(data.whoseBirthday, data.channelID)
done()
});
let scheduleWish = function(whoseBirthday, whenBirthday, channelID) {
agenda
.create('send birthday wish', {whoseBirthday, channelID})
.schedule(whenBirthday)
.repeatEvery('1 year')
.save()
}
Code for handling the second point is a bit longer, but, again, if you read the comments, should be pretty self-explanatory!
bot.on('message', function(user, userID, channelID, message, event) {
if (!message.includes(`<@${bot.id}>`)) return
let botSays = "
let cleanedMsg = message.replace(`<@${bot.id}>`,").replace(/ +(?= )/g,").trim()
let whoseBirthday = cleanedMsg.split(' ')[0]
let whenBirthday = cleanedMsg.split(' ')[1]
let whenBirthdayMoment = moment(whenBirthday, ['MM-DD','DD/MM'])
if ( !whenBirthdayMoment.isValid() ) {
botSays = `@${user} Sorry Boss, I dint get what you just said!`
} else {
if ( whenBirthdayMoment.isBefore(moment(), 'days') ) {
whenBirthdayMoment.add(1, 'year')
}
scheduleWish(whoseBirthday, whenBirthdayMoment.toDate(), channelID)
botSays = `@${user} OK Boss! I'll wish "${whoseBirthday}" every year on ${whenBirthdayMoment.format("Do MMMM")}!`
}
bot.sendMessage({
to: userID,
message: botSays
})
});
So, that’s about it! You can view the completed code at Github.
To run the code, just clone the repo, cd
into the directory, run npm install
and finally issue the command: MONGO_CONNECTION_STRING="mongodb://:@:/" BOT_TOKEN="" node index.js
. Obviously, replace the MONGO_CONNECTION_STRING and BOT_TOKEN values with your own.
Now you can go to your discord channel and chat around with the bot!
You’d notice that some extra stuff has been added in the github code. In particular, it has some random greeting texts instead of the hardcoded one discussed here. Also, not everybody is allowed to “command” the bot; a list of such privileged users who can (I call them bosses) have been incorporated. You just need to add your own User ID to the list.