@hubot-friends/hubot-slack 0.0.0-development

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.
Files changed (72) hide show
  1. package/.github/CODE_OF_CONDUCT.md +11 -0
  2. package/.github/contributing.md +60 -0
  3. package/.github/issue_template.md +48 -0
  4. package/.github/maintainers_guide.md +91 -0
  5. package/.github/pull_request_template.md +8 -0
  6. package/.github/workflows/ci-build.yml +74 -0
  7. package/LICENSE +22 -0
  8. package/README.md +29 -0
  9. package/docs/Gemfile +2 -0
  10. package/docs/README.md +15 -0
  11. package/docs/_config.yml +29 -0
  12. package/docs/_includes/analytics.html +7 -0
  13. package/docs/_includes/head.html +33 -0
  14. package/docs/_includes/page_header.html +20 -0
  15. package/docs/_includes/side_nav.html +22 -0
  16. package/docs/_includes/tag_manager.html +13 -0
  17. package/docs/_layouts/changelog.html +15 -0
  18. package/docs/_layouts/default.html +47 -0
  19. package/docs/_layouts/page.html +10 -0
  20. package/docs/_pages/FAQ.md +63 -0
  21. package/docs/_pages/about.md +16 -0
  22. package/docs/_pages/advanced_usage.md +102 -0
  23. package/docs/_pages/auth.md +44 -0
  24. package/docs/_pages/basic_usage.md +302 -0
  25. package/docs/_pages/changelog.html +17 -0
  26. package/docs/_pages/upgrading.md +49 -0
  27. package/docs/_posts/2016-07-15-v4.0.0.md +14 -0
  28. package/docs/_posts/2016-07-19-v4.0.1.md +5 -0
  29. package/docs/_posts/2016-08-03-v4.0.2.md +4 -0
  30. package/docs/_posts/2016-09-12-v4.0.3.md +8 -0
  31. package/docs/_posts/2016-09-12-v4.0.4.md +4 -0
  32. package/docs/_posts/2016-09-14-v4.0.5.md +4 -0
  33. package/docs/_posts/2016-09-21-v4.1.0.md +4 -0
  34. package/docs/_posts/2016-10-12-v4.2.0.md +4 -0
  35. package/docs/_posts/2016-10-12-v4.2.1.md +4 -0
  36. package/docs/_posts/2016-11-05-v4.2.2.md +8 -0
  37. package/docs/_posts/2017-01-05-v4.3.0.md +5 -0
  38. package/docs/_posts/2017-01-09-v4.3.1.md +5 -0
  39. package/docs/_posts/2017-02-15-v4.3.2.md +5 -0
  40. package/docs/_posts/2017-02-17-v4.3.3.md +4 -0
  41. package/docs/_posts/2017-03-29-v4.3.4.md +5 -0
  42. package/docs/_posts/2017-08-24-v4.4.0.md +7 -0
  43. package/docs/_posts/2018-06-08-v4.5.0.md +13 -0
  44. package/docs/_posts/2018-06-14-v4.5.1.md +4 -0
  45. package/docs/_posts/2018-07-03-v4.5.2.md +5 -0
  46. package/docs/_posts/2018-07-17-v4.5.3.md +12 -0
  47. package/docs/_posts/2018-08-10-v4.5.4.md +7 -0
  48. package/docs/_posts/2018-10-01-v4.5.5.md +7 -0
  49. package/docs/_posts/2018-12-21-v4.6.0.md +6 -0
  50. package/docs/_posts/2019-04-29-v4.7.0.md +6 -0
  51. package/docs/_posts/2019-04-30-v4.7.1.md +5 -0
  52. package/docs/_posts/2020-04-03-v4.7.2.md +6 -0
  53. package/docs/_posts/2020-05-19-v4.8.0.md +10 -0
  54. package/docs/_posts/2020-10-19-v4.8.1.md +7 -0
  55. package/docs/_posts/2021-01-26-v4.9.0.md +8 -0
  56. package/docs/_posts/2022-01-12-v4.10.0.md +8 -0
  57. package/docs/index.md +93 -0
  58. package/docs/styles/docs.css +37 -0
  59. package/package.json +50 -0
  60. package/slack.js +20 -0
  61. package/src/SlackAdapter.mjs +302 -0
  62. package/src/SlackAdapter.test.mjs +342 -0
  63. package/src/bot.js +526 -0
  64. package/src/client.js +445 -0
  65. package/src/extensions.js +82 -0
  66. package/src/mention.js +15 -0
  67. package/src/message.js +307 -0
  68. package/src/testing.mjs +24 -0
  69. package/test/bot.js +769 -0
  70. package/test/client.js +446 -0
  71. package/test/message.js +329 -0
  72. package/test/stubs.js +388 -0
package/src/client.js ADDED
@@ -0,0 +1,445 @@
1
+ const EventEmitter = require('events');
2
+ const SocketModeClient = require('@slack/socket-mode').SocketModeClient;
3
+ const WebClient = require('@slack/web-api').WebClient;
4
+
5
+ class SlackClient extends EventEmitter {
6
+ /**
7
+ * Number of milliseconds which the information returned by `conversations.info` is considered to be valid. The default
8
+ * value is 5 minutes, and it can be customized by setting the `HUBOT_SLACK_CONVERSATION_CACHE_TTL_MS` environment
9
+ * variable. Setting this number higher will reduce the number of requests made to the Web API, which may be helpful if
10
+ * your Hubot is experiencing rate limiting errors. However, setting this number too high will result in stale data
11
+ * being referenced, and your scripts may experience errors related to channel info (like the name) being incorrect
12
+ * after a user changes it in Slack.
13
+ * @private
14
+ */
15
+ static CONVERSATION_CACHE_TTL_MS =
16
+ process.env.HUBOT_SLACK_CONVERSATION_CACHE_TTL_MS
17
+ ? parseInt(process.env.HUBOT_SLACK_CONVERSATION_CACHE_TTL_MS, 10)
18
+ : (5 * 60 * 1000);
19
+
20
+ /**
21
+ * @constructor
22
+ * @param {Object} options - Configuration options for this SlackClient instance
23
+ * @param {string} options.token - Slack API token for authentication
24
+ * @param {string} options.apiPageSize - Number used for limit when making paginated requests to Slack Web API list methods
25
+ * @param {Object} [options.socketModeOptions={}] - Configuration options for owned SocketModeClient instance
26
+ * @param {Robot} robot - Hubot robot instance
27
+ */
28
+ constructor(options, robot) {
29
+ super();
30
+ this.robot = robot;
31
+ this.socket = new SocketModeClient({ appToken: options.appToken, ...options.socketModeOptions });
32
+ this.web = new WebClient(options.botToken, {
33
+ logger: robot.logger,
34
+ logLevel: options.logLevel ?? 'info',
35
+ maxRequestConcurrency: options?.maxRequestConcurrency ?? 1,
36
+ retryConfig: options?.retryConfig,
37
+ agent: options?.agent,
38
+ tls: options?.tls,
39
+ timeout: options?.timeout,
40
+ rejectRateLimitedCalls: options?.rejectRateLimitedCalls,
41
+ headers: options?.headers,
42
+ teamId: options?.teamId
43
+ });
44
+
45
+ this.apiPageSize = 100;
46
+ if (!isNaN(options.apiPageSize)) {
47
+ this.apiPageSize = parseInt(options.apiPageSize, 10);
48
+ }
49
+
50
+ this.robot.logger.debug(`SocketModeClient initialized with options: ${JSON.stringify(options?.socketModeOptions)}`);
51
+
52
+ // Map to convert bot user IDs (BXXXXXXXX) to user representations for events from custom
53
+ // integrations and apps without a bot user
54
+ this.botUserIdMap = {
55
+ "B01": { id: "B01", user_id: "USLACKBOT" }
56
+ };
57
+
58
+ // Map to convert conversation IDs to conversation representations
59
+ this.channelData = {};
60
+
61
+ // Event handling
62
+ // NOTE: add channel join and leave events
63
+ this.socket.on('authenticated', this.eventWrapper, this);
64
+ this.socket.on("message", this.eventWrapper, this);
65
+ this.socket.on("reaction_added", this.eventWrapper, this);
66
+ this.socket.on("reaction_removed", this.eventWrapper, this);
67
+ this.socket.on("member_joined_channel", this.eventWrapper, this);
68
+ this.socket.on("member_left_channel", this.eventWrapper, this);
69
+ this.socket.on("file_shared", this.eventWrapper, this);
70
+ this.socket.on("user_change", this.updateUserInBrain, this);
71
+ this.eventHandler = undefined;
72
+ }
73
+
74
+ /**
75
+ * Open connection to the Slack Socket
76
+ *
77
+ * @public
78
+ */
79
+ async connect() {
80
+ this.robot.logger.debug(`SocketModeClient#start()`);
81
+ let startResponse = null;
82
+ try {
83
+ startResponse = await this.socket.start()
84
+ this.robot.logger.info(startResponse);
85
+ const response = await this.web.auth.test();
86
+ this.robot.self = response.user;
87
+ this.robot.logger.info('Connected to Slack after starting socket client.');
88
+ // this.emit('connected');
89
+ } catch (e) {
90
+ this.robot.logger.error(`Error connecting to Slack: ${e.message}`);
91
+ this.emit('error', e);
92
+ }
93
+ return startResponse;
94
+ }
95
+
96
+ /**
97
+ * Set event handler
98
+ *
99
+ * @public
100
+ * @param {SlackClient~eventHandler} callback
101
+ */
102
+ onEvent(callback) {
103
+ if (this.eventHandler !== callback) {
104
+ return this.eventHandler = callback;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Disconnect from the Slack Socket
110
+ *
111
+ * @public
112
+ */
113
+ disconnect() {
114
+ this.socket.disconnect();
115
+ // NOTE: removal of event listeners possibly does not belong in disconnect, because they are not added in connect.
116
+ return this.socket.removeAllListeners();
117
+ }
118
+
119
+ /**
120
+ * Set a channel's topic
121
+ *
122
+ * @public
123
+ * @param {string} conversationId - Slack conversation ID
124
+ * @param {string} topic - new topic
125
+ */
126
+ async setTopic(conversationId, topic) {
127
+ this.robot.logger.debug(`SlackClient#setTopic() with topic ${topic}`);
128
+
129
+ // The `conversations.info` method is used to find out if this conversation can have a topic set
130
+ // NOTE: There's a performance cost to making this request, which can be avoided if instead the attempt to set the
131
+ // topic is made regardless of the conversation type. If the conversation type is not compatible, the call would
132
+ // fail, which is exactly the outcome in this implementation.
133
+ try {
134
+ const res = await this.web.conversations.info(conversationId)
135
+ const conversation = res.channel;
136
+ if (!conversation.is_im && !conversation.is_mpim) {
137
+ return await this.web.conversations.setTopic(conversationId, topic);
138
+ } else {
139
+ return this.robot.logger.debug(`Conversation ${conversationId} is a DM or MPDM. These conversation types do not have topics.`);
140
+ }
141
+ } catch (e) {
142
+ this.robot.logger.error(`Error setting topic in conversation ${conversationId}: ${e.message}`);
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Send a message to Slack using the Web API.
148
+ *
149
+ * This method is usually called when a Hubot script is sending a message in response to an incoming message. The
150
+ * response object has a `send()` method, which triggers execution of all response middleware, and ultimately calls
151
+ * `send()` on the Adapter. SlackBot, the adapter in this case, delegates that call to this method; once for every item
152
+ * (since its method signature is variadic). The `envelope` is created by the Hubot Response object.
153
+ *
154
+ * This method can also be called when a script directly calls `robot.send()` or `robot.adapter.send()`. That bypasses
155
+ * the execution of the response middleware and directly calls into SlackBot#send(). In this case, the `envelope`
156
+ * parameter is up to the script.
157
+ *
158
+ * The `envelope.room` property is intended to be a conversation ID. Even when that is not the case, this method will
159
+ * makes a reasonable attempt at sending the message. If the property is set to a public or private channel name, it
160
+ * will still work. When there's no `room` in the envelope, this method will fallback to the `id` property. That
161
+ * affordance allows scripts to use Hubot User objects, Slack users (as obtained from the response to `users.info`),
162
+ * and Slack conversations (as obtained from the response to `conversations.info`) as possible envelopes. In the first
163
+ * two cases, envelope.id` will contain a user ID (`Uxxx` or `Wxxx`). Since Hubot runs using a bot token (`xoxb`),
164
+ * passing a user ID as the `channel` argument to `chat.postMessage` (with `as_user=true`) results in a DM from the bot
165
+ * user (if `as_user=false` it would instead result in a DM from slackbot). Leaving `as_user=true` has no effect when
166
+ * the `channel` argument is a conversation ID.
167
+ *
168
+ * NOTE: This method no longer accepts `envelope.room` set to a user name. Using it in this manner will result in a
169
+ * `channel_not_found` error.
170
+ *
171
+ * @public
172
+ * @param {Object} envelope - a Hubot Response envelope
173
+ * @param {Message} [envelope.message] - the Hubot Message that was received and generated the Response which is now
174
+ * being used to send an outgoing message
175
+ * @param {User} [envelope.user] - the Hubot User object representing the user who sent `envelope.message`
176
+ * @param {string} [envelope.room] - a Slack conversation ID for where `envelope.message` was received, usually an
177
+ * alias of `envelope.user.room`
178
+ * @param {string} [envelope.id] - a Slack conversation ID similar to `envelope.room`
179
+ * @param {string|Object} message - the outgoing message to be sent, can be a simple string or a key/value object of
180
+ * optional arguments for the Slack Web API method `chat.postMessage`.
181
+ */
182
+ send(envelope, message) {
183
+ const room = envelope.room || envelope.id;
184
+ if ((room == null)) {
185
+ this.robot.logger.error("Cannot send message without a valid room. Envelopes should contain a room property set to " +
186
+ "a Slack conversation ID."
187
+ );
188
+ return;
189
+ }
190
+
191
+ this.robot.logger.debug(`SlackClient#send() room: ${room}, message: ${message}`);
192
+
193
+ const options = {
194
+ as_user: true,
195
+ link_names: 1,
196
+ // when the incoming message was inside a thread, send responses as replies to the thread
197
+ // NOTE: consider building a new (backwards-compatible) format for room which includes the thread_ts.
198
+ // e.g. "#{conversationId} #{thread_ts}" - this would allow a portable way to say the message is in a thread
199
+ thread_ts: (envelope.message != null ? envelope.message.thread_ts : undefined)
200
+ };
201
+
202
+ if (typeof message !== "string") {
203
+ return this.web.chat.postMessage({channel: room, text: message.text}, Object.assign(message, options))
204
+ .catch(error => {
205
+ return this.robot.logger.error(`SlackClient#send() error: ${error.message}`);
206
+ });
207
+ } else {
208
+ return this.web.chat.postMessage({channel: room, text: message.text}, options)
209
+ .catch(error => {
210
+ return this.robot.logger.error(`SlackClient#send() error: ${error.message}`);
211
+ });
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Fetch users from Slack API using pagination
217
+ *
218
+ * @public
219
+ * @param {SlackClient~usersCallback} callback
220
+ */
221
+ loadUsers(callback) {
222
+ // some properties of the real results are left out because they are not used
223
+ const combinedResults = { members: [] };
224
+ var pageLoaded = (error, results) => {
225
+ if (error) { return callback(error); }
226
+ // merge results into combined results
227
+ for (var member of results.members) { combinedResults.members.push(member); }
228
+
229
+ if(results?.response_metadata?.next_cursor) {
230
+ // fetch next page
231
+ return this.web.users.list({
232
+ limit: this.apiPageSize,
233
+ cursor: results.response_metadata.next_cursor
234
+ }, pageLoaded);
235
+ } else {
236
+ // pagination complete, run callback with results
237
+ return callback(null, combinedResults);
238
+ }
239
+ };
240
+ return this.web.users.list({ limit: this.apiPageSize }, pageLoaded);
241
+ }
242
+
243
+ /**
244
+ * Fetch user info from the brain. If not available, call users.info
245
+ * @public
246
+ */
247
+ async fetchUser(userId) {
248
+ // User exists in the brain - retrieve this representation
249
+ if (this.robot.brain.data.users[userId] != null) {
250
+ return Promise.resolve(this.robot.brain.data.users[userId]);
251
+ }
252
+
253
+ // User is not in brain - call users.info
254
+ // The user will be added to the brain in EventHandler
255
+ const r = await this.web.users.info({user: userId})
256
+ return this.updateUserInBrain(r.user);
257
+ }
258
+
259
+ /**
260
+ * Fetch bot user info from the bot -> user map
261
+ * @public
262
+ */
263
+ async fetchBotUser(botId) {
264
+ if (this.botUserIdMap[botId] != null) {
265
+ return Promise.resolve(this.botUserIdMap[botId]);
266
+ }
267
+
268
+ // Bot user is not in mapping - call bots.info
269
+ this.robot.logger.debug(`SlackClient#fetchBotUser() Calling bots.info API for bot_id: ${botId}`);
270
+ const r = await this.web.bots.info({bot: botId})
271
+ return r.bot;
272
+ }
273
+
274
+ /**
275
+ * Fetch conversation info from conversation map. If not available, call conversations.info
276
+ * @public
277
+ */
278
+ async fetchConversation(conversationId) {
279
+ // Current date minus time of expiration for conversation info
280
+ const expiration = Date.now() - SlackClient.CONVERSATION_CACHE_TTL_MS;
281
+
282
+ // Check whether conversation is held in client's channelData map and whether information is expired
283
+ if (((this.channelData[conversationId] != null ? this.channelData[conversationId].channel : undefined) != null) &&
284
+ (expiration < (this.channelData[conversationId] != null ? this.channelData[conversationId].updated : undefined))) { return Promise.resolve(this.channelData[conversationId].channel); }
285
+
286
+ // Delete data from map if it's expired
287
+ if (this.channelData[conversationId] != null) { delete this.channelData[conversationId]; }
288
+
289
+ // Return conversations.info promise
290
+ const r = await this.web.conversations.info(conversationId)
291
+ if (r.channel != null) {
292
+ this.channelData[conversationId] = {
293
+ channel: r.channel,
294
+ updated: Date.now()
295
+ };
296
+ }
297
+ return r.channel;
298
+ }
299
+
300
+ /**
301
+ * Will return a Hubot user object in Brain.
302
+ * User can represent a Slack human user or bot user
303
+ *
304
+ * The returned user from a message or reaction event is guaranteed to contain:
305
+ *
306
+ * id {String}: Slack user ID
307
+ * slack.is_bot {Boolean}: Flag indicating whether user is a bot
308
+ * name {String}: Slack username
309
+ * real_name {String}: Name of Slack user or bot
310
+ * room {String}: Slack channel ID for event (will be empty string if no channel in event)
311
+ *
312
+ * This may be called as a handler for `user_change` events or to update a
313
+ * a single user with its latest SlackUserInfo object.
314
+ *
315
+ * @private
316
+ * @param {SlackUserInfo|SlackUserChangeEvent} event_or_user - an object containing information about a Slack user
317
+ * that should be updated in the brain
318
+ */
319
+ updateUserInBrain(event_or_user) {
320
+ // if this method was invoked as a `user_change` event handler, unwrap the user from the event
321
+ let key, value;
322
+ const user = event_or_user.type === 'user_change' ? event_or_user.user : event_or_user;
323
+
324
+ // create a full representation of the user in the shape we persist for Hubot brain based on the parameter
325
+ // all top-level properties of the user are meant to be shared across adapters
326
+ const newUser = {
327
+ id: user.id,
328
+ name: user.name,
329
+ real_name: user.real_name,
330
+ slack: {}
331
+ };
332
+ // don't create keys for properties that have no value, because the empty value will become authoritative
333
+ if ((user.profile != null ? user.profile.email : undefined) != null) { newUser.email_address = user.profile.email; }
334
+ // all "non-standard" keys of a user are namespaced inside the slack property, so they don't interfere with other
335
+ // adapters (in case this hubot switched between adapters)
336
+ for (key in user) {
337
+ value = user[key];
338
+ newUser.slack[key] = value;
339
+ }
340
+
341
+ // merge any existing representation of this user already stored in the brain into the new representation
342
+ if (user.id in this.robot.brain.data.users) {
343
+ for (key in this.robot.brain.data.users[user.id]) {
344
+ // the merge strategy is to only copy over data for keys that do not exist in the new representation
345
+ // this means the entire `slack` property is treated as one value
346
+ value = this.robot.brain.data.users[user.id][key];
347
+ if (!(key in newUser)) {
348
+ newUser[key] = value;
349
+ }
350
+ }
351
+ }
352
+
353
+ // remove the existing representation and write the new representation to the brain
354
+ delete this.robot.brain.data.users[user.id];
355
+ return this.robot.brain.userForId(user.id, newUser);
356
+ }
357
+
358
+ /**
359
+ * Processes events to fetch additional data or rearrange the shape of an event before handing off to the eventHandler
360
+ *
361
+ * @private
362
+ * @param {SlackEvent} event - One of any of the events listed in <https://api.slack.com/events> with Events API enabled.
363
+ */
364
+ eventWrapper(event) {
365
+ if (this.eventHandler) {
366
+ // fetch full representations of the user, bot, and potentially the item_user.
367
+ const fetches = [];
368
+ if (event.user) {
369
+ fetches.push(this.fetchUser(event.user));
370
+ } else if (event.bot_id) {
371
+ fetches.push(this.fetchBotUser(event.bot_id));
372
+ }
373
+
374
+ if (event.item_user) {
375
+ fetches.push(this.fetchUser(event.item_user));
376
+ }
377
+ // after fetches complete...
378
+ return Promise.all(fetches)
379
+ .then(fetched => {
380
+ if (event.user) {
381
+ event.user = fetched.shift();
382
+ } else if (event.bot_id) {
383
+ let bot = fetched.shift();
384
+ if (this.botUserIdMap[event.bot_id]) {
385
+ event.user = bot;
386
+ // bot_id exists on all messages with subtype bot_message
387
+ // these messages only have a user_id property if sent from a bot user (xoxb token). therefore
388
+ // the above assignment will not happen for all messages from custom integrations or apps without a bot user
389
+ } else if (bot.user_id != null) {
390
+ return this.web.users.info({user: bot.user_id}).then(res => {
391
+ event.user = res.user;
392
+ this.botUserIdMap[event.bot_id] = res.user;
393
+ return event;
394
+ });
395
+ } else {
396
+ // bot doesn't have an associated user id
397
+ this.botUserIdMap[event.bot_id] = false;
398
+ event.user = {};
399
+ }
400
+ } else {
401
+ event.user = {};
402
+ }
403
+
404
+ if (event.item_user) {
405
+ event.item_user = fetched.shift();
406
+ }
407
+ return event;
408
+ }).then(fetchedEvent => {
409
+ // hand the event off to the eventHandler
410
+ try {
411
+ this.eventHandler(fetchedEvent);
412
+ }
413
+ catch (error) {
414
+ this.robot.logger.error(`An error occurred while processing an event: from Slack Client ${error.message}.`);
415
+ }
416
+ }).catch(error => {
417
+ return this.robot.logger.error(`Incoming message dropped due to error fetching info for a property: ${error.message}.`);
418
+ });
419
+ }
420
+ }
421
+ }
422
+
423
+ /**
424
+ * A handler for all incoming Slack events that are meaningful for the Adapter
425
+ *
426
+ * @callback SlackClient~eventHandler
427
+ * @param {Object} event
428
+ * @param {SlackUserInfo} event.user
429
+ * @param {string} event.channel
430
+ */
431
+
432
+ /**
433
+ * Callback that recieves a list of users
434
+ *
435
+ * @callback SlackClient~usersCallback
436
+ * @param {Error|null} error - an error if one occurred
437
+ * @param {Object} results
438
+ * @param {Array<SlackUserInfo>} results.members
439
+ */
440
+
441
+ if (SlackClient.CONVERSATION_CACHE_TTL_MS === NaN) {
442
+ throw new Error('HUBOT_SLACK_CONVERSATION_CACHE_TTL_MS must be a number. It could not be parsed.');
443
+ }
444
+
445
+ module.exports = SlackClient;
@@ -0,0 +1,82 @@
1
+ let {Robot} = require.main.require("hubot/es2015.js");
2
+ const {ReactionMessage, FileSharedMessage, MeMessage} = require("./message");
3
+
4
+ /**
5
+ * Adds a Listener for ReactionMessages with the provided matcher, options, and callback
6
+ *
7
+ * @public
8
+ * @param {Function} [matcher] - a function to determine if the listener should run. must return something
9
+ * truthy if it should and that value with be available on `response.match`.
10
+ * @param {Object} [options] - an object of additional parameters keyed on extension name.
11
+ * @param {Function} callback - a function that is called with a Response object if the matcher function returns true
12
+ */
13
+ Robot.prototype.hearReaction = function(matcher, options, callback) {
14
+ let matchReaction = msg => msg instanceof ReactionMessage;
15
+
16
+ if (!options && !callback) {
17
+ return this.listen(matchReaction, matcher);
18
+
19
+ } else if (matcher instanceof Function) {
20
+ matchReaction = msg => msg instanceof ReactionMessage && matcher(msg);
21
+
22
+ } else {
23
+ callback = options;
24
+ options = matcher;
25
+ }
26
+
27
+ return this.listen(matchReaction, options, callback);
28
+ };
29
+
30
+ /**
31
+ * Adds a Listener for MeMessages with the provided matcher, options, and callback
32
+ *
33
+ * @public
34
+ * @param {Function} [matcher] - a function to determine if the listener should run. must return something
35
+ * truthy if it should and that value with be available on `response.match`.
36
+ * @param {Object} [options] - an object of additional parameters keyed on extension name.
37
+ * @param {Function} callback - a function that is called with a Response object if the matcher function returns true
38
+ */
39
+ Robot.prototype.hearMeMessage = function(matcher, options, callback) {
40
+ let matchMeMessage = msg => msg instanceof MeMessage;
41
+
42
+ if (!options && !callback) {
43
+ return this.listen(matchMeMessage, matcher);
44
+
45
+ } else if (matcher instanceof Function) {
46
+ matchMeMessage = msg => msg instanceof MeMessage && matcher(msg);
47
+
48
+ } else {
49
+ callback = options;
50
+ options = matcher;
51
+ }
52
+
53
+ return this.listen(matchMeMessage, options, callback);
54
+ };
55
+
56
+ /**
57
+ * Adds a Listener for FileSharedMessages with the provided matcher, options, and callback
58
+ *
59
+ * @public
60
+ * @param {Function} [matcher] - a function to determine if the listener should run. must return something
61
+ * truthy if it should and that value with be available on `response.match`.
62
+ * @param {Object} [options] - an object of additional parameters keyed on extension name.
63
+ * @param {Function} callback - a function that is called with a Response object if the matcher function returns true
64
+ */
65
+ Robot.prototype.fileShared = function(matcher, options, callback) {
66
+ let matchFileShare = msg => msg instanceof FileSharedMessage;
67
+
68
+ if (!options && !callback) {
69
+ return this.listen(matchFileShare, matcher);
70
+
71
+ } else if (matcher instanceof Function) {
72
+ matchFileShare = msg => msg instanceof FileSharedMessage && matcher(msg);
73
+
74
+ } else {
75
+ callback = options;
76
+ options = matcher;
77
+ }
78
+
79
+ return this.listen(matchFileShare, options, callback);
80
+ };
81
+
82
+ // NOTE: extend Response type with a method for creating a new thread from the incoming message
package/src/mention.js ADDED
@@ -0,0 +1,15 @@
1
+ class SlackMention {
2
+ /**
3
+ * SlackMention is an instance of a mention within a SlackTextMessage.
4
+ * @constructor
5
+ * @param {string} id - The user or conversation id that the mention references
6
+ * @param {string} type - The type of mention ('user' or 'conversation')
7
+ * @param {Object} info - An object with additional info about the message reference, not guaranteed
8
+ */
9
+ constructor(id, type, info) {
10
+ this.id = id;
11
+ this.type = type;
12
+ this.info = info ?? undefined;
13
+ }
14
+ }
15
+ module.exports = SlackMention;