Книга: Automation and Monitoring with Hubot: For DevOps and Developers
Назад: Using Hubot Scripts
Дальше: Roles And Authentication

Hubot Scripting

Hubot is written in , using , 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:

Callback can result in:

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 - 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 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 and assign it to fs variable.

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

We check if ./logs/ directory , 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 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 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 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 - 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 , 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 . 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 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 . 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 . 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 .

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 , 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:

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.

Назад: Using Hubot Scripts
Дальше: Roles And Authentication