Slackbots, Javascript and NPM, oh my!

We build professional apps for Android, iOS and mobile web.
Have a look

Why? Just....Why?

First thing's first I'm the realest, I still find it surprising that, because of the software of the same name, the word slack has become synonymous with work.

Indeed, Slack is an indispensable software tool for intra and inter-office communication at jtribe. As well as using Slack to ask each other questions over the course of the day, our boss and director sometimes reminds us of upcoming events. Often, we're all reminded by our CTO or our Delivery Manager that we need to update our timesheets, because our boss needs to send out an invoice to whichever client we happen to be working with.

Spotting some room for improvement, I set about researching how to automate this procedure. Nobody likes to remind people of something that they don't particularly enjoy doing, so I figured out that some sort of Slackbot should do it for us. Recording entries in Mite is easy enough, but many of us often forget to do it on a regular basis. If we forget and remember three or four days later, we need to trawl through our git commit history or recently closed project pull requests, and figure out what it is we did on the days we need to record.

In a wild departure from iOS development, I began wading through the thick marsh of web dev land. I picked NodeJS as my language of choice, and commenced writing a functioning (and extremely passive-aggressive) Slackbot, for use by the team.

Let me just say this. Aside from Ruby, Javascript is the only language that I've found where you can pass through an arbitrary amount of parameters to a function, and it still does the thing! It's sort of frustrating, but it's also brilliant. It provided a nice break from the rigid and prescribed world of iOS development. It served as some sort of stress relief for me, because I was essentially beating the keyboard until something worked. Once it worked, I focused and refactored it down into some pretty and functional Javascript code.


The master plan, based on feedback and #featurerequests from the team, became as follows:

  • Check Mite for entries made today, yesterday OR .
  • Create an entry in Mite for today, yesterday OR .
  • Get a list of services and projects available in Mite.
  • Remind people at N that Mite entries haven't been done that day.
  • Remind people of our daily Scrum Stand-up at 11am, which people seem to forget often when "in the zone"
  • Provide some comic relief.

The First-Boot of MA-RV-IN 6000

While creating the Slackbot, I thought that because we're a tech company we should appropriate elements of pop culture and use them to add some comic relief to the office. So, I decided to dub him "Marvin" after the paranoid-android of the same name in Hitchhiker's Guide to the Galaxy.

Giving MA-RV-IN a personality and a backstory (his family tree consists of his MU-TH-UR 60001, 1A, and father HAL-90002 from the get-go, made him a fun side-project to work on. The added bonus of having a sarcastic Slackbot in our work's Slack channels, is some comic relief whenever you need to use him. After all, isn't laughter the best medicine?

The first port of call, was to upgrade MA-RV-IN's mainframe with the botkit3 module from npm. This is the magic that lets us easily communicate with Slack's RTM (Real-time Messaging) service. Once this was done, I added a few more required modules and commenced work on the most important functions from the previous list.


Conversational UI is cool 🌨

controller.hears(['make'], 'direct_message,direct_mention,mention', function(bot, message) {  
  // Insert stuff here!
});

This is the core of making MA-RV-IN respond to commands. If you direct message, direct mention or just mention him, he'll respond to the situation. Here, we're giving him the word 'make' to listen to. So, whenever you go:

@bot make or make @bot, we can reply publicly or initiate a private message with the user who mentioned him. As follows:

controller.hears(['make'], 'direct_message,direct_mention,mention', function(bot, message) {  
  bot.api.users.list({ },function(err,response) {
    let foundMember = response.members.filter( member => member.id === message.user );
    let member = foundMember.pop();

    var askServiceNumber = function(response, convo) {
      convo.sayFirst("Hey lets have a conversation!");
      convo.ask('_Number of Service in Mite:_', function(response, convo) {
        askProjectNumber(response, convo);
        convo.next();
      });
    }
    var askProjectNumber = function(response, convo) {
      convo.ask('_Number of Project in Mite:_', function(response, convo) {
        askHours(response, convo);
        convo.next();
      });
    }
    var askHours = function(response, convo) {
      convo.ask('_Hours spent on it:_', function(response, convo) {
        askNotes(response, convo);
        convo.next();
      });
    }
    var askNotes = function(response, convo) {
      convo.ask('_Notes:_', function(response, convo) {
        submitToMite(response, convo);
        convo.say('`MA-RV-IN 6000 SUBMIT MITE COMPLETE`');
        convo.next();
      });
    }
    var submitToMite = function(response, convo) {
      //...do the thing.
    }

    bot.startPrivateConversation(message, askServiceNumber);
  });
});

What we're doing above, is initiating a private (direct message) conversation between MA-RV-IN and the user who requested him. A series of questions can be asked (as many or as few as you want) and something can be done in the last function to complete the action.

For simple "Yes", "No" conversations, you can use the following format:

convo.ask('Are you sure you want me to shutdown, Dave?', [  
  {
    pattern: bot.utterances.yes,
    callback: function(response, convo) {
        convo.sayFirst('`ENDING PROCESS...`');
        convo.say('`DAISY...DAISY..GIVE ME YOOOOUUURR ANSSSWEEERRRRRRRRRR DOOOO...`');
        convo.next();
        setTimeout(function() {
            process.exit();
        }, 3000);
    }
  },
  {
    pattern: bot.utterances.no,
    default: true, // if an entirely unrecognised response is entered, this is triggered by default.
    callback: function(response, convo) {
        convo.say('`TERMINATION ABORTED`');
        convo.next();
    }
}]);

It quickly became apparent that I'd need to handle any invalid input when mentioning him, so we're able to do something similar to the following:

controller.on('direct_message,mention,direct_mention,conversation', function (bot, message) {  
   bot.api.reactions.add({ // Add a reaction to the message
       timestamp: message.ts,
       channel: message.channel,
       name: 'robot_face', // And the robot face emoji
   }, function (err) {
       if (err) {
           console.log(err)
       }
       bot.reply(message, 'Coming through loud and clear, boss.'); // And a sarcastic quip, because why not.
   });
});

I was even able to schedule reminders with the handy node-schedule4 module, which allowed me to create RecurrenceRule()s, that fire at a preset time and call a function. In this case, MA-RV-IN will loop over all available users on the team, and remind them to do their Mite entries if he finds that they haven't been done by the time he checks. He'll also sadly remind people of our daily standup (which people seem to forget), and of any other recurring events in our work calendar.

A more in-depth tutorial on how to write a Slackbot is coming soon. This is just the awesome precursor to demonstrate that a Slackbot is not useless, and can be quite helpful!


References

1, 1A Alien (1979) IMDb, MU-TH-UR 6000 Alien Anthology Wikia
2 2001: A Space Odyssey IMDb
3 botkit
4 node-schedule