Discord provides us developers a vast playground to play with their lots of API endpoints and amazing libraries like Discord JS make it easy for Javascript programmers to build Discord bot in their favorite, fastly adopting and growing language. Today we'll be going to learn how to build a Discord bot. We aren't just going to build a simple Hello World like bots, we'll build a bot to solve a massive real world problem in the Discord gaming communities, our bot will solve the following problem:

1 player post in a Discord gaming server that he needs 2 more players for Trio but eventually sometime 5-7 players reply him in DM or sometime no one, our bot will be going to solve this problem.

We called our bot Teamate, it'll have the following main features:

  1. Ask 3 questions in a channel chat and collect a player response.
  2. Show those responses in a second channel as a Discord Embed and gather other willing to play people emojis.
  3. Send messages in DM of the player to notify him/her that these many people are interesting in playing with you.

Now lets start coding it, first of all we need few prerequisite:

  • Node JS
  • MongoDB Atlas
  • Hosting

You can cut down few things if you are just learning, so intead of MongoDB Atlas you can download their community version in your PC and instead of Hosting you may just use your PC to run your bot.

After installing and configuring Node JS in your computer create a folder called Teamate (or anything else you want your bot to be called), visit Discord Developer Portal tap on New Application name it as your decided bot name, click on Bot under Settings, click on Add Bot, on the prompt modal click on Yes, do it, remember this place to Copy your token, token is a sort of random string which we'll use to login as a bot owner through coding, please never share the token.

Open command line (cmd) in your bot PC folder and write npm init, it's asking you to insert package name just hit enter, it now ask for version again hit enter and it's good to hit enter 6-8 times until you view it asking for Is this OK? (yes) type yes and hit enter, package.json will be created into your folder. Now type npm install discord.js into the cmd, hit enter to install Discord JS. After installation completed type npm install mongoose to install mongoose into your folder, mongoose helps us to control our MongoDB through Javascript coding.

After installation completed of mongoose create a index.js file into your folder and open it in your code editor (VScode is recommended). I'll provide the whole code in a GitHub at the end to copy and use, so right now just understand below and upcoming code, in this way you'll have good knowledge of bot development and less work to write code.

                            const Discord =  require('discord.js');
                                const mongoose = require('mongoose');
                            
                        

In the above code block we are importing both packages/libraries as a starting lines in our index.js file, we had installed both of these packages through npm which is stored in our node_modules folder and referenced in our package.json file.

Visit MongoDB, create your account, and follow Part 1-4 from this MongoDB docs, we now have to connect to our cluster as Part 5 says but we are going to use our mongoose instead of the MongoDb docs suggested to use MongoDB Node.js driver because mongoose is built on top of MongoDB Node.js driver and have more features and functionalities.

                            const Schema = mongoose.Schema;
                                dbURI = process.env.dbURI;
                                mongoose.connect(dbURI, { useNewUrlParser: true, useUnifiedTopology: true })
                                    .then((result) => console.log('MongoDB connected!'))
                                    .catch((err) => console.log(err))
                                
                                const teamateSchema = new Schema({
                                    guildID: {
                                        type: String,
                                        required: true
                                    },
                                    guildName: {
                                        type: String,
                                        required: true
                                    },
                                    embedId: {
                                        type: String,
                                        required: true
                                    },
                                }, {timestamps: true})
                                const Teamate = mongoose.model('Teamate', teamateSchema);
                            
                        

We assign a constant to mongoose Schema, and you'll see process.env in this bot development two times first one with dbURI and second with Discord Bot token at the end of this index.js file, any secret value needs to be stored in a .env file or in the Hosting's env and can be called through Example: process.env.secretName, you may find your dbURI by signing in in MongoDB website, inside Databases click on Connect button alongside with your Cluster Name, tap on Connect your application and copy the string under Add your connection string into your application code, we'll store dbURI and Discord token into our Hosting. We did mongoose.connect and pass our dbURI and an object as a parameter to make connection with our MongoDB through code. Then we show the result of our connection and catch any error if occur during the connection. We created a teamate Schema, it's a Collections of data which includes guildID, guildName and embedId and stored into our database with timestamp of when this data is created. We make a model called Teamate which takes teamateSchema and will be used to query data to and from the database.

                            const client = new Discord.Client();
                                const prefix = '!';
                                const botId = '835829445167939644';
                                client.once('ready', () => {
                                    console.log('Teamate is online!');
                                });
                            
                        

Client provides functionalities to get the state of Discord, we are using exclamation mark as a prefix to distinguish our bot with other bots in a Discord server and you may use other strings if you want. You can get your botId which is actually your APPLICATION ID inside General information of your Application in Discord developer portal. We check if the client is ready and show our bot is online in the console to confirm our coding till now is working or not.

                            client.on('message', message => {
                                if(!message.content.startsWith(prefix) || message.author.bot) return;
                                const args = message.content.slice(prefix.length).split(/ +/);
                                const command = args.shift();
                            
                        

We scan every message from every channel in the Discord server, if it's not started with our exclamation mark prefix or also not a message from our bot then we'll stop scanning that message because it's some general message and not for our bot then we'll start scanning next message if it's meant for our bot then we'll slice it up, split it and shift it to get the message send from a player removing our prefix from it and name it as a command.

                            if(command.startsWith("tset")){
                                embedId = command.split("set")[1];
                                const teamate = new Teamate({
                                    guildID: message.guild.id,
                                    guildName: message.guild.name,
                                    embedId: embedId
                                })
                                teamate.save();
                            }
                        

tset (full form: teamate set) is a command Discord server owners will use after integrating our bot into their server to set teamate embed to a specific channel in their server, so teamate bot only send embed in that specific channel instead of spamming embed in any or every channel. We are checking if the user send a tset command followed by ! mark at the start and channel id at the end like !tset81325342646345 in the server chat, if yes then we'll save it into our database, server id as guildID, server name as guildName and channel id as embedId.

                            if(command.startsWith("team")){
                                Teamate.find({ guildID: message.guild.id }).sort({'createdAt':-1}).limit(1)
                                .exec(function(err, teamates){
                                    if(err){
                                        console.log('Error while connecting finding data');
                                    }else{
                                        if(teamates.length === 0){
                                            message.channel.send('Use !tset following with Embed Channel ID first then use !team command');
                                        }
                            
                        

team is a command used by a player to start the question answer phase, it's is the second and last command we build for this bot. We find and check if guildID set before through tset command if not our bot will send a message to tell the server to use !tset first then use !team.

                            else{
                                console.log('new', teamates.length);
                                const embedChannelId = teamates[0].embedId;
                                const questions = [
                                    'Which mode do you want to play? (Eg: Trio Creative Map)',
                                    'How many players do you need? (Eg: 2)',
                                    'Minimum rank/level of players you are looking for? (Eg: Arena 4000 Points, 1 Cash Cup win)',
                                    'Congratulation! Now in any moment you\'ll start receiving DMs from players'
                                ]
                                let counter = 0
                                const filter = m => m.author.id === message.author.id;
                                const collector = new Discord.MessageCollector(message.channel, filter, {
                                    max: questions.length,
                                    time: 1000 * 30
                                })
                                message.channel.send(questions[counter++])
                                collector.on('collect', m => {
                                    if(counter < questions.length){
                                        m.channel.send(questions[counter++])
                                    }
                                })
                            
                        

If we find the guildID we take embedChannelId from the database and send all three questions one by one. We create a counter to send one message at a time and filter response so it only collects answers coming from player.

                            collector.on('end', collected => {
                                console.log(`Collected ${collected.size} messages`);
                                let counter = 0
                                let collectorQuestions = [];
                                collected.forEach((value) => {
                                    collectorQuestions.push(value.content);
                                    console.log(questions[counter++], value.content)
                                });
                                const mainEmbed = new Discord.MessageEmbed()
                                    .setColor('#0099ff')
                                    .addFields(
                                        { name: 'Game Mode', value: collectorQuestions[0] },
                                        { name: 'Number of Players', value: collectorQuestions[1] },
                                        { name: 'Eligibility', value: collectorQuestions[2] },
                                    )
                                    .setTimestamp()
                                    .setFooter('Teamate', 'https://i.ibb.co/4MDQFq5/Teamate.png');
                                client.channels.cache.get(embedChannelId).send(mainEmbed)
                            
                        

After a certain time or when all questions has been answered we create an embed in the embed channel with the questions and answers collected from the player.

                            .then(embedMessage => {
                                        embedMessage.react("✅");
                                        const filter = (reaction, user) => {
                                            return reaction.emoji.name === '✅' && user.id != message.author.id && user.id != botId;
                                        };
                                        const collector = embedMessage.createReactionCollector(filter, { max: collectorQuestions[1], time: 1000 * 30 });
                        
                                        collector.on('collect', (reaction, user) => {
                                            if(reaction.count <= collectorQuestions[1] + 1){
                                                message.author.send(`Hi, <@${user.id}> (${user.id}) is interested in playing with you, just send him/her a DM`);
                                            }
                                            console.log(`Collected ${reaction.emoji.name} from ${user.tag}`);
                                        });
                                        
                                        collector.on('end', collected => {
                                            console.log(`Collected ${collected.size} items`);
                                        });
                                    })
                                })}}})}});
                                client.login(process.env.token);
                            
                        

Attaching an emoji help us to collect willing to play users' interest, if a user is interested in playing with the player, bot will send a message with the user id to let the player know about that specific user, so both users and player can play the multiplayer game together! Through Teamate bot the number of emojis reacted let server users know how many player already show the interest, if player needs two users for a trio and already 2 people reacted then they know that the slot is full, they react on the next embed, and if emoji has zero reaction then the server users know that this person needs two more players and the slot is empty for them. Bot only send DM to the player until the slot is empty then it's stopped, it help us to stop spam DM to the player.

Create a file called Procfile under your bot PC folder and write the below code into it.

                            worker: node index.js
                        

Hosting provider we are going to use is Heroku, worker help us to keep our bot running in the Heroku hosting. Signup in GitHub and upload the bot folder into a new repository (repo). Signup in Heroku, inside Dashboard click on New then Create new app, insert the bot name into the App name input field, select Create new pipeline inside Choose a pipeline, name pipeline something like teamate-pipeline and change staging to production, click on Create app, under Pipeline > Production, click you app, inside Deploy > Deployment method, choose GitHub and connect it with Heroku and deploy your teamate repo. In Settings tab > Config Vars, Add two configs first one's KEY is dbURI and VALUE is yours dbURI string you got from MongoDB website, and second config KEY is token and VALUE is yours Discord Application ID you got from Discord developer portal.

To integrate your bot into a Discord server visit Discord Permissions Calculator, insert your Application ID into Client ID field beneath OAuth URL Generator and copy the Link, open this link into your browser and integrate your bot into your server, share this link, so other can also integrate and use your bot into their servers.

Hope you guys like this tutorial and learn something new and helpful from it.

Teamate GitHub Source Code

Hire Us For Discord Bot Development