hubot 9.1.0 → 10.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/LICENSE.md +1 -1
- package/README.md +10 -5
- package/package.json +4 -3
- package/sfab-hooks/SfabHook.mjs +11 -0
- package/src/adapters/shell.js +68 -91
- package/src/robot.js +34 -28
package/LICENSE.md
CHANGED
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+

|
|
2
|
+
|
|
1
3
|

|
|
2
4
|

|
|
3
5
|

|
|
@@ -5,11 +7,11 @@
|
|
|
5
7
|
# Hubot
|
|
6
8
|
|
|
7
9
|
Hubot is a framework to build chat bots, modeled after GitHub's Campfire bot of the same name, hubot.
|
|
8
|
-
He's pretty cool. He's [extendable with scripts](
|
|
9
|
-
on [many different chat services](https://
|
|
10
|
+
He's pretty cool. He's [extendable with scripts](https://hubotio.github.io/hubot/docs#scripts) and can work
|
|
11
|
+
on [many different chat services](https://hubotio.github.io/hubot/adapters.html).
|
|
10
12
|
|
|
11
13
|
This repository provides a library that's distributed by `npm` that you
|
|
12
|
-
use for building your own bots. See the [documentation](
|
|
14
|
+
use for building your own bots. See the [documentation](https://hubotio.github.io/hubot/docs.html)
|
|
13
15
|
for details on getting up and running with your very own robot friend.
|
|
14
16
|
|
|
15
17
|
In most cases, you'll probably never have to hack on this repo directly if you
|
|
@@ -17,10 +19,13 @@ are building your own bot. But if you do, check out [CONTRIBUTING.md](CONTRIBUTI
|
|
|
17
19
|
|
|
18
20
|
# Create your own Hubot instance
|
|
19
21
|
|
|
22
|
+
This will create a directory called `myhubot` in the current working directory.
|
|
23
|
+
|
|
20
24
|
```sh
|
|
21
|
-
mkdir myhubot
|
|
22
|
-
cd myhubot
|
|
23
25
|
npx hubot --create myhubot --adapter @hubot-friends/hubot-slack
|
|
26
|
+
npx hubot --create myhubot --adapter @hubot-friends/hubot-discord
|
|
27
|
+
npx hubot --create myhubot --adapter @hubot-friends/hubot-ms-teams
|
|
28
|
+
npx hubot --create myhubot --adapter @hubot-friends/hubot-irc
|
|
24
29
|
```
|
|
25
30
|
|
|
26
31
|
Review `scripts/example.mjs`. Create more scripts in the `scripts` folder.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hubot",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
4
4
|
"author": "hubot",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"github",
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
"url": "https://github.com/hubotio/hubot.git"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"cline": "^0.8.2",
|
|
19
18
|
"coffeescript": "^2.7.0",
|
|
20
19
|
"connect-multiparty": "^2.2.0",
|
|
21
20
|
"express": "^4.18.2",
|
|
@@ -40,7 +39,9 @@
|
|
|
40
39
|
"pretest": "standard",
|
|
41
40
|
"test": "node --test",
|
|
42
41
|
"test:smoke": "node src/**/*.js",
|
|
43
|
-
"test:e2e": "bin/e2e-test.sh"
|
|
42
|
+
"test:e2e": "bin/e2e-test.sh",
|
|
43
|
+
"build:local": "npx @hubot-friends/sfab --folder ./docs --destination ./_site --verbose --serve /hubot/ --watch-path ./docs --scripts ./sfab-hooks",
|
|
44
|
+
"build": "npx @hubot-friends/sfab --folder ./docs --destination ./_site --verbose --scripts ./sfab-hooks"
|
|
44
45
|
},
|
|
45
46
|
"release": {
|
|
46
47
|
"branches": [
|
package/src/adapters/shell.js
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const fs = require('fs')
|
|
4
|
-
const readline = require('readline')
|
|
5
|
-
const Stream = require('stream')
|
|
6
|
-
const cline = require('cline')
|
|
7
|
-
|
|
4
|
+
const readline = require('node:readline')
|
|
8
5
|
const Adapter = require('../adapter')
|
|
9
|
-
|
|
10
|
-
const _require = require('../message')
|
|
11
|
-
|
|
12
|
-
const TextMessage = _require.TextMessage
|
|
6
|
+
const { TextMessage } = require('../message')
|
|
13
7
|
|
|
14
8
|
const historySize = process.env.HUBOT_SHELL_HISTSIZE != null ? parseInt(process.env.HUBOT_SHELL_HISTSIZE) : 1024
|
|
15
|
-
|
|
16
9
|
const historyPath = '.hubot_history'
|
|
10
|
+
|
|
11
|
+
const completer = line => {
|
|
12
|
+
const completions = '\\q exit \\? help \\c clear'.split(' ')
|
|
13
|
+
const hits = completions.filter((c) => c.startsWith(line))
|
|
14
|
+
// Show all completions if none found
|
|
15
|
+
return [hits.length ? hits : completions, line]
|
|
16
|
+
}
|
|
17
|
+
const showHelp = () => {
|
|
18
|
+
console.log('usage:')
|
|
19
|
+
console.log('\\q, exit - close shell and exit')
|
|
20
|
+
console.log('\\?, help - show this help')
|
|
21
|
+
console.log('\\c, clear - clear screen')
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
const bold = str => `\x1b[1m${str}\x1b[22m`
|
|
18
25
|
|
|
19
26
|
class Shell extends Adapter {
|
|
@@ -37,32 +44,45 @@ class Shell extends Adapter {
|
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
async run () {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const { readlineInterface, history } = await this.#loadHistory()
|
|
43
|
-
this.cli.history(history)
|
|
44
|
-
this.cli.interact(`${this.robot.name ?? this.robot.alias}> `)
|
|
45
|
-
this.#rl = readlineInterface
|
|
46
|
-
this.emit('connected', this)
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.log(error)
|
|
47
|
+
if (!fs.existsSync(historyPath)) {
|
|
48
|
+
fs.writeFileSync(historyPath, '')
|
|
49
49
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
super.close()
|
|
54
|
-
// Getting an error message on GitHubt Actions: error: 'this[#rl].close is not a function'
|
|
55
|
-
if (this.#rl?.close) {
|
|
56
|
-
this.#rl.close()
|
|
50
|
+
const stats = fs.statSync(historyPath)
|
|
51
|
+
if (stats.size > historySize) {
|
|
52
|
+
fs.unlinkSync(historyPath)
|
|
57
53
|
}
|
|
58
|
-
this.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
this.#rl = readline.createInterface({
|
|
55
|
+
input: process.stdin,
|
|
56
|
+
output: process.stdout,
|
|
57
|
+
prompt: `${this.robot.name ?? this.robot.alias}> `,
|
|
58
|
+
completer
|
|
59
|
+
})
|
|
60
|
+
this.#rl.on('line', async (line) => {
|
|
61
|
+
const input = line.trim()
|
|
62
|
+
switch (input) {
|
|
63
|
+
case '\\q':
|
|
64
|
+
case 'exit':
|
|
65
|
+
this.#rl.close()
|
|
66
|
+
break
|
|
67
|
+
case '\\?':
|
|
68
|
+
case 'help':
|
|
69
|
+
showHelp()
|
|
70
|
+
break
|
|
71
|
+
case '\\c':
|
|
72
|
+
case 'clear':
|
|
73
|
+
this.#rl.write(null, { ctrl: true, name: 'l' })
|
|
74
|
+
this.#rl.prompt()
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
const history = fs.readFileSync(historyPath, 'utf-8').split('\n').reverse()
|
|
78
|
+
this.#rl.history = history
|
|
79
|
+
this.#rl.on('line', line => {
|
|
80
|
+
const input = line.trim()
|
|
81
|
+
if (input.length === 0) return
|
|
82
|
+
fs.appendFile(historyPath, `${input}\n`, err => {
|
|
83
|
+
if (err) console.error(err)
|
|
84
|
+
})
|
|
85
|
+
})
|
|
66
86
|
let userId = process.env.HUBOT_SHELL_USER_ID || '1'
|
|
67
87
|
if (userId.match(/A\d+z/)) {
|
|
68
88
|
userId = parseInt(userId)
|
|
@@ -71,68 +91,25 @@ class Shell extends Adapter {
|
|
|
71
91
|
const userName = process.env.HUBOT_SHELL_USER_NAME || 'Shell'
|
|
72
92
|
const user = this.robot.brain.userForId(userId, { name: userName, room: 'Shell' })
|
|
73
93
|
await this.receive(new TextMessage(user, input, 'messageId'))
|
|
94
|
+
this.#rl.prompt()
|
|
95
|
+
}).on('close', () => {
|
|
96
|
+
process.exit(0)
|
|
74
97
|
})
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (item.length > 0 && item !== 'exit' && item !== 'history') {
|
|
82
|
-
fs.appendFile(historyPath, `${item}\n`, error => {
|
|
83
|
-
if (error) {
|
|
84
|
-
this.robot.emit('error', error)
|
|
85
|
-
}
|
|
86
|
-
})
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
this.cli.on('close', () => {
|
|
91
|
-
let history, i, item, len
|
|
92
|
-
|
|
93
|
-
history = this.cli.history()
|
|
94
|
-
|
|
95
|
-
if (history.length <= historySize) {
|
|
96
|
-
return
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const startIndex = history.length - historySize
|
|
100
|
-
history = history.reverse().splice(startIndex, historySize)
|
|
101
|
-
const fileOpts = {
|
|
102
|
-
mode: 0x180
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const outstream = fs.createWriteStream(historyPath, fileOpts)
|
|
106
|
-
for (i = 0, len = history.length; i < len; i++) {
|
|
107
|
-
item = history[i]
|
|
108
|
-
outstream.write(item + '\n')
|
|
109
|
-
}
|
|
110
|
-
outstream.end()
|
|
111
|
-
})
|
|
98
|
+
try {
|
|
99
|
+
this.#rl.prompt()
|
|
100
|
+
this.emit('connected', this)
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.log(error)
|
|
103
|
+
}
|
|
112
104
|
}
|
|
113
105
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
106
|
+
close () {
|
|
107
|
+
super.close()
|
|
108
|
+
if (this.#rl?.close) {
|
|
109
|
+
this.#rl.close()
|
|
117
110
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
outstream.readable = true
|
|
121
|
-
outstream.writable = true
|
|
122
|
-
const history = []
|
|
123
|
-
const readlineInterface = readline.createInterface({ input: instream, output: outstream, terminal: false })
|
|
124
|
-
return new Promise((resolve, reject) => {
|
|
125
|
-
readlineInterface.on('line', line => {
|
|
126
|
-
line = line.trim()
|
|
127
|
-
if (line.length > 0) {
|
|
128
|
-
history.push(line)
|
|
129
|
-
}
|
|
130
|
-
})
|
|
131
|
-
readlineInterface.on('close', () => {
|
|
132
|
-
resolve({ readlineInterface, history })
|
|
133
|
-
})
|
|
134
|
-
readlineInterface.on('error', reject)
|
|
135
|
-
})
|
|
111
|
+
this.cli.removeAllListeners()
|
|
112
|
+
this.cli.close()
|
|
136
113
|
}
|
|
137
114
|
}
|
|
138
115
|
|
package/src/robot.js
CHANGED
|
@@ -37,6 +37,7 @@ class Robot {
|
|
|
37
37
|
this.brain = new Brain(this)
|
|
38
38
|
this.alias = alias
|
|
39
39
|
this.adapter = null
|
|
40
|
+
this.shouldEnableHttpd = httpd ?? true
|
|
40
41
|
this.datastore = null
|
|
41
42
|
this.Response = Response
|
|
42
43
|
this.commands = []
|
|
@@ -55,18 +56,13 @@ class Robot {
|
|
|
55
56
|
this.globalHttpOptions = {}
|
|
56
57
|
|
|
57
58
|
this.parseVersion()
|
|
58
|
-
if (httpd) {
|
|
59
|
-
this.setupExpress()
|
|
60
|
-
} else {
|
|
61
|
-
this.setupNullRouter()
|
|
62
|
-
}
|
|
63
|
-
|
|
64
59
|
this.adapterName = adapter ?? 'shell'
|
|
65
60
|
this.errorHandlers = []
|
|
66
61
|
|
|
67
62
|
this.on('error', (err, res) => {
|
|
68
63
|
return this.invokeErrorHandlers(err, res)
|
|
69
64
|
})
|
|
65
|
+
this.on('listening', this.herokuKeepalive.bind(this))
|
|
70
66
|
}
|
|
71
67
|
|
|
72
68
|
// Public: Adds a custom Listener with the provided matcher, options, and
|
|
@@ -407,8 +403,8 @@ class Robot {
|
|
|
407
403
|
|
|
408
404
|
// Setup the Express server's defaults.
|
|
409
405
|
//
|
|
410
|
-
// Returns
|
|
411
|
-
setupExpress () {
|
|
406
|
+
// Returns Server.
|
|
407
|
+
async setupExpress () {
|
|
412
408
|
const user = process.env.EXPRESS_USER
|
|
413
409
|
const pass = process.env.EXPRESS_PASSWORD
|
|
414
410
|
const stat = process.env.EXPRESS_STATIC
|
|
@@ -444,27 +440,18 @@ class Robot {
|
|
|
444
440
|
if (stat) {
|
|
445
441
|
app.use(express.static(stat))
|
|
446
442
|
}
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
throw error
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
let herokuUrl = process.env.HEROKU_URL
|
|
457
|
-
|
|
458
|
-
if (herokuUrl) {
|
|
459
|
-
if (!/\/$/.test(herokuUrl)) {
|
|
460
|
-
herokuUrl += '/'
|
|
461
|
-
}
|
|
462
|
-
this.pingIntervalId = setInterval(() => {
|
|
463
|
-
HttpClient.create(`${herokuUrl}hubot/ping`).post()((_err, res, body) => {
|
|
464
|
-
this.logger.info('keep alive ping!')
|
|
443
|
+
const p = new Promise((resolve, reject) => {
|
|
444
|
+
try {
|
|
445
|
+
this.server = app.listen(port, address, () => {
|
|
446
|
+
this.router = app
|
|
447
|
+
this.emit('listening', this.server)
|
|
448
|
+
resolve(this.server)
|
|
465
449
|
})
|
|
466
|
-
}
|
|
467
|
-
|
|
450
|
+
} catch (err) {
|
|
451
|
+
reject(err)
|
|
452
|
+
}
|
|
453
|
+
})
|
|
454
|
+
return p
|
|
468
455
|
}
|
|
469
456
|
|
|
470
457
|
// Setup an empty router object
|
|
@@ -645,6 +632,11 @@ class Robot {
|
|
|
645
632
|
//
|
|
646
633
|
// Returns whatever the adapter returns.
|
|
647
634
|
async run () {
|
|
635
|
+
if (this.shouldEnableHttpd) {
|
|
636
|
+
await this.setupExpress()
|
|
637
|
+
} else {
|
|
638
|
+
this.setupNullRouter()
|
|
639
|
+
}
|
|
648
640
|
this.emit('running')
|
|
649
641
|
|
|
650
642
|
return await this.adapter.run()
|
|
@@ -712,6 +704,20 @@ class Robot {
|
|
|
712
704
|
|
|
713
705
|
return HttpClient.create(url, httpOptions).header('User-Agent', `Hubot/${this.version}`)
|
|
714
706
|
}
|
|
707
|
+
|
|
708
|
+
herokuKeepalive (server) {
|
|
709
|
+
let herokuUrl = process.env.HEROKU_URL
|
|
710
|
+
if (herokuUrl) {
|
|
711
|
+
if (!/\/$/.test(herokuUrl)) {
|
|
712
|
+
herokuUrl += '/'
|
|
713
|
+
}
|
|
714
|
+
this.pingIntervalId = setInterval(() => {
|
|
715
|
+
HttpClient.create(`${herokuUrl}hubot/ping`).post()((_err, res, body) => {
|
|
716
|
+
this.logger.info('keep alive ping!')
|
|
717
|
+
})
|
|
718
|
+
}, 5 * 60 * 1000)
|
|
719
|
+
}
|
|
720
|
+
}
|
|
715
721
|
}
|
|
716
722
|
|
|
717
723
|
module.exports = Robot
|