@hubot-friends/hubot-slack 1.0.11 → 3.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.
@@ -11,8 +11,8 @@ Describe your issue here.
11
11
  - [ ] discussion
12
12
 
13
13
  ### Requirements (place an `x` in each of the `[ ]`)
14
- * [ ] I've read and understood the [Contributing guidelines](https://github.com/slackapi/hubot-slack/blob/master/.github/contributing.md) and have done my best effort to follow them.
15
- * [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).
14
+ * [ ] I've read and understood the [Contributing guidelines](./contributing.md) and have done my best effort to follow them.
15
+ * [ ] I've read and agree to the [Code of Conduct](./CODE_OF_CONDUCT.md).
16
16
  * [ ] I've searched for any related issues and avoided creating a duplicate issue.
17
17
 
18
18
  ---
@@ -4,5 +4,4 @@ Describe the goal of this PR. Mention any related Issue numbers.
4
4
 
5
5
  ### Requirements (place an `x` in each `[ ]`)
6
6
 
7
- * [ ] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/hubot-slack/blob/master/.github/contributing.md) and have done my best effort to follow them.
8
- * [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).
7
+ * [ ] I've read and understood the [Contributing Guidelines](./contributing.md) and have done my best effort to follow them.
package/index.mjs ADDED
@@ -0,0 +1,21 @@
1
+ import { SlackBot } from './src/Bot.mjs'
2
+ import './src/Extensions.mjs'
3
+ export default {
4
+ use (robot) {
5
+ let e
6
+ const options = {
7
+ appToken: process.env.HUBOT_SLACK_APP_TOKEN,
8
+ botToken: process.env.HUBOT_SLACK_BOT_TOKEN,
9
+ disableUserSync: (process.env.DISABLE_USER_SYNC != null),
10
+ apiPageSize: process.env.API_PAGE_SIZE,
11
+ installedTeamOnly: (process.env.INSTALLED_TEAM_ONLY != null)
12
+ }
13
+ try {
14
+ options.socketModeOptions = JSON.parse(process.env.HUBOT_SLACK_SOCKET_MODE_OPTS || '{}')
15
+ } catch (error) {
16
+ e = error
17
+ console.error(e)
18
+ }
19
+ return new SlackBot(robot, options)
20
+ }
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hubot-friends/hubot-slack",
3
- "version": "1.0.11",
3
+ "version": "3.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",
@@ -24,14 +24,13 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@slack/socket-mode": "^1.3.2",
27
- "@slack/web-api": "^6.8.1"
27
+ "@slack/web-api": "^6.9.1"
28
28
  },
29
29
  "peerDependencies": {
30
- "hubot": ">= 7.0.0"
30
+ "hubot": ">= 11"
31
31
  },
32
32
  "engines": {
33
- "node": ">= 18.16.0",
34
- "npm": ">= 9.5.1"
33
+ "node": ">= 18"
35
34
  },
36
35
  "devDependencies": {
37
36
  "pino-pretty": "^10.0.1"
@@ -1,10 +1,8 @@
1
- const {Adapter, TextMessage, EnterMessage, LeaveMessage, TopicMessage, CatchAllMessage, User} = require.main.require("hubot/es2015.js");
2
- const {SlackTextMessage, ReactionMessage, FileSharedMessage, MeMessage} = require("./message");
3
-
4
- const SocketModeClient = require('@slack/socket-mode').SocketModeClient;
5
- const WebClient = require('@slack/web-api').WebClient;
6
-
7
- const pkg = require("../package.json");
1
+ import { Adapter, EnterMessage, LeaveMessage } from 'hubot'
2
+ import { SlackTextMessage, ReactionMessage, FileSharedMessage } from './Message.mjs'
3
+ import { SocketModeClient } from '@slack/socket-mode'
4
+ import { WebClient } from '@slack/web-api'
5
+ import pkg from '../package.json' assert { type: 'json' }
8
6
 
9
7
  class SlackClient {
10
8
  static CONVERSATION_CACHE_TTL_MS =
@@ -20,7 +18,7 @@ class SlackClient {
20
18
  this.apiPageSize = parseInt(options.apiPageSize, 10);
21
19
  }
22
20
 
23
- this.robot.logger.debug(`SocketModeClient initialized with options: ${JSON.stringify(options.socketModeOptions)}`);
21
+ this.robot.logger.debug(`SocketModeClient initialized with options: ${JSON.stringify(options.socketModeOptions) ?? ''}`);
24
22
 
25
23
  // Map to convert bot user IDs (BXXXXXXXX) to user representations for events from custom
26
24
  // integrations and apps without a bot user
@@ -58,23 +56,21 @@ class SlackClient {
58
56
  return this.socket.removeAllListeners();
59
57
  }
60
58
 
61
- setTopic(conversationId, topic) {
59
+ async setTopic(conversationId, topic) {
62
60
  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
- });
61
+ try {
62
+ const res = await this.web.conversations.info({channel: conversationId})
63
+ const conversation = res.channel;
64
+ if (!conversation.is_im && !conversation.is_mpim) {
65
+ return this.web.conversations.setTopic({channel: conversationId, topic});
66
+ } else {
67
+ return this.robot.logger.debug(`Conversation ${conversationId} is a DM or MPDM. These conversation types do not have topics.`);
68
+ }
69
+ } catch (error) {
70
+ this.robot.logger.error(error, `Error setting topic in conversation ${conversationId}: ${error.message}`);
71
+ }
76
72
  }
77
- send(envelope, message) {
73
+ async send(envelope, message) {
78
74
  const room = envelope.room || envelope.id;
79
75
  if (room == null) {
80
76
  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 +79,19 @@ class SlackClient {
83
79
  this.robot.logger.debug(`SlackClient#send() room: ${room}, message: ${message}`);
84
80
  if (typeof message !== "string") {
85
81
  message.channel = room
86
- return this.web.chat.postMessage(message).then(result => {
82
+ try {
83
+ const result = await this.web.chat.postMessage(message)
87
84
  this.robot.logger.debug(`Successfully sent message to ${room}`)
88
- }).catch(e => this.robot.logger.error(e, `SlackClient#send(message) error: ${e.message}`))
85
+ } catch (e) {
86
+ this.robot.logger.error(e, `SlackClient#send(message) error: ${e.message}`)
87
+ }
89
88
  } else {
90
- return this.web.chat.postMessage({ channel: room, text: message }).then(result => {
89
+ try {
90
+ const result = await this.web.chat.postMessage({ channel: room, text: message })
91
91
  this.robot.logger.debug(`Successfully sent message (string) to ${room}`)
92
- }).catch(e => this.robot.logger.error(e, `SlackClient#send(string) error: ${e.message}`))
92
+ } catch (e) {
93
+ this.robot.logger.error(e, `SlackClient#send(string) error: ${e.message}`)
94
+ }
93
95
  }
94
96
  }
95
97
  loadUsers(callback) {
@@ -121,20 +123,19 @@ class SlackClient {
121
123
  this.updateUserInBrain(r.user);
122
124
  return r.user;
123
125
  }
124
- fetchConversation(conversationId) {
126
+ async fetchConversation(conversationId) {
125
127
  const expiration = Date.now() - SlackClient.CONVERSATION_CACHE_TTL_MS;
126
128
  if (((this.channelData[conversationId] != null ? this.channelData[conversationId].channel : undefined) != null) &&
127
129
  (expiration < (this.channelData[conversationId] != null ? this.channelData[conversationId].updated : undefined))) { return Promise.resolve(this.channelData[conversationId].channel); }
128
130
  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
- });
131
+ const r = await this.web.conversations.info({channel: conversationId})
132
+ if (r.channel != null) {
133
+ this.channelData[conversationId] = {
134
+ channel: r.channel,
135
+ updated: Date.now()
136
+ };
137
+ }
138
+ return r.channel;
138
139
  }
139
140
  updateUserInBrain(event_or_user) {
140
141
  let key, value;
@@ -246,7 +247,7 @@ class SlackBot extends Adapter {
246
247
  * @param {Object} envelope - fully documented in SlackClient
247
248
  * @param {...(string|Object)} messages - fully documented in SlackClient
248
249
  */
249
- send(envelope, ...messages) {
250
+ async send(envelope, ...messages) {
250
251
  this.robot.logger.debug('Sending message to Slack');
251
252
  let callback = function() {};
252
253
  if (typeof(messages[messages.length - 1]) === "function") {
@@ -257,7 +258,15 @@ class SlackBot extends Adapter {
257
258
  // NOTE: perhaps do envelope manipulation here instead of in the client (separation of concerns)
258
259
  if (message !== "") { return this.client.send(envelope, message); }
259
260
  });
260
- return Promise.all(messagePromises).then(callback.bind(null, null), callback);
261
+ let results = [];
262
+ try {
263
+ results = await Promise.all(messagePromises)
264
+ callback(null, null)
265
+ } catch (e) {
266
+ this.robot.logger.error(e);
267
+ callback(e, null);
268
+ }
269
+ return results;
261
270
  }
262
271
 
263
272
  /**
@@ -266,7 +275,7 @@ class SlackBot extends Adapter {
266
275
  * @param {Object} envelope - fully documented in SlackClient
267
276
  * @param {...(string|Object)} messages - fully documented in SlackClient
268
277
  */
269
- reply(envelope, ...messages) {
278
+ async reply(envelope, ...messages) {
270
279
  this.robot.logger.debug('replying to message');
271
280
  let callback = function() {};
272
281
  if (typeof(messages[messages.length - 1]) === "function") {
@@ -283,7 +292,15 @@ class SlackBot extends Adapter {
283
292
  return this.client.send(envelope, message);
284
293
  }
285
294
  });
286
- return Promise.all(messagePromises).then(callback.bind(null, null), callback);
295
+ let results = [];
296
+ try {
297
+ results = await Promise.all(messagePromises)
298
+ callback(null, null)
299
+ } catch (e) {
300
+ this.robot.logger.error(e);
301
+ callback(e, null);
302
+ }
303
+ return results;
287
304
  }
288
305
 
289
306
  /**
@@ -292,10 +309,8 @@ class SlackBot extends Adapter {
292
309
  * @param {Object} envelope - fully documented in SlackClient
293
310
  * @param {...string} strings - strings that will be newline separated and set to the conversation topic
294
311
  */
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"));
312
+ async setTopic(envelope, ...strings) {
313
+ return await this.client.setTopic(envelope.room, strings.join("\n"));
299
314
  }
300
315
 
301
316
  /**
@@ -457,16 +472,13 @@ class SlackBot extends Adapter {
457
472
  // Hubot expects all user objects to have a room property that is used in the envelope for the message after it
458
473
  // is received
459
474
  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
- }
475
+ from.name = from?.profile?.display_name ?? null;
465
476
 
466
477
  // add the bot id to the message if it's a direct message
467
478
  message.body.event.text = this.addBotIdToMessage(message.body.event);
468
479
  message.body.event.text = this.replaceBotIdWithName(message.body.event);
469
480
  this.robot.logger.debug(`Text = ${message.body.event.text}`);
481
+ this.robot.logger.debug(`Event subtype = ${message.body.event?.subtype}`);
470
482
  try {
471
483
  switch (message.event.type) {
472
484
  case "member_joined_channel":
@@ -474,13 +486,13 @@ class SlackBot extends Adapter {
474
486
  this.robot.logger.debug(`Received enter message for user: ${from.id}, joining: ${channel}`);
475
487
  msg = new EnterMessage(from);
476
488
  msg.ts = message.event.ts;
477
- this.receive(msg);
489
+ await this.receive(msg);
478
490
  break;
479
491
  case "member_left_channel":
480
492
  this.robot.logger.debug(`Received leave message for user: ${from.id}, joining: ${channel}`);
481
493
  msg = new LeaveMessage(user);
482
494
  msg.ts = message.ts;
483
- this.receive(msg);
495
+ await this.receive(msg);
484
496
  break;
485
497
  case "reaction_added": case "reaction_removed":
486
498
  // Once again Hubot expects all user objects to have a room property that is used in the envelope for the message
@@ -494,18 +506,20 @@ class SlackBot extends Adapter {
494
506
  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
507
 
496
508
  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));
509
+ 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
510
  break;
499
511
  case "file_shared":
500
512
  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));
513
+ await this.receive(new FileSharedMessage(from, message.body.event.file_id, message.body.event.event_ts));
502
514
  break;
503
515
  default:
504
516
  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
- });
517
+ try {
518
+ const msg = await SlackTextMessage.makeSlackTextMessage(from, null, message?.body?.event.text, message?.body?.event, channel, this.robot.name, this.robot.alias, this.client)
519
+ await this.receive(msg);
520
+ } catch (error) {
521
+ this.robot.logger.error(error, `Dropping message due to error ${error.message}`);
522
+ }
509
523
  break;
510
524
  }
511
525
  } catch (e) {
@@ -532,5 +546,7 @@ class SlackBot extends Adapter {
532
546
  return res.members.map((member) => this.client.updateUserInBrain(member));
533
547
  }
534
548
  }
535
-
536
- module.exports = SlackBot;
549
+ export {
550
+ SlackClient,
551
+ SlackBot
552
+ }
@@ -1,5 +1,5 @@
1
- let {Robot} = require.main.require("hubot/es2015.js");
2
- const {ReactionMessage, FileSharedMessage, MeMessage} = require("./message");
1
+ import { Robot } from 'hubot'
2
+ import { ReactionMessage, FileSharedMessage, MeMessage } from './Message.mjs'
3
3
 
4
4
  /**
5
5
  * Adds a Listener for ReactionMessages with the provided matcher, options, and callback
@@ -1,4 +1,4 @@
1
- class SlackMention {
1
+ export class SlackMention {
2
2
  /**
3
3
  * SlackMention is an instance of a mention within a SlackTextMessage.
4
4
  * @constructor
@@ -12,4 +12,3 @@ class SlackMention {
12
12
  this.info = info ?? undefined;
13
13
  }
14
14
  }
15
- module.exports = SlackMention;
@@ -1,6 +1,6 @@
1
- const {Message, TextMessage} = require.main.require("hubot/es2015.js");
2
- const SlackClient = require("./client");
3
- const SlackMention = require("./mention");
1
+ import { Message, TextMessage, TopicMessage } from 'hubot'
2
+ import { SlackClient } from './Bot.mjs'
3
+ import { SlackMention } from './Mention.mjs'
4
4
 
5
5
  class ReactionMessage extends Message {
6
6
 
@@ -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,26 +269,19 @@ 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
-
293
- exports.SlackTextMessage = SlackTextMessage;
294
- exports.ReactionMessage = ReactionMessage;
295
- exports.FileSharedMessage = FileSharedMessage;
296
- exports.MeMessage = MeMessage;
282
+ export {
283
+ SlackTextMessage,
284
+ ReactionMessage,
285
+ FileSharedMessage,
286
+ MeMessage
287
+ }
@@ -1,6 +1,4 @@
1
- import Adapter from 'hubot/src/adapter.js'
2
- import { TextMessage } from 'hubot/src/message.js'
3
- import User from 'hubot/src/user.js'
1
+ import { TextMessage, Adapter, User } from 'hubot'
4
2
  import { SocketModeClient } from '@slack/socket-mode'
5
3
  import { WebClient } from '@slack/web-api'
6
4
 
@@ -1,8 +1,7 @@
1
1
  import { describe, it, beforeEach, afterEach } from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
3
  import { SlackAdapter } from './SlackAdapter.mjs'
4
- import Robot from 'hubot/src/robot.js'
5
- import { TextMessage } from 'hubot/src/message.js'
4
+ import { Robot, TextMessage } from 'hubot'
6
5
  import EventEmitter from 'node:events'
7
6
 
8
7
  const BOT_ID = 'U0AAATTTTTAAAAA'