Video icon 64
Learning to code? Skill up faster with our practical video courses. Start your free trial today.
Advertisement

Writing Hubot Plugins with CoffeeScript

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

In case you've been hiding under a rock, Campfire is a real-time chat application, written by our friends at 37 Signals. Campfire has a robust API, giving you the ability to bolt on tools to better the team environment.

Campfire is widely used by companies with remote workers and allows quick collaboration between distributed teams. Keep in mind that, in some cases, such as at my job at Emma, Inc., remote could mean "in the next room." At Emma, we can check the status of our systems, quickly retrieve customer data, and many other useful tasks that makes our work easier. Many of these tasks are made possible with the implementation of Hubot.


What's Hubot?

Plugins are fun to write and even more fun to use.

Hubot is a scriptable framework created by the folks at Github; they describe it as "a customizable, kegerator-powered life embetterment robot". Hubot is open source, written in CoffeeScript on Node.js, and easily deployed on platforms like Heroku. While Hubot can run within many different environments, I'll focus on running Hubot within the confines of a Campfire chat room.

In addition to releasing the source for Hubot, Github created a small number of pre-built scripts that ship with the Hubot source. These scripts allow Hubot to do things such as easily import images /img cute kittens:

image

Or you can import videos /youtube breakdancing:

image

Github also created a Hubot plugin repository where users can submit new plugins. As of this writing, there are 294 plugins in the public repository, covering all sorts of functionality ranging from the useful: checking the status of an AWS service, interacting with a Travis-CI server or base64 encoding; to the humorous: playing a rimshot audio clip; to the absurd: add a mustache to a photograph. You can even check out the nickname generator plugin that I wrote!

The sky's the limit with Hubot. If something can be done from within Node.js, then it can be automated using Hubot. With just a little bit of CoffeeScript knowledge, you can write the next great Hubot plugin. Speaking of, let's take a quick refresher course in CoffeeScript before we write our first Hubot plugin. If you're already familiar with CoffeeScript then feel free to jump ahead to the next section.


What's CoffeeScript?

CofeeeScript describes itself as a "little language that compiles into JavaScript" and "an attempt to expose the good parts of JavaScript in a simple way". CoffeeScript's goal is to remove the tedium of boilerplate (all those pesky curly braces, semicolons, and parentheses) from the life of developers and distill JavaScript down to its bare essence. As a result, your code becomes easier to read, and there's less of it to boot. Let's take a look at a few simple examples and compare the resulting JavaScript you compile CoffeeScript.

Oh wait, did I say "compile"?

I sure did, and how do you do that? I'm glad you asked... there's a number of tools that offer this service. My personal favorite is CodeKit, but be sure to check out the command line driven Yeoman. You can also directly compile CoffeeScript if you've installed Node.js, and you can even use a real-time conversion tool like JS2Coffee, which lets you convert back and forth between CoffeeScript and JavaScript.

Strings

So, what does CoffeeScript look like? Let's start with a line of JavaScript:

var author = 'Ernest Cline';

The CofeeScript equivalent is:

author = 'Ernest Cline'

Objects

That's a simple example, but it starts to show what CoffeeScript does for you... removing verbosity. Note the abscence of the var keyword and the semicolon. You'll never need those when you write in CoffeScript. How about an object reference in JavaScript?

book = {
    title: 'Ready Player One',
    date: '10/16/2011',
    references: {
        games: ['Street Fighter', 'Pac-Man'],
        music: ['Oingo Boingo', 'Men Without Hats'],
        movies: ['Back To The Future', 'The Last Starfighter']
    }
}

Here's the CoffeeScript version:

book =
  title: "Ready Player One"
  date: "10/16/2011"
  references:
    games: ["Street Fighter", "Pac-Man"]
    music: ["Oingo Boingo", "Men Without Hats"]
    movies: ["Back To The Future", "The Last Starfighter"]

A key thing to remember about CoffeeScript is that your code is still there, but the extra fluff of some delimiters, terminators, and keywords are gone. CoffeeScript goes an extra step (or three) and assumes those characters for you.

Functions

What about functions you might ask? They're similarly neat and tidy, removing braces and the return keyword. Like before, here's the JavaScript:

function openGate(key) {
    var gates = {
        'Copper': 'You opened the Copper Gate',
        'Jade': 'You opened the Jade Gate',
        'Crystal': 'You opened the Crystal Gate'
    };
    return gates[key] || 'Your key is invalid'
}

openGate('Jade')

And here's the same thing in CoffeeScript:

openGate = (key) ->
  gates =
    Copper: "You opened the Copper Gate"
    Jade: "You opened the Jade Gate"
    Crystal: "You opened the Crystal Gate"

  gates[key] | "Your key is invalid"
openGate "Jade"

CoffeeScript has a number of other extremely useful features that make it a compelling choice. Features like comprehensions (basically single line loops), "true" classes, handy string replacement, chained comparisons and more. You can read more about CoffeeScript on its website at CoffeeScript.org.


Setting the Stage

We'll need to install a few items before we can start working on our plugin. We'll need Node.js, NPM, and Hubot--along with their various dependencies.

Installation

The sky’s the limit with Hubot.

Let's first install Node.js. Open a terminal window and type which node. If you get back a file system path, then you can skip this section. If you see node not found or something similar, then you'll need to install it. Head over to the Node.js website and download (and install) the appropriate binary for your operating system. Unless you've recently installed Node, it's probably a good idea to go ahead and install the most recent version. Newer versions of Node ship with NPM (or Node Package Manager) which we'll use to install our software.

Next up we'll need to install Hubot. Type npm install hubot -g into your terminal window and let NPM do its work. I prefer to install plugins like this globally, thus the -g flag.

Using Hubot Locally

After the installation completes, we'll cd to the hubot install directory and run it for the first time. That directory can differ depending upon your paricular machine, but it is at /usr/local/lib/node_modules/hubot on my machine. Fire up hubot with the following command . bin/hubot. Then test it out with the command hubot ping. Hubot should immediately respond with PONG. Let's take a quick look at that plugin before writing our own. Its three lines of code are the guts of almost every other Hubot plugin. Here it is in all its glory:

module.exports = (robot) ->
    robot.respond /ping$/i, (msg) ->
        msg.send "ping"

When Hubot first starts up, it runs through every plugin in the scripts directory. Each plugin is written using the common module.exports Node pattern, which allows the plugin to identify itself to Hubot, and it also allows Hubot access to the plugin's inner workings. Also found in a plugin are one or more respond function calls. Each of these calls correlates to a an event listener that waits to hear a specific keyword or pattern. Lastly, this plugin sends back a value using msg.send, returning any arbitrary msg you prefer.

By the way, if you're curious (like I was) to see just what the robot, or msg, arguments contain, simply add a console.log statement anywhere in the code. For example, adding console.log(robot) immediately after the module.exports statements displays the following information:

{
      name: 'Hubot',
      commands: [],
      version: '2.3.4',
      server: {}
      documentation: {},
      listeners:
      [
            {
                  robot: [Circular],
                  regex: /^Hubot[:,]?\s*(?:PING$)/i,
                  callback: [Function],
                  matcher: [Function]
            }
      ],
      [more stuff]
}

Now you're ready to start working on our first Hubot plugin.


Your First Hubot Plugin

Okay, enough already. I know you're ready to write your own plugin, so lets do a quick one of our own. Create a new file within the scr/scripts directory of your Hubot install. Name it deepthoughts.coffee, open it in your editor of choice, and then input the following lines:

# Configures the plugin
module.exports = (robot) ->
    # waits for the string "hubot deep" to occur
    robot.respond /deep/i, (msg) ->
        # Configures the url of a remote server
        msg.http('http://andymatthews.net/code/deepthoughts/get.cfm')
            # and makes an http get call
            .get() (error, response, body) ->
                # passes back the complete reponse
                msg.send body

You're already familiar with the first two lines so we won't review them. The third line begins the set up of an HTTP request; in this case, it's a GET that sends no parameters to the remote site. The fourth line executes the HTTP request and sets up a callback function that receives any errors, the raw response, and the body of the returned page. In this case, the loaded page's body doesn't even have any HTML...it's simply a string. This allows us to return it directly to the user by way of msg.send. Save that file, restart Hubot with a hubot die and a bin/hubot, and then get yourself a random deep thought with a hubot deep. Hopefully, it's something profound, deeply thought provoking and not the one about the trampoline salesman or the golden skunk.

Your Hubot Homework

Now that you've written your first plugin, here's the code for another one. See if you can figure out what it does and how to use it.

QS = require 'querystring'

module.exports = (robot) ->
    robot.respond /post (.+)/i, (msg) ->
        url = 'http://httpbin.org/post'
        data = QS.stringify({'hubot-post': msg.match[1]})

        msg.http(url)
            .post(data) (err, res, body) ->
                msg.send body
  • Notice the import happening at the top.
  • What's the respond method listening for?
  • What is msg.match?
  • See that the plugin can also do post requests?

Go Forth and do Likewise

As you can see from these few examples, writing Hubot plugins is a fairly straightforward task. Plugins can be useful or whimsical, but they're fun to write and even more fun to use. What sort of plugin will you create for the world?

The one unfortunate thing about writing Hubot plugins is that the documentation isn't super clear on some subjects, and you might sometimes spin your wheels trying to figure out what part belongs to what app if you're unfamiliar with Node, CoffeeScript, or Hubot. But with a little perseverence, and this article, you'll be on your way.

Advertisement