hubot 3.3.2 → 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.
package/es2015.js CHANGED
@@ -13,7 +13,7 @@ module.exports = {
13
13
  User,
14
14
  Brain,
15
15
  Robot,
16
- Adapter: Adapter,
16
+ Adapter,
17
17
  Response,
18
18
  Listener: Listener.Listener,
19
19
  TextListener: Listener.TextListener,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hubot",
3
- "version": "3.3.2",
3
+ "version": "3.5.0",
4
4
  "author": "hubot",
5
5
  "keywords": [
6
6
  "github",
@@ -15,27 +15,24 @@
15
15
  "url": "https://github.com/hubotio/hubot.git"
16
16
  },
17
17
  "dependencies": {
18
- "async": ">=0.1.0 <1.0.0",
19
- "chalk": "^1.0.0",
18
+ "async": "^3.2.4",
20
19
  "cline": "^0.8.2",
21
- "coffeescript": "1.6.3",
22
- "connect-multiparty": "^2.1.1",
23
- "express": "^4.16.3",
20
+ "coffeescript": "^2.7.0",
21
+ "connect-multiparty": "^2.2.0",
22
+ "express": "^4.18.2",
23
+ "express-basic-auth": "^1.2.1",
24
24
  "log": "1.4.0",
25
- "optparse": "1.0.4",
26
- "scoped-http-client": "0.11.0"
25
+ "optparse": "^1.0.5"
27
26
  },
28
27
  "devDependencies": {
29
- "chai": "~2.1.0",
30
- "coveralls": "^3.0.2",
28
+ "chai": "^4.3.7",
31
29
  "is-circular": "^1.0.2",
32
- "mocha": "^5.2.0",
33
- "mockery": "^1.4.0",
34
- "nyc": "^13.0.0",
35
- "semantic-release": "^15.9.3",
36
- "sinon": "~1.17.0",
37
- "sinon-chai": "^2.8.0",
38
- "standard": "^10.0.2"
30
+ "mocha": "^10.2.0",
31
+ "mockery": "^2.1.0",
32
+ "semantic-release": "^21.0.1",
33
+ "sinon": "^15.0.4",
34
+ "sinon-chai": "^3.7.0",
35
+ "standard": "^17.0.0"
39
36
  },
40
37
  "engines": {
41
38
  "node": "> 4.0.0",
@@ -48,9 +45,13 @@
48
45
  "scripts": {
49
46
  "start": "bin/hubot",
50
47
  "pretest": "standard",
51
- "test": "nyc --reporter=html --reporter=text mocha",
52
- "coverage": "nyc report --reporter=text-lcov | coveralls",
53
- "test:smoke": "node src/**/*.js",
54
- "semantic-release": "semantic-release pre && npm publish && semantic-release post"
48
+ "test": "mocha --exit",
49
+ "test:smoke": "node src/**/*.js"
50
+ },
51
+ "release": {
52
+ "branches": [
53
+ "master"
54
+ ],
55
+ "dryRun": false
55
56
  }
56
57
  }
@@ -229,30 +229,30 @@ class CampfireStreaming extends EventEmitter {
229
229
  },
230
230
 
231
231
  speak (text, callback) {
232
- const body = { message: { 'body': text } }
232
+ const body = { message: { body: text } }
233
233
  return self.post(`/room/${id}/speak`, body, callback)
234
234
  },
235
235
 
236
236
  message (text, type, callback) {
237
- const body = { message: { 'body': text, 'type': type } }
237
+ const body = { message: { body: text, type } }
238
238
  return self.post(`/room/${id}/speak`, body, callback)
239
239
  },
240
240
 
241
241
  // listen for activity in channels
242
242
  listen () {
243
243
  const headers = {
244
- 'Host': 'streaming.campfirenow.com',
245
- 'Authorization': self.authorization,
244
+ Host: 'streaming.campfirenow.com',
245
+ Authorization: self.authorization,
246
246
  'User-Agent': `Hubot/${this.robot != null ? this.robot.version : undefined} (${this.robot != null ? this.robot.name : undefined})`
247
247
  }
248
248
 
249
249
  const options = {
250
- 'agent': false,
251
- 'host': 'streaming.campfirenow.com',
252
- 'port': 443,
253
- 'path': `/room/${id}/live.json`,
254
- 'method': 'GET',
255
- 'headers': headers
250
+ agent: false,
251
+ host: 'streaming.campfirenow.com',
252
+ port: 443,
253
+ path: `/room/${id}/live.json`,
254
+ method: 'GET',
255
+ headers
256
256
  }
257
257
 
258
258
  const request = HTTPS.request(options, function (response) {
@@ -326,19 +326,19 @@ class CampfireStreaming extends EventEmitter {
326
326
  const logger = this.robot.logger
327
327
 
328
328
  const headers = {
329
- 'Authorization': this.authorization,
330
- 'Host': this.host,
329
+ Authorization: this.authorization,
330
+ Host: this.host,
331
331
  'Content-Type': 'application/json',
332
332
  'User-Agent': `Hubot/${this.robot != null ? this.robot.version : undefined} (${this.robot != null ? this.robot.name : undefined})`
333
333
  }
334
334
 
335
335
  const options = {
336
- 'agent': false,
337
- 'host': this.host,
338
- 'port': 443,
339
- 'path': path,
340
- 'method': method,
341
- 'headers': headers
336
+ agent: false,
337
+ host: this.host,
338
+ port: 443,
339
+ path,
340
+ method,
341
+ headers
342
342
  }
343
343
 
344
344
  if (method === 'POST' || method === 'PUT') {
@@ -4,23 +4,23 @@ const fs = require('fs')
4
4
  const readline = require('readline')
5
5
  const Stream = require('stream')
6
6
  const cline = require('cline')
7
- const chalk = require('chalk')
8
7
 
9
8
  const Adapter = require('../adapter')
10
9
 
11
- var _require = require('../message')
10
+ const _require = require('../message')
12
11
 
13
12
  const TextMessage = _require.TextMessage
14
13
 
15
14
  const historySize = process.env.HUBOT_SHELL_HISTSIZE != null ? parseInt(process.env.HUBOT_SHELL_HISTSIZE) : 1024
16
15
 
17
16
  const historyPath = '.hubot_history'
17
+ const bold = str => `\x1b[1m${str}\x1b[22m`
18
18
 
19
19
  class Shell extends Adapter {
20
20
  send (envelope/* , ...strings */) {
21
21
  const strings = [].slice.call(arguments, 1)
22
22
 
23
- Array.from(strings).forEach(str => console.log(chalk.bold(`${str}`)))
23
+ Array.from(strings).forEach(str => console.log(bold(str)))
24
24
  }
25
25
 
26
26
  emote (envelope/* , ...strings */) {
@@ -36,15 +36,13 @@ class Shell extends Adapter {
36
36
 
37
37
  run () {
38
38
  this.buildCli()
39
-
40
39
  loadHistory((error, history) => {
41
40
  if (error) {
42
41
  console.log(error.message)
43
42
  }
44
-
45
43
  this.cli.history(history)
46
44
  this.cli.interact(`${this.robot.name}> `)
47
- return this.emit('connected')
45
+ return this.emit('connected', this)
48
46
  })
49
47
  }
50
48
 
@@ -82,7 +80,7 @@ class Shell extends Adapter {
82
80
  })
83
81
 
84
82
  this.cli.on('close', () => {
85
- let fileOpts, history, i, item, len, outstream, startIndex
83
+ let history, i, item, len
86
84
 
87
85
  history = this.cli.history()
88
86
 
@@ -90,25 +88,24 @@ class Shell extends Adapter {
90
88
  return this.shutdown()
91
89
  }
92
90
 
93
- startIndex = history.length - historySize
91
+ const startIndex = history.length - historySize
94
92
  history = history.reverse().splice(startIndex, historySize)
95
- fileOpts = {
93
+ const fileOpts = {
96
94
  mode: 0x180
97
95
  }
98
96
 
99
- outstream = fs.createWriteStream(historyPath, fileOpts)
100
- outstream.on('finish', this.shutdown.bind(this))
101
-
97
+ const outstream = fs.createWriteStream(historyPath, fileOpts)
98
+ outstream.on('end', this.shutdown.bind(this))
102
99
  for (i = 0, len = history.length; i < len; i++) {
103
100
  item = history[i]
104
101
  outstream.write(item + '\n')
105
102
  }
106
-
107
- outstream.end(this.shutdown.bind(this))
108
103
  })
109
104
  }
110
105
  }
111
106
 
107
+ // Prevent output buffer "swallowing" every other character on OSX / Node version > 16.19.0.
108
+ process.stdout._handle.setBlocking(false)
112
109
  exports.use = robot => new Shell(robot)
113
110
 
114
111
  // load history from .hubot_history.
package/src/brain.js CHANGED
@@ -10,19 +10,19 @@ const User = require('./user')
10
10
  // 2. If the original object was a User object, the original object
11
11
  // 3. If the original object was a plain JavaScript object, return
12
12
  // a User object with all of the original object's properties.
13
- let reconstructUserIfNecessary = function (user, robot) {
13
+ const reconstructUserIfNecessary = function (user, robot) {
14
14
  if (!user) {
15
15
  return null
16
16
  }
17
17
 
18
18
  if (!user.constructor || (user.constructor && user.constructor.name !== 'User')) {
19
- let id = user.id
19
+ const id = user.id
20
20
  delete user.id
21
21
  // Use the old user as the "options" object,
22
22
  // populating the new user with its values.
23
23
  // Also add the `robot` field so it gets a reference.
24
24
  user.robot = robot
25
- let newUser = new User(id, user)
25
+ const newUser = new User(id, user)
26
26
  delete user.robot
27
27
 
28
28
  return newUser
@@ -142,14 +142,14 @@ class Brain extends EventEmitter {
142
142
  //
143
143
  // Caveats: Deeply nested structures don't merge well.
144
144
  mergeData (data) {
145
- for (let k in data || {}) {
145
+ for (const k in data || {}) {
146
146
  this.data[k] = data[k]
147
147
  }
148
148
 
149
149
  // Ensure users in the brain are still User objects.
150
150
  if (data && data.users) {
151
- for (let k in data.users) {
152
- let user = this.data.users[k]
151
+ for (const k in data.users) {
152
+ const user = this.data.users[k]
153
153
  this.data.users[k] = reconstructUserIfNecessary(user, this.getRobot())
154
154
  }
155
155
  }
@@ -195,8 +195,8 @@ class Brain extends EventEmitter {
195
195
  let result = null
196
196
  const lowerName = name.toLowerCase()
197
197
 
198
- for (let k in this.data.users || {}) {
199
- const userName = this.data.users[k]['name']
198
+ for (const k in this.data.users || {}) {
199
+ const userName = this.data.users[k].name
200
200
  if (userName != null && userName.toString().toLowerCase() === lowerName) {
201
201
  result = this.data.users[k]
202
202
  }
package/src/datastore.js CHANGED
@@ -22,7 +22,7 @@ class DataStore {
22
22
  // present, it's instantiated as an empty object.
23
23
  setObject (key, objectKey, value) {
24
24
  return this.get(key).then((object) => {
25
- let target = object || {}
25
+ const target = object || {}
26
26
  target[objectKey] = value
27
27
  return this.set(key, target)
28
28
  })
@@ -33,7 +33,7 @@ class DataStore {
33
33
  // present, it's instantiated as an empty array.
34
34
  setArray (key, value) {
35
35
  return this.get(key).then((object) => {
36
- let target = object || []
36
+ const target = object || []
37
37
  // Extend the array if the value is also an array, otherwise
38
38
  // push the single value on the end.
39
39
  if (Array.isArray(value)) {
@@ -56,7 +56,7 @@ class DataStore {
56
56
  // contain an `objectKey`, returns `undefined`.
57
57
  getObject (key, objectKey) {
58
58
  return this.get(key).then((object) => {
59
- let target = object || {}
59
+ const target = object || {}
60
60
  return target[objectKey]
61
61
  })
62
62
  }
@@ -0,0 +1,312 @@
1
+ /*
2
+ Copyright (c) 2014 rick
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ */
23
+
24
+ /*
25
+
26
+ April 15, 2023
27
+ Reasoning:
28
+ ScopedHttpClient is no longer maintained.
29
+
30
+ Decision:
31
+ Implement a phased approach to deprecate `robot.http` all together in favor of `fetch`.
32
+ 1. Convert ScopedHttpClient to Javascript and include the module in this repo
33
+ 2. Add a deprecation warning to `robot.http`
34
+ 3. Remove `robot.http` in a future release
35
+ */
36
+ const path = require('path')
37
+ const http = require('http')
38
+ const https = require('https')
39
+ const qs = require('querystring')
40
+
41
+ const nonPassThroughOptions = [
42
+ 'headers', 'hostname', 'encoding', 'auth', 'port',
43
+ 'protocol', 'agent', 'httpAgent', 'httpsAgent', 'query', 'host', 'path',
44
+ 'pathname', 'slashes', 'hash'
45
+ ]
46
+
47
+ class ScopedClient {
48
+ constructor (url, options) {
49
+ this.options = this.buildOptions(url, options)
50
+ this.passthroughOptions = reduce(extend({}, this.options), nonPassThroughOptions)
51
+ }
52
+
53
+ request (method, reqBody, callback) {
54
+ let req
55
+ if (typeof (reqBody) === 'function') {
56
+ callback = reqBody
57
+ reqBody = null
58
+ }
59
+
60
+ try {
61
+ let requestModule
62
+ const headers = extend({}, this.options.headers)
63
+ const sendingData = reqBody && (reqBody.length > 0)
64
+ headers.Host = this.options.hostname
65
+ if (this.options.port) { headers.Host += `:${this.options.port}` }
66
+
67
+ // If `callback` is `undefined` it means the caller isn't going to stream
68
+ // the body of the request using `callback` and we can set the
69
+ // content-length header ourselves.
70
+ //
71
+ // There is no way to conveniently assert in an else clause because the
72
+ // transfer encoding could be chunked or using a newer framing mechanism.
73
+ // And this is why we should'nt be creating a wrapper around http.
74
+ // Please just use `fetch`.
75
+ if (callback === undefined) {
76
+ headers['Content-Length'] = sendingData ? Buffer.byteLength(reqBody, this.options.encoding) : 0
77
+ }
78
+
79
+ if (this.options.auth) {
80
+ headers.Authorization = 'Basic ' + Buffer.from(this.options.auth, 'base64')
81
+ }
82
+
83
+ const port = this.options.port ||
84
+ ScopedClient.defaultPort[this.options.protocol] || 80
85
+
86
+ let {
87
+ agent
88
+ } = this.options
89
+ if (this.options.protocol === 'https:') {
90
+ requestModule = https
91
+ if (this.options.httpsAgent) { agent = this.options.httpsAgent }
92
+ } else {
93
+ requestModule = http
94
+ if (this.options.httpAgent) { agent = this.options.httpAgent }
95
+ }
96
+
97
+ const requestOptions = {
98
+ port,
99
+ host: this.options.hostname,
100
+ method,
101
+ path: this.fullPath(),
102
+ headers,
103
+ agent
104
+ }
105
+
106
+ // Extends the previous request options with all remaining options
107
+ extend(requestOptions, this.passthroughOptions)
108
+
109
+ req = requestModule.request(requestOptions)
110
+
111
+ if (this.options.timeout) {
112
+ req.setTimeout(this.options.timeout, () => req.abort())
113
+ }
114
+
115
+ if (callback) {
116
+ req.on('error', callback)
117
+ }
118
+ if (sendingData) { req.write(reqBody, this.options.encoding) }
119
+ if (callback) { callback(null, req) }
120
+ } catch (err) {
121
+ if (callback) { callback(err, req) }
122
+ }
123
+
124
+ return callback => {
125
+ if (callback) {
126
+ req.on('response', res => {
127
+ res.setEncoding(this.options.encoding)
128
+ let body = ''
129
+ res.on('data', chunk => {
130
+ body += chunk
131
+ })
132
+
133
+ return res.on('end', () => callback(null, res, body))
134
+ })
135
+ req.on('error', error => callback(error, null, null))
136
+ }
137
+
138
+ req.end()
139
+ return this
140
+ }
141
+ }
142
+
143
+ // Adds the query string to the path.
144
+ fullPath (p) {
145
+ const search = qs.stringify(this.options.query)
146
+ let full = this.join(p)
147
+ if (search.length > 0) { full += `?${search}` }
148
+ return full
149
+ }
150
+
151
+ scope (url, options, callback) {
152
+ const override = this.buildOptions(url, options)
153
+ const scoped = new ScopedClient(this.options)
154
+ .protocol(override.protocol)
155
+ .host(override.hostname)
156
+ .path(override.pathname)
157
+
158
+ if (typeof (url) === 'function') {
159
+ callback = url
160
+ } else if (typeof (options) === 'function') {
161
+ callback = options
162
+ }
163
+ if (callback) { callback(scoped) }
164
+ return scoped
165
+ }
166
+
167
+ join (suffix) {
168
+ const p = this.options.pathname || '/'
169
+ if (suffix && (suffix.length > 0)) {
170
+ if (suffix.match(/^\//)) {
171
+ return suffix
172
+ } else {
173
+ return path.join(p, suffix)
174
+ }
175
+ } else {
176
+ return p
177
+ }
178
+ }
179
+
180
+ path (p) {
181
+ this.options.pathname = this.join(p)
182
+ return this
183
+ }
184
+
185
+ query (key, value) {
186
+ if (!this.options.query) { this.options.query = {} }
187
+ if (typeof (key) === 'string') {
188
+ if (value) {
189
+ this.options.query[key] = value
190
+ } else {
191
+ delete this.options.query[key]
192
+ }
193
+ } else {
194
+ extend(this.options.query, key)
195
+ }
196
+ return this
197
+ }
198
+
199
+ host (h) {
200
+ if (h && (h.length > 0)) { this.options.hostname = h }
201
+ return this
202
+ }
203
+
204
+ port (p) {
205
+ if (p && ((typeof (p) === 'number') || (p.length > 0))) {
206
+ this.options.port = p
207
+ }
208
+ return this
209
+ }
210
+
211
+ protocol (p) {
212
+ if (p && (p.length > 0)) { this.options.protocol = p }
213
+ return this
214
+ }
215
+
216
+ encoding (e) {
217
+ if (e == null) { e = 'utf-8' }
218
+ this.options.encoding = e
219
+ return this
220
+ }
221
+
222
+ timeout (time) {
223
+ this.options.timeout = time
224
+ return this
225
+ }
226
+
227
+ auth (user, pass) {
228
+ if (!user) {
229
+ this.options.auth = null
230
+ } else if (!pass && user.match(/:/)) {
231
+ this.options.auth = user
232
+ } else {
233
+ this.options.auth = `${user}:${pass}`
234
+ }
235
+ return this
236
+ }
237
+
238
+ header (name, value) {
239
+ this.options.headers[name] = value
240
+ return this
241
+ }
242
+
243
+ headers (h) {
244
+ extend(this.options.headers, h)
245
+ return this
246
+ }
247
+
248
+ buildOptions () {
249
+ const options = {}
250
+ let i = 0
251
+ while (arguments[i]) {
252
+ const ty = typeof arguments[i]
253
+ if (ty === 'string') {
254
+ const parsedUrl = new URL(arguments[i])
255
+ const query = {}
256
+ parsedUrl.searchParams.forEach((v, k) => {
257
+ query[k] = v
258
+ })
259
+
260
+ extend(options, {
261
+ href: parsedUrl.href,
262
+ origin: parsedUrl.origin,
263
+ protocol: parsedUrl.protocol,
264
+ username: parsedUrl.username,
265
+ password: parsedUrl.password,
266
+ host: parsedUrl.host,
267
+ hostname: parsedUrl.hostname,
268
+ port: parsedUrl.port,
269
+ pathname: parsedUrl.pathname,
270
+ search: parsedUrl.search,
271
+ searchParams: parsedUrl.searchParams,
272
+ query,
273
+ hash: parsedUrl.hash
274
+ })
275
+ delete options.url
276
+ delete options.href
277
+ delete options.search
278
+ } else if (ty !== 'function') {
279
+ extend(options, arguments[i])
280
+ }
281
+ i += 1
282
+ }
283
+ if (!options.headers) { options.headers = {} }
284
+ if (options.encoding == null) { options.encoding = 'utf-8' }
285
+ return options
286
+ }
287
+ }
288
+
289
+ ScopedClient.methods = ['GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'HEAD']
290
+ ScopedClient.methods.forEach(method => {
291
+ ScopedClient.prototype[method.toLowerCase()] = function (body, callback) { return this.request(method, body, callback) }
292
+ })
293
+ ScopedClient.prototype.del = ScopedClient.prototype.delete
294
+
295
+ ScopedClient.defaultPort = { 'http:': 80, 'https:': 443, http: 80, https: 443 }
296
+
297
+ const extend = function (a, b) {
298
+ Object.keys(b).forEach(prop => {
299
+ a[prop] = b[prop]
300
+ })
301
+ return a
302
+ }
303
+
304
+ // Removes keys specified in second parameter from first parameter
305
+ const reduce = function (a, b) {
306
+ for (const propName of Array.from(b)) {
307
+ delete a[propName]
308
+ }
309
+ return a
310
+ }
311
+
312
+ exports.create = (url, options) => new ScopedClient(url, options)