Try Tuts+ Premium, Get Cash Back!
Writing Hubot Plugins with CoffeeScript

Writing Hubot Plugins with CoffeeScript

Tutorial Details
  • Difficulty: Intermediate
  • Completion Time: 1 Hour

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.

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • Ged Quayle

    Cannot put into words what an utter waste of time, life, and the Internet coffeescript is. An abstraction layer that makes javascript look a bit like python. Woo. If you want to code in python code in python.

    • akmjenkins

      +1 CoffeeScript – the “language” for people who don’t know how to properly use “var”

      • randito

        +1 Coffeescript – the language for people who aren’t interested in learning javascript quirks mode inside and out.

      • pixelBender67

        you must not know how to use cs then, if you don’t know your js quirks

      • JeffWay

        Not sure where you got that idea.

      • spam

        If u don’t like CS then don’t use it. Or better create ur own

    • http://www.facebook.com/shane.h.davis.3 Shane Howling Wolf Davis

      Thats pretty harsh, working with coffeescript is really useful, ie. when working with pseudo-classes. Plus, you can find yourself writing a lot less boilerplate…and your code can look a lot cleaner. Have CS –watch your files and you don’t even have to add additional steps. As a bonus, for beginners/intermediate javaScripters, it can be really good to inspect the code it outputs to get an insight into ‘the good parts’. TDD is a lot quicker and cleaner in CS also.

    • JeffWay

      I couldn’t disagree with this statement more. CoffeeScript isn’t for everyone, but once you use it for a while, it’s very, very difficult to go back to vanilla JS.

    • http://twitter.com/vladimir_light Vladimir L

      Uhmm JS is a client-side language (used mostly in a Browser) and python is a high-livel lang ….so why on earth you suggest to use one (python) insted of other ?

      • pixelBender67

        we’re talking about syntax

    • pixelBender67

      couldn’t agree more, I hate the way it compiles my js, I don’t write js like that. It tries to make JS into a classical language which it’s not, it’s prototypical, which is much more flexible and dynamic

  • yarcowang

    -1000000. Well, sorry, i don’t like CoffeeScript…i want “Writing Hubot Plugins without CoffeeScript”. I don’t want to learn another language.

    • http://www.facebook.com/shane.h.davis.3 Shane Howling Wolf Davis

      what don’t you like specifically?

      • yarcowang

        except:
        alert “I knew it!” if elvis?
        cubes = (math.cube num for num in list)

        but i think these two features should be supported in future.

      • pixelBender67

        take a look at the top node developers, none of them use cs

      • http://yaroslav0rudenok.blogspot.com Yaroslav

        if top node developers don’t use cs that does not mean that it is bad, it means you do not have own opinion

      • pixelBender67

        Yea okay

      • pixelBender67

        Again, I don’t like how it writes my JS for me, that’s not the way I think, it’s just not for me

    • lyphtec

      There’s nothing stopping you from writing plugins in Javascript as Hubot supports it as well… see https://github.com/blog/968-say-hello-to-hubot

  • tracy

    great tutorial~

  • pixelBender67

    The only people that I know that really like coffee script are ruby and python devs the others are just hipsters that want to be cool, just MHO

    • pixelBender67

      except for Jeffery Way of course lol

  • http://www.famesbond.com/ aditya menon

    NetTuts posts an awesome article on how to use a piece of niche tech to accomplish a task, and most comments are about how that tech sucks so much. Please guys, lets just encourage nettuts to continue being this spectacular free resource.

    ” If you want to code in python code in python.”, said some guy below: conversely, if you don’t want to use CoffeeScript stay out of CoffeScript articles. Especially ones that say “COFFEESCRIPT” pretty clearly in the headline, you weren’t tricked into reading it.

    Thanks for the tutorial guys, NetTuts articles come in so much use for me all the time. Offtopic but, I suggest you guys do a piece on PHPExcel, just found it yesterday but it’s a giant library with too little documentation.

  • http://twitter.com/commadelimited Andy Matthews

    @Ged…

    I primarily chose Coffeescript for this article because all of the Hubot plugins I found online used it. I’m not a fan of CoffeeScript either but I actually enjoyed working with it while I wrote. It’s a little simpler to write, and cleaner than normal JavaScript. I won’t be replacing my normal workflow anytime soon but it was a good exercise.

    Other than CS, did you have a comment about the actual content of the article?

    • pixelBender67

      Andy, the tutorial is well written and other than the cs excellent, thanks for the introduction to hubspot ( I never heard of it ).

  • http://twitter.com/_ismaelga Ismael Abreu

    You guys just have a well known problem –> change resistance. CoffeeScript just lets you create js code with pleasure, what’s wrong with that? I love it and I miss it when I can’t use it. If you don’t like to use it because of reason X, it’s ok, but that doesn’t make this a bad article just beacause it’s in a more pleasant syntax !

  • sergeylukin

    I think you wanted to write

    return gates[key] || ‘Your key is invalid’

    instead of

    return gates[key] | ‘Your key is invalid’

    • http://twitter.com/commadelimited Andy Matthews

      I think you’re right. I’ll get that changed. Thanks Sergey!

  • http://amalrivers.webnode.com/ UlricRay

    I feel you are too good to write Genius!Thanks for posting, maybe we can see more on this.