Publicising your Slack Bot through Slack Apps + the Add to Slack button

In my previous blog posts on Slack bots, I spoke about making bots, both using a simple script, and using incoming webhooks. We are now well versed with how to code up a slack bot and code it according to our needs.

Now that our Slack bot is made. How do we get it through to everyone?

Slack offers an amazing feature for the same, the Add to Slack button. Using this button, other teams can simply add your bot to them, and this is one of the best ways of publicising, because this button can be kept anywhere: your website, your README.md on github, a blog post etc.

Add to Slack, however works with OAuth, so that only authorised teams can take in your bot. Also, for distributing your bot, you will have to package it into a Slack app. So, let’s get started!

First off, we’ll make a Slack app:

1. Login into your team on Slack.
2. Go to the Apps page here and click on “Create an App”.

3. Fill out the relevant details for your app. You should especially fill out the redirect_uri (it’s not compulsory but you’ll have to fill it sometime) since it is needed for OAuth (when other users use your bot). Once form filled, click on Add App.

4. Go to the main page of your app, and under Bot Integrations, add your bot (keep the name as @susi, or whatever you like. You’ll have to change the bot name then in the code).

5. Go to App Credentials, and save the client_id and the client_secret for reference. We need it for OAuth.

Don’t worry, we’ll handle the redirect_uri in a short while!

So the flow of this goes as follows:

1. When a team clicks on “Add to Slack” button, they are led to a page, where they have to verify that a bot is being added to their team. Once they have verified, you click on “Authorize”.

2. When one clicks on Authorize, Slack generates a code, and appends it to the redirect_uri as a GET parameter, and leads you there.

3. Your redirect_uri needs to handle this code, and then send the client_secret, client_id and this code as GET parameters to http://slack.com/api/oauth.access, so that your OAuth request is verified.

4. Once request is verified and the parameters match, the bot is successfully deployed onto your team. Additionally, a JSON is returned, specifying your access_token for the bot you just deployed, as well as the incoming webhook URL (incase you have incoming webhook in your code). You need to now use this very access token and the webhook URL to control your bot.

Let’s get started on implementing this then.

1. We first go to the Slack Button page. In the bottom of the page under the section “Add to Slack Button”, there is a box, where there’s a custom url so that you can add the bot to your website etc (where people will click on it). There are three checkboxes there as you can see. Check whichever one you need for your bot:

Screen Shot 2016-08-29 at 6.37.26 PM

2. Once you have selected this, you can embed this into your website / README file. That’s half the job done!

Now let’s dive into the code. We need to take in the code that’s sent as a GET parameter to our redirect_uri. Once we get this code, we need to send in a GET request to http://slack.com/api/oauth.access with the client_id, client_secret and this code. If the bot is approved, we take up the webhook url / bot token and use it for the deployed bot so that it runs properly.

Here, the redirect_uri I’ll use is the Slack deployment URL I have on Heroku (http://asksusisunode.herokuapp.com). I’ll just create a path on Express, named ‘/slackbot’, and get started from there. The entire process starts when you get the code as a GET parameter on the redirect_uri. So do the following:

1. Go to your Apps page on Slack, under App credentials, add http://yourherokuurl.com/slackbot (or obviously any other URL you have) as the redirect_uri. I used http://asksusisunode.herokuapp.com/slackbot as the redirect_uri.

2. Let’s dive into the code now. Below is the final code that handles the Add to Slack button:


'use strict';
/* global require, process, console */

var express = require('express');
var bodyParser = require('body-parser');
var request = require('request');
var SlackBot = require('slackbots');
var http = require("http");
var Slack = require('node-slackr')
var app = express();
var custom_slack_token;
var slack_token = process.env.SLACK_TOKEN
var payload;
var payload_url = process.env.PAYLOAD_URL //this is just the webhook URL
var custom_payload_url;
var slack; 

var slack_code;
var client_id = process.env.CLIENT_ID;
var client_secret = process.env.CLIENT_SECRET;

app.set('port', (process.env.PORT || 5000));

app.use(bodyParser.urlencoded({extended: false}));

app.use(bodyParser.json());

app.get('/', function (req, res) {
	res.send('Susi says Hello.');
});

app.get('/slackbot', function(req, res) {
	slack_code = req.param('code'); //getting the code GET parameter
	var queryurl = 'http://slack.com/api/oauth.access?client_id='+client_id+'&client_secret='+client_secret+'&code='+slack_code;
	console.log(queryurl);
	request(queryurl, {json:true}, function(error, response, body) { // we get a JSON response
		if(!error && response.statusCode == 200 && body.ok == 'true'){ //i.e if bot has been installed

//take in the slack token and webhook url
			custom_slack_token = body.bot.bot_access_token;
			custom_payload_url = body.incoming_webhook.url;
			console.log(body);
			console.log(slack_token);
			res.send('Susi has been installed to your team!');
		} else{
			res.send('Could not install');
		}
	});
});

function slackbot(){
	
	setInterval(function() {
		http.get("http://asksusisunode.herokuapp.com");
	}, 1800000); 

	if (custom_slack_token && custom_payload_url){
		slack_token = custom_slack_token;
		payload_url = custom_payload_url;
	}
	var slack_bot = new SlackBot({
		token: slack_token, 
		name: 'susi'
	})

	slack = new Slack(payload_url);

	slack_bot.on('message', function(data){
		var slackdata = data;
		var msg, channel, output, user;
		if(Object.keys(slackdata).length > 0){
			if('text' in slackdata && slackdata['username'] != 'susi'){
				msg = data['text'];
				channel = data['channel']
			}
			else {
				msg = null;
				channel = null;
			}
		}
		if(msg != null && channel !=null){
			var botid = '<@U1UK6DANT>' //need to change
			if (msg.split(" ")[0] != botid){
			//do nothing
		} else{
			var apiurl = 'http://loklak.org/api/susi.json?q=' + msg;
			var payload;
			request(apiurl, function (error, response, body) {
				if (!error && response.statusCode === 200) {
					var data = JSON.parse(body);
					if(data.answers[0].actions.length == 1){
						var susiresponse = data.answers[0].actions[0].expression;
						payload = {
							text: susiresponse,
							channel: channel
						}
						slack.notify(payload)

					} else if(data.answers[0].actions.length == 2 && data.answers[0].actions[1].type == "table"){
						payload = {
							text: data.answers[0].actions[0].expression + " (" + data.answers[0].data.length + " results)",
							channel: channel
						}
						slack.notify(payload)
						for(var i = 0; i < data.answers[0].data.length; ++i){
							var response = data.answers[0].data[i];
							var ansstring = "";
							for(var resp in response){
								ansstring += (resp + ": " + response[resp] + ", ");
							}
							payload = {
								text: ansstring,
								channel: channel
							}
							slack.notify(payload);
						}
					}
				}
			});
		}
	}
});
}

// Getting Susi up and running.
app.listen(app.get('port'), function() {
	console.log('running on port', app.get('port'));
	slackbot();
});

There’s just one small shortcoming: the bot id used above won’t be the same for the deployed bot, so there can be cases where you message the bot but it does not reply. So we need to actually use the RTM API to figure out the bot id directly. We’re in the process of fixing it. But the bot will definitely be installed into your team, just that in some cases it won’t message and will stay “Away”.

See? It was as simple as adding another path in Express, and the awesome Requests package does the rest. Your bot will successfully be added to your team as a result, and anyone can use it. 🙂

Apart from publicising using the Add to Slack button, additionally, you can also publicise your app on the Slack Apps directory by going here and filling out the form.

So now we know how to make a Slack bot from scratch, from two different methods, and how to effectively publicise it. This is another great way by which Susi will be publicised to everyone and more people can use it. Amazing, right?

By the way, please go to https://github.com/fossasia/asksusi_messengers and add more bots for Susi there. We wish to add Susi on as many platforms as possible. We really value your contributions 🙂

So that’s it for today! Feedback is welcome as always 🙂 See you later!

Publicising your Slack Bot through Slack Apps + the Add to Slack button

Making Slack Chatbots using Incoming Webhooks + The Idling problem

The last time I spoke about Chatbots, I spoke about the need of increasing Susi’s reach, how Slack is a great platform because of how it works within teams, and how to make a Slack bot yourself.

However, if you see the code snippet I posted in that blog post, you’ll see that the Slack bot I have is just a Python script, while the rest of the index.js code (which contains the Messenger and Telegram bots) is an Express application. We are basically just using a package (slackbots, if you remember), and it simply takes in your Slack token and POSTs to the Slack interface. Also, that is a custom bot, it will only be in use for us right now, we need to distribute it (which we do using Slack apps, we’ll talk about that later).

Today, I’ll be describing another method of making Slackbots: using Incoming Webhooks.

Incoming Webhook is a way by which you don’t directly POST to the Slack interface, but you POST to a webhook generated by Slack. It is a very convenient way of posting messages from external sources into Slack. Moreover, when you distribute your Slack bot, you can distribute your Webhook separately so that your reach can increase more (we’ll talk about distributions and OAuth in the next blog post). Incoming webhooks are seamlessly integrated within your Slack apps, so that your Slack bot can be distributed efficiently.

So let’s get started. To create an Incoming webhook integration:

1. Go to the Incoming Webhook Integration page here.

2. Fill in the details and select the channel you wish to post to.

3. Save the webhook URL for reference. We’ll need it.

Incoming Webhooks work with a payload. A payload is a JSON which contains all the information of the message (text, emojis, files etc). A normal payload looks like:

payload={"text":"This is a line of text.\nAnd this is another one."}

Now all we need to do is POST our message, AS a payload, to this URL, instead of directly posting to Slack. For easily handling payloads, we use a library named node-slackr. You can install it as follows:

npm install --save node-slackr

To post a payload to the URL, we first instantiate the node-slackr object using our webhook URL:

var slack = new Slack(webhook_url);

When we have the payload ready, all we need to POST to the webhook is simply do:

slack.notify(payload);

So here’s the final modified code that’s used for making bots using incoming webhooks. We just make a few changes to our original bot code in the last post on Slack bots on this blog:


'use strict';
/* global require, process, console */

var express = require('express');
var bodyParser = require('body-parser');
var request = require('request');
var SlackBot = require('slackbots');
var Slack = require('node-slackr')
var app = express();
var slack_token = process.env.SLACK_TOKEN
var webhook_url = process.env.WEBHOOK_URL
var heroku_url = process.env.HEROKU_URL
var slack; 

app.set('port', (process.env.PORT || 5000));

app.use(bodyParser.urlencoded({extended: false}));

app.use(bodyParser.json());

app.get('/', function (req, res) {
	res.send('Susi says Hello.');
});

function slackbot(){
	
	setInterval(function() {
		http.get(heroku_url);
	}, 1800000); 

	var slack_bot = new SlackBot({
		token: slack_token, 
		name: 'susi'
	})

	slack = new Slack(payload_url);

	slack_bot.on('message', function(data){
		var slackdata = data;
		var msg, channel, output, user;
		if(Object.keys(slackdata).length > 0){
			if('text' in slackdata && slackdata['username'] != 'susi'){
				msg = data['text'];
				channel = data['channel']
			}
			else {
				msg = null;
				channel = null;
			}
		}
		if(msg != null && channel !=null){
			var botid = ':' 
			if (msg.split(" ")[0] != botid){
			//do nothing
		} else{
			var apiurl = 'http://loklak.org/api/susi.json?q=' + msg;
			var payload;
			request(apiurl, function (error, response, body) {
				if (!error && response.statusCode === 200) {
					var data = JSON.parse(body);
					if(data.answers[0].actions.length == 1){
						var susiresponse = data.answers[0].actions[0].expression;
						payload = {
							text: susiresponse,
							channel: channel
						}
						slack.notify(payload)

					} else if(data.answers[0].actions.length == 2 && data.answers[0].actions[1].type == "table"){
						payload = {
							text: data.answers[0].actions[0].expression + " (" + data.answers[0].data.length + " results)",
							channel: channel
						}
						slack.notify(payload)
						for(var i = 0; i < data.answers[0].data.length; ++i){
							var response = data.answers[0].data[i];
							var ansstring = "";
							for(var resp in response){
								ansstring += (resp + ": " + response[resp] + ", ");
							}
							payload = {
								text: ansstring,
								channel: channel
							}
							slack.notify(payload);
						}
					}
				}
			});
		}
	}
});
}

// Getting Susi up and running.
app.listen(app.get('port'), function() {
	console.log('running on port', app.get('port'));
	slackbot();
});

All we did is set the webhook url as an environment variable, and used that, and just did slack.notify. Also, I encapsulated the function inside app.listen so that it runs up as soon as the app starts and stays alive.

But here comes another problem: We used heroku dynos for deployment. Heroku dynos have a sleep period of 6 hours. In those 6 hours, the bot would just be idle and would not work. We wish to circumvent this.

There are three ways of doing so. One way is to install the newrelic plugin of Heroku and using it (you can read more about it here). The second way is to simply use Kaffeine so that your heroku url is pinged every 30 minutes and the bot stays alive.

Or you can programatically solve it as well. Look at the code snippet above and notice:


setInterval(function() {
		http.get(heroku_url);
	}, 1800000); 

We’re basically pinging the Heroku URL (again stored as env var) every 1800000 milliseconds, i.e 30 minutes. This is a more convenient approach to solve this problem of idling too.

So now we know how to make our bot using two different methods, and how to solve the idling problem. To get this full circle, in the next blog post, I will talk about distribution of your bot, and how people can know about it. Feedback is welcome as always 🙂

Making Slack Chatbots using Incoming Webhooks + The Idling problem

Bot integrations of Susi on Online Social Media: Slack

In my past few posts, I have explained the use of Susi in detail. We have come to see Susi as an intelligent chat bot cum search engine, which answers Natural Language queries, and has a large dataset to support it thanks to the various sites we scrape from, the APIs we integrate, and also, the additional services that we make (like the TwitterAnalysisService I talked about). All of these make Susi an excellent chat service.

So now the question comes up: how do we increase its reach?

This is where bot integration comes up. Services like Messenger (Facebook), Google Hangouts, Slack, Gitter etc have a large number of user chatting on their platform, but in addition, they also added an additional service of bot users. These users, when messaged about related queries, answer those queries to the user. We have recently seen a very interesting example of this, when the White House used FB Messenger Bots for people to reach out to President Obama (link). This makes users get quick and instant replies to specific queries, and also, bot integrations on these big platforms make more and more people connect with the bot and its maintainers too.

That is why we believed it would be amazing if Susi were integrated onto these platforms as a bot, so that people realise all the things it is able to do. Now, we need to implement these.

As Sudheesh must have spoken about, we are following a system of maintaining all the bots on one index.js file, all of the bots post to different routes, and we deploy this file and the npm requirements in the package.json so that all run concurrently. Keeping this in mind, I developed the Slack Bot for Susi.

I actually developed the bot both in Python and node, but we will only be using node because of the easiness of deployment. For those who wish to check out the Python code, head over here. The Slack API usage remains the same though.

The main part of our bot will be the Slack RTM (Real Time Messaging) API. It basically reads all the conversation going on, and reports every message in a specified format, like:


{
    "id": 1,
    "type": "message",
    "channel": "C024BE91L",
    "text": "Hello world"
}

There are other parameters also included, like username. More info on all the parameters can be found here.

So this is the API schema. We will be using an npm package called slackbots for implementing our bot. Slackbots gives an easy way of interfacing with the RTM API, so that we can focus on implementing the Susi API in the bot without having to worry much about the RTM. You could read Slackbots’ documentation here.

For making the bot, first go here, register your bot, and get the access token. We will need this token to make authorised requests to the API. For keeping it in a secure place, store it as an environment variable:

export SLACK_TOKEN=<access token>

Now comes the main code. Create a new node project using npm init. Once the package.json is created, execute the following commands:


npm install --save requests
npm install --save slackbots

This install the slackbots and the requests packages in our project. We will need requests to make a connection with the Susi API on http://loklak.org/api/susi.json.

Now we are all set to use slackbots and write our code. Make a new file index.js (add this to the package.json as well). Here’s the code for our slackbot.


'use strict';
/* global require, process, console */
var request = require('request');
var SlackBot = require('slackbots');
var slack_token = process.env.SLACK_TOKEN; //accessing the slack token from environment
var slack_bot = new SlackBot({
	token: slack_token, 
	name: 'susi'
});

slack_bot.on('message', function(data){
	var slackdata = data;
	var msg, channel, output, user;
	if(Object.keys(slackdata).length > 0){
		if('text' in slackdata && slackdata['username'] != 'susi'){
			msg = data['text'];
			channel = data['channel']
		}
		else {
			msg = null;
			channel = null;
		}
	}
	if(msg != null && channel !=null){
		var botid = ':' 
		if (msg.split(" ")[0] != botid){
			//do nothing
		} else{
			var apiurl = 'http://loklak.org/api/susi.json?q=' + msg;
			request(apiurl, function (error, response, body) {
				if (!error && response.statusCode === 200) {
					var data = JSON.parse(body);
					if(data.answers[0].actions.length == 1){
						var susiresponse = data.answers[0].actions[0].expression;
						slack_bot.postMessage(channel, susiresponse);
					} else if(data.answers[0].actions.length == 2 && data.answers[0].actions[1].type == "table"){
						slack_bot.postMessage(channel, data.answers[0].actions[0].expression + " (" + data.answers[0].data.length + " results)");
						for(var i = 0; i < data.answers[0].data.length; ++i){
							var response = data.answers[0].data[i];
							var ansstring = "";
							for(var resp in response){
								ansstring += (resp + ": " + response[resp] + ", ");
							}
							slack_bot.postMessage(channel, ansstring);
						}
					}
				}
			});
		}
	}
});

Let’s go over this code bit by bit. We instantiate SlackBots using our token first. Then, the line slack_bot.on('message', function(data) triggers the RTM API. We first get the message in the conversation, check if its JSON is empty or not. Also, our bot should only reply when the user asks, it should not reply to the queries of itself (because RTM continuously reads input, so even the bot’s replies come under it, so we don’t want the bot to react to its own replies lest we get an infinite loop). This check is done through:


if(Object.keys(slackdata).length > 0){
		if('text' in slackdata && slackdata['username'] != 'susi'){
			msg = data['text'];
			channel = data['channel']
		}
		else {
			msg = null;
			channel = null;
		}
	}

We also get the text message and the channel to post the message into.

Next, we check for an empty message. If there is a message, we check if the message starts with @susi: (my bot was named susi, and the bot id came from the RTM API itself, I hardcoded it). We should only query the Susi API in such a case where the message starts with @susi. And once that check is done, we query the Susi API, and the response is data.answers[0].actions[0].expression (except when it’s a table, then we use data.answers[0].data). Once we get what we need to send, we use SlackBot’s postMessage method, and post the message onto the channel using the RTM API. That’s what the rest of the code does.


if(msg != null && channel !=null){
		var botid = ':' 
		if (msg.split(" ")[0] != botid){
			//do nothing
		} else{
			var apiurl = 'http://loklak.org/api/susi.json?q=' + msg;
			request(apiurl, function (error, response, body) {
				if (!error && response.statusCode === 200) {
					var data = JSON.parse(body);
					if(data.answers[0].actions.length == 1){
						var susiresponse = data.answers[0].actions[0].expression;
						slack_bot.postMessage(channel, susiresponse);
					} else if(data.answers[0].actions.length == 2 && data.answers[0].actions[1].type == "table"){
						slack_bot.postMessage(channel, data.answers[0].actions[0].expression + " (" + data.answers[0].data.length + " results)");
						for(var i = 0; i < data.answers[0].data.length; ++i){
							var response = data.answers[0].data[i];
							var ansstring = "";
							for(var resp in response){
								ansstring += (resp + ": " + response[resp] + ", ");
							}
							slack_bot.postMessage(channel, ansstring);
						}
					}
				}
			});
		}
	}
});

This completes the bot. When you shoot it up from your terminal using node index.js, or deploy it, it will work perfectly.

This can now be used by a wide range of people, and everyone can see all that Susi can do. 🙂

We are still in the process of making bots. FB, Telegram and Slack bots have been made till now, and we will be making more. Feedback, as usual, is welcome. 🙂

Bot integrations of Susi on Online Social Media: Slack

Telegram Chatbot using loklak

It is now possible to retrieve single tweets within telegram using a telegram loklak bot. This is a kind of ‘first try’ to make an AI out of the tweet database.

Enjoy!

telegram_bot_screenshot

Telegram Chatbot using loklak