hubot 3.4.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/scripting.md CHANGED
@@ -4,21 +4,22 @@ permalink: /docs/scripting/
4
4
 
5
5
  # Scripting
6
6
 
7
- Hubot out of the box doesn't do too much but it is an extensible, scriptable robot friend. There are [hundreds of scripts written and maintained by the community](index.md#scripts) and it's easy to write your own. You can create a custom script in hubot's `scripts` directory or [create a script package](#creating-a-script-package) for sharing with the community!
7
+ Hubot out of the box doesn't do too much, but it is an extensible, scriptable robot friend. There are [hundreds of scripts written and maintained by the community](index.md#scripts) and it's easy to write your own. You can create a custom script in Hubot's `scripts` directory or [create a script package](#creating-a-script-package) for sharing with the community!
8
8
 
9
9
  ## Anatomy of a script
10
10
 
11
- When you created your hubot, the generator also created a `scripts` directory. If you peek around there, you will see some examples of scripts. For a script to be a script, it needs to:
11
+ When you created your Hubot, the generator also created a `scripts` directory. If you peek around there, you will see some examples. For a script to be a script, it needs to:
12
12
 
13
- * live in a directory on the hubot script load path (`src/scripts` and `scripts` by default)
14
- * be a `.coffee` or `.js` file
15
- * export a function
13
+ * live in a directory on the Hubot script load path (`src/scripts` and `scripts` by default)
14
+ * be a `.js` file
15
+ * export a function whos signature takes 1 parameter (`robot`)
16
16
 
17
17
  By export a function, we just mean:
18
18
 
19
- ```coffeescript
20
- module.exports = (robot) ->
21
- # your code here
19
+ ```javascript
20
+ module.exports = (robot) => {
21
+ // your code here
22
+ }
22
23
  ```
23
24
 
24
25
  The `robot` parameter is an instance of your robot friend. At this point, we can start scripting up some awesomeness.
@@ -27,22 +28,25 @@ The `robot` parameter is an instance of your robot friend. At this point, we can
27
28
 
28
29
  Since this is a chat bot, the most common interactions are based on messages. Hubot can `hear` messages said in a room or `respond` to messages directly addressed at it. Both methods take a regular expression and a callback function as parameters. For example:
29
30
 
30
- ```coffeescript
31
- module.exports = (robot) ->
32
- robot.hear /badger/i, (res) ->
33
- # your code here
31
+ ```javascript
32
+ module.exports = (robot) => {
33
+ robot.hear(/badger/i, (res) => {
34
+ // your code here
35
+ })
34
36
 
35
- robot.respond /open the pod bay doors/i, (res) ->
36
- # your code here
37
+ robot.respond(/open the pod bay doors/i, (res) => {
38
+ // your code here
39
+ }
40
+ }
37
41
  ```
38
42
 
39
- The `robot.hear /badger/` callback is called anytime a message's text matches. For example:
43
+ The `robot.hear(/badger/)` callback is called anytime a message's text matches. For example:
40
44
 
41
45
  * Stop badgering the witness
42
46
  * badger me
43
47
  * what exactly is a badger anyways
44
48
 
45
- The `robot.respond /open the pod bay doors/i` callback is only called for messages that are immediately preceded by the robot's name or alias. If the robot's name is HAL and alias is /, then this callback would be triggered for:
49
+ The `robot.respond(/open the pod bay doors/i)` callback is only called for messages that are immediately preceded by the robot's name or alias. If the robot's name is HAL and alias is /, then this callback would be triggered for:
46
50
 
47
51
  * hal open the pod bay doors
48
52
  * HAL: open the pod bay doors
@@ -52,178 +56,187 @@ The `robot.respond /open the pod bay doors/i` callback is only called for messag
52
56
  It wouldn't be called for:
53
57
 
54
58
  * HAL: please open the pod bay doors
55
- * because its `respond` is bound to the text immediately following the robot name
59
+ * because its `respond` is expecting the text to be prefixed with the robots name
56
60
  * has anyone ever mentioned how lovely you are when you open the pod bay doors?
57
- * because it lacks the robot's name
61
+ * because it lacks the robot's name at the beginning
58
62
 
59
63
  ## Send & reply
60
64
 
61
65
  The `res` parameter is an instance of `Response` (historically, this parameter was `msg` and you may see other scripts use it this way). With it, you can `send` a message back to the room the `res` came from, `emote` a message to a room (If the given adapter supports it), or `reply` to the person that sent the message. For example:
62
66
 
63
- ```coffeescript
64
- module.exports = (robot) ->
65
- robot.hear /badger/i, (res) ->
66
- res.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"
67
+ ```javascript
68
+ module.exports = (robot) => {
69
+ robot.hear(/badger/i, (res) => {
70
+ res.send(`Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS`)
71
+ }
67
72
 
68
- robot.respond /open the pod bay doors/i, (res) ->
69
- res.reply "I'm afraid I can't let you do that."
73
+ robot.respond(/open the pod bay doors/i, (res) => {
74
+ res.reply(`I'm afraid I can't let you do that.`)
75
+ }
70
76
 
71
- robot.hear /I like pie/i, (res) ->
72
- res.emote "makes a freshly baked pie"
77
+ robot.hear(/I like pie/i, (res) => {
78
+ res.emote('makes a freshly baked pie')
79
+ }
80
+ }
73
81
  ```
74
82
 
75
- The `robot.hear /badgers/` callback sends a message exactly as specified regardless of who said it, "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS".
83
+ The `robot.hear(/badgers/)` callback sends a message exactly as specified regardless of who said it, "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS".
76
84
 
77
- If a user Dave says "HAL: open the pod bay doors", `robot.respond /open the pod bay doors/i` callback sends a message "Dave: I'm afraid I can't let you do that."
85
+ If a user Dave says "HAL: open the pod bay doors", `robot.respond(/open the pod bay doors/i)` callback sends a message "Dave: I'm afraid I can't let you do that."
78
86
 
79
87
  ## Messages to a room or user
80
88
 
81
89
  Messages can be sent to a specified room or user using the messageRoom function.
82
90
 
83
- ```coffeescript
84
- module.exports = (robot) ->
85
-
86
- robot.hear /green eggs/i, (res) ->
87
- room = "mytestroom"
88
- robot.messageRoom room, "I do not like green eggs and ham. I do not like them sam-I-am."
91
+ ```javascript
92
+ module.exports = (robot) => {
93
+ robot.hear(/green eggs/i, (res) => {
94
+ const room = 'mytestroom'
95
+ robot.messageRoom(room, 'I do not like green eggs and ham. I do not like them Sam-I-Am.')
96
+ }
97
+ }
89
98
  ```
90
99
 
91
- User name can be explicitely specified if desired ( for a cc to an admin/manager), or using
92
- the response object a private message can be sent to the original sender.
100
+ User name can be explicitely specified if desired ( for a cc to an admin/manager), or using the response object a private message can be sent to the original sender.
93
101
 
94
- ```coffeescript
95
- robot.respond /I don't like Sam-I-am/i, (res) ->
96
- room = 'joemanager'
97
- robot.messageRoom room, "Someone does not like Dr. Seus"
98
- res.reply "That Sam-I-am\nThat Sam-I-am\nI do not like\nthat Sam-I-am"
102
+ ```javascript
103
+ robot.respond(/I don't like sam-i-am/i, (res) => {
104
+ const room = 'joemanager'
105
+ robot.messageRoom(room, 'Someone does not like Dr. Seus')
106
+ res.reply('That Sam-I-Am\nThat Sam-I-Am\nI do not like\nthat Sam-I-Am')
107
+ }
99
108
 
100
- robot.hear /Sam-I-am/i, (res) ->
101
- room = res.envelope.user.name
102
- robot.messageRoom room, "That Sam-I-am\nThat Sam-I-am\nI do not like\nthat Sam-I-am"
109
+ robot.hear(/Sam-I-Am/i, (res) => {
110
+ const room = res.envelope.user.name
111
+ robot.messageRoom(room, 'That Sam-I-Am\nThat Sam-I-Am\nI do not like\nthat Sam-I-Am')
112
+ }
103
113
  ```
104
114
 
105
115
  ## Capturing data
106
116
 
107
- So far, our scripts have had static responses, which while amusing, are boring functionality-wise. `res.match` has the result of `match`ing the incoming message against the regular expression. This is just a [JavaScript thing](http://www.w3schools.com/jsref/jsref_match.asp), which ends up being an array with index 0 being the full text matching the expression. If you include capture groups, those will be populated `res.match`. For example, if we update a script like:
117
+ So far, our scripts have had static responses, which while amusing, are boring functionality-wise. `res.match` has the result of `match`ing the incoming message against the regular expression. This is just a [JavaScript thing](http://www.w3schools.com/jsref/jsref_match.asp), which ends up being an array with index 0 being the full text matching the expression. If you include capture groups, those will be populated on `res.match`. For example, if we update a script like:
108
118
 
109
- ```coffeescript
110
- robot.respond /open the (.*) doors/i, (res) ->
111
- # your code here
119
+ ```javascript
120
+ robot.respond(/open the (.*) doors/i, (res) => {
121
+ // your code here
122
+ }
112
123
  ```
113
124
 
114
125
  If Dave says "HAL: open the pod bay doors", then `res.match[0]` is "open the pod bay doors", and `res.match[1]` is just "pod bay". Now we can start doing more dynamic things:
115
126
 
116
- ```coffeescript
117
- robot.respond /open the (.*) doors/i, (res) ->
118
- doorType = res.match[1]
119
- if doorType is "pod bay"
120
- res.reply "I'm afraid I can't let you do that."
121
- else
122
- res.reply "Opening #{doorType} doors"
127
+ ```javascript
128
+ robot.respond(/open the (.*) doors/i, (res) => {
129
+ const doorType = res.match[1]
130
+ if (doorType == 'pod bay') {
131
+ res.reply(`I'm afraid I can't let you do that.`)
132
+ } else {
133
+ res.reply(`Opening ${doorType} doors`)
134
+ }
135
+ }
123
136
  ```
124
137
 
125
- ## Making HTTP calls
138
+ ## Making HTTP calls (please use `fetch` instead)
126
139
 
127
- Hubot can make HTTP calls on your behalf to integrate & consume third party APIs. This can be through an instance of [node-scoped-http-client](https://github.com/technoweenie/node-scoped-http-client) available at `robot.http`. The simplest case looks like:
140
+ Hubot can make HTTP calls on your behalf to integrate & consume third party APIs. This can be through an instance of [ScopedHttpClient](../src/httpclient.js) available at `robot.http`. The simplest case looks like:
128
141
 
129
142
 
130
- ```coffeescript
131
- robot.http("https://midnight-train")
132
- .get() (err, response, body) ->
133
- # your code here
143
+ ```javascript
144
+ robot.http('https://midnight-train').get()((err, res, body) => {
145
+ // your code here
146
+ })
134
147
  ```
135
148
 
136
149
  A post looks like:
137
150
 
138
- ```coffeescript
139
- data = JSON.stringify({
151
+ ```javascript
152
+ const data = JSON.stringify({
140
153
  foo: 'bar'
141
154
  })
142
- robot.http("https://midnight-train")
155
+ robot.http('https://midnight-train')
143
156
  .header('Content-Type', 'application/json')
144
- .post(data) (err, response, body) ->
145
- # your code here
157
+ .post(data)((err, res, body) => {
158
+ // your code here
159
+ })
146
160
  ```
147
161
 
148
162
 
149
163
  `err` is an error encountered on the way, if one was encountered. You'll generally want to check for this and handle accordingly:
150
164
 
151
- ```coffeescript
152
- robot.http("https://midnight-train")
153
- .get() (err, response, body) ->
154
- if err
155
- res.send "Encountered an error :( #{err}"
156
- return
157
- # your code here, knowing it was successful
165
+ ```javascript
166
+ robot.http('https://midnight-train')
167
+ .get()((err, res, body) => {
168
+ if (err){
169
+ return res.send `Encountered an error :( ${err}`
170
+ }
171
+ // your code here, knowing it was successful
172
+ })
158
173
  ```
159
174
 
160
- `res` is an instance of node's [http.ServerResponse](http://nodejs.org/api/http.html#http_class_http_serverresponse). Most of the methods don't matter as much when using node-scoped-http-client, but of interest are `statusCode` and `getHeader`. Use `statusCode` to check for the HTTP status code, where usually non-200 means something bad happened. Use `getHeader` for peeking at the header, for example to check for rate limiting:
161
-
162
- ```coffeescript
163
- robot.http("https://midnight-train")
164
- .get() (err, response, body) ->
165
- # pretend there's error checking code here
175
+ `res` is an instance of node's [http.ServerResponse](http://nodejs.org/api/http.html#http_class_http_serverresponse). Most of the methods don't matter as much when using `ScopedHttpClient`, but of interest are `statusCode` and `getHeader`. Use `statusCode` to check for the HTTP status code, where usually non-200 means something bad happened. Use `getHeader` for peeking at the header, for example to check for rate limiting:
166
176
 
167
- if response.statusCode isnt 200
168
- res.send "Request didn't come back HTTP 200 :("
169
- return
177
+ ```javascript
178
+ robot.http('https://midnight-train')
179
+ .get() ((err, res, body) => {
180
+ // pretend there's error checking code here
181
+ if (res.statusCode <> 200)
182
+ return res.send(`Request didn't come back HTTP 200 :(`)
170
183
 
171
- rateLimitRemaining = parseInt response.getHeader('X-RateLimit-Limit') if response.getHeader('X-RateLimit-Limit')
172
- if rateLimitRemaining and rateLimitRemaining < 1
173
- res.send "Rate Limit hit, stop believing for awhile"
184
+ const rateLimitRemaining = res.getHeader('X-RateLimit-Limit') ? parseInt(res.getHeader('X-RateLimit-Limit')) : 1
185
+ if (rateLimitRemaining && rateLimitRemaining < 1)
186
+ return res.send('Rate Limit hit, stop believing for awhile')
174
187
 
175
- # rest of your code
188
+ // rest of your code
189
+ }
176
190
  ```
177
191
 
178
192
  `body` is the response's body as a string, the thing you probably care about the most:
179
193
 
180
- ```coffeescript
181
- robot.http("https://midnight-train")
182
- .get() (err, response, body) ->
183
- # error checking code here
184
-
185
- res.send "Got back #{body}"
194
+ ```javascript
195
+ robot.http('https://midnight-train')
196
+ .get()((err, res, body) => {
197
+ // error checking code here
198
+ res.send(`Got back ${body}`)
199
+ })
186
200
  ```
187
201
 
188
202
  ### JSON
189
203
 
190
- If you are talking to APIs, the easiest way is going to be JSON because it doesn't require any extra dependencies. When making the `robot.http` call, you should usually set the `Accept` header to give the API a clue that's what you are expecting back. Once you get the `body` back, you can parse it with `JSON.parse`:
204
+ If you are talking to Web Services that respond with JSON representation, then when making the `robot.http` call, you will usually set the `Accept` header to give the Web Service a clue that's what you are expecting back. Once you get the `body` back, you can parse it with `JSON.parse`:
191
205
 
192
- ```coffeescript
193
- robot.http("https://midnight-train")
206
+ ```javascript
207
+ robot.http('https://midnight-train')
194
208
  .header('Accept', 'application/json')
195
- .get() (err, response, body) ->
196
- # error checking code here
197
-
198
- data = JSON.parse body
199
- res.send "#{data.passenger} taking midnight train going #{data.destination}"
209
+ .get()((err, res, body) => {
210
+ // error checking code here
211
+ const data = JSON.parse(body)
212
+ res.send(`${data.passenger} taking midnight train going ${data.destination}`)
213
+ })
200
214
  ```
201
215
 
202
- It's possible to get non-JSON back, like if the API hit an error and it tries to render a normal HTML error instead of JSON. To be on the safe side, you should check the `Content-Type`, and catch any errors while parsing.
216
+ It's possible to get non-JSON back, like if the Web Service has an error and renders HTML instead of JSON. To be on the safe side, you should check the `Content-Type`, and catch any errors while parsing.
203
217
 
204
- ```coffeescript
205
- robot.http("https://midnight-train")
218
+ ```javascript
219
+ robot.http('https://midnight-train')
206
220
  .header('Accept', 'application/json')
207
- .get() (err, response, body) ->
208
- # err & response status checking code here
209
-
210
- if response.getHeader('Content-Type') isnt 'application/json'
211
- res.send "Didn't get back JSON :("
212
- return
213
-
214
- data = null
215
- try
216
- data = JSON.parse body
217
- catch error
218
- res.send "Ran into an error parsing JSON :("
219
- return
220
-
221
- # your code here
221
+ .get()((err, res, body) => {
222
+ // err & res status checking code here
223
+ if (res.getHeader('Content-Type') != 'application/json'){
224
+ return res.send(`Didn't get back JSON :(`)
225
+ }
226
+ let data = null
227
+ try {
228
+ data = JSON.parse(body)
229
+ } catch (error) {
230
+ res.send(`Ran into an error parsing JSON :(`)
231
+ }
232
+
233
+ // your code here
234
+ })
222
235
  ```
223
236
 
224
237
  ### XML
225
238
 
226
- XML APIs are harder because there's not a bundled XML parsing library. It's beyond the scope of this documentation to go into detail, but here are a few libraries to check out:
239
+ XML Web Services require installing a XML parsing library. It's beyond the scope of this documentation to go into detail, but here are a few libraries to check out:
227
240
 
228
241
  * [xml2json](https://github.com/buglabs/node-xml2json) (simplest to use, but has some limitations)
229
242
  * [jsdom](https://github.com/tmpvar/jsdom) (JavaScript implementation of the W3C DOM)
@@ -231,7 +244,7 @@ XML APIs are harder because there's not a bundled XML parsing library. It's beyo
231
244
 
232
245
  ### Screen scraping
233
246
 
234
- For those times that there isn't an API, there's always the possibility of screen-scraping. It's beyond the scope of this documentation to go into detail, but here's a few libraries to check out:
247
+ For consuming a Web Service that responds with HTML, you'll need an HTML parser. It's beyond the scope of this documentation to go into detail, but here's a few libraries to check out:
235
248
 
236
249
  * [cheerio](https://github.com/MatthewMueller/cheerio) (familiar syntax and API to jQuery)
237
250
  * [jsdom](https://github.com/tmpvar/jsdom) (JavaScript implementation of the W3C DOM)
@@ -239,125 +252,143 @@ For those times that there isn't an API, there's always the possibility of scree
239
252
 
240
253
  ### Advanced HTTP and HTTPS settings
241
254
 
242
- As mentioned, hubot uses [node-scoped-http-client](https://github.com/technoweenie/node-scoped-http-client) to provide a simple interface for making HTTP and HTTPS requests. Under its hood, it's using node's builtin [http](http://nodejs.org/api/http.html) and [https](http://nodejs.org/api/https.html) libraries, but providing an easy DSL for the most common kinds of interaction.
255
+ As mentioned previously, Hubot uses [ScopedHttpClient](../src/httpclient.js) to provide a simple interface for making HTTP and HTTPS requests. Under the hood, it's using node's [http](http://nodejs.org/api/http.html) and [https](http://nodejs.org/api/https.html) modules, but tries to provide an easier Domain Specific Language (DSL) for common kinds of Web Service interactions.
243
256
 
244
- If you need to control options on http and https more directly, you pass a second argument to `robot.http` that will be passed on to node-scoped-http-client which will be passed on to http and https:
257
+ If you need to control options on `http` and `https` more directly, you pass a second parameter to `robot.http` that will be passed on to `ScopedHttpClient` which will be passed on to `http` and `https`:
245
258
 
246
- ```
247
- options =
248
- # don't verify server certificate against a CA, SCARY!
249
- rejectUnauthorized: false
250
- robot.http("https://midnight-train", options)
259
+ ```javascript
260
+ const options = {
261
+ rejectUnauthorized: false // don't verify server certificate against a CA, SCARY!
262
+ }
263
+ robot.http('https://midnight-train', options)
251
264
  ```
252
265
 
253
- In addition, if node-scoped-http-client doesn't suit you, you can use [http](http://nodejs.org/api/http.html) and [https](http://nodejs.org/api/https.html) yourself directly, or any other node library like [request](https://github.com/request/request).
266
+ In addition, if `ScopedHttpClient` doesn't suit you, you can use [http](http://nodejs.org/api/http.html), [https](http://nodejs.org/api/https.html) or `fetch` directly.
254
267
 
255
268
  ## Random
256
269
 
257
- A common pattern is to hear or respond to commands, and send with a random funny image or line of text from an array of possibilities. It's annoying to do this in JavaScript and CoffeeScript out of the box, so Hubot includes a convenience method:
270
+ A common pattern is to hear or respond to commands, and send with a random funny image or line of text from an array of possibilities. Hubot includes a convenience method:
258
271
 
259
- ```coffeescript
260
- lulz = ['lol', 'rofl', 'lmao']
261
-
262
- res.send res.random lulz
272
+ ```javascript
273
+ const lulz = ['lol', 'rofl', 'lmao']
274
+ res.send(res.random(lulz))
263
275
  ```
264
276
 
265
277
  ## Topic
266
278
 
267
279
  Hubot can react to a room's topic changing, assuming that the adapter supports it.
268
280
 
269
- ```coffeescript
270
- module.exports = (robot) ->
271
- robot.topic (res) ->
272
- res.send "#{res.message.text}? That's a Paddlin'"
281
+ ```javascript
282
+ module.exports = (robot) => {
283
+ robot.topic((res) => {
284
+ res.send()`${res.message.text}? That's a Paddlin'`)
285
+ })
286
+ }
273
287
  ```
274
288
 
275
289
  ## Entering and leaving
276
290
 
277
291
  Hubot can see users entering and leaving, assuming that the adapter supports it.
278
292
 
279
- ```coffeescript
280
- enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you']
281
- leaveReplies = ['Are you still there?', 'Target lost', 'Searching']
282
-
283
- module.exports = (robot) ->
284
- robot.enter (res) ->
285
- res.send res.random enterReplies
286
- robot.leave (res) ->
287
- res.send res.random leaveReplies
293
+ ```javascript
294
+ const enterReplies = ['Hi', 'Target Acquired', 'Firing', 'Hello friend.', 'Gotcha', 'I see you']
295
+ const leaveReplies = ['Are you still there?', 'Target lost', 'Searching']
296
+
297
+ module.exports = (robot) => {
298
+ robot.enter(res) => {
299
+ res.send(res.random(enterReplies))
300
+ }
301
+ robot.leave(res) => {
302
+ res.send(res.random(leaveReplies))
303
+ }
304
+ }
288
305
  ```
289
306
 
290
307
  ## Custom Listeners
291
308
 
292
309
  While the above helpers cover most of the functionality the average user needs (hear, respond, enter, leave, topic), sometimes you would like to have very specialized matching logic for listeners. If so, you can use `listen` to specify a custom match function instead of a regular expression.
293
310
 
294
- The match function must return a truthy value if the listener callback should be executed. The truthy return value of the match function is then passed to the callback as response.match.
311
+ The match function must return a truthy value if the listener callback should be executed. The truthy return value of the match function is then passed to the callback as `res.match`.
295
312
 
296
- ```coffeescript
297
- module.exports = (robot) ->
313
+ ```javascript
314
+ module.exports = (robot) =>{
298
315
  robot.listen(
299
- (message) -> # Match function
300
- # only match messages with text (ie ignore enter and other events)
301
- return unless message.text
302
-
303
- # Occassionally respond to things that Steve says
304
- message.user.name is "Steve" and Math.random() > 0.8
305
- (response) -> # Standard listener callback
306
- # Let Steve know how happy you are that he exists
307
- response.reply "HI STEVE! YOU'RE MY BEST FRIEND! (but only like #{response.match * 100}% of the time)"
316
+ (message) => {
317
+ // Match function
318
+ // only match messages with text (ie ignore enter and other events)
319
+ if(!message?.text) return
320
+
321
+ // Occassionally respond to things that Steve says
322
+ return message.user.name == 'Steve' && Math.random() > 0.8
323
+ },
324
+ (res) => {
325
+ // Standard listener callback
326
+ // Let Steve know how happy you are that he exists
327
+ res.reply(`HI STEVE! YOU'RE MY BEST FRIEND! (but only like ${res.match * 100}% of the time)`)
328
+ }
308
329
  )
330
+ }
309
331
  ```
310
332
 
311
333
  See [the design patterns document](patterns.md#dynamic-matching-of-messages) for examples of complex matchers.
312
334
 
313
335
  ## Environment variables
314
336
 
315
- Hubot can access the environment he's running in, just like any other node program, using [`process.env`](http://nodejs.org/api/process.html#process_process_env). This can be used to configure how scripts are run, with the convention being to use the `HUBOT_` prefix.
337
+ Hubot can access the environment he's running in, just like any other Node.js program, using [`process.env`](http://nodejs.org/api/process.html#process_process_env). This can be used to configure how scripts are run, with the convention being to use the `HUBOT_` prefix.
316
338
 
317
- ```coffeescript
318
- answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
339
+ ```javascript
340
+ const answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
319
341
 
320
- module.exports = (robot) ->
321
- robot.respond /what is the answer to the ultimate question of life/, (res) ->
322
- res.send "#{answer}, but what is the question?"
342
+ module.exports = (robot) => {
343
+ robot.respond(/what is the answer to the ultimate question of life/, (res) => {
344
+ res.send(`${answer}, but what is the question?`)
345
+ }
346
+ }
323
347
  ```
324
348
 
325
349
  Take care to make sure the script can load if it's not defined, give the Hubot developer notes on how to define it, or default to something. It's up to the script writer to decide if that should be a fatal error (e.g. hubot exits), or not (make any script that relies on it to say it needs to be configured. When possible and when it makes sense to, having a script work without any other configuration is preferred.
326
350
 
327
351
  Here we can default to something:
328
352
 
329
- ```coffeescript
330
- answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING or 42
353
+ ```javascript
354
+ const answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING ?? 42
331
355
 
332
- module.exports = (robot) ->
333
- robot.respond /what is the answer to the ultimate question of life/, (res) ->
334
- res.send "#{answer}, but what is the question?"
356
+ module.exports = (robot) => {
357
+ robot.respond(/what is the answer to the ultimate question of life/, (res) => {
358
+ res.send(`${answer}, but what is the question?`)
359
+ }
360
+ }
335
361
  ```
336
362
 
337
363
  Here we exit if it's not defined:
338
364
 
339
- ```coffeescript
340
- answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
341
- unless answer?
342
- console.log "Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again"
365
+ ```javascript
366
+ const answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
367
+ if(!answer) {
368
+ console.log(`Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again`)
343
369
  process.exit(1)
370
+ }
344
371
 
345
- module.exports = (robot) ->
346
- robot.respond /what is the answer to the ultimate question of life/, (res) ->
347
- res.send "#{answer}, but what is the question?"
372
+ module.exports = (robot) => {
373
+ robot.respond(/what is the answer to the ultimate question of life/, (res) => {
374
+ res.send(`${answer}, but what is the question?`)
375
+ }
376
+ }
348
377
  ```
349
378
 
350
379
  And lastly, we update the `robot.respond` to check it:
351
380
 
352
- ```coffeescript
353
- answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
381
+ ```javascript
382
+ const answer = process.env.HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING
354
383
 
355
- module.exports = (robot) ->
356
- robot.respond /what is the answer to the ultimate question of life/, (res) ->
357
- unless answer?
358
- res.send "Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again"
359
- return
360
- res.send "#{answer}, but what is the question?"
384
+ module.exports = (robot) => {
385
+ robot.respond(/what is the answer to the ultimate question of life/, (res) => {
386
+ if(!answer) {
387
+ return res.send('Missing HUBOT_ANSWER_TO_THE_ULTIMATE_QUESTION_OF_LIFE_THE_UNIVERSE_AND_EVERYTHING in environment: please set and try again')
388
+ }
389
+ res.send(`${answer}, but what is the question?`)
390
+ }
391
+ }
361
392
  ```
362
393
 
363
394
  ## Dependencies
@@ -366,7 +397,7 @@ Hubot uses [npm](https://github.com/isaacs/npm) to manage its dependencies. To a
366
397
 
367
398
  ```json
368
399
  "dependencies": {
369
- "hubot": "2.5.5",
400
+ "hubot": "2.5.5",
370
401
  "lolimadeupthispackage": "1.2.3"
371
402
  },
372
403
  ```
@@ -377,48 +408,56 @@ If you are using scripts from hubot-scripts, take note of the `Dependencies` doc
377
408
 
378
409
  Hubot can run code later using JavaScript's built-in [setTimeout](http://nodejs.org/api/timers.html#timers_settimeout_callback_delay_arg). It takes a callback method, and the amount of time to wait before calling it:
379
410
 
380
- ```coffeescript
381
- module.exports = (robot) ->
382
- robot.respond /you are a little slow/, (res) ->
383
- setTimeout () ->
384
- res.send "Who you calling 'slow'?"
385
- , 60 * 1000
411
+ ```javascript
412
+ module.exports = (robot) => {
413
+ robot.respond(/you are a little slow/, (res) => {
414
+ setTimeout(() => {
415
+ res.send(`Who you calling 'slow'?`)
416
+ }, 60 * 1000)
417
+ })
418
+ }
386
419
  ```
387
420
 
388
421
  Additionally, Hubot can run code on an interval using [setInterval](http://nodejs.org/api/timers.html#timers_setinterval_callback_delay_arg). It takes a callback method, and the amount of time to wait between calls:
389
422
 
390
- ```coffeescript
391
- module.exports = (robot) ->
392
- robot.respond /annoy me/, (res) ->
393
- res.send "Hey, want to hear the most annoying sound in the world?"
394
- setInterval () ->
395
- res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
396
- , 1000
423
+ ```javascript
424
+ module.exports = (robot) => {
425
+ robot.respond(/annoy me/, (res) => {
426
+ res.send('Hey, want to hear the most annoying sound in the world?')
427
+ setInterval(() => {
428
+ res.send('AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH')
429
+ }, 1000)
430
+ })
431
+ }
397
432
  ```
398
433
 
399
434
  Both `setTimeout` and `setInterval` return the ID of the timeout or interval it created. This can be used to to `clearTimeout` and `clearInterval`.
400
435
 
401
- ```coffeescript
402
- module.exports = (robot) ->
403
- annoyIntervalId = null
436
+ ```javascript
437
+ module.exports = (robot) => {
438
+ let annoyIntervalId = null
404
439
 
405
- robot.respond /annoy me/, (res) ->
406
- if annoyIntervalId
407
- res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
408
- return
440
+ robot.respond(/annoy me/, (res) => {
441
+ if (annoyIntervalId) {
442
+ return res.send('AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH')
443
+ }
409
444
 
410
- res.send "Hey, want to hear the most annoying sound in the world?"
411
- annoyIntervalId = setInterval () ->
412
- res.send "AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH"
413
- , 1000
445
+ res.send('Hey, want to hear the most annoying sound in the world?')
446
+ annoyIntervalId = setInterval(() => {
447
+ res.send('AAAAAAAAAAAEEEEEEEEEEEEEEEEEEEEEEEEIIIIIIIIHHHHHHHHHH')
448
+ }, 1000)
449
+ }
414
450
 
415
- robot.respond /unannoy me/, (res) ->
416
- if annoyIntervalId
417
- res.send "GUYS, GUYS, GUYS!"
451
+ robot.respond(/unannoy me/, (res) => {
452
+ if (annoyIntervalId) {
453
+ res.send('GUYS, GUYS, GUYS!')
418
454
  clearInterval(annoyIntervalId)
419
455
  annoyIntervalId = null
420
- else
421
- res.send "Not annoying you right now, am I?"
456
+ } else {
457
+ res.send('Not annoying you right now, am I?')
458
+ }
459
+ }
460
+ }
422
461
  ```
423
462
 
424
463
  ## HTTP Listener
@@ -430,25 +469,28 @@ You can increase the [maximum request body size](https://github.com/expressjs/bo
430
469
  The most common use of this is for providing HTTP end points for services with webhooks to push to, and have those show up in chat.
431
470
 
432
471
 
433
- ```coffeescript
434
- module.exports = (robot) ->
435
- # the expected value of :room is going to vary by adapter, it might be a numeric id, name, token, or some other value
436
- robot.router.post '/hubot/chatsecrets/:room', (request, response) ->
437
- room = request.params.room
438
- data = if request.body.payload? then JSON.parse request.body.payload else request.body
439
- secret = data.secret
472
+ ```javascript
473
+ module.exports = (robot) => {
474
+ // the expected value of :room is going to vary by adapter, it might be a numeric id, name, token, or some other value
475
+ robot.router.post('/hubot/chatsecrets/:room', (req, res) => {
476
+ const room = req.params.room
477
+ const data = req.body?.payload ? JSON.parse(req.body.payload) : req.body
478
+ const secret = data.secret
440
479
 
441
- robot.messageRoom room, "I have a secret: #{secret}"
480
+ robot.messageRoom(room, `I have a secret: ${secret}`)
442
481
 
443
- response.send 'OK'
482
+ res.send('OK')
483
+ })
484
+ }
444
485
  ```
445
486
 
446
487
  Test it with curl; also see section on [error handling](#error-handling) below.
447
- ```shell
448
- // raw json, must specify Content-Type: application/json
488
+
489
+ ```sh
490
+ # raw json, must specify Content-Type: application/json
449
491
  curl -X POST -H "Content-Type: application/json" -d '{"secret":"C-TECH Astronomy"}' http://127.0.0.1:8080/hubot/chatsecrets/general
450
492
 
451
- // defaults Content-Type: application/x-www-form-urlencoded, must st payload=...
493
+ # defaults Content-Type: application/x-www-form-urlencoded, must st payload=...
452
494
  curl -d 'payload=%7B%22secret%22%3A%22C-TECH+Astronomy%22%7D' http://127.0.0.1:8080/hubot/chatsecrets/general
453
495
  ```
454
496
 
@@ -456,27 +498,31 @@ All endpoint URLs should start with the literal string `/hubot` (regardless of w
456
498
 
457
499
  ## Events
458
500
 
459
- Hubot can also respond to events which can be used to pass data between scripts. This is done by encapsulating node.js's [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) with `robot.emit` and `robot.on`.
501
+ Hubot can also respond to events which can be used to pass data between scripts. This is done by encapsulating Node.js's [EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) with `robot.emit` and `robot.on`.
460
502
 
461
503
  One use case for this would be to have one script for handling interactions with a service, and then emitting events as they come up. For example, we could have a script that receives data from a GitHub post-commit hook, make that emit commits as they come in, and then have another script act on those commits.
462
504
 
463
- ```coffeescript
464
- # src/scripts/github-commits.coffee
465
- module.exports = (robot) ->
466
- robot.router.post "/hubot/gh-commits", (request, response) ->
467
- robot.emit "commit", {
468
- user : {}, #hubot user object
469
- repo : 'https://github.com/github/hubot',
470
- hash : '2e1951c089bd865839328592ff673d2f08153643'
471
- }
505
+ ```javascript
506
+ // src/scripts/github-commits.js
507
+ module.exports = (robot) => {
508
+ robot.router.post('/hubot/gh-commits', (req, res) => {
509
+ robot.emit('commit', {
510
+ user: {}, //hubot user object
511
+ repo: 'https://github.com/github/hubot',
512
+ hash: '2e1951c089bd865839328592ff673d2f08153643'
513
+ })
514
+ })
515
+ }
472
516
  ```
473
517
 
474
- ```coffeescript
475
- # src/scripts/heroku.coffee
476
- module.exports = (robot) ->
477
- robot.on "commit", (commit) ->
478
- robot.send commit.user, "Will now deploy #{commit.hash} from #{commit.repo}!"
479
- #deploy code goes here
518
+ ```javascript
519
+ // src/scripts/heroku.js
520
+ module.exports = (robot) => {
521
+ robot.on('commit', (commit) => {
522
+ robot.send(commit.user, `Will now deploy ${commit.hash} from ${commit.repo}!`)
523
+ // deploy code goes here
524
+ }
525
+ }
480
526
  ```
481
527
 
482
528
  If you provide an event, it's highly recommended to include a hubot user or room object in its data. This would allow for hubot to notify a user or room in chat.
@@ -485,42 +531,49 @@ If you provide an event, it's highly recommended to include a hubot user or room
485
531
 
486
532
  No code is perfect, and errors and exceptions are to be expected. Previously, an uncaught exceptions would crash your hubot instance. Hubot now includes an `uncaughtException` handler, which provides hooks for scripts to do something about exceptions.
487
533
 
488
- ```coffeescript
489
- # src/scripts/does-not-compute.coffee
490
- module.exports = (robot) ->
491
- robot.error (err, res) ->
492
- robot.logger.error "DOES NOT COMPUTE"
534
+ ```javascript
535
+ // src/scripts/does-not-compute.js
536
+ module.exports = (robot) => {
537
+ robot.error((err, res) => {
538
+ robot.logger.error('DOES NOT COMPUTE')
493
539
 
494
- if res?
495
- res.reply "DOES NOT COMPUTE"
540
+ if(res) {
541
+ res.reply('DOES NOT COMPUTE')
542
+ }
543
+ }
544
+ }
496
545
  ```
497
546
 
498
547
  You can do anything you want here, but you will want to take extra precaution of rescuing and logging errors, particularly with asynchronous code. Otherwise, you might find yourself with recursive errors and not know what is going on.
499
548
 
500
- Under the hood, there is an 'error' event emitted, with the error handlers consuming that event. The uncaughtException handler [technically leaves the process in an unknown state](http://nodejs.org/api/process.html#process_event_uncaughtexception). Therefore, you should rescue your own exceptions whenever possible, and emit them yourself. The first argument is the error emitted, and the second argument is an optional message that generated the error.
549
+ Under the hood, there is an 'error' event emitted, with the error handlers consuming that event. The uncaughtException handler [technically leaves the process in an unknown state](http://nodejs.org/api/process.html#process_event_uncaughtexception). Therefore, you should rescue your own exceptions whenever possible, and emit them yourself. The first parameter is the error emitted, and the second parameter is an optional message that generated the error.
501
550
 
502
551
  Using previous examples:
503
552
 
504
- ```coffeescript
505
- robot.router.post '/hubot/chatsecrets/:room', (request, response) ->
506
- room = request.params.room
507
- data = null
508
- try
509
- data = JSON.parse request.body.payload
510
- catch err
511
- robot.emit 'error', err
512
-
513
- # rest of the code here
514
-
515
-
516
- robot.hear /midnight train/i, (res) ->
517
- robot.http("https://midnight-train")
518
- .get() (err, response, body) ->
519
- if err
520
- res.reply "Had problems taking the midnight train"
521
- robot.emit 'error', err, res
553
+ ```javascript
554
+ robot.router.post()'/hubot/chatsecrets/:room', (req, res) => {
555
+ const room = req.params.room
556
+ let data = null
557
+ try {
558
+ data = JSON.parse(req.body.payload)
559
+ } catch(err) {
560
+ robot.emit('error', err)
561
+ }
562
+
563
+ // rest of the code here
564
+ }
565
+
566
+ robot.hear(/midnight train/i, (res) => {
567
+ robot.http('https://midnight-train')
568
+ .get()((err, res, body) => {
569
+ if (err) {
570
+ res.reply('Had problems taking the midnight train')
571
+ robot.emit('error', err, res)
522
572
  return
523
- # rest of code here
573
+ }
574
+ // rest of code here
575
+ })
576
+ })
524
577
  ```
525
578
 
526
579
  For the second example, it's worth thinking about what messages the user would see. If you have an error handler that replies to the user, you may not need to add a custom message and could send back the error message provided to the `get()` request, but of course it depends on how public you want to be with your exception reporting.
@@ -529,113 +582,122 @@ For the second example, it's worth thinking about what messages the user would s
529
582
 
530
583
  Hubot scripts can be documented with comments at the top of their file, for example:
531
584
 
532
- ```coffeescript
533
- # Description:
534
- # <description of the scripts functionality>
535
- #
536
- # Dependencies:
537
- # "<module name>": "<module version>"
538
- #
539
- # Configuration:
540
- # LIST_OF_ENV_VARS_TO_SET
541
- #
542
- # Commands:
543
- # hubot <trigger> - <what the respond trigger does>
544
- # <trigger> - <what the hear trigger does>
545
- #
546
- # Notes:
547
- # <optional notes required for the script>
548
- #
549
- # Author:
550
- # <github username of the original script author>
551
- ```
552
-
553
- The most important and user facing of these is `Commands`. At load time, Hubot looks at the `Commands` section of each scripts, and build a list of all commands. The included `help.coffee` lets a user ask for help across all commands, or with a search. Therefore, documenting the commands make them a lot more discoverable by users.
585
+ ```javascript
586
+ // Description:
587
+ // <description of the scripts functionality>
588
+ //
589
+ // Dependencies:
590
+ // "<module name>": "<module version>"
591
+ //
592
+ // Configuration:
593
+ // LIST_OF_ENV_VARS_TO_SET
594
+ //
595
+ // Commands:
596
+ // hubot <trigger> - <what the respond trigger does>
597
+ // <trigger> - <what the hear trigger does>
598
+ //
599
+ // Notes:
600
+ // <optional notes required for the script>
601
+ //
602
+ // Author:
603
+ // <github username of the original script author>
604
+ ```
605
+
606
+ The most important and user facing of these is `Commands`. At load time, Hubot looks at the `Commands` section of each scripts, and build a list of all commands. The [hubot-help](https://github.com/hubotio/hubot-help) script lets a user ask for help across all commands, or with a search. Therefore, documenting the commands make them a lot more discoverable by users.
554
607
 
555
608
  When documenting commands, here are some best practices:
556
609
 
557
610
  * Stay on one line. Help commands get sorted, so would insert the second line at an unexpected location, where it probably won't make sense.
558
611
  * Refer to the Hubot as hubot, even if your hubot is named something else. It will automatically be replaced with the correct name. This makes it easier to share scripts without having to update docs.
559
612
  * For `robot.respond` documentation, always prefix with `hubot`. Hubot will automatically replace this with your robot's name, or the robot's alias if it has one
560
- * Check out how man pages document themselves. In particular, brackets indicate optional parts, '...' for any number of arguments, etc.
613
+ * Check out how man pages document themselves. In particular, brackets indicate optional parts, '...' for any number of parameters, etc.
561
614
 
562
615
  The other sections are more relevant to developers of the bot, particularly dependencies, configuration variables, and notes. All contributions to [hubot-scripts](https://github.com/github/hubot-scripts) should include all these sections that are related to getting up and running with the script.
563
616
 
564
-
565
-
566
617
  ## Persistence
567
618
 
568
- Hubot has two persistence methods available that can be
569
- used to store and retrieve data by scripts: an in-memory key-value store exposed as `robot.brain`, and an optional persistent database-backed key-value store expsoed as `robot.datastore`
619
+ Hubot has two persistence methods available that can be used to store and retrieve data by scripts: an in-memory key-value store exposed as `robot.brain`, and an optional persistent database-backed key-value store expsoed as `robot.datastore`.
570
620
 
571
621
  ### Brain
572
622
 
573
- ```coffeescript
574
- robot.respond /have a soda/i, (res) ->
575
- # Get number of sodas had (coerced to a number).
576
- sodasHad = robot.brain.get('totalSodas') * 1 or 0
577
-
578
- if sodasHad > 4
579
- res.reply "I'm too fizzy.."
580
- else
581
- res.reply 'Sure!'
582
- robot.brain.set 'totalSodas', sodasHad + 1
583
-
584
- robot.respond /sleep it off/i, (res) ->
585
- robot.brain.set 'totalSodas', 0
586
- res.reply 'zzzzz'
623
+ ```javascript
624
+ robot.respond(/have a soda/i, (res) => {
625
+ // Get number of sodas had (coerced to a number).
626
+ const sodasHad = robot.brain.get('totalSodas') * 1 ?? 0
627
+
628
+ if (sodasHad > 4) {
629
+ res.reply(`I'm too fizzy..`)
630
+ } else {
631
+ res.reply('Sure!')
632
+ robot.brain.set('totalSodas', sodasHad + 1)
633
+ }
634
+ })
635
+
636
+ robot.respond(/sleep it off/i, (res) => {
637
+ robot.brain.set('totalSodas', 0)
638
+ res.reply('zzzzz')
639
+ }
587
640
  ```
588
641
 
589
642
  If the script needs to lookup user data, there are methods on `robot.brain` for looking up one or many users by id, name, or 'fuzzy' matching of name: `userForName`, `userForId`, `userForFuzzyName`, and `usersForFuzzyName`.
590
643
 
591
- ```coffeescript
592
- module.exports = (robot) ->
593
-
594
- robot.respond /who is @?([\w .\-]+)\?*$/i, (res) ->
595
- name = res.match[1].trim()
596
-
597
- users = robot.brain.usersForFuzzyName(name)
598
- if users.length is 1
599
- user = users[0]
600
- # Do something interesting here..
644
+ ```javascript
645
+ module.exports = (robot) => {
646
+ robot.respond(/who is @?([\w .\-]+)\?*$/i, (res) => {
647
+ const name = res.match[1].trim()
601
648
 
602
- res.send "#{name} is user - #{user}"
649
+ const users = robot.brain.usersForFuzzyName(name)
650
+ if (users.length == 1) {
651
+ const user = users[0]
652
+ // Do something interesting here..
653
+ }
654
+ res.send(`${name} is user - ${user}`)
655
+ })
656
+ }
603
657
  ```
604
658
 
605
659
  ### Datastore
606
660
 
607
661
  Unlike the brain, the datastore's getter and setter methods are asynchronous and don't resolve until the call to the underlying database has resolved. This requires a slightly different approach to accessing data:
608
662
 
609
- ```coffeescript
610
- robot.respond /have a soda/i, (res) ->
611
- # Get number of sodas had (coerced to a number).
612
- robot.datastore.get('totalSodas').then (value) ->
613
- sodasHad = value * 1 or 0
614
-
615
- if sodasHad > 4
616
- res.reply "I'm too fizzy.."
617
- else
618
- res.reply 'Sure!'
619
- robot.brain.set 'totalSodas', sodasHad + 1
663
+ ```javascript
664
+ robot.respond(/have a soda/i, (res) => {
665
+ // Get number of sodas had (coerced to a number).
666
+ robot.datastore.get('totalSodas').then((value) => {
667
+ const sodasHad = value * 1 ?? 0
668
+
669
+ if (sodasHad > 4) {
670
+ res.reply(`I'm too fizzy..`)
671
+ } else {
672
+ res.reply('Sure!')
673
+ robot.brain.set('totalSodas', sodasHad + 1)
674
+ }
675
+ })
676
+ })
620
677
 
621
- robot.respond /sleep it off/i, (res) ->
622
- robot.datastore.set('totalSodas', 0).then () ->
623
- res.reply 'zzzzz'
678
+ robot.respond(/sleep it off/i, (res) => {
679
+ robot.datastore.set('totalSodas', 0).then(() => {
680
+ res.reply('zzzzz')
681
+ })
682
+ })
624
683
  ```
625
684
 
626
685
  The datastore also allows setting and getting values which are scoped to individual users:
627
686
 
628
- ```coffeescript
687
+ ```javascript
629
688
  module.exports = (robot) ->
630
689
 
631
- robot.respond /who is @?([\w .\-]+)\?*$/i, (res) ->
632
- name = res.match[1].trim()
690
+ robot.respond(/who is @?([\w .\-]+)\?*$/i, (res) => {
691
+ const name = res.match[1].trim()
633
692
 
634
- users = robot.brain.usersForFuzzyName(name)
635
- if users.length is 1
636
- user = users[0]
637
- user.get('roles').then (roles) ->
693
+ const users = robot.brain.usersForFuzzyName(name)
694
+ if (users.length == 1) {
695
+ const user = users[0]
696
+ user.get('roles').then((roles) => {
638
697
  res.send "#{name} is #{roles.join(', ')}"
698
+ })
699
+ }
700
+ })
639
701
  ```
640
702
 
641
703
  ## Script Loading
@@ -648,9 +710,9 @@ There are three main sources to load scripts from:
648
710
 
649
711
  Scripts loaded from the `scripts/` directory are loaded in alphabetical order, so you can expect a consistent load order of scripts. For example:
650
712
 
651
- * `scripts/1-first.coffee`
652
- * `scripts/_second.coffee`
653
- * `scripts/third.coffee`
713
+ * `scripts/1-first.js`
714
+ * `scripts/_second.js`
715
+ * `scripts/third.js`
654
716
 
655
717
  # Sharing Scripts
656
718
 
@@ -662,7 +724,7 @@ Start by [checking if an NPM package](index.md#scripts) for a script like yours
662
724
 
663
725
  ## Creating A Script Package
664
726
 
665
- Creating a script package for hubot is very simple. Start by installing the `hubot` [yeoman](http://yeoman.io/) generator:
727
+ Creating a script package for hubot is very simple. Start by installing the `hubot` [yeoman](http://yeoman.io/) generator:
666
728
 
667
729
 
668
730
  ```
@@ -687,10 +749,10 @@ If you are using git, the generated directory includes a .gitignore, so you can
687
749
  % git commit -m "Initial commit"
688
750
  ```
689
751
 
690
- You now have a hubot script repository that's ready to roll! Feel free to crack open the pre-created `src/awesome-script.coffee` file and start building up your script! When you've got it ready, you can publish it to [npmjs](http://npmjs.org) by [following their documentation](https://docs.npmjs.com/getting-started/publishing-npm-packages)!
752
+ You now have a hubot script repository that's ready to roll! Feel free to crack open the pre-created `src/awesome-script.js` file and start building up your script! When you've got it ready, you can publish it to [npmjs](http://npmjs.org) by [following their documentation](https://docs.npmjs.com/getting-started/publishing-npm-packages)!
691
753
 
692
- You'll probably want to write some unit tests for your new script. A sample test script is written to
693
- `test/awesome-script-test.coffee`, which you can run with `grunt`. For more information on tests,
754
+ You'll probably want to write some unit tests for your new script. A sample test script is written to
755
+ `test/awesome-script-test.js`, which you can run with `grunt`. For more information on tests,
694
756
  see the [Testing Hubot Scripts](#testing-hubot-scripts) section.
695
757
 
696
758
  # Listener Metadata
@@ -703,13 +765,16 @@ Additional extensions may define and handle additional metadata keys. For more i
703
765
 
704
766
  Returning to an earlier example:
705
767
 
706
- ```coffeescript
707
- module.exports = (robot) ->
708
- robot.respond /annoy me/, id:'annoyance.start', (res)
709
- # code to annoy someone
768
+ ```javascript
769
+ module.exports = (robot) => {
770
+ robot.respond(/annoy me/, id:'annoyance.start', (res) => {
771
+ // code to annoy someone
772
+ })
710
773
 
711
- robot.respond /unannoy me/, id:'annoyance.stop', (res)
712
- # code to stop annoying someone
774
+ robot.respond(/unannoy me/, id:'annoyance.stop', (res) => {
775
+ // code to stop annoying someone
776
+ })
777
+ }
713
778
  ```
714
779
 
715
780
  These scoped identifiers allow you to externally specify new behaviors like:
@@ -734,10 +799,10 @@ Middleware is called with:
734
799
  - See the each middleware type's API to see what the context will expose.
735
800
  - `next`
736
801
  - a Function with no additional properties that should be called to continue on to the next piece of middleware/execute the Listener callback
737
- - `next` should be called with a single, optional argument: either the provided `done` function or a new function that eventually calls `done`. If the argument is not given, the provided `done` will be assumed.
802
+ - `next` should be called with a single, optional parameter: either the provided `done` function or a new function that eventually calls `done`. If the parameter is not given, the provided `done` will be assumed.
738
803
  - `done`
739
804
  - a Function with no additional properties that should be called to interrupt middleware execution and begin executing the chain of completion functions.
740
- - `done` should be called with no arguments
805
+ - `done` should be called with no parameters
741
806
 
742
807
  Every middleware receives the same API signature of `context`, `next`, and
743
808
  `done`. Different kinds of middleware may receive different information in the
@@ -745,7 +810,7 @@ Every middleware receives the same API signature of `context`, `next`, and
745
810
 
746
811
  ### Error Handling
747
812
 
748
- For synchronous middleware (never yields to the event loop), hubot will automatically catch errors and emit an an `error` event, just like in standard listeners. Hubot will also automatically call the most recent `done` callback to unwind the middleware stack. Asynchronous middleware should catch its own exceptions, emit an `error` event, and call `done`. Any uncaught exceptions will interrupt all execution of middleware completion callbacks.
813
+ For synchronous middleware (never yields to the event loop), hubot will automatically catch errors and emit an `error` event, just like in standard listeners. Hubot will also automatically call the most recent `done` callback to unwind the middleware stack. Asynchronous middleware should catch its own exceptions, emit an `error` event, and call `done`. Any uncaught exceptions will interrupt all execution of middleware completion callbacks.
749
814
 
750
815
  # Listener Middleware
751
816
 
@@ -757,56 +822,65 @@ A fully functioning example can be found in [hubot-rate-limit](https://github.co
757
822
 
758
823
  A simple example of middleware logging command executions:
759
824
 
760
- ```coffeescript
761
- module.exports = (robot) ->
762
- robot.listenerMiddleware (context, next, done) ->
763
- # Log commands
764
- robot.logger.info "#{context.response.message.user.name} asked me to #{context.response.message.text}"
765
- # Continue executing middleware
825
+ ```javascript
826
+ module.exports = (robot) => {
827
+ robot.listenerMiddleware((context, next, done) => {
828
+ // Log commands
829
+ robot.logger.info(`${context.response.message.user.name} asked me to ${context.response.message.text}`)
830
+ // Continue executing middleware
766
831
  next()
832
+ })
833
+ }
767
834
  ```
768
835
 
769
836
  In this example, a log message will be written for each chat message that matches a Listener.
770
837
 
771
838
  A more complex example making a rate limiting decision:
772
839
 
773
- ```coffeescript
774
- module.exports = (robot) ->
775
- # Map of listener ID to last time it was executed
776
- lastExecutedTime = {}
777
-
778
- robot.listenerMiddleware (context, next, done) ->
779
- try
780
- # Default to 1s unless listener provides a different minimum period
781
- minPeriodMs = context.listener.options?.rateLimits?.minPeriodMs? or 1000
782
-
783
- # See if command has been executed recently
784
- if lastExecutedTime.hasOwnProperty(context.listener.options.id) and
785
- lastExecutedTime[context.listener.options.id] > Date.now() - minPeriodMs
786
- # Command is being executed too quickly!
787
- done()
788
- else
789
- next ->
840
+ ```javascript
841
+ module.exports = (robot) => {
842
+ // Map of listener ID to last time it was executed
843
+ let lastExecutedTime = {}
844
+
845
+ robot.listenerMiddleware((context, next, done) => {
846
+ try {
847
+ // Default to 1s unless listener provides a different minimum period
848
+ const minPeriodMs = context.listener.options?.rateLimits?.minPeriodMs ?? 1000
849
+
850
+ // See if command has been executed recently
851
+ if (lastExecutedTime.hasOwnProperty(context.listener.options.id) &&
852
+ lastExecutedTime[context.listener.options.id] > Date.now() - minPeriodMs) {
853
+ // Command is being executed too quickly!
854
+ done()
855
+ } else {
856
+ next(()=> {
790
857
  lastExecutedTime[context.listener.options.id] = Date.now()
791
858
  done()
792
- catch err
859
+ })
860
+ }
861
+ } catch(err) {
793
862
  robot.emit('error', err, context.response)
863
+ }
864
+ })
865
+ }
794
866
  ```
795
867
 
796
868
  In this example, the middleware checks to see if the listener has been executed in the last 1,000ms. If it has, the middleware calls `done` immediately, preventing the listener callback from being called. If the listener is allowed to execute, the middleware attaches a `done` handler so that it can record the time the listener *finished* executing.
797
869
 
798
870
  This example also shows how listener-specific metadata can be leveraged to create very powerful extensions: a script developer can use the rate limiting middleware to easily rate limit commands at different rates by just adding the middleware and setting a listener option.
799
871
 
800
- ```coffeescript
801
- module.exports = (robot) ->
802
- robot.hear /hello/, id: 'my-hello', rateLimits: {minPeriodMs: 10000}, (res) ->
803
- # This will execute no faster than once every ten seconds
804
- res.reply 'Why, hello there!'
872
+ ```javascript
873
+ module.exports = (robot) => {
874
+ robot.hear(/hello/, id: 'my-hello', rateLimits: {minPeriodMs: 10000}, (res) => {
875
+ // This will execute no faster than once every ten seconds
876
+ res.reply('Why, hello there!')
877
+ })
878
+ }
805
879
  ```
806
880
 
807
881
  ## Listener Middleware API
808
882
 
809
- Listener middleware callbacks receive three arguments, `context`, `next`, and
883
+ Listener middleware callbacks receive three parameters, `context`, `next`, and
810
884
  `done`. See the [middleware API](#execution-process-and-api) for a description
811
885
  of `next` and `done`. Listener middleware context includes these fields:
812
886
  - `listener`
@@ -820,7 +894,7 @@ of `next` and `done`. Listener middleware context includes these fields:
820
894
  # Receive Middleware
821
895
 
822
896
  Receive middleware runs before any listeners have executed. It's suitable for
823
- blacklisting commands that have not been updated to add an ID, metrics, and more.
897
+ excluded commands that have not been updated to add an ID, metrics, and more.
824
898
 
825
899
  ## Receive Middleware Example
826
900
 
@@ -828,30 +902,33 @@ This simple middlware bans hubot use by a particular user, including `hear`
828
902
  listeners. If the user attempts to run a command explicitly, it will return
829
903
  an error message.
830
904
 
831
- ```coffeescript
832
- BLACKLISTED_USERS = [
833
- '12345' # Restrict access for a user ID for a contractor
905
+ ```javascript
906
+ const EXCLUDED_USERS = [
907
+ '12345' // Restrict access for a user ID for a contractor
834
908
  ]
835
909
 
836
- robot.receiveMiddleware (context, next, done) ->
837
- if context.response.message.user.id in BLACKLISTED_USERS
838
- # Don't process this message further.
910
+ robot.receiveMiddleware((context, next, done) => {
911
+ if (EXCLUDED_USERS.some( id => context.response.message.user.id == id)) {
912
+ // Don't process this message further.
839
913
  context.response.message.finish()
840
914
 
841
- # If the message starts with 'hubot' or the alias pattern, this user was
842
- # explicitly trying to run a command, so respond with an error message.
843
- if context.response.message.text?.match(robot.respondPattern(''))
844
- context.response.reply "I'm sorry @#{context.response.message.user.name}, but I'm configured to ignore your commands."
915
+ // If the message starts with 'hubot' or the alias pattern, this user was
916
+ // explicitly trying to run a command, so respond with an error message.
917
+ if (context.response.message.text?.match(robot.respondPattern(''))) {
918
+ context.response.reply(`I'm sorry @${context.response.message.user.name}, but I'm configured to ignore your commands.`)
919
+ }
845
920
 
846
- # Don't process further middleware.
921
+ // Don't process further middleware.
847
922
  done()
848
- else
923
+ } else {
849
924
  next(done)
925
+ }
926
+ })
850
927
  ```
851
928
 
852
929
  ## Receive Middleware API
853
930
 
854
- Receive middleware callbacks receive three arguments, `context`, `next`, and
931
+ Receive middleware callbacks receive three parameters, `context`, `next`, and
855
932
  `done`. See the [middleware API](#execution-process-and-api) for a description
856
933
  of `next` and `done`. Receive middleware context includes these fields:
857
934
  - `response`
@@ -870,17 +947,21 @@ This simple example changes the format of links sent to a chat room from
870
947
  markdown links (like [example](https://example.com)) to the format supported
871
948
  by [Slack](https://slack.com), <https://example.com|example>.
872
949
 
873
- ```coffeescript
874
- module.exports = (robot) ->
875
- robot.responseMiddleware (context, next, done) ->
876
- return unless context.plaintext?
877
- context.strings = (string.replace(/\[([^\[\]]*?)\]\((https?:\/\/.*?)\)/, "<$2|$1>") for string in context.strings)
950
+ ```javascript
951
+ module.exports = (robot)=> {
952
+ robot.responseMiddleware((context, next, done) => {
953
+ if(!context.plaintext) return
954
+ context.strings.forEach(string => {
955
+ string.replace(/\[([^\[\]]*?)\]\((https?:\/\/.*?)\)/, "<$2|$1>"
956
+ })
878
957
  next()
958
+ })
959
+ }
879
960
  ```
880
961
 
881
962
  ## Response Middleware API
882
963
 
883
- Response middleware callbacks receive three arguments, `context`, `next`, and
964
+ Response middleware callbacks receive three parameters, `context`, `next`, and
884
965
  `done`. See the [middleware API](#execution-process-and-api) for a description
885
966
  of `next` and `done`. Receive middleware context includes these fields:
886
967
  - `response`
@@ -895,8 +976,8 @@ of `next` and `done`. Receive middleware context includes these fields:
895
976
  # Testing Hubot Scripts
896
977
 
897
978
  [hubot-test-helper](https://github.com/mtsmfm/hubot-test-helper) is a good
898
- framework for unit testing Hubot scripts. (Note that, in order to use
899
- hubot-test-helper, you'll need a recent Node version with support for Promises.)
979
+ framework for unit testing Hubot scripts. (Note that, in order to use
980
+ hubot-test-helper, you'll need a recent Node.js version with support for Promises.)
900
981
 
901
982
  Install the package in your Hubot instance:
902
983
 
@@ -909,10 +990,11 @@ You'll also need to install:
909
990
 
910
991
  You may also want to install:
911
992
 
912
- * *coffeescript* (if you're writing your tests in CoffeeScript rather than JavaScript)
913
993
  * a mocking library such as *Sinon.js* (if your script performs webservice calls or
914
994
  other asynchronous actions)
915
995
 
996
+ [Note: This section is still refering to Coffeescript, but we've update Hubot for Javascript. We'll have to replace this when we get a JavaScript example.]
997
+
916
998
  Here is a sample script that tests the first couple of commands in the
917
999
  [Hubot sample script](https://github.com/hubotio/generator-hubot/blob/master/generators/app/templates/scripts/example.coffee). This script uses *Mocha*, *chai*, *coffeescript*, and of course *hubot-test-helper*:
918
1000