Building a Slack Bot with Node.js and Chuck Norris Super Powers

If you haven't been living isolated in an igloo with no internet connection during the last year, I am sure you already heard about Slack, the famous real time messaging app for teams. We already talked about it and we even wrote a guide about how to code a complete Slack clone!

Slack has been built to be easy and fun to use and it offers a broad set of APIs that allows developers to extend its capabilities to make it even more useful and funny. One of the features I love from Slack is the Slackbot, a friendly robot available in every Slack team to guide users to create their profiles and to explain them how Slack works. But what I love even more is the possibility to build your own custom bots, special automated users that can respond to specific events and do useful things to help your team.

In this article, we are going to build a custom Slack bot using NodeJs, and we want it to be a funny exercise that should put us in a good mood. For this sake we are going to build the mighty "NorrisBot", a bot that basically kicks asses, or, to put it another way, it brings a bit of Chuck Norris into your Slack team!

Our NorrisBot will be loaded with guns and jokes about Chuck Norris and it will tell a random one every time that someone says "Chuck Norris" in the chatroom.

Get ready, it will be a funny ride!

Slack real time APIs

To create a custom Slack bot or an even more complex application that reacts to what happens in your Slack organization you can use the Slack Real Time messaging API, a websocket-based API that allows you to receive events in real time and send messages to channels, private groups and users. This API is really well constructed and the documentation is clear, but instead of using the websocket directly we can use a very nice NodeJs module that makes things a lot easier, the slackbots module written by Mikhail Mokrushin. With this library we will just need to write some straightforward Javascript code. But before moving to our text editor we need to configure our channel extensions and create a new bot. This way we will obtain the API token that is required to authenticate our bot.

Create a new Bot on your Slack organization

To add a new Bot in your Slack organization you must visit the following url: https://yourorganization.slack.com/services/new/bot, where yourorganization must be substituted with the name of your organization (e.g. https://scotchio.slack.com/services/new/bot). Ensure you are logged to your Slack organization in your browser and you have the admin rights to add a new bot.

In the first step you need to choose a name for your bot. Then you will move to another screen where you will be able to copy your API token:

Copy the token in a safe place and save it, you will need it in a while. In this section you can also specify some more details about your bot, like the name and surname and also add an avatar image to make your bot look even more cooler. If you need an nice Chuck Norris face picture you can find it here.

Setting up the project

Now that we have our API token we can start to setup our NodeJs project. I already created the NorrisBot Github repository with all the code that we are going to write. You can clone it if you would like to save you some keystrokes.

For the sake of brevity I am assuming you already have NodeJs (at least version 0.10) and NPM installed in your machine.

First thing to do is to create our packages.json file with the command

npm init

Follow the guided configuration procedure specifying lib/norrisbot.js under the entry point option, we will create this file in a moment.

Ok, now let's install our dependencies:

npm i --save slackbots@0.3.0 sqlite3

As we said before we will use the module slackbots as abstraction layer to deal with the Slack API but we will also want to use an SQLite database as data source

Note: Be sure to use version 0.3.0 of slackbots as newer version are not perfectly compatible with what's illustrated in this tutorial.

The database

Our SQLite database will allow us to store all the jokes and some configuration data. So we will have basically 2 tables inside it: the jokes table and the info table. The Internet Chuck Norris Database offers a very nice REST API that allows us to fetch a huge number of jokes about Chuck Norris, so I already preconfigured the database file by importing all the jokes from there. You can download the last version of the database from the GitHub repository. You will need to save it under the folder data/ inside your project folder. The script I used to generate the database is available in the GitHub repository as well. It's not a big deal and it's out of the scope of this article, but you can have a look at it if you want.

The Slackbots package

Before starting to write our bot let's have a look at the main functions offered by the slackbots module.

As you can see from the code you have to require the SlackBot constructor, from there you can instantiate a new bot object and attach callbacks to specific events. In the example we use the event start that is triggered when the bot is successfully connected to the Slack server. Then we can use the methods offered by the library to post a message in a channel, to a user as private message or in a private group conversation.

Implementing the bot

Finally we can move to our favourite text editor and create the file lib/norrisbot.js within our project folder. We will follow a slightly different approach compared to the previous example. Indeed instead of creating an instance of the Bot class and listen for its events we will create a new class that inherits from the Bot class. let's see how we can do this:

With the previous snippet of code we basically created the constructor function for our new Javascript class NorrisBot. This class inherits all the methods of the Bot class thanks to the util.inherits function of NodeJs.

In our constructor function we want to setup all the variables that our bot needs. The settings object accepted as parameter is meant to be an extension of the original Bot class, so we expect it to contain a token and a name. Furthermore we want to have the path where our SQLite database is stored (the dbPath attribute) and we are defaulting the bot name to norrisbot.

We are also declaring the variables user and db that we are going to use later to store the current user information and the connection instance to the database.

This function is meant to allow us to instantiate our bot, but it will not connect to the Slack servers unless you explicitly call the run method. This method calls the original constructor of the Bot class and attaches two callback function respectively to the start event and to the message event. The latter event is fired when a real time message is received in the underline websocket connection managed by the Slackbots module.

Ok so we need to write the _onStart and _onMessage functions which are the real core of our NorrisBot.

The _onStart function

When our bot connects to the Slack server we want it to do the following 3 things:

Load all the metadata related to the user representing the bot itself on the current Slack organization

Connect to the SQLite database

Check if it's the first time the bot is executed and if so send a greeting messages to alle the users

This function is very simple. When the original Bot class connects to the Slack server it downloads a list with all the users in the organization and it saves it in the users attribute as an array of objects. We just need to find the object that has the same username of our bot within that array.

The _firstRunCheck method is a little bit more complex than the previous two. We are using the info table (defined as a key-value table) to see if the bot has been previously run. In fact we check if the record with name lastrun already exists in the table, if it exists we just update the timestamp to the current one, otherwise we call the function _welcomeMessage and create a new lastrun record.

We are basically using the function postMessageToChanel of the Bot class. We select the first channel where the Bot is installed. An important detail to notice is the as_user attribute passed in the configuration object. It allows the bot to post the message as itself (the message will be visualized with the avatar of the bot and it's name). You can also post messages with different avatars (also using emojis) or with different authors. Check out the Slack Real Time API documentation to understand how you can configure your messages.

The _onMessage function

Our _onMessage function will intercept every real time API message that is readable by our bot, literally every chat message in the channels where the bot is installed, but also private messages directed to the bot or other real time notifications as notifications of user typing in a channel, edited or deleted messages, users joining or leaving the channel and so on.

So keep in mind that in this case the naming can be a little bit confusing. Real time API messages are not just chat messages, but any kind of event that occurs within our Slack organization.

We want our bot to filter all these events to detect public messages in channels that mentions "Chuck Norris" or the name of our bot, then we want to react to this message by replying with a random joke.

If we want to split all these checks in a list of operations this is exactly what we need to do:

Check if the event represents a chat message

Check if the message has been sent to a channel

Check if the message come from a user that is different from the NorrisBot (to avoid loops)

The function receives a message object as parameter. The message contains all the informations that describes the real time event received through the Slack real time API. As you can see we have several helper functions that allows us to express the checking and processing logic in a more readable way.

The second check allows us to verify if the message is directed to a channel. Almost every real time message contains the attribute channel that is an ID of the channel to which the event occurred. Again in this case the naming is a little bit misleading. The attribute channel refers to any virtual communication channel that can be a "real" chat channel but also a private conversation between two users or a private group conversation. Every communication channel is identified by an alphanumeric ID, and to distinguish between these three cases we can have a look at the first character of the ID, when it starts with a "C" it represents a chat channel.

The third check allows us to see if the message comes from a user who is not the NorrisBot itself. The NorrisBot very often writes messages that contains the string "Chuck Norris" and without this check we would end up with an infinite loop of infinite jokes. Yes, it might be fun at first but I guess anyone will get bored pretty soon!

This function extracts a joke at random from the database and posts it in the channel where the original message was written. Let's spend two minutes to see how the random pick is made and why. Our database jokes table contains three columns: id, joke and used. It's quite obvious from the code that the text of our joke is stored in the joke field, but it's less obvious how the used field works. Well, the used field counts how many times a joke has been told. All the jokes are initialized with the used count to zero, then the counter is incremented every time a joke has been told. This allow us to avoid to repeat the same joke in a short amount of time until all the other jokes has been told the same amount of time. Furthermore this trick makes the random pick a little bit faster than a generic ORDER BY RANDOM().

Notice that we are using another helper function called _getChannelById. Realtime messages reference channels with IDs, but all the functions to post messages uses the name of the channel as parameter, so we need to retrieve the name of the channel given its ID. Here's the code of this last helper function:

The code is clean and simple: we just import our NorrisBot class, we instantiate it and we launch the bot with the run method. It's worth noticing that we are using some environment variables to make our bot configurable:

BOT_API_KEY: this variable is mandatory and must be used to specify the API token needed by the bot to connect to your Slack organization

BOT_DB_PATH: optional variable that allows you to use a different database or to move the default one to a different path

BOT_NAME: the name of your bot, it's optional and it will default to norrisbot

Testing our bot

Ok, now we have everything we need to launch and test our bot. To start our bot we can run the launcher script with node bin/bot.js but we also need to provide the API key with the env variable.

On Linux and Mac OSX you can simply run:

BOT_API_KEY=your_api_key node bin/bot.js

On Windows you should get it working with:

set BOT_API_KEY=your_api_key & node bin/bot.js

If everything went fine you will see your NorrisBot appear in your Slack channel and a greeting message will be posted there. Try to invoke him by name several times and enjoy his jokes!

Ok, now shut down the node process with ctrl+c in your console, you will see your NorrisBot immediately disconnected from the channel.

I guess you don't want to keep the bot process running in your machine all the time so we need to deploy it into a real server to make it always available in your organization.

Deploy the NorrisBot on Heroku

A great place where to deploy our NorrisBot is Heroku. We can go reasonably well with their free worker tier and the deploy process is easy and convenient. Let's see how we can do that.

I am assuming you already have and account on Heroku and that you have installed git and the Heroku toolbelt on your machine.

First thing to do is to create a new Heroku app. Select a unique name for your app and a region and click on the "Create App" button.

Then you need to go on your app settings tab and configure your environment variables.

As you can see in the picture you need to specify the BOT_API_KEY variable with your Slack API key and, if you want you can also specify the BOT_NAME variable and, in case you moved your database somewhere different from the standard location, the BOT_DB_PATH variable.

Now we need to define how Heroku will launch our application. To do so we need to create a file called Procfile in the root of our project. This file must contain the following code:

worker: node bin/bot.js

This way we are telling Heroku to create a worker dyno for our application by running our launcher script.

Once you are done with creating and configurig your Heroku app we need to deploy the code on the server. Heroku deployment is based on git so we need to create a new local git repository in our project folder. To do so just move to your project folder on the command line and run:

git init

It will initialize a new repository. Now we need to add the project files to the repository and make the first commit, but before doing this we need to make sure the node_modules folder is not accidentally committed to the repository as Heroky will take care to use Npm to download and keep our dependecies updated. The simplest way to do this is to create a .gitignore file and write node_modules/ inside it. On Linux and Mac OSX you can do this by just running:

echo "node_modules/" >> .gitignore

Now don't forget to add all your files to the local repository and make your first commit with:

git add --all
git commit -am "first version"

The next step is to connect our local git repository to our Heroku application using the Heroku toolbelt. To do so we need to login with the heroku login command and then to launch

heroku git:remote -a

Where, of course, must be change with the real name of your heroku application (norrisbot in my case).

This was the last step to configure your Heroku app and the deployment process using git. From now on you can deploy your app (or new version of it) by just pushing your git code to the Heroku remote with the following git command:

git push heroku master

This command will take care of deploying the app on the server, downloading the new dependencies with Npm and finally to run your app on the server.

At this stage your NorrisBot will be available on your Slack channel and all your organization will be able to enjoy is company!

If you want to update your bot you just need to commit the new code in the local repository and push it to the Heroku remote.

Conclusion

I really hope you enjoyied this article and that from now on you will spend a bit of your time to write your own Slack bots.

If you want some ideas on how to improve the NorrisBot you can add new features as statistics, or a periodic chart with the top NorrisBot callers, the most appreciated jokes (by analyzing the reactions) and so on. Just break your imagination free and enjoy some coding for the sake of it.

If you want to develop more useful bots I can give you some other ideas:

The WatchdogBot, a bot that notifies when your website goes donw

The CashierBot, a bot that notifies you channel about new orders on your e-commerce website

The MusicSpyBot, a bot that monitor the last.fm profile of your team members and make charts with the most appreciated songs and groups within your organization.

Again, the possibilities are endless, your imagination and your coding skills are the limit! :)

If you have some other brilliant idea about how to improve the NorrisBot or about new Slack bots drop a line in the comments, I will be really happy to discuss those ideas with you.

Oops! I almost forgot! Of course we deployed the NorrisBot in our Slack, go and check it out!

Thanks for reading. See you in the next post!

<ol> of Contents

Luciano is a Software Engineer born in 1987, the same year that the Nintendo released “Super Mario Bros” in Europe, which, by chance is his favourite video-game!

He is passionate about code, the web, smart apps and everything that’s creative like music, art and design. As web developer his experience has been mostly with PHP and Symfony2, even if he recently fell in love with Javascript, NodeJS. In his free time he writes on his personal blog at http://loige.co. He also co-authored the book "Node.js Design Patterns (Packt)": https://www.nodejsdesignpatterns.com

Luciano is a Software Engineer born in 1987, the same year that the Nintendo released “Super Mario Bros” in Europe, which, by chance is his favourite video-game!

He is passionate about code, the web, smart apps and everything that’s creative like music, art and design. As web developer his experience has been mostly with PHP and Symfony2, even if he recently fell in love with Javascript, NodeJS. In his free time he writes on his personal blog at http://loige.co. He also co-authored the book "Node.js Design Patterns (Packt)": https://www.nodejsdesignpatterns.com