hubot 3.4.0 → 3.5.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.
@@ -0,0 +1,36 @@
1
+ name: Release
2
+ on:
3
+ push:
4
+ branches:
5
+ - master
6
+ permissions:
7
+ contents: read
8
+ jobs:
9
+ release:
10
+ name: Release
11
+ runs-on: ubuntu-latest
12
+ permissions:
13
+ contents: write
14
+ issues: write
15
+ pull-requests: write
16
+ id-token: write
17
+ steps:
18
+ - name: Checkout
19
+ uses: actions/checkout@v3
20
+ with:
21
+ fetch-depth: 0
22
+ - name: Setup Node.js
23
+ uses: actions/setup-node@v3
24
+ with:
25
+ node-version: "lts/*"
26
+ - name: Install Dependencies
27
+ run: npm clean-install
28
+ - name: Verify the integrity of provenance attestations and registry signatures for installed dependencies
29
+ run: npm audit signatures
30
+ - name: Run Tests
31
+ run: npm test
32
+ - name: Release
33
+ env:
34
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
36
+ run: npx semantic-release
package/.node-version ADDED
@@ -0,0 +1 @@
1
+ 18.16.0
package/README.md CHANGED
@@ -1,4 +1,6 @@
1
- [![Build Status](https://travis-ci.org/hubotio/hubot.svg?branch=master)](https://travis-ci.org/hubotio/hubot) [![Coverage Status](https://coveralls.io/repos/github/hubotio/hubot/badge.svg?branch=master)](https://coveralls.io/github/hubotio/hubot?branch=master)
1
+ ![Build Status: MacOS](https://github.com/hubotio/hubot/actions/workflows/nodejs-macos.yml/badge.svg)
2
+ ![Build Status: Ubuntu](https://github.com/hubotio/hubot/actions/workflows/nodejs-ubuntu.yml/badge.svg)
3
+ ![Build Status: Window](https://github.com/hubotio/hubot/actions/workflows/nodejs-windows.yml/badge.svg)
2
4
 
3
5
  # Hubot
4
6
 
@@ -16,3 +18,4 @@ are building your own bot. But if you do, check out [CONTRIBUTING.md](CONTRIBUTI
16
18
  ## License
17
19
 
18
20
  See the [LICENSE](LICENSE.md) file for license rights and limitations (MIT).
21
+ `
package/bin/hubot CHANGED
@@ -3,7 +3,7 @@
3
3
  # While all other files have been converted to JavaScript via https://github.com/github/hubot/pull/1347,
4
4
  # we left the `bin/hubot` file to remain in CoffeeScript in order prevent
5
5
  # breaking existing 3rd party adapters of which some are still written in
6
- # CoffeeScript themselves. We will depracate and eventually remove this file
6
+ # CoffeeScript themselves. We will deprecate and eventually remove this file
7
7
  # in a future version of hubot
8
8
 
9
9
  require './hubot.js'
@@ -6,43 +6,40 @@ permalink: /docs/adapters/development/
6
6
 
7
7
  ## Adapter Basics
8
8
 
9
- All adapters inherit from the Adapter class in the `src/adapter.coffee` file. If you're writing your adapter in CoffeeScript, require the primary version of the adapter:
10
-
11
- ```coffee
12
- Adapter = require('hubot').Adapter
13
- ```
9
+ All adapters inherit from the Adapter class in the `src/adapter.js` file.
14
10
 
15
11
  If you're writing your adapter in ES2015, you must require the ES2015 entrypoint instead:
16
12
 
17
- ```js
13
+ ```javascript
18
14
  const Adapter = require('hubot/es2015').Adapter;
19
15
  ```
20
16
 
21
17
  There are certain methods that you will want to override. Here is a basic stub of what an extended Adapter class would look like:
22
18
 
23
- ```coffee
24
- class Sample extends Adapter
25
-
26
- constructor: ->
27
- super
28
- @robot.logger.info "Constructor"
29
-
30
- send: (envelope, strings...) ->
31
- @robot.logger.info "Send"
32
-
33
- reply: (envelope, strings...) ->
34
- @robot.logger.info "Reply"
35
-
36
- run: ->
37
- @robot.logger.info "Run"
38
- @emit "connected"
39
- user = new User 1001, name: 'Sample User'
40
- message = new TextMessage user, 'Some Sample Message', 'MSG-001'
41
- @robot.receive message
42
-
43
-
44
- exports.use = (robot) ->
45
- new Sample robot
19
+ ```javascript
20
+ const Adapter = require('../adapter')
21
+ const User = require('../user')
22
+ const TextMessage = require('../message').TextMessage
23
+ class Sample extends Adapter {
24
+ constructor(robot) {
25
+ super(robot)
26
+ this.robot.logger.info('Constructor')
27
+ }
28
+ send(envelope, ...strings) {
29
+ this.robot.logger.info('Send')
30
+ }
31
+ reply(envelope, ...strings) {
32
+ this.robot.logger.info('Reply')
33
+ }
34
+ run() {
35
+ this.robot.logger.info('Run')
36
+ this.emit('connected')
37
+ const user = new User(1001, 'Sample User')
38
+ const message = new TextMessage(user, 'Some Sample Message', 'MSG-001')
39
+ this.robot.receive(message)
40
+ }
41
+ }
42
+ exports.use = (robot) => new Sample(robot)
46
43
  ```
47
44
 
48
45
  ## Setting Up Your Development Environment
@@ -76,16 +73,18 @@ exports.use = (robot) ->
76
73
 
77
74
  ## Gotchas
78
75
 
79
- There is a an open issue in the node community around [npm linked peer dependencies not working](https://github.com/npm/npm/issues/5875). To get this working for our project you will need to do some minor changes to your code.
76
+ There is a an open issue in the node community around [npm linked peer dependencies not working](https://github.com/npm/npm/issues/5875). To get this working for our project you will need to do some minor changes to your code.
80
77
 
81
78
  1. For the import in your `hubot-sample` adapter, add the following code
82
79
 
83
- ```coffee
84
- try
85
- {Robot,Adapter,TextMessage,User} = require 'hubot'
86
- catch
87
- prequire = require('parent-require')
88
- {Robot,Adapter,TextMessage,User} = prequire 'hubot'
80
+ ```javascript
81
+ let {Robot,Adapter,TextMessage,User} = {}
82
+ try {
83
+ {Robot,Adapter,TextMessage,User} = require('hubot')
84
+ } catch {
85
+ const prequire = require('parent-require')
86
+ {Robot,Adapter,TextMessage,User} = prequire('hubot')
87
+ }
89
88
  ```
90
89
  2. In your `hubot-sample` folder, modify the `package.json` to include the following dependency so this custom import mechanism will work
91
90
 
@@ -43,9 +43,7 @@ Furthermore, Hubot scripts exist to enable persistence across Hubot restarts.
43
43
  `hubot-redis-brain` is such a script and uses a backend Redis server.
44
44
 
45
45
  By default, the brain contains a list of all users seen by Hubot.
46
- Therefore, without persistence across restarts, the brain will contain the list of users encountered so far, during the current run of Hubot.
47
- On the other hand, with persistence across restarts, the brain will contain all users encountered by Hubot during all of its runs.
48
- This list of users can be accessed through `hubot.brain.users()` and other utility methods.
46
+ Therefore, without persistence across restarts, the brain will contain the list of users encountered so far, during the current run of Hubot. On the other hand, with persistence across restarts, the brain will contain all users encountered by Hubot during all of its runs. This list of users can be accessed through `hubot.brain.users()` and other utility methods.
49
47
 
50
48
  ### Datastore
51
49
 
package/docs/index.md CHANGED
@@ -44,15 +44,12 @@ You now have your own functional hubot! There's a `bin/hubot`
44
44
  command for convenience, to handle installing npm dependencies, loading scripts,
45
45
  and then launching your hubot.
46
46
 
47
- Hubot needs Redis to persist data, so before you can start hubot on your own computer, you should have Redis installed on your localhost. If just want to test Hubot without Redis, then you can remove `hubot-redis-brain` from `external-scripts.json`.
47
+ Note: Hubot can use Redis to persist data, so before you can start hubot on your own computer, if you want to persist data, then you should have Redis running on your machine accessible via `localhost`. Then, ensure that `hubot-redis-brain` is listed in `external-scripts.json` as an `Array` of module names (e.g. `['hubot-redis-brain']`) or an `object` where the key is the name of the module (e.g. `{'hubot-redis-brain': 'some arbitrary value'}`) where the value of the property in the object is passed to the module function as the second argument. The first argument being the hubot Robot instance.
48
48
 
49
49
  % bin/hubot
50
50
  Hubot>
51
51
 
52
- This starts hubot using the [shell adapter](./adapters/shell.md), which
53
- is mostly useful for development. Make note of the name in the `hubot>` prompt;
54
- this is the name your hubot will respond to with commands. If the prompt
55
- reads `myhubot>` then your commands must start with `myhubot <command>`
52
+ This starts hubot using the [shell adapter](./adapters/shell.md), which is mostly useful for development. Make note of the name in the `hubot>` prompt; this is the name your hubot will respond to with commands. If the prompt reads `myhubot>` then your commands must start with `myhubot <command>`.
56
53
 
57
54
  For example, to list available commands:
58
55
 
@@ -107,7 +104,7 @@ To use a script from an NPM package:
107
104
  2. Add the package to `external-scripts.json`.
108
105
  3. Run `npm home <package-name>` to open a browser window for the homepage of the script, where you can find more information about configuring and installing the script.
109
106
 
110
- You can also put your own scripts under the `scripts/` directory. All scripts placed there are automatically loaded and ready to use with your hubot. Read more about customizing hubot by [writing your own scripts](scripting.md).
107
+ You can also put your own scripts under the `scripts/` directory. All scripts (files ending with either `.js` or `.mjs`) placed there are automatically loaded and ready to use with your hubot. Read more about customizing hubot by [writing your own scripts](scripting.md).
111
108
 
112
109
  ## Adapters
113
110
 
@@ -115,9 +112,7 @@ Hubot uses the adapter pattern to support multiple chat-backends. Here is a [lis
115
112
 
116
113
  ## Deploying
117
114
 
118
- You can deploy hubot to Heroku, which is the officially supported method.
119
- Additionally you are able to deploy hubot to a UNIX-like system or Windows.
120
- Please note the support for deploying to Windows isn't officially supported.
115
+ You can deploy hubot to Heroku, which is the officially supported method. Additionally you are able to deploy hubot to a UNIX-like system or Windows. Please note the support for deploying to Windows isn't officially supported.
121
116
 
122
117
  * [Deploying Hubot onto Azure](./deploying/azure.md)
123
118
  * [Deploying Hubot onto Bluemix](./deploying/bluemix.md)
@@ -127,4 +122,4 @@ Please note the support for deploying to Windows isn't officially supported.
127
122
 
128
123
  ## Patterns
129
124
 
130
- Using custom scripts, you can quickly customize Hubot to be the most life embettering robot he or she can be. Read [docs/patterns.md](patterns.md) for some nifty tricks that may come in handy as you teach your hubot new skills.
125
+ Using custom scripts, you can quickly customize Hubot to be the most life embettering robot he or she can be. Read [docs/patterns.md](patterns.md) for some nifty tricks that may come in handy as you teach your hubot new skills.
package/docs/patterns.md CHANGED
@@ -15,23 +15,23 @@ When you rename Hubot, he will no longer respond to his former name. In order to
15
15
 
16
16
  Setting this up is very easy:
17
17
 
18
- 1. Create a [bundled script](scripting.md) in the `scripts/` directory of your Hubot instance called `rename-hubot.coffee`
18
+ 1. Create a [bundled script](scripting.md) in the `scripts/` directory of your Hubot instance called `rename-hubot.js`
19
19
  2. Add the following code, modified for your needs:
20
20
 
21
- ```coffeescript
22
- # Description:
23
- # Tell people hubot's new name if they use the old one
24
- #
25
- # Commands:
26
- # None
27
- #
28
- module.exports = (robot) ->
29
- robot.hear /^hubot:? (.+)/i, (res) ->
30
- response = "Sorry, I'm a diva and only respond to #{robot.name}"
31
- response += " or #{robot.alias}" if robot.alias
32
- res.reply response
33
- return
21
+ ```javascript
22
+ // Description:
23
+ // Tell people hubot's new name if they use the old one
34
24
 
25
+ // Commands:
26
+ // None
27
+
28
+ module.exports = (robot) => {
29
+ robot.hear(/^hubot:? (.+)/i, (res) => {
30
+ let response = `Sorry, I'm a diva and only respond to ${robot.name}`
31
+ response += robot.alias ? ` or ${robot.alias}` : ''
32
+ return res.reply(response)
33
+ })
34
+ }
35
35
  ```
36
36
 
37
37
  In the above pattern, modify both the hubot listener and the response message to suit your needs.
@@ -49,70 +49,72 @@ This pattern is similar to the Renaming the Hubot Instance pattern above:
49
49
 
50
50
  Here is the setup:
51
51
 
52
- 1. Create a [bundled script](scripting.md) in the `scripts/` directory of your Hubot instance called `deprecations.coffee`
52
+ 1. Create a [bundled script](scripting.md) in the `scripts/` directory of your Hubot instance called `deprecations.js`
53
53
  2. Copy any old command listeners and add them to that file. For example, if you were to rename the help command for some silly reason:
54
54
 
55
- ```coffeescript
56
- # Description:
57
- # Tell users when they have used commands that are deprecated or renamed
58
- #
59
- # Commands:
60
- # None
61
- #
62
- module.exports = (robot) ->
63
- robot.respond /help\s*(.*)?$/i, (res) ->
64
- res.reply "That means nothing to me anymore. Perhaps you meant `docs` instead?"
65
- return
55
+ ```javascript
56
+ // Description:
57
+ // Tell users when they have used commands that are deprecated or renamed
58
+ //
59
+ // Commands:
60
+ // None
61
+ //
62
+ module.exports = (robot) => {
63
+ robot.respond(/help\s*(.*)?$/i, (res) => {
64
+ return res.reply('That means nothing to me anymore. Perhaps you meant "docs" instead?')
65
+ })
66
+ }
66
67
 
67
68
  ```
68
69
 
69
70
  ## Preventing Hubot from Running Scripts Concurrently
70
71
 
71
- Sometimes you have scripts that take several minutes to execute. If these scripts are doing something that could be interfered
72
- with by running subsequent commands, you may wish to code your scripts to prevent concurrent access.
72
+ Sometimes you have scripts that take several minutes to execute. If these scripts are doing something that could be interfered with by running subsequent commands, you may wish to code your scripts to prevent concurrent access.
73
73
 
74
- To do this, you can set up a lock in the Hubot [brain](scripting.md#persistence) object. The lock is set up here so that different scripts
75
- can share the same lock if necessary.
74
+ To do this, you can set up a lock in the Hubot [brain](scripting.md#persistence) object. The lock is set up here so that different scripts can share the same lock if necessary.
76
75
 
77
76
  Setting up the lock looks something like this:
78
77
 
79
- ```coffeescript
80
- module.exports = (robot) ->
81
- robot.brain.on 'loaded', ->
82
- # Clear the lock on startup in case Hubot has restarted and Hubot's brain has persistence (e.g. redis).
83
- # We don't want any orphaned locks preventing us from running commands.
78
+ ```javascript
79
+ module.exports = (robot) => {
80
+ robot.brain.on('loaded', ()=>{
81
+ // Clear the lock on startup in case Hubot has restarted and Hubot's brain has persistence (e.g. redis).
82
+ // We don't want any orphaned locks preventing us from running commands.
84
83
  robot.brain.remove('yourLockName')
84
+ }
85
85
 
86
- robot.respond /longrunningthing/i, (msg) ->
87
- lock = robot.brain.get('yourLockName')
88
-
89
- if lock?
90
- msg.send "I'm sorry, #{msg.message.user.name}, I'm afraid I can't do that. I'm busy doing something for #{lock.user.name}."
91
- return
86
+ robot.respond(/longrunningthing/i, (msg) => {
87
+ const lock = robot.brain.get('yourLockName')
88
+ if (lock) {
89
+ return msg.send(`I'm sorry, ${msg.message.user.name}, I'm afraid I can't do that. I'm busy doing something for ${lock.user.name}.`)
90
+ }
92
91
 
93
- robot.brain.set('yourLockName', msg.message) # includes user, room, etc about who locked
92
+ robot.brain.set('yourLockName', msg.message) // includes user, room, etc about who locked
94
93
 
95
- yourLongClobberingAsyncThing (err, response) ->
96
- # Clear the lock
94
+ yourLongClobberingAsyncThing(err, res).then(
95
+ // Clear the lock
97
96
  robot.brain.remove('yourLockName')
98
- msg.reply "Finally Done"
97
+ msg.reply('Finally Done')
98
+ )).catch(e => console.error(e))
99
+ }
99
100
  ```
100
101
 
101
102
  ## Forwarding all HTTP requests through a proxy
102
103
 
103
104
  In many corporate environments, a web proxy is required to access the Internet and/or protected resources. For one-off control, use can specify an [Agent](https://nodejs.org/api/http.html) to use with `robot.http`. However, this would require modifying every script your robot uses to point at the proxy. Instead, you can specify the agent at the global level and have all HTTP requests use the agent by default.
104
105
 
105
- Due to the way node.js handles HTTP and HTTPS requests, you need to specify a different Agent for each protocol. ScopedHTTPClient will then automatically choose the right ProxyAgent for each request.
106
+ Due to the way Node.js handles HTTP and HTTPS requests, you need to specify a different Agent for each protocol. ScopedHTTPClient will then automatically choose the right ProxyAgent for each request.
106
107
 
107
108
  1. Install ProxyAgent. `npm install proxy-agent`
108
- 2. Create a [bundled script](scripting.md) in the `scripts/` directory of your Hubot instance called `proxy.coffee`
109
+ 2. Create a [bundled script](scripting.md) in the `scripts/` directory of your Hubot instance called `proxy.js`
109
110
  3. Add the following code, modified for your needs:
110
111
 
111
- ```coffeescript
112
- proxy = require 'proxy-agent'
113
- module.exports = (robot) ->
112
+ ```javascript
113
+ const proxy = require('proxy-agent')
114
+ module.exports = (robot) => {
114
115
  robot.globalHttpOptions.httpAgent = proxy('http://my-proxy-server.internal', false)
115
116
  robot.globalHttpOptions.httpsAgent = proxy('http://my-proxy-server.internal', true)
117
+ }
116
118
  ```
117
119
 
118
120
  ## Dynamic matching of messages
@@ -123,27 +125,40 @@ In a simple robot, this isn't much different from just putting the conditions in
123
125
 
124
126
  For example, the [factoid lookup command](https://github.com/github/hubot-scripts/blob/bd810f99f9394818a9dcc2ea3729427e4101b96d/src/scripts/factoid.coffee#L95-L99) could be reimplemented as:
125
127
 
126
- ```coffeescript
127
- module.exports = (robot) ->
128
- # Dynamically populated list of factoids
129
- facts =
130
- fact1: 'stuff'
131
- fact2: 'other stuff'
132
-
133
- robot.listen(
134
- # Matcher
135
- (message) ->
136
- match = message.match(/^~(.*)$/)
137
- # Only match if there is a matching factoid
138
- if match and match[1] in facts
139
- match[1]
140
- else
141
- false
142
- # Callback
143
- (response) ->
144
- fact = response.match
145
- res.reply "#{fact} is #{facts[fact]}"
146
- )
128
+ ```javascript
129
+ // use case: Hubot>fact1
130
+ // This listener doesn't require you to type the bot's name first
131
+
132
+ const {TextMessage} = require('../src/message')
133
+ module.exports = (robot) => {
134
+ // Dynamically populated list of factoids
135
+ const facts = {
136
+ fact1: 'stuff',
137
+ fact2: 'other stuff'
138
+ }
139
+ robot.listen(
140
+ // Matcher
141
+ (message) => {
142
+ // Check that message is a TextMessage type because
143
+ // if there is no match, this matcher function will
144
+ // be called again but the message type will be CatchAllMessage
145
+ // which doesn't have a `match` method.
146
+ if(!(message instanceof TextMessage)) return false
147
+ const match = message.match(/^(.*)$/)
148
+ // Only match if there is a matching factoid
149
+ if (match && match[1] in facts) {
150
+ return match[1]
151
+ } else {
152
+ return false
153
+ }
154
+ },
155
+ // Callback
156
+ (res) => {
157
+ const fact = res.match
158
+ res.reply(`${fact} is ${facts[fact]}`)
159
+ }
160
+ )
161
+ }
147
162
  ```
148
163
 
149
164
  ## Restricting access to commands
@@ -152,54 +167,69 @@ One of the awesome features of Hubot is its ability to make changes to a product
152
167
 
153
168
  There are a variety of different patterns for restricting access that you can follow depending on your specific needs:
154
169
 
155
- * Two buckets of access: full and restricted with whitelist/blacklist
170
+ * Two buckets of access: full and restricted with include/exclude list
156
171
  * Specific access rules for every command (Role-based Access Control)
157
- * Blacklisting/whitelisting commands in specific rooms
172
+ * Include/exclude listing commands in specific rooms
158
173
 
159
174
  ### Simple per-listener access
160
175
 
161
176
  In some organizations, almost all employees are given the same level of access and only a select few need to be restricted (e.g. new hires, contractors, etc.). In this model, you partition the set of all listeners to separate the "power commands" from the "normal commands".
162
177
 
163
- Once you have segregated the listeners, you need to make some tradeoff decisions around whitelisting/blacklisting users and listeners.
178
+ Once you have segregated the listeners, you need to make some tradeoff decisions around include/exclude users and listeners.
164
179
 
165
- The key deciding factors for whitelisting vs blacklisting of users are the number of users in each category, the frequency of change in either category, and the level of security risk your organization is willing to accept.
166
- * Whitelisting users (users X, Y, Z have access to power commands; all other users only get access to normal commands) is a more secure method of access (new users have no default access to power commands), but has higher maintenance overhead (you need to add each new user to the "approved" list).
167
- * Blacklisting users (all users get access to power commands, except for users X, Y, Z, who only get access to normal commands) is a less secure method (new users have default access to power commands until they are added to the blacklist), but has a much lower maintenance overhead if the blacklist is small/rarely updated.
180
+ The key deciding factors for inclusion vs exclusion of users are the number of users in each category, the frequency of change in either category, and the level of security risk your organization is willing to accept.
181
+
182
+ * Including users (users X, Y, Z have access to power commands; all other users only get access to normal commands) is a more secure method of access (new users have no default access to power commands), but has higher maintenance overhead (you need to add each new user to the "include" list).
183
+ * Excluding users (all users get access to power commands, except for users X, Y, Z, who only get access to normal commands) is a less secure method (new users have default access to power commands until they are added to the exclusion list), but has a much lower maintenance overhead if the exclusion list is small/rarely updated.
168
184
 
169
185
  The key deciding factors for selectively allowing vs restricting listeners are the number of listeners in each category, the ratio of internal to external scripts, and the level of security risk your organization is willing to accept.
186
+
170
187
  * Selectively allowing listeners (all listeners are power commands, except for listeners A, B, C, which are considered normal commands) is a more secure method (new listeners are restricted by default), but has a much higher maintenance overhead (every silly/fun listener needs to be explicity downgraded to "normal" status).
171
- * Selectively restricting listeners (listeners A, B, C are power commands, everything else is a normal command) is a less secure method (new listeners are put into the normal category by default, which could give unexpected access; external scripts are particularly scary here), but has a lower maintenance overhead (no need to modify/enumerate all the fun/culture scripts in your access policy).
188
+ * Selectively restricting listeners (listeners A, B, C are power commands, everything else is a normal command) is a less secure method (new listeners are put into the normal category by default, which could give unexpected access; external scripts are particularly risky here), but has a lower maintenance overhead (no need to modify/enumerate all the fun/culture scripts in your access policy).
172
189
 
173
190
  As an additional consideration, most scripts do not currently have listener IDs, so you will likely need to open PRs (or fork) any external scripts you use to add listener IDs. The actual modification is easy, but coordinating with lots of maintainers can be time consuming.
174
191
 
175
192
  Once you have decided which of the four possible models to follow, you need to build the appropriate lists of users and listeners to plug into your authorization middleware.
176
193
 
177
- Example: whitelist of users given access to selectively restricted power commands
178
- ```coffeescript
179
- POWER_COMMANDS = [
180
- 'deploy.web' # String that matches the listener ID
181
- ]
194
+ Example: inclusion list of users given access to selectively restricted power commands
182
195
 
183
- POWER_USERS = [
184
- 'jdoe' # String that matches the user ID set by the adapter
196
+ ```javascript
197
+ const POWER_COMMANDS = [
198
+ 'deploy.web' // String that matches the listener ID
185
199
  ]
186
200
 
187
- module.exports = (robot) ->
188
- robot.listenerMiddleware (context, next, done) ->
189
- if context.listener.options.id in POWER_COMMANDS
190
- if context.response.message.user.id in POWER_USERS
191
- # User is allowed access to this command
192
- next()
193
- else
194
- # Restricted command, but user isn't in whitelist
195
- context.response.reply "I'm sorry, @#{context.response.message.user.name}, but you don't have access to do that."
196
- done()
197
- else
198
- # This is not a restricted command; allow everyone
199
- next()
201
+ // Change name to something else to see it reject the command.
202
+ const POWER_USERS = [
203
+ 'Shell' // String that matches the user ID set by the adapter
204
+ ]
205
+
206
+ module.exports = (robot) => {
207
+ robot.listenerMiddleware((context, next, done) => {
208
+ if (POWER_COMMANDS.indexOf(context.listener.options.id) > -1) {
209
+ if (POWER_USERS.indexOf(context.response.message.user.name) > -1){
210
+ // User is allowed access to this command
211
+ next()
212
+ } else {
213
+ // Restricted command, but user isn't in whitelist
214
+ context.response.reply(`I'm sorry, @${context.response.message.user.name}, but you don't have access to do that.`)
215
+ done()
216
+ }
217
+ } else {
218
+ // This is not a restricted command; allow everyone
219
+ next()
220
+ }
221
+ })
222
+
223
+ robot.listen(message => {
224
+ return true
225
+ }, {id: 'deploy.web'},
226
+ res => {
227
+ res.reply('Deploying web...')
228
+ })
229
+ }
200
230
  ```
201
231
 
202
- Remember that middleware executes for ALL listeners that match a given message (including `robot.hear /.+/`), so make sure you include them when categorizing your listeners.
232
+ Remember that middleware executes for ALL listeners that match a given message (including `robot.hear(/.+/)`), so make sure you include them when categorizing your listeners.
203
233
 
204
234
  ### Specific access rules per listener
205
235
 
@@ -210,13 +240,13 @@ Example access policy:
210
240
  * The Operations group has access to deploy all services (but not cut releases)
211
241
  * The front desk cannot cut releases nor deploy services
212
242
 
213
- Complex policies like this are currently best implemented in code directly, though there is [ongoing work](https://github.com/michaelansel/hubot-rbac) to build a generalized framework for access management.
243
+ Complex policies like this are currently best implemented in code directly.
214
244
 
215
245
  ### Specific access rules per room
216
246
 
217
247
  Organizations that have a number of chat rooms that serve different purposes often want to be able to use the same instance of hubot but have a different set of commands allowed in each room.
218
248
 
219
- Work on generalized blacklist solution is [ongoing](https://github.com/kristenmills/hubot-command-blacklist). A whitelist soultion could take a similar approach.
249
+ Work on generalized exlusion list solution is [ongoing](https://github.com/kristenmills/hubot-command-blacklist). An inclusive list soultion could take a similar approach.
220
250
 
221
251
  ## Use scoped npm packages as adapter
222
252
 
@@ -227,6 +257,7 @@ npm install <alias>@npm:<name>
227
257
  ```
228
258
 
229
259
  So for example to use `@foo/hubot-adapter` package as the adapter, you can:
260
+
230
261
  ```bash
231
262
  npm install hubot-foo@npm:@foo/hubot-adapter
232
263