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.
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?
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.
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.
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.
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.
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
}
"
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.
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.
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.
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
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.
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.
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
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.
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.
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.