1 |
|
2 |
|
3 | const inspect = require('util').inspect
|
4 |
|
5 | const TextMessage = require('./message').TextMessage
|
6 | const Middleware = require('./middleware')
|
7 |
|
8 | class Listener {
|
9 | // Listeners receive every message from the chat source and decide if they
|
10 | // want to act on it.
|
11 | // An identifier should be provided in the options parameter to uniquely
|
12 | // identify the listener (options.id).
|
13 | //
|
14 | // robot - A Robot instance.
|
15 | // matcher - A Function that determines if this listener should trigger the
|
16 | // callback.
|
17 | // options - An Object of additional parameters keyed on extension name
|
18 | // (optional).
|
19 | // callback - A Function that is triggered if the incoming message matches.
|
20 | constructor (robot, matcher, options, callback) {
|
21 | this.robot = robot
|
22 | this.matcher = matcher
|
23 | this.options = options
|
24 | this.callback = callback
|
25 |
|
26 | if (this.matcher == null) {
|
27 | throw new Error('Missing a matcher for Listener')
|
28 | }
|
29 |
|
30 | if (this.callback == null) {
|
31 | this.callback = this.options
|
32 | this.options = {}
|
33 | }
|
34 |
|
35 | if (this.options.id == null) {
|
36 | this.options.id = null
|
37 | }
|
38 |
|
39 | if (this.callback == null || typeof this.callback !== 'function') {
|
40 | throw new Error('Missing a callback for Listener')
|
41 | }
|
42 | }
|
43 |
|
44 | // Public: Determines if the listener likes the content of the message. If
|
45 | // so, a Response built from the given Message is passed through all
|
46 | // registered middleware and potentially the Listener callback. Note that
|
47 | // middleware can intercept the message and prevent the callback from ever
|
48 | // being executed.
|
49 | //
|
50 | // message - A Message instance.
|
51 | // middleware - Optional Middleware object to execute before the Listener callback
|
52 | // callback - Optional Function called with a boolean of whether the matcher matched
|
53 | //
|
54 | // Returns a boolean of whether the matcher matched.
|
55 | // Returns before executing callback
|
56 | call (message, middleware, didMatchCallback) {
|
57 | // middleware argument is optional
|
58 | if (didMatchCallback == null && typeof middleware === 'function') {
|
59 | didMatchCallback = middleware
|
60 | middleware = undefined
|
61 | }
|
62 |
|
63 | // ensure we have a Middleware object
|
64 | if (middleware == null) {
|
65 | middleware = new Middleware(this.robot)
|
66 | }
|
67 |
|
68 | const match = this.matcher(message)
|
69 | if (match) {
|
70 | if (this.regex) {
|
71 | this.robot.logger.debug(`Message '${message}' matched regex /${inspect(this.regex)}/; listener.options = ${inspect(this.options)}`)
|
72 | }
|
73 |
|
74 | // special middleware-like function that always executes the Listener's
|
75 | // callback and calls done (never calls 'next')
|
76 | const executeListener = (context, done) => {
|
77 | this.robot.logger.debug(`Executing listener callback for Message '${message}'`)
|
78 | try {
|
79 | this.callback(context.response)
|
80 | } catch (err) {
|
81 | this.robot.emit('error', err, context.response)
|
82 | }
|
83 | done()
|
84 | }
|
85 |
|
86 | // When everything is finished (down the middleware stack and back up),
|
87 | // pass control back to the robot
|
88 | const allDone = function allDone () {
|
89 | // Yes, we tried to execute the listener callback (middleware may
|
90 | // have intercepted before actually executing though)
|
91 | if (didMatchCallback != null) {
|
92 | process.nextTick(() => didMatchCallback(true))
|
93 | }
|
94 | }
|
95 |
|
96 | const response = new this.robot.Response(this.robot, message, match)
|
97 | middleware.execute({ listener: this, response }, executeListener, allDone)
|
98 | return true
|
99 | } else {
|
100 | if (didMatchCallback != null) {
|
101 | // No, we didn't try to execute the listener callback
|
102 | process.nextTick(() => didMatchCallback(false))
|
103 | }
|
104 | return false
|
105 | }
|
106 | }
|
107 | }
|
108 |
|
109 | class TextListener extends Listener {
|
110 | // TextListeners receive every message from the chat source and decide if they
|
111 | // want to act on it.
|
112 | //
|
113 | // robot - A Robot instance.
|
114 | // regex - A Regex that determines if this listener should trigger the
|
115 | // callback.
|
116 | // options - An Object of additional parameters keyed on extension name
|
117 | // (optional).
|
118 | // callback - A Function that is triggered if the incoming message matches.
|
119 | constructor (robot, regex, options, callback) {
|
120 | function matcher (message) {
|
121 | if (message instanceof TextMessage) {
|
122 | return message.match(regex)
|
123 | }
|
124 | }
|
125 |
|
126 | super(robot, matcher, options, callback)
|
127 | this.regex = regex
|
128 | }
|
129 | }
|
130 |
|
131 | module.exports = {
|
132 | Listener,
|
133 | TextListener
|
134 | }
|