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 CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2017 GitHub Inc.
1
+ Copyright (c) 2011-2023 GitHub Inc.
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
package/README.md CHANGED
@@ -1,3 +1,5 @@
1
+ ![Pipeline Status](https://github.com/hubotio/hubot/actions/workflows/pipeline.yml/badge.svg)
2
+
1
3
  ![Build Status: MacOS](https://github.com/hubotio/hubot/actions/workflows/nodejs-macos.yml/badge.svg)
2
4
  ![Build Status: Ubuntu](https://github.com/hubotio/hubot/actions/workflows/nodejs-ubuntu.yml/badge.svg)
3
5
  ![Build Status: Window](https://github.com/hubotio/hubot/actions/workflows/nodejs-windows.yml/badge.svg)
@@ -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](http://hubot.github.com/docs/#scripts) and can work
9
- on [many different chat services](https://hubot.github.com/docs/adapters/).
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](http://hubot.github.com/docs)
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": "9.1.0",
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": [
@@ -0,0 +1,11 @@
1
+ export default () => {
2
+ return {
3
+ model (file, model) {
4
+ return {
5
+ base: {
6
+ href: '/hubot/'
7
+ }
8
+ }
9
+ }
10
+ }
11
+ }
@@ -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
- this.buildCli()
41
- try {
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
- close () {
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.cli.removeAllListeners()
59
- this.cli.close()
60
- }
61
-
62
- buildCli () {
63
- this.cli = cline()
64
-
65
- this.cli.command('*', async input => {
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
- this.cli.command('history', () => {
77
- Array.from(this.cli.history()).map(item => console.log(item))
78
- })
79
-
80
- this.cli.on('history', item => {
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
- async #loadHistory () {
115
- if (!fs.existsSync(historyPath)) {
116
- return new Error('No history available')
106
+ close () {
107
+ super.close()
108
+ if (this.#rl?.close) {
109
+ this.#rl.close()
117
110
  }
118
- const instream = fs.createReadStream(historyPath)
119
- const outstream = new Stream()
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 nothing.
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
- try {
449
- this.server = app.listen(port, address)
450
- this.router = app
451
- } catch (error) {
452
- this.logger.error(`Error trying to start HTTP server: ${error}\n${error.stack}`)
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
- }, 5 * 60 * 1000)
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