The Beginner's Guide to Building An Intelligent Bot on Slack

Danny Cohn

4.25.2016

In today’s workplace, it’s hard to come by a team that isn’t using some sort of team messaging service. Email inboxes have become so bogged down with junk and noise that it’s becoming extremely difficult for teams work efficiently and be productive. Employees are now recognizing that when in-person chats aren’t an option, a messaging service is the next best thing to help streamline and improve team communication.

But today’s messaging services are not the AIMs and Live Messangers of yesteryear. Today we find ourselves using services such as Slack and HipChat that target the enterprise crowd, offering deep integrations with services such as Google Calendar, Github, Trello (the list goes on on and on), and opening APIs for building useful custom tools to integrate just about anything else. 

In this guide we are putting the focus on Slack. When joining a Slack team, one of the first integrations a user encounters is a bot, or a program running on a server or computer that interacts with users to help automate tasks. In this case, Slack's bot, Slackbot, is there to help you get oriented in Slack. A Slackbot can be programmed to do just about anything such as order you an Uber or serve up Chuck Norris jokes. Whatever the need, you can most likely create a bot to help out. Below we’ll go through the steps to building an intelligent bot on Slack to retrieve weather information.

To learn more about Bots, check out Solstice's recent report There's a Bot for That: The new mobile strategy.

Getting Started

To start things off, you’ll need a Slack account. An account is always associated with a team, so go ahead and create a team as well if you’re not part of one already. You can do this all from www.slack.com.

Once you’ve got an account, you can head to the New Application page to create a new Slack app (feel free to explore the API docs as well). Most of the fields are self-explanatory. The Redirect URI(s) field is where you’ll put the URI to which you want Slack to send users after they’re done authorizing the bot to be added to their team. Since I plan on hosting this bot on AWS Elastic Beanstalk, I’ll set my Redirect URI to the URL of my Beanstalk application.

Slack_bots1.png

On the next screen, you’ll be given your Client ID and Client Secret. These are the two pieces of information you’ll need to be able to connect to a Slack team from your code. Copy those down and keep them private.

Slack_bots2.png

Then scroll down to the “Bot” section and click the “Add a bot to this app” button to create a bot user for your app.

Slack_bots3.png

Authorization

Before your app can connect to a team’s Slack, you need an admin from the team to give permission. Permission is provided via OAuth. Slack provides a nice tool for generating an “Add to Slack” button for your app. The basic idea is that the button links to an OAuth page on Slack to request permission from the user. Once the admin grants permission, the user is redirected to your server (at one of the Redirect URLs you set up when you created your app), with a code as one of the URL parameters that your server can connect to the user’s Slack team. The basic format of the OAuth URL is:

https://slack.com/oauth/authorize?scope=bot&client_id=***********.***********

There are other scopes, but for a simple bot, you only need to request the bot scope. When the user clicks the link, they’ll see a page similar to this:

Slack_bots4.png

Once they click “Authorize”, they’ll be sent to the redirect URI that you set up, with a URL parameter called “code” added to the end. This code will be exchanged for an access token. Now, let’s get started writing code and explore how to do that.

OAUTH Code/Token Exchange

When the user clicks authorize, they are redirected to the redirect URL with a code parameter. This code by itself is useless. It must be exchanged for an access token that gives you the ability to connect to a Slack team. The exchange is made via a simple HTTP GET request.

var request = require('request'),
url = require('url');

var requestDetails = url.parse(request.url, true);

request('https://slack.com/api/oauth.access?client_id=' + CLIENT_ID + '&client_secret=' + CLIENT_SECRET + '&code=' + requestDetails.query.code,
function(error, response, body) {
var responseJson = JSON.parse(body);
if (responseJson.ok) {
var botAccessToken = responseJson['bot']['bot_access_token'];
var botUserId = responseJson['bot']['bot_user_id'];
var teamId = responseJson['team_id'];
}
}
);

Connecting to Slack

Now that we have an access token, we can connect to Slack. We will use the Slack client for Node.js.

var RtmClient = require('@slack/client').RtmClient,
RTM_EVENTS = require('@slack/client').RTM_EVENTS;

var rtm = new RtmClient(botAccessToken);

rtm.start();

Listening and Responding to Messages

Now that we’re connected to the Slack team, we can listen for messages and respond to them as desired.

rtm.on(RTM_EVENTS.MESSAGE, function(message) {
    rtm.sendMessage('I heard you say "' + message.text + '"', message.channel);
});

 

This will simply echo back whatever the user says to the bot. This isn’t very interesting, so let’s have the bot assume that the user is saying a location and look up the weather.

rtm.on(RTM_EVENTS.MESSAGE, function(message) {
processMessage(message, rtm);
});

function processMessage(message, rtm) {
var locationName = message.text;
var query = (isNaN(locationName) ? 'q=' + locationName : 'zip=' + locationName) + '&units=imperial&APPID=' + WEATHER_API_KEY;
rtm.sendMessage('I\'ll get you the current weather for "' + locationName + '"', message.channel, function() {
getAndSendCurrentWeather(locationName, query, message.channel, rtm);
});
}

function getAndSendCurrentWeather(locationName, query, channel, rtm) {
request('http://api.openweathermap.org/data/2.5/weather?' + query, function(error, response, body) {
var weatherData = JSON.parse(body);
if (weatherData.cod == "404") {
rtm.sendMessage('I\'m sorry, but I couldn\'t find a city called "' + locationName + '"', channel, function() {
getAndSendForcast(locationName, channel, rtm);
});
} else {
var message = 'The weather in ' + weatherData.name + ' is ' + weatherData.weather[0].main + ' and it\'s ' + weatherData.main.temp + ' degrees.';
rtm.sendMessage(message, channel, function() {
getAndSendForcast(locationName, query, channel, rtm);
});
}
});
}



function getAndSendForcast(locationName, query, channel, rtm) {
request('http://api.openweathermap.org/data/2.5/forecast/daily?' + query, function(error, response, body) {
var message;
var weatherData = JSON.parse(body);
if (weatherData.cod == "404") {
message = 'I\'m sorry, but I couldn\'t find a city called "' + locationName + '"';
} else {
message = 'The high is ' + weatherData.list[0].temp.max + ' and the low is ' + weatherData.list[0].temp.min;
}
rtm.sendMessage(message, channel, function() {});
});
}

Reconnecting After Server Restart

The connection that we made to Slack above will be closed if the Node.js process shuts down for any reason. For this reason, in order to reconnect after a restart, we have to store off the access token. Since I plan to host my bot on AWS Elastic Beanstalk, the easiest place for me to store this data is in AWS DynamoDB.

request('https://slack.com/api/oauth.access?client_id=' + CLIENT_ID + '&client_secret=' + CLIENT_SECRET + '&code=' + code,
function(error, response, body) {
var responseJson = JSON.parse(body);
if (responseJson.ok) {
var botAccessToken = responseJson['bot']['bot_access_token'];
var botUserId = responseJson['bot']['bot_user_id'];
var teamId = responseJson['team_id'];
connectToTeam(botAccessToken);
storeTeamInfo(teamId, botAccessToken, botUserId);
} else {
console.error('could not exchange token ' + responseJson);
}
}
);

function storeTeamInfo(teamId, accessToken, botUserId) {
documentClient.put({
TableName: TABLE_NAME,
Item: {
teamId: teamId,
accessToken: accessToken,
botUserId: botUserId
}
}, function(error, data) {
if (error) {
console.log('error writing team info to db ' + error);
} else {
console.log('wrote to db');
}
});
}

function restoreExistingClients() {
documentClient.scan({
TableName: TABLE_NAME
}, function(error, data) {
if (error) {
console.error('Error getting previous teams', JSON.stringify(error));
} else {
data.Items.forEach(function(team) {
connectToTeam(team.accessToken);
});
}
});
}

Wrapping Up

There’s a lot more that you can do with bots than look up the weather. With machine learning, you can create a bot that doesn’t just respond to specific commands or keywords but understands what the user is saying and responds accordingly. There’s a great toolkit that can be used to help take care of the nitty-gritty details when creating bots for Slack (or Facebook Messenger), allowing you to remain focused on the intelligence of the bot.

Check out the complete source for the weather bot here, and have fun building your own bots!