Automation and Monitoring with Hubot
Automation and Monitoring with Hubot
Tomas Varaneckas
Buy on Leanpub

Hubot Scripting

Hubot is written in Node.js, using CoffeeScript, which is a JavaScript wrapper that resembles Python and aims to remove the shortcomings of JavaScript and make use of it’s wonderful object model. Following the tradition of Hubot, all scripts in this book will be written in CoffeeScript, but you may use JavaScript, simply name your scripts with .js rather than .coffee file extension.

Hello, World!

To start with, create scripts/hello.coffee in your hubot directory with following contents:

# Description:
#   Greet the world
#
# Commands:
#   hubot greet - Say hello to the world

module.exports = (robot) ->
  robot.respond /greet/i, (msg) ->
    msg.send "Hello, World!"

Now restart Hubot and try it out in your chatroom.

Tomas V.  hubot help greet
Hubot     hubot greet - Say hello to the world
Tomas V.  hubot greet
Hubot     Hello, World!

Wonderful, isn’t it?

Basic Operations

Hubot is event driven, and when you write scripts for it, you define callbacks that should happen when some event occurs. Event can be:

  • Message in the chatroom
  • Private message to Hubot
  • A text pattern detected in any message
  • HTTP request

Callback can result in:

  • Message in the chatroom
  • Reply to a message
  • Emotion in the chatroom
  • HTTP response (if trigger was HTTP request)
  • New HTTP request
  • Executing a shell command
  • Executing something on a remote server

Hubot can do anything that can be done with Node.js.

We’ll learn how to exploit everything Hubot can offer by writing a fully functional script that covers a different piece of functionality. We will be analyzing it line by line, so you will get a perfectly clear understanding of what’s happening.

Reacting To Messages In Chatroom

Let’s try to create something more useful than hello world. We want Hubot to print out this month’s calendar when we say “hubot calendar” or “hubot calendar me”. We will use cal - a shell command that prints out a calendar like this:

hubot@botserv:~$ cal
    January 2014
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

To do that, we will create calendar.coffee in scripts/ directory and use robot.respond to handle the event.

scripts/calendar.coffee


1 child_process = require('child_process')
2 module.exports = (robot) ->
3   robot.respond /calendar( me)?/i, (msg) ->
4     child_process.exec 'cal -h', (error, stdout, stderr) ->
5       msg.send(stdout)

Let’s analyze what happens line by line.

1 child_process = require('child_process')

Here we require child_process - a node module for making system calls. We assign the module to child_process variable.

2 module.exports = (robot) ->

When Hubot requires calendar.coffee, module.exports is the object that gets returned. The (robot) -> part is a function that takes robot argument. This is how this line would look like in JavaScript:

module.exports = function(robot) {

Every Hubot script must export a function that takes robot argument and uses it to set up event listeners.

3   robot.respond /calendar( me)?/i, (msg) ->

robot.respond is a function that takes two arguments - a regular expression to match the message, and a callback function that takes msg argument, which has a variety of functions for doing various actions. The regex /calendar( me)?/i would match calendar and calendar me in case insensitive fashion. Since we are using respond, it also expects the message to begin with hubot, or whatever your bot name is.

4     child_process.exec 'cal -h', (error, stdout, stderr) ->

Here we call exec function on child_process variable, and provide two parameters - a system call that should be executed, and a callback function that takes 3 arguments - error, stdout, and stderr. cal -h displays ASCII calendar without highlighting current day.

5       msg.send(stdout)

Finally, we use msg, which was passed into robot.respond callback function, to send standard output from cal -h command that we just executed.

To understand Hubot scripting better, you can try to understand concepts of Node.js. It’s all about callbacks. In our calendar script there are two nested callbacks, one for robot.respond, another for child_process.exec.

Now restart Hubot and test the new script.

Tomas V.  hubot calendar
Hubot         January 2014
          Su Mo Tu We Th Fr Sa
                    1  2  3  4
           5  6  7  8  9 10 11
          12 13 14 15 16 17 18
          19 20 21 22 23 24 25
          26 27 28 29 30 31

It works as expected, but we also want this command to appear in hubot help, since it’s not useful to have commands that nobody knows about. We have to add a documentation block on top of our script to get the effect. The final version of our script looks like this:

script/calendar.coffee


# Description:
#   Prints out this month's ASCII calendar.
#
# Commands:
#   hubot calendar [me] - Print out this month's calendar

child_process = require('child_process')
module.exports = (robot) ->
  robot.respond /calendar( me)?/i, (msg) ->
    child_process.exec 'cal -h', (error, stdout, stderr) ->
      msg.send(stdout)

Now hubot help and hubot help calendar will tell everyone about your script.

Reacting To Message Parts

Hubot can eavesdrop on chatrooms and react to certain words or phrases that were said without talking to the bot directly. Use robot.hear to do it.

Our new script will listen for “weather in <…>”, query Open Weather Map API and post the weather information.

script/weather.coffee


 1 # Description:
 2 #   Tells the weather
 3 #
 4 # Configuration:
 5 #   HUBOT_WEATHER_API_URL - Optional openweathermap.org API endpoint to use
 6 #   HUBOT_WEATHER_UNITS - Temperature units to use. 'metric' or 'imperial'
 7 #
 8 # Commands:
 9 #   weather in <location> - Tells about the weather in given location
10 #
11 # Author:
12 #   spajus
13 
14 process.env.HUBOT_WEATHER_API_URL ||=
15   'http://api.openweathermap.org/data/2.5/weather'
16 process.env.HUBOT_WEATHER_UNITS ||= 'imperial'
17 
18 module.exports = (robot) ->
19   robot.hear /weather in (\w+)/i, (msg) ->
20     city = msg.match[1]
21     query = { units: process.env.HUBOT_WEATHER_UNITS, q: city }
22     url = process.env.HUBOT_WEATHER_API_URL
23     msg.robot.http(url).query(query).get() (err, res, body) ->
24       data = JSON.parse(body)
25       weather = [ "#{Math.round(data.main.temp)} degrees" ]
26       for w in data.weather
27         weather.push w.description
28       msg.reply "It's #{weather.join(', ')} in #{data.name}, #{data.sys.count\
29 ry}"

Run it for a test drive.

Tomas V.  I wonder what is the weather in Vilnius right now
Hubot     Tomas Varaneckas: It's 28 degrees, shower snow, mist in Vilnius, LT
Tomas V.  and weather in California?
Hubot     Tomas Varaneckas: It's 37 degrees, Sky is Clear in California, US

I wish I were in California right now. Anyway, let’s take this script apart. We’ll skip documentation, since it’s pretty straightforward.

14 process.env.HUBOT_WEATHER_API_URL ||=
15   'http://api.openweathermap.org/data/2.5/weather'
16 process.env.HUBOT_WEATHER_UNITS ||= 'imperial'

process.env allows you to read and set environmental variables, and our script uses a couple of them. One for defining the API endpoint, another one for measurment unit type. In CoffeeScript x ||= y is a shorthand for x = (x != null) ? x : y, meaning it will only set the variable if it has not been set before. This way you can override the values and set HUBOT_WEATHER_UNITS=metric to get Hubot tell degrees in Celsius rather than Farenheit.

19   robot.hear /weather in (\w+)/i, (msg) ->

robot.hear works almost like robot.respond, with one exception. robot.respond requires message to begin with Hubot’s name, while robot.hear reacts on any part of message, which is exactly what we want. It takes two arguments, a regex that matches “weather in <location>", and a callback function which will be triggered if a match is found.</location>

20     city = msg.match[1]

msg.match is an array of regex matches, with 0 being the full message, and in our case 1 being the content of the parentheses, which is simply any word. Yes, this script will fail to work with “San Francisco”. So, we set city to be the first word that comes after “weather in”.

21     query = { units: process.env.HUBOT_WEATHER_UNITS, q: city }
22     url = process.env.HUBOT_WEATHER_API_URL

Here we construct a query string parameters that will be passed to the weather API, and set the URL we are going to call. We will read units from HUBOT_WEATHER_UNITS environmental variable, and set query to city. If we would construct the query string ourselves, we would need to worry about URL-encoding special characters, but since we’re passing an object, it will be taken care of for us. Final request will be made to following url: http://api.openweathermap.org/data/2.5/weather?units=imperial&q=chicago.

23     msg.robot.http(url).query(query).get() (err, res, body) ->
24       data = JSON.parse(body)

Now we call the url using HTTP GET, set the query string parametrs using .query(), and provide a callback function to handle the response. Callback parameters are error (if any), HTTP response object and plain text response body. Our API returns JSON, so we parse the response body into data variable.

25       weather = [ "#{Math.round(data.main.temp)} degrees" ]

Here we create a weather array with single element - data.main.temp is { main: { temp: ... } } from the response JSON, and since it is returned in high precision, we round it to an integer with Math.round. And finally we make it a string with “degrees” at the end.

26       for w in data.weather
27         weather.push w.description

We loop { weather: [ ... ] } from response JSON, getting the description out of every element and pushing it to the end of weather array.

28       msg.reply "It's #{weather.join(', ')} in #{data.name}, #{data.sys.count\
29 ry}"

When we have our weather array all packed up with data, we join it into comma separated string and form a nice string containing the weather data, city name and country code.

Capturing All Messages

Sometimes you may want Hubot to process all messages in all chatrooms. For example, if you are writing a logging system. Here is a simple one:

scripts/logger.coffee


 1 # Description
 2 #   Logs all conversations
 3 #
 4 # Notes:
 5 #   Logs can be found at bot's logs/ directory
 6 #
 7 # Author:
 8 #   spajus
 9 
10 module.exports = (robot) ->
11   fs = require 'fs'
12   fs.exists './logs/', (exists) ->
13     if exists
14       startLogging()
15     else
16       fs.mkdir './logs/', (error) ->
17         unless error
18           startLogging()
19         else
20           console.log "Could not create logs directory: #{error}"
21   startLogging = ->
22     console.log "Started logging"
23     robot.hear //, (msg) ->
24       fs.appendFile logFileName(msg), formatMessage(msg), (error) ->
25         console.log "Could not log message: #{error}" if error
26   logFileName = (msg) ->
27     safe_room_name = "#{msg.message.room}".replace /[^a-z0-9]/ig, ''
28     "./logs/#{safe_room_name}.log"
29   formatMessage = (msg) ->
30     "[#{new Date()}] #{msg.message.user.name}: #{msg.message.text}\n"

The breakdown:

11   fs = require 'fs'

We require Node’s built in file system module and assign it to fs variable.

12   fs.exists './logs/', (exists) ->

We check if ./logs/ directory exists, and since NodeJS is asynchronous, we have to provide a callback function (exists) ->, that will get called with true or false after file system check actually happens.

13     if exists
14       startLogging()
15     else
16       fs.mkdir './logs/', (error) ->
17         unless error
18           startLogging()
19         else
20           console.log "Could not create logs directory: #{error}"

All this is happening in the (exists) -> callback function. If directory ./logs/ exists, we start logging by calling startLogging() function immediately, otherwise we call mkdir to create this directory. It has another callback function, (error) ->. It gets called after directory creation is over. If there was no error, we call startLogging() function, otherwise we use console.log to inform that we failed to start logging because directory could not be created.

21   startLogging = ->
22     console.log "Started logging"
23     robot.hear //, (msg) ->

This is the definition of startLogging() function we’ve called above. It uses console.log to announce that logging was initiated, then uses robot.hear //, (msg) -> to register a listener that reacts to all chat messages. That is because robot.hear does not require a message to be prefixed with hubot, and // is a regular expression that would match just anything.

24       fs.appendFile logFileName(msg), formatMessage(msg), (error) ->
25         console.log "Could not log message: #{error}" if error

When robot.hear gets triggered, (msg) -> is called, and this is what happens inside. We use appendFile to create or append a file that logFileName(msg) function will return, and write the output of formatMessage(msg) function there. appendFile has a callback function to handle errors. We define it as (error) -> and use console.log to inform about the failure if error is present.

Time to try this out. After restarting Hubot, say something:

Tomas V.  Hello, anybody here?
          hubot ping
Hubot     PONG
Tomas V.  oh good, I hope you're not logging anything

It should appear in your Hubot’s logs/ directory:

hubot@botserv: ~/campfire$ cat logs/585164.log
[2014-03-22 21:54:26] Tomas Varaneckas: Hello, anybody here?
[2014-03-22 21:54:32] Tomas Varaneckas: hubot ping
[2014-03-22 21:54:47] Tomas Varaneckas: oh good, I hope you're not logging an\
ything

Unfortunately Hubot will not be able to see it’s own messages. It can be done after tweaking the internals, but that’s a whole different story. Other than that, all messages will get logged.

Capturing Unhandled Messages

If you want to capture only those messages that were not handled by any Hubot script, it’s very simple to do:

1 module.exports = (robot) ->
2   robot.catchAll (msg) ->
3     msg.send "I don't know how to react to: #{msg.message.text}"

Serving HTTP Requests

Hubot has a built-in express web framework that can serve HTTP requests. By default it runs on port 8080, but you can change the value using PORT environmental variable. This time we will create a script that responds to HTTP requests and posts request body in one or more rooms. We’ll name this script notifier.coffee.

It will accept HTTP POST requests, so there will be no limits for what the body can be.

scripts/notifier.coffee


 1 # Description:
 2 #   Send message to chatroom using HTTP POST
 3 #
 4 # URLS:
 5 #   POST /hubot/notify/<room> (message=<message>)
 6 
 7 module.exports = (robot) ->
 8   robot.router.post '/hubot/notify/:room', (req, res) ->
 9     room = req.params.room
10     message = req.body.message
11     robot.messageRoom room, message
12     res.end()

To try it out, we will make a POST request using curl.

hubot@botserv:~$ curl -X POST \
                      -d message="Hello from $(hostname) shell" \
                      http://localhost:8080/hubot/notify/585164

And we get this in our chatroom.

Hubot   Hello from botserv shell

Let’s dig in to the source.

8   robot.router.post '/hubot/notify/:room', (req, res) ->

robot.router.post creates a listener for HTTP POST requests to /hubot/notify/:room URL, where :room is a variable defining your room. It also takes a callback function that has two parameters, request and response. You can find out everything about robot.router by examiming express api documentation - robot.router is the express app.

9     room = req.params.room

req.params contains params from the URL, so in our case, if URL is /hubot/notify/123, variable room is set to 123.

10     message = req.body.message

We read the value of message POST parameter and assign it to message variable.

11     robot.messageRoom room, message
12     res.end()

Now, we send the message to given room and end the HTTP response. It would work without res.end(), but it’s always nice to respond to the request, otherwise the HTTP client may hang while expecting a response.

While this script looks nothing important, this concept is incredibly useful in building your own chat based monitoring. You can trigger any sort of events from anywhere and make Hubot tell everything about it by doing an HTTP request.

Cross Script Communication With Events

To reduce script complexity, or to introduce communication between two or more scripts, one can use Hubot event system, which consists of two simple functions: robot.emit event, args and robot.on event, (args) ->. We will now write two scrips - event-master.coffee and event-slave.coffee. Master will listen to us and trigger events that Slave will listen to and process.

scripts/event-master.coffee


 1 # Description:
 2 #   Controls slave at event-slave.coffee
 3 #
 4 # Commands:
 5 #   hubot tell slave to <action> - Emits event to slave to do the action
 6 
 7 module.exports = (robot) ->
 8   robot.respond /tell slave to (.*)/i, (msg) ->
 9     action = msg.match[1]
10     room = msg.message.room
11     msg.send "Master: telling slave to #{action}"
12     robot.emit 'slave:command', action, room

scripts/event-slave.coffee


1 # Description:
2 #   Executes commands from `event-master.coffee`
3 
4 module.exports = (robot) ->
5   robot.on 'slave:command', (action, room) ->
6     robot.messageRoom room, "Slave: doing as told: #{action}"
7     console.log 'Screw you, master...'

It runs like this:

Tomas V.  hubot tell slave to bring beer
Hubot     Slave: doing as told: bring beer
Hubot     Master: telling slave to bring beer

Meanwhile in hubot.log:

hubot.log


Screw you, master...

Notice that “Slave” responded before “Master”, even though msg.send was called in “Master” script first. It’s a perfect example to help you understand how Node.js works. Nearly everything is being done asynchronously using callback functions, the only way to ensure the order of execution is to use callbacks. To make “Master” send his message first, we have to put robot.emit in msg.send callback in event-master.coffee, like this:

11 msg.send "Master: telling slave to #{action}", ->
12   robot.emit 'slave:command', action, room

This way msg.send is execute first, and only when it’s done, the callback function is called and robot.emit gets executed.

In robot.emit call, slave:command is just a string that describes the event, action and room are the parameters that are passed along with the event trigger. There can be as many listeners as needed for every event type. We have placed ours in event-slave.coffee:

5   robot.on 'slave:command', (action, room) ->
6     robot.messageRoom room, "Slave: doing as told: #{action}"
7     console.log 'Screw you, master...'

Our callback function is pretty simple, it just posts a message to given room, and logs “Screw you, master…” behind everyone’s back using console.log. It’s a good technique for debugging your scripts.

Periodic Task Execution

You can make Hubot execute something using node-cron, which works perfectly with combination of firing events - let one of your scripts listen to an event, and another one fire them periodically.

First install the dependencies in your Hubot directory:

hubot@botserv:~campfire$ npm install --save cron time

Then create a script called scripts/cron.coffee and define all periodic executions there:

scripts/cron.coffee


 1 # Description:
 2 #   Defines periodic executions
 3 
 4 module.exports = (robot) ->
 5   cronJob = require('cron').CronJob
 6   tz = 'America/Los_Angeles'
 7   new cronJob('0 0 9 * * 1-5', workdaysNineAm, null, true, tz)
 8   new cronJob('0 */5 * * * *', everyFiveMinutes, null, true, tz)
 9 
10   room = 12345678
11 
12   workdaysNineAm = ->
13     robot.emit 'slave:command', 'wake everyone up', room
14 
15   everyFiveMinutes = ->
16     robot.messageRoom room, 'I will nag you every 5 minutes'

Now let’s break it down:

5   cronJob = require('cron').CronJob
6   tz = 'America/Los_Angeles'
7   new cronJob('0 0 9 * * 1-5', workdaysNineAm, null, true, tz)
8   new cronJob('0 */5 * * * *', everyFiveMinutes, null, true, tz)

Here we require the cron dependency and assign it’s CronJob prototype to cronJob variable and assign our desired time zone to tz. Then we create two jobs, first will run every workday at 9 AM in Los Angeles time and will execute workdaysNineAm function. The other one will execute every five minutes and call everyFiveMinutes function.

10   room = 12345678
11 
12   workdaysNineAm = ->
13     robot.emit 'slave:command', 'wake everyone up', room
14 
15   everyFiveMinutes = ->
16     robot.messageRoom room, 'I will nag you every 5 minutes'

We assign room id to room variable, which we will use in following functions. workdaysNineAm emits an event for the slave script we created earlier, and everyFiveMinutes just posts a message to a room.

You can also do the same automation using your OS cron that would run curl on Hubot’s HTTP endpoints, but this is more elegant.

Debugging Your Scripts

It’s frustrating when things don’t work the way they should, but there are several techniques to help you narrow down the problem.

Log strings to hubot.log:

console.log "Something happened: #{this} and #{that}"

Inspect an object and print it in the chatroom:

util = require('util')
msg.send util.inspect(strange_object)

Recover from an error and log it:

try
  dangerous.actions()
catch e
  console.log "My script failed", e

Advanced Debugging With Node Inspector

Sometimes it’s not enough just to print out the errors. For those occasions you may need heavy artillery - a full fledged debugger. Luckily, there is node-inspector. You will be especially happy with it if you are familiar with Chrome’s web inspector. To use node-inspector, first install the npm package. You should do it once, using -g switch to install it globally. Install as root.

root@botserv:~# npm install -g node-inspector

To start the debugger, run node-inspector either in the background (followed by &) or in a new shell. In following example it’s started without preloading all scripts (otherwise it’s a long wait), and inspector console running on port 8123, because both hubot and node-inspector use port 8080 by default. We could set PORT=8123 for hubot instead, but setting it for node-inspector is more convenient.

hubot@focus:~/campfire$ node-inspector --no-preload --web-port 8123
Node Inspector v0.7.0-1
   info  - socket.io started
   Visit http://127.0.0.1:8123/debug?port=5858 to start debugging.

Now, we will put debugger to add a breakpoint to our weather.coffee script and debugger will stop on that line when it gets executed.

script/weather.coffee


27       for w in data.weather
28         weather.push w.description
29       debugger
30       msg.reply "It's #{weather.join(', ')} in #{data.name}, #{data.sys.count\
31 ry}"

Now we have to start Hubot in a little different way:

hubot@focus:~/campfire$ coffee --nodejs --debug node_modules/.bin/hubot
debugger listening on port 5858

Then open http://127.0.0.1:8123/debug?port=5858 - the link that node-inspector gave you in it’s output in Chrome, or any other Blink based browser. Expect a little delay, because it will load all the scripts that Hubot normally requires just in time when needed. When you are able to see Sources tree in the top-left corner of your browser (you may need to click on the icon to expand it), get back to Hubot console and ask for the weather:

Hubot> what is the weather in Hawaii?
Hubot>

Don’t expect a response, because Chrome should now switch to weather.coffee and stop the execution at debugger line. Now you can step over the script line by line, add additional breakpoints by clicking on line nubers in any souce file from the Source tree in the left, or use the interactive console - there is Console tab at the top of the debugger, and a small > icon in bottom-left corner, which I prefer because it doesn’t close the source view.

You can type any JavaScript in the console, and it will execute. Let’s examine our weather array:

> weather
  - Array[2]
    0: "74 degrees"
    1: "broken clouds"
    length: 2

And the response from the weather API:

> data
  - Object
    base: "cmc stations"
    + clouds: Object
    cod: 200
    + coord: Object
    dt: 1389847230
    id: 5856195
    + main: Object
    name: ""
    - sys: Object
      country: "United States of America"
      message: 0.308
      sunrise: 1389892287
      sunset: 1389931892
    - weather: Array[1]
      - 0: Object
        description: "broken clouds"
        icon: "04n"
        id: 803
        main: "Clouds"
      length: 1
    + wind: Object

You can expand any part of the object tree to see what’s in it. You can also call functions:

> msg.send("Hello from node-inspector")

And in Hubot shell you should see:

Hubot> Hello from node-inspector

You can debug your web applications or any other JavaScript or CoffeeScript code using this technique. It’s even easier for web applications - just open Chrome Inspector and you’re set.

Writing Unit Tests For Hubot Scripts

Unit tests for Hubot scripts are a tricky subject that is either misunderstood or avoided. There is a strange trend among packages in github.com/hubot-scripts to write tests like this one:

chai = require 'chai'
sinon = require 'sinon'
chai.use require 'sinon-chai'

expect = chai.expect

describe 'hangouts', ->
  beforeEach ->
    @robot =
      respond: sinon.spy()
      hear: sinon.spy()

    require('../src/hangouts')(@robot)

  it 'registers a respond listener', ->
    expect(@robot.respond).to.have.been.calledWith(/hangout/)

You can find this test at hubot-scripts/hubot-google-hangouts. This test checks that Hubot script compiles and that it has the following lines:

module.exports = (robot) ->
  robot.respond /hangout( me)?\s*(.+)?/, (msg) ->

That’s better than nothing, but still a bit pointless, don’t you think? Luckily, there are better ways to do this. Take a look at hubot-mock-adapter. Tests will certainly be more difficult to write, but they would actually test the script itself, not just the fact that it gets loaded.

To see an example of hubot-mock-adapter in action, take a look at tests of hubot-pubsub.

Since unit testing is a vast subject and it can take another book to fully cover, we’re not going to dig any deeper.

Hubot Script Template

You can use this template as a starting point for your new Hubot scripts. It is taken from Hubot Control, which also gives you a web based IDE for quick scripting.

scripts/template.coffee


# Description
#   <description of the scripts functionality>
#
# Dependencies:
#   "<module name>": "<module version>"
#
# Configuration:
#   LIST_OF_ENV_VARS_TO_SET
#
# Commands:
#   hubot <trigger> - <what the respond trigger does>
#   <trigger> - <what the hear trigger does>
#
# URLS:
#   GET /path?param=<val> - <what the request does>
#
# Notes:
#   <optional notes required for the script>
#
# Author:
#   <github username of the original script author>

module.exports = (robot) ->

  robot.respond /jump/i, (msg) ->
    msg.emote "jumping!"

  robot.hear /your'e/i, (msg) ->
    msg.send "you're"

  robot.hear /what year is it\?/i, (msg) ->
    msg.reply new Date().getFullYear()

  robot.router.get "/foo", (req, res) ->
    res.end "bar"

This is how these examples look in action:

Tomas V.  hubot jump
Hubot     *jumping!*
Tomas V.  wow, your'e amazing
Hubot     you're
Tomas V.  anybody knows what year is it?
Hubot     Tomas Varaneckas: 2014

To check HTTP response, we’ll use curl:

hubot@botserv:~$ curl http://localhost:8080/foo
bar

Using Hubot Shell Adapter For Script Development

You may find it inconvenient to restart Hubot every time you change your script. In many cases you can test your work using built-in shell adapter, like this:

hubot@botserv:~/campfire$ PORT=8888 bin/hubot
[Fri Jan 10 2014 01:35:37 GMT-0500 (EST)] INFO Data for brain retrieved from \
Redis
Hubot> hubot help greet
Hubot> Hubot greet - Say hello to the world
Hubot> hubot greet
Hubot> Hello, World!
Hubot> exit

In this example we set PORT=8888 to avoid “Address already in use” error if Hubot is alread running as a service.

Developing Scripts With Hubot Control

If you use Hubot Control, you can develop scripts with it’s web based editor, which offers syntax checking and highlighting, integration with git, and a way to restart Hubot without logging in to the server.

Learning More

We’ve scratched the surface of what you can do with Hubot. One of the best ways to learn more about writing Hubot scripts by studying the source code of existing ones. Best places to start:

  • The old script catalog: https://github.com/github/hubot-scripts
  • The new script packages: https://github.com/hubot-scripts

Throughout the rest of the book we will cover a number of use cases of integrating Hubot with a variety of applications and web services. You will learn how to make Hubot an invaluable addition to your DevOps stack.