@hubot-friends/hubot-slack 1.0.11 → 2.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/index.js CHANGED
@@ -1,5 +1,5 @@
1
- const SlackBot = require('./src/bot');
2
- require('./src/extensions');
1
+ const SlackBot = require('./src/bot.js').SlackBot;
2
+ require('./src/extensions.js');
3
3
 
4
4
  exports.use = function(robot) {
5
5
  let e;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubot-friends/hubot-slack",
3
- "version": "1.0.11",
3
+ "version": "2.0.0",
4
4
  "description": "A new Slack adapter for Hubot",
5
5
  "homepage": "https://github.com/hubot-friends/hubot-slack#readme",
6
6
  "main": "./index.js",
@@ -27,7 +27,7 @@
27
27
  "@slack/web-api": "^6.8.1"
28
28
  },
29
29
  "peerDependencies": {
30
- "hubot": ">= 7.0.0"
30
+ "hubot": ">= 9.0.0"
31
31
  },
32
32
  "engines": {
33
33
  "node": ">= 18.16.0",
package/src/bot.js CHANGED
@@ -20,7 +20,7 @@ class SlackClient {
20
20
  this.apiPageSize = parseInt(options.apiPageSize, 10);
21
21
  }
22
22
 
23
- this.robot.logger.debug(`SocketModeClient initialized with options: ${JSON.stringify(options.socketModeOptions)}`);
23
+ this.robot.logger.debug(`SocketModeClient initialized with options: ${JSON.stringify(options.socketModeOptions) ?? ''}`);
24
24
 
25
25
  // Map to convert bot user IDs (BXXXXXXXX) to user representations for events from custom
26
26
  // integrations and apps without a bot user
@@ -58,23 +58,21 @@ class SlackClient {
58
58
  return this.socket.removeAllListeners();
59
59
  }
60
60
 
61
- setTopic(conversationId, topic) {
61
+ async setTopic(conversationId, topic) {
62
62
  this.robot.logger.debug(`SlackClient#setTopic() with topic ${topic}`);
63
- return this.web.conversations.info({channel: conversationId})
64
- .then(res => {
65
- const conversation = res.channel;
66
- if (!conversation.is_im && !conversation.is_mpim) {
67
- return this.web.conversations.setTopic({channel: conversationId, topic});
68
- } else {
69
- return this.robot.logger.debug(`Conversation ${conversationId} is a DM or MPDM. ` +
70
- "These conversation types do not have topics."
71
- );
72
- }
73
- }).catch(error => {
74
- return this.robot.logger.error(error, `Error setting topic in conversation ${conversationId}: ${error.message}`);
75
- });
63
+ try {
64
+ const res = await this.web.conversations.info({channel: conversationId})
65
+ const conversation = res.channel;
66
+ if (!conversation.is_im && !conversation.is_mpim) {
67
+ return this.web.conversations.setTopic({channel: conversationId, topic});
68
+ } else {
69
+ return this.robot.logger.debug(`Conversation ${conversationId} is a DM or MPDM. These conversation types do not have topics.`);
70
+ }
71
+ } catch (error) {
72
+ this.robot.logger.error(error, `Error setting topic in conversation ${conversationId}: ${error.message}`);
73
+ }
76
74
  }
77
- send(envelope, message) {
75
+ async send(envelope, message) {
78
76
  const room = envelope.room || envelope.id;
79
77
  if (room == null) {
80
78
  this.robot.logger.error("Cannot send message without a valid room. Envelopes should contain a room property set to a Slack conversation ID.");
@@ -83,13 +81,19 @@ class SlackClient {
83
81
  this.robot.logger.debug(`SlackClient#send() room: ${room}, message: ${message}`);
84
82
  if (typeof message !== "string") {
85
83
  message.channel = room
86
- return this.web.chat.postMessage(message).then(result => {
84
+ try {
85
+ const result = await this.web.chat.postMessage(message)
87
86
  this.robot.logger.debug(`Successfully sent message to ${room}`)
88
- }).catch(e => this.robot.logger.error(e, `SlackClient#send(message) error: ${e.message}`))
87
+ } catch (e) {
88
+ this.robot.logger.error(e, `SlackClient#send(message) error: ${e.message}`)
89
+ }
89
90
  } else {
90
- return this.web.chat.postMessage({ channel: room, text: message }).then(result => {
91
+ try {
92
+ const result = await this.web.chat.postMessage({ channel: room, text: message })
91
93
  this.robot.logger.debug(`Successfully sent message (string) to ${room}`)
92
- }).catch(e => this.robot.logger.error(e, `SlackClient#send(string) error: ${e.message}`))
94
+ } catch (e) {
95
+ this.robot.logger.error(e, `SlackClient#send(string) error: ${e.message}`)
96
+ }
93
97
  }
94
98
  }
95
99
  loadUsers(callback) {
@@ -121,20 +125,19 @@ class SlackClient {
121
125
  this.updateUserInBrain(r.user);
122
126
  return r.user;
123
127
  }
124
- fetchConversation(conversationId) {
128
+ async fetchConversation(conversationId) {
125
129
  const expiration = Date.now() - SlackClient.CONVERSATION_CACHE_TTL_MS;
126
130
  if (((this.channelData[conversationId] != null ? this.channelData[conversationId].channel : undefined) != null) &&
127
131
  (expiration < (this.channelData[conversationId] != null ? this.channelData[conversationId].updated : undefined))) { return Promise.resolve(this.channelData[conversationId].channel); }
128
132
  if (this.channelData[conversationId] != null) { delete this.channelData[conversationId]; }
129
- return this.web.conversations.info({channel: conversationId}).then(r => {
130
- if (r.channel != null) {
131
- this.channelData[conversationId] = {
132
- channel: r.channel,
133
- updated: Date.now()
134
- };
135
- }
136
- return r.channel;
137
- });
133
+ const r = await this.web.conversations.info({channel: conversationId})
134
+ if (r.channel != null) {
135
+ this.channelData[conversationId] = {
136
+ channel: r.channel,
137
+ updated: Date.now()
138
+ };
139
+ }
140
+ return r.channel;
138
141
  }
139
142
  updateUserInBrain(event_or_user) {
140
143
  let key, value;
@@ -246,7 +249,7 @@ class SlackBot extends Adapter {
246
249
  * @param {Object} envelope - fully documented in SlackClient
247
250
  * @param {...(string|Object)} messages - fully documented in SlackClient
248
251
  */
249
- send(envelope, ...messages) {
252
+ async send(envelope, ...messages) {
250
253
  this.robot.logger.debug('Sending message to Slack');
251
254
  let callback = function() {};
252
255
  if (typeof(messages[messages.length - 1]) === "function") {
@@ -257,7 +260,15 @@ class SlackBot extends Adapter {
257
260
  // NOTE: perhaps do envelope manipulation here instead of in the client (separation of concerns)
258
261
  if (message !== "") { return this.client.send(envelope, message); }
259
262
  });
260
- return Promise.all(messagePromises).then(callback.bind(null, null), callback);
263
+ let results = [];
264
+ try {
265
+ results = await Promise.all(messagePromises)
266
+ callback(null, null)
267
+ } catch (e) {
268
+ this.robot.logger.error(e);
269
+ callback(e, null);
270
+ }
271
+ return results;
261
272
  }
262
273
 
263
274
  /**
@@ -266,7 +277,7 @@ class SlackBot extends Adapter {
266
277
  * @param {Object} envelope - fully documented in SlackClient
267
278
  * @param {...(string|Object)} messages - fully documented in SlackClient
268
279
  */
269
- reply(envelope, ...messages) {
280
+ async reply(envelope, ...messages) {
270
281
  this.robot.logger.debug('replying to message');
271
282
  let callback = function() {};
272
283
  if (typeof(messages[messages.length - 1]) === "function") {
@@ -283,7 +294,15 @@ class SlackBot extends Adapter {
283
294
  return this.client.send(envelope, message);
284
295
  }
285
296
  });
286
- return Promise.all(messagePromises).then(callback.bind(null, null), callback);
297
+ let results = [];
298
+ try {
299
+ results = await Promise.all(messagePromises)
300
+ callback(null, null)
301
+ } catch (e) {
302
+ this.robot.logger.error(e);
303
+ callback(e, null);
304
+ }
305
+ return results;
287
306
  }
288
307
 
289
308
  /**
@@ -292,10 +311,8 @@ class SlackBot extends Adapter {
292
311
  * @param {Object} envelope - fully documented in SlackClient
293
312
  * @param {...string} strings - strings that will be newline separated and set to the conversation topic
294
313
  */
295
- setTopic(envelope, ...strings) {
296
- // TODO: if the sender is interested in the completion, the last item in `messages` will be a function
297
- // TODO: this will fail if sending an object as a value in strings
298
- return this.client.setTopic(envelope.room, strings.join("\n"));
314
+ async setTopic(envelope, ...strings) {
315
+ return await this.client.setTopic(envelope.room, strings.join("\n"));
299
316
  }
300
317
 
301
318
  /**
@@ -457,16 +474,13 @@ class SlackBot extends Adapter {
457
474
  // Hubot expects all user objects to have a room property that is used in the envelope for the message after it
458
475
  // is received
459
476
  from.room = channel ?? '';
460
-
461
- // looks like sometimes the profile is not set
462
- if (from.profile) {
463
- from.name = from.profile.display_name;
464
- }
477
+ from.name = from?.profile?.display_name ?? null;
465
478
 
466
479
  // add the bot id to the message if it's a direct message
467
480
  message.body.event.text = this.addBotIdToMessage(message.body.event);
468
481
  message.body.event.text = this.replaceBotIdWithName(message.body.event);
469
482
  this.robot.logger.debug(`Text = ${message.body.event.text}`);
483
+ this.robot.logger.debug(`Event subtype = ${message.body.event?.subtype}`);
470
484
  try {
471
485
  switch (message.event.type) {
472
486
  case "member_joined_channel":
@@ -474,13 +488,13 @@ class SlackBot extends Adapter {
474
488
  this.robot.logger.debug(`Received enter message for user: ${from.id}, joining: ${channel}`);
475
489
  msg = new EnterMessage(from);
476
490
  msg.ts = message.event.ts;
477
- this.receive(msg);
491
+ await this.receive(msg);
478
492
  break;
479
493
  case "member_left_channel":
480
494
  this.robot.logger.debug(`Received leave message for user: ${from.id}, joining: ${channel}`);
481
495
  msg = new LeaveMessage(user);
482
496
  msg.ts = message.ts;
483
- this.receive(msg);
497
+ await this.receive(msg);
484
498
  break;
485
499
  case "reaction_added": case "reaction_removed":
486
500
  // Once again Hubot expects all user objects to have a room property that is used in the envelope for the message
@@ -494,18 +508,20 @@ class SlackBot extends Adapter {
494
508
  const item_user = (message.body.event.item_user != null) ? this.robot.brain.userForId(message.body.event.item_user.id, message.body.event.item_user) : {};
495
509
 
496
510
  this.robot.logger.debug(`Received reaction message from: ${from.id}, reaction: ${message.body.event.reaction}, item type: ${message.body.event.item.type}`);
497
- this.receive(new ReactionMessage(message.body.event.type, from, message.body.event.reaction, item_user, message.body.event.item, message.body.event.event_ts));
511
+ await this.receive(new ReactionMessage(message.body.event.type, from, message.body.event.reaction, item_user, message.body.event.item, message.body.event.event_ts));
498
512
  break;
499
513
  case "file_shared":
500
514
  this.robot.logger.debug(`Received file_shared message from: ${message.body.event.user_id}, file_id: ${message.body.event.file_id}`);
501
- this.receive(new FileSharedMessage(from, message.body.event.file_id, message.body.event.event_ts));
515
+ await this.receive(new FileSharedMessage(from, message.body.event.file_id, message.body.event.event_ts));
502
516
  break;
503
517
  default:
504
518
  this.robot.logger.debug(`Received generic message: ${message.event.type}`);
505
- SlackTextMessage.makeSlackTextMessage(from, null, message?.body?.event.text, message?.body?.event, channel, this.robot.name, this.robot.alias, this.client, (error, message) => {
506
- if (error) { return this.robot.logger.error(error, `Dropping message due to error ${error.message}`); }
507
- return this.receive(message);
508
- });
519
+ try {
520
+ const msg = await SlackTextMessage.makeSlackTextMessage(from, null, message?.body?.event.text, message?.body?.event, channel, this.robot.name, this.robot.alias, this.client)
521
+ await this.receive(msg);
522
+ } catch (error) {
523
+ this.robot.logger.error(error, `Dropping message due to error ${error.message}`);
524
+ }
509
525
  break;
510
526
  }
511
527
  } catch (e) {
@@ -532,5 +548,5 @@ class SlackBot extends Adapter {
532
548
  return res.members.map((member) => this.client.updateUserInBrain(member));
533
549
  }
534
550
  }
535
-
536
- module.exports = SlackBot;
551
+ module.exports.SlackClient = SlackClient;
552
+ module.exports.SlackBot = SlackBot;
package/src/message.js CHANGED
@@ -1,5 +1,5 @@
1
- const {Message, TextMessage} = require.main.require("hubot/es2015.js");
2
- const SlackClient = require("./client");
1
+ const { Message, TextMessage, TopicMessage } = require.main.require("hubot/es2015.js");
2
+ const SlackClient = require("./bot.js");
3
3
  const SlackMention = require("./mention");
4
4
 
5
5
  class ReactionMessage extends Message {
@@ -114,10 +114,9 @@ class SlackTextMessage extends TextMessage {
114
114
  * @param {SlackClient} client - a client that can be used to get more data needed to build the text
115
115
  * @param {function} cb - callback for the result
116
116
  */
117
- buildText(client, cb) {
117
+ async buildText(client) {
118
118
  // base text
119
119
  let text = (this.rawMessage.text != null) ? this.rawMessage.text : "";
120
-
121
120
  // flatten any attachments into text
122
121
  if (this.rawMessage.attachments) {
123
122
  const attachment_text = this.rawMessage.attachments.map(a => a.fallback).join("\n");
@@ -128,19 +127,18 @@ class SlackTextMessage extends TextMessage {
128
127
  const mentionFormatting = this.replaceLinks(client, text);
129
128
  // Fetch conversation info
130
129
  const fetchingConversationInfo = client.fetchConversation(this._channel_id);
131
- return Promise.all([mentionFormatting, fetchingConversationInfo])
132
- .then(results => {
133
- const [ replacedText, conversationInfo ] = results;
134
- text = replacedText;
135
- text = text.replace(/&lt;/g, "<");
136
- text = text.replace(/&gt;/g, ">");
137
- text = text.replace(/&amp;/g, "&");
138
- this.text = text;
139
- return cb();
140
- }).catch(error => {
141
- client.robot.logger.error(error, `An error occurred while building text: ${error.message}`);
142
- return cb(error);
143
- });
130
+ let results = [];
131
+ try {
132
+ results = await Promise.all([mentionFormatting, fetchingConversationInfo]);
133
+ const [ replacedText, conversationInfo ] = results;
134
+ text = replacedText;
135
+ text = text.replace(/&lt;/g, "<");
136
+ text = text.replace(/&gt;/g, ">");
137
+ text = text.replace(/&amp;/g, "&");
138
+ } catch (e) {
139
+ client.robot.logger.error(e, `An error occurred while building text: ${e.message}`);
140
+ }
141
+ return text;
144
142
  }
145
143
 
146
144
  /**
@@ -271,22 +269,14 @@ class SlackTextMessage extends TextMessage {
271
269
  * @param {SlackClient} client - client used to fetch more data
272
270
  * @param {function} cb - callback to return the result
273
271
  */
274
- static makeSlackTextMessage(user, text, rawText, rawMessage, channel_id, robot_name, robot_alias, client, cb) {
275
- const message = new SlackTextMessage(user, text, rawText, rawMessage, channel_id, robot_name, robot_alias);
276
-
277
- // creates a completion function that consistently calls the callback after this function has returned
278
- const done = message => setImmediate(() => cb(null, message));
279
-
280
- if ((message.text == null)) {
281
- return message.buildText(client, function(error) {
282
- if (error) {
283
- return cb(error);
284
- }
285
- return done(message);
286
- });
287
- } else {
288
- return done(message);
272
+ static async makeSlackTextMessage(user, text, rawText, rawMessage, channel_id, robot_name, robot_alias, client) {
273
+ if(rawMessage?.subtype) {
274
+ return new TopicMessage(user, rawMessage.text, rawMessage.event_ts)
289
275
  }
276
+ const message = new SlackTextMessage(user, text, rawText, rawMessage, channel_id, robot_name, robot_alias);
277
+ if (message.text !== null) return message;
278
+ message.text = await message.buildText(client);
279
+ return message;
290
280
  }
291
281
  }
292
282
 
package/test/bot.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const {describe, it, beforeEach, before, after} = require('node:test');
2
2
  const assert = require('node:assert/strict');
3
3
  const Module = require('module');
4
- const SlackBot = require('../src/bot.js');
4
+ const SlackBot = require('../src/bot.js').SlackBot;
5
5
 
6
6
  const hookModuleToReturnMockFromRequire = (module, mock) => {
7
7
  const originalRequire = Module.prototype.require;
@@ -208,23 +208,6 @@ describe('Send Messages', function() {
208
208
  });
209
209
  });
210
210
 
211
- describe('Client sending message', function() {
212
- let stubs, client;
213
- beforeEach(function() {
214
- ({stubs, client} = require('./stubs.js')());
215
- });
216
-
217
- it('Should append as_user = true', function() {
218
- client.send({room: stubs.channel.id}, {text: 'foo', user: stubs.user, channel: stubs.channel.id});
219
- assert.ok(stubs._opts.as_user);
220
- });
221
-
222
- it('Should append as_user = true only as a default', function() {
223
- client.send({room: stubs.channel.id}, {text: 'foo', user: stubs.user, channel: stubs.channel.id, as_user: false});
224
- assert.deepEqual(stubs._opts.as_user, true);
225
- });
226
- });
227
-
228
211
  describe('Reply to Messages', function() {
229
212
  let stubs, slackbot;
230
213
  beforeEach(function() {
@@ -290,16 +273,18 @@ describe('Setting the channel topic', function() {
290
273
  ({stubs, slackbot} = require('./stubs.js')());
291
274
  });
292
275
 
293
- it('Should set the topic in channels', function(t, done) {
276
+ it('Should set the topic in channels', async () => {
277
+ let wasCalled = false;
294
278
  stubs.receiveMock.onTopic = function(topic) {
295
279
  assert.deepEqual(topic, 'channel');
296
- done();
280
+ wasCalled = true;
297
281
  };
298
- slackbot.setTopic({room: stubs.channel.id}, 'channel');
282
+ await slackbot.setTopic({room: stubs.channel.id}, 'channel');
283
+ assert.deepEqual(wasCalled, true);
299
284
  });
300
285
 
301
- it('Should NOT set the topic in DMs', function() {
302
- slackbot.setTopic({room: 'D1232'}, 'DM');
286
+ it('Should NOT set the topic in DMs', async () => {
287
+ await slackbot.setTopic({room: 'D1232'}, 'DM');
303
288
  assert.equal(stubs._topic, undefined);
304
289
  });
305
290
  });