Slack Enterprise Grid and BotKit

I have the pleasure of using Slack Enterprise Grid on a daily basis as IBM has adopted it internally, I work there, and there are far too many of us to use a single slack instance. The main downside of Slack Enterprise Grid (apart from having about 15 slack organisations running in your client all the time) is that it returns different data when integrating with the Slack Web API and RTM (Real Time Messaging) systems. We had some bot integration that stopped working as a result, and I ended up moving to BotKit and then using a mixture of the RTM and Web API from Slack to get things to work. I won't remember how I did it so I thought I'd better write it down.

The Problem: No User Information

The basic "problem" is actually a perfectly sane decision from slack. By default they throw around lists of all users and all channels and so on ... it's unwieldy and slow on the bigger platforms like ours, so they stopped returning all that data. However there don't seem to be all that many enterprise grid users around and certainly not in the open source javascript bot code world where I was hoping to find working software!

What happens is that the code assumes the user data will already be in local storage somewhere (I started with Hubot and moved onto some other bot frameworks, nothing worked and it all smelled pretty similar), but for this type of slack account the data isn't returned.

The Solution: use the Slack Web API

Next I tried BotKit which seemed a bit more lightweight (I just started from scratch rather than using any of the prebuilt skeleton apps) and it uses the Slack RTM API and can connect out of the box, which was progress. However it still doesn't find the user information where it expects to. Instead, I added a call to the Slack Web API (this one is HTTP request and response, instead of constant chatter over a web socket) to look up the user information when I needed it.

Here is the code example: if you say hello to the bot, the bot looks up who you are and says hello back to you. If it didn't get the user info, you just get a general hello:

var Botkit = require('botkit');
var os = require('os');
var controller = Botkit.slackbot({});

var bot = controller.spawn({
    token: process.env.token
}).startRTM();

controller.hears(['hello', 'hi'], 'direct_message,direct_mention,mention', function(bot, message) {
    bot.api.users.info({user: message.user}, function(err, response) {
        user = response.user;
        if (user && user.name) {
            bot.reply(message, 'Hello ' + user.name + '!!');
        } else {
            bot.reply(message, 'Hello.');
        }
    });
});

It's a little bit slow since it's an extra HTTP call but it doesn't feel terrible - it's still faster than a user typing! You could probably combine this approach with the existing controller.storage to save the users you've already looked up and then use the local storage if you can. Or maybe I should be loading the users from the channel list when the bot starts? This newly working solution will probably evolve over time.

Bonus Example

I also wanted to get the detail of a message that a user had reacted to, and I had to figure out a few things for that here's some bonus code that isn't really related to the blog post but might help as an additional example for another developer working through some of this stuff.

controller.on('reaction_added', function(bot, message) {
    // we really only care about "our" channel but we'll get events from them all!
    if(message.item.channel && message.item.channel == [CHANNEL_ID]) {
        bot.api.users.info({user: message.user}, function(err, response) {
            user = response.user;
            var msg = {
                type: "message",
                channel: [CHANNEL_ID]
                text: user.name + " reacted with " + message.reaction
            }
            bot.api.conversations.history({channel: message.item.channel, latest: message.item.ts, inclusive: true, limit:1}, function(err, response) {
                var orig_message = response.messages[0].text;
                msg.text += " to this message: " + orig_message;
                bot.say(msg);
            });
        });
    }
});

This registers a handler for the event "reaction_added" but you'll get everything happening on the entire slack team which might not be what you want! I'm only interested in a specific channel so this code just checks for that and doesn't do anything if it doesn't match. If it does, we first get the user's information and then look up which message this was that we responded to. The docs on BotKit for calling the Slack Web API are very minimal (see about four lines of documentation) but it was just enough to get me started and hopefully these examples help someone else. Basically you can call any Slack Web endpoint that you like so here I'm using both users and conversations to get the info I need to understand which user did what to which message - and my app will then go on and do something with the event once it has everything.

I've been impressed with BotKit, how lightweight it is. It took me a while to begin and then to understand how to call the Web API to fill in the missing channel/user/conversation information that I needed but things are now working well. I didn't find many resources on this so if you have links or tips to share, I'd love to hear them.

Leave a Reply

Please use [code] and [/code] around any source code you wish to share.