Go to Slack

Building a sentiment analysis bot with IBM Watson and Raspberry Pi

By Tomomi Imura

Published: June 12, 2017
Updated: May 29, 2019

This article has been updated since originally published in 2017.

The updated tutorial contains code re-written for a newer version of the IBM Tone Analyzer API, and updated screenshots to reflect changes in Slack and IBM Cloud.

Do you ever wonder how your teammates perceive your Slack chat messages? Maybe you sound heroic! Or unpleasant. Perhaps you project harsh undertones you haven’t even noticed!

Imagine a helpful robot that analyzes your written communications. Now-- stop imagining, because I created this (physical!) bot that analyzes message tone using IBM Watson, “reading” the emotion of each posted message!

In this article, I’ll share how I created the bot.

Slack Sentiment Analysis Bot from Tomomi Imura on Vimeo.

First I’ll show you how to use the Events API to capture messages, send them to IBM Watson for analysis, and then post the result using chat.postMessage.

Then I will present a totally optional but fun exercise! We will port our bot to a Raspberry Pi and reflect emotions using colors produced by an RGB LED.

🐙🐱 The source code is available on GitHub.

Before starting this project

First, sign up for an IBM Cloud account, then enable Tone Analyzer and your API credentials:

In addition to the software bot, if you want to build a physical Raspberry Pi bot that shows the results with color LEDs, you’ll need some affordable hack-friendly hardware. You can either buy the IBM TJBot Kit that includes the cardboard robot, or prepare separately:

You need to install the latest Raspbian OS, connect to WiFi, and update and upgrade the system. See Raspberrypi.org for additional instruction.

Building a sentiment analysis bot

OK, let’s build a Slack bot that reads messages and analyzes for emotional content!

Our bot’s workflow is:

  1. Read each message on a channel
  2. Send the message to IBM Watson for examination
  3. If the likelihood of an emotion is above the given confidence threshold, post the most prominent emotion to the Slack channel

Creating and configuring your Slack app

First, let’s create a new app. Fill out the (1) App Name and (2) choose a Slack workspace that you are allowed to develop apps on:

Create an App

Then click Create App when finished. Next, you need to configure your App.

At Add features and functionality, enable “Bots”, “Event Subscription”, and “Permissions”. We will walk through them soon.

First, get your API credentials:

  1. Go to Basic Information.
  2. Scroll down to App Credentials section.
  3. Click "Show" to reveal Signing Secret. Keep it in your credential file, in this example, .env file.

For this tutorial, you will only build a bot and install it on your own workspace, so the only credential you need here is the Signing Secret.

App - Credentials

Setting up a bot user

Click the Bots button under Add features and functionality, or the (1) Bot Users menu from the left navigation to set up a bot user.

App - Bot Users

Make sure to click the Save Changes every time you make a change in this configuration process.

Setting OAuth & permissions

(1) Click OAuth & Permissions from the left menu.

Scroll down to Permission Scopes section to add the scopes you need to use for the bot. (2) Make sure the bot scope has been pre-selected.

Then, install the app once to get an auth token. Copy the bot token, which begins with xoxb-, and keep and keep it in your .env file. You will need to use the OAuth token every time you call an API method. (Note: To distribute your bot to the rest of the world, you will need to set up an OAuth button, etc, however, this tutorial does not cover the process. Read our guide to using OAuth with bots to find out more)

App - Permission

Setting event subscriptions

To receive channel messages, we are going to use the Events API. (1) Click Event Subscriptions. You need to enable the feature by setting the Request URL, which we'll cover a little later

Scroll down to Subscribe to Bot Events. Click the Add Bot User Event button, and (2) select message.channels:

App - Event

Make sure to (3) click the Save button.

Next, you will need to write an webhook scaffold with Node.js, and come back to the page for the Request URL, and finish the rest of the App configuration.

The Request URL used by the Events API is a kind of webhook, where the URL you provide will receive a HTTP request for each posted message.

Setting up your Request URL

During the development, one of my recommendations is to use a localhost tunnel, such as ngrok, which gives you a temporary public URL. Download ngrok for your OS, unzip it, and install it by moving the file to the appropriate directory, for example, if you are using Mac, use the command, mv ngrok /usr/local/bin. You can learn more about ngrok at Using ngrok to develop locally for Slack.

Once you install it, run it on terminal:

$ ngrok http 5000

The ngrok tool generates a URL looking like http://618830b2.ngrok.io for your local server at localhost:5000. Copy the ngrok URL and paste it into the configuration setup, but not so fast! The URL must be validated, and in order to do that you’ll need to finally write some code. We will come back here later.

Running an Express server

In this example, I am using Node.js with Express to run the server and handle HTTP request calls.

Once finished installing Express and other dependencies, create a index.js file, and instantiate the Express server, listening on port 5000. Since you’ve set ngrok to localhost:5000, you must use the same port!

'use strict';
const express = require('express');
const bodyParser = require('body-parser');
const app = express();

...

const server = app.listen(5000, () => {  
  console.log('Express server listening on port %d in %s mode', server.address().port, app.settings.env);});

Now, create HTTP POST route to handle the validation code. Let’s create an /event route. This will be triggered every time an event arrives, like the message.channels event we want from public Slack channels.

When you enter the webhook URL on your configuration page, Slack will send a request with a challenge parameter, so your endpoint must respond with the challenge value to complete the handshake.

app.post('/event', (req, res) => {
  if (req.body.type === 'url_verification') {
    res.send(req.body.challenge);
  }

  // will implement at the next step ...
});

Now, let’s run this node script.

Then (1) go back to the Event Subscriptions, and (2) enter your ngrok URL with the route, for instance, https://618830b2.ngrok.io/event. If everything works fine, the Request URL will verify, and you should see a green checkmark like this:

App - Events

Handling event subscriptions

Let’s start coding your bot!

Go back to your index.js file and at the /events POST route you defined at the last step, we’ll receive and handle the message payload.

Within the request handler, you need to check the payload to verify if the requests received are authentically from Slack. You can do this by using your signing secret.

If the request is coming from somewhere fraudulent, throw some HTTP status back, maybe 401, but I am just giving the imposter a 404. Why not?

app.post('/events', (req, res) => {
  const {type, subtype, text, user, channel} = req.body.event;

  if (req.body.type === 'url_verification') {
    res.send(req.body.challenge);
  }
  else if (req.body.type === 'event_callback') {
    // To see if the request is coming from Slack
    if (!signature.isVerified(req)) {
      res.sendStatus(404);
      return;
    } else {
      res.sendStatus(200);
    }

    if(!text) return;

    // Exclude the message from a bot, also slash command
    let regex = /(^\/)/;
    if(subtype === 'bot_message' || regex.test(text)) return;
      analyzeTone(text, user, channel);
  }
});

To learn more about the signing secret implementation, see my previous tutorial including the topic

Okay, now create a function, analyzeTone, which take the message text to be analyzed and some other info from the payload.

Using the sentiment analysis with Watson

Now, let’s use IBM Watson for the sentiment analysis, so sign in to your IBM Cloud account.

Then (1) go to Catalog, (2) click AI, and (3) choose Tone Analyzer.

IBM Cloud

At the next screen, click Create.

Once you activate the Tone Analyzer, (1) click the Service Credentials from the left menu, and you should be able to obtain your API key for this specific service by (2) clicking the little triangle by “View credentials”. Copy the key and keep it in your .env file.

IBM Tone Analyzer

To use the Watson Node.js library, install the watson-developer-cloud (I am using the version 3.x in this example) module from npm, then instantiate the tone analyzer with your credentials:

const ToneAnalyzerV3 = require('watson-developer-cloud/tone-analyzer/v3');
const toneAnalyzer = new ToneAnalyzerV3({
  iam_apikey: process.env.TONE_ANALYZER_IAM_APIKEY,
  url: 'https://gateway.watsonplatform.net/tone-analyzer/api',
  version: '2017-09-21',
});

Now, you sent the Slack message text to the Tone Analysis:

const confidencethreshold = 0.55;

toneAnalyzer.tone({text: text}, (err, result) => {
  if (err) {
    console.log(err);
  } else {
    let tones = result.document_tone.tones;
    let emotions = []; // put all emotions results in an array

    for (let v of tones) {
      if(v.score >= confidencethreshold) { // pulse only if the likelihood of an emotion is above the given confidencethreshold
        console.log(`Current Emotion is ${v.tone_id}, ${v.score}`);
        emotions.push(v.tone_id);
      }
    }

    if(emotions.length) postEmotion(emotions, user, channel)
  }
});

Watson can analyze a lot more from a given text, but for this project we are only using the emotion analysis. The results will be returned as a JSON that provides a hierarchical representation of the analysis. Each emotion has an index, so define the confidence threshold (let’s set it 0.55 for now. You can adjust it later!) and use only the emotion the exceeded the threshold value! Learn more about the watson-developer-cloud module on the GitHub repo.

Posting a sentiment back on a channel

Now, let’s create a postEmotion function and post the result back on the Slack channel! To post a message, use chat.postMessage API. This call requires values for the token, channel, and text attributes.

const postEmotion = async(emotions, user, channel) => { 
  const args = {
    token: process.env.SLACK_ACCESS_TOKEN,
    channel: channel,
    text: `<@${user}> is feeling: ${emotions.join(', ')}`
  };
  const result = await axios.post(`${apiUrl}/chat.postMessage`, qs.stringify(args));
};

Yipee, your bot should be working now! Let’s run your node code. Add the “sentiment_analysis” bot to your Slack channel, and try posting some emotional messages!

Bot demo GIF (I am using Glitch to test the app and print the log in this GIF, in case you're wondering!)

The code samples used here are simplified to fit in the tutorial, however, if you wish to include the teammates’ usernames in the result messages as seen in this screenshot, view the entire source code on the GitHub repo!

You can end the project right here, or proceed this tutorial for more fun with hardware!

Making the bot into a physical bot with Raspberry Pi!

More bots are more fun! Let’s port this code to Raspberry Pi to make it as a physical bot!

TJ Bot

Installing Node.js on Pi

You need to boot up your Pi with Raspbian, connect to Internet, and upgrade the system. If you are new to Raspberry Pi, I recommend to learn how to set up on RaspberryPi.org!

Then you can either directly work on the terminal on Pi, or SSH into the Pi from your computer, and install Node.js for Linux ARM:

$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
$ sudo apt-get install -y nodejs

Installing ngrok on Pi

A nice thing about ngrok is that it supports ARM, so you can use in on a Raspberry Pi too!

$ cd /tmp
$ wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-arm.zip
$ unzip ngrok*
$ sudo mv ngrok /usr/bin/

SSH into the Pi to copy your index.js and all the module dependencies over to the Pi from your computer. Run the index.js, and now you should be able to tunnel requests to your Pi, just like you did before on more expensive computer:

$ ngrok http 5000

This gives you a new ngrok forwarding URL, so don’t forget to update the Request URL at the Event Subscription on your Slack App setting page!

Try posting some message on Slack and see if it works.

Now, let’s work on hardware!

Wiring up the Pi and an LED

Follow the diagram below and wire a Neopixel RGB LED. Find a flat side of the LED, and the leg closest to the flat side is a data-out (no wire), then ground, power, and data. Connect each leg to a corresponding pin:

Raspberry Pi wiring

Light up an Emotion!

Now you programmatically light the LED up, in a color depending on the emotion. First, install the rpi-ws281x-native via npm:

$ npm install rpi-ws281x-native --save

Then write the code to turn the LED on. Let’s define the colors too:

const ws281x = require('rpi-ws281x-native');
const NUM_LEDS = 1;
ws281x.init(NUM_LEDS);
const color = new Uint32Array(NUM_LEDS);
process.on('SIGINT', () => {
  ws281x.reset();
  process.nextTick(() => { process.exit(0); });
});
const red = 0x00ff00;
const green = 0xff0000;
const blue = 0x0000ff;
const yellow = 0xffff00;
const purple = 0x00ffff;

// Show a specific color for each emotion
function colorEmotion(emotion) {
  if (emotion.tone_id === 'anger') {
    setLED(red);
  } else if(emotion.tone_id === 'joy') {
    setLED(yellow);
  } else if(emotion.tone_id === 'fear') {
    setLED(purple);
  } else if(emotion.tone_id === 'disgust') {
    setLED(green);
  } else if(emotion.tone_id === 'sadness') {
    setLED(blue);
  }
}
// Set the LED to the given color value
function setLED(colorval){
  color[0] = colorval ;
  ws281x.render(color);
}

Now, call the colorEmotion function at where you are calling the postEmotion().

Run the Node code, type some message on Slack and see the LED lights up to match your feelings. Hooray!

Hooray!

I hope you enjoyed the tutorial!

Learn more

Related documentation