@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/message.js ADDED
@@ -0,0 +1,307 @@
1
+ const {Message, TextMessage} = require.main.require("hubot/es2015.js");
2
+ const SlackClient = require("./client");
3
+ const SlackMention = require("./mention");
4
+
5
+ class ReactionMessage extends Message {
6
+
7
+ /**
8
+ * Represents a message generated by an emoji reaction event
9
+ *
10
+ * @constructor
11
+ * @param {string} type - A String indicating 'reaction_added' or 'reaction_removed'
12
+ * @param {User} user - A User instance that reacted to the item.
13
+ * @param {string} reaction - A String identifying the emoji reaction.
14
+ * @param {Object} item - An Object identifying the target message, file, or comment item.
15
+ * @param {User} [item_user] - A String indicating the user that posted the item. If the item was created by a
16
+ * custom integration (not part of a Slack app with a bot user), then this value will be undefined.
17
+ * @param {string} event_ts - A String of the reaction event timestamp.
18
+ */
19
+ constructor(type, user, reaction, item_user, item, event_ts) {
20
+ super(user);
21
+ this.type = type;
22
+ this.user = user;
23
+ this.reaction = reaction;
24
+ this.item_user = item_user;
25
+ this.item = item;
26
+ this.event_ts = event_ts;
27
+ this.type = this.type.replace("reaction_", "");
28
+ }
29
+ }
30
+
31
+ class FileSharedMessage extends Message {
32
+
33
+ /**
34
+ * Represents a message generated by an file_shared event
35
+ *
36
+ * @constructor
37
+ * @param {User} user - A User instance that reacted to the item.
38
+ * @param {string} file_id - A String identifying the file_id of the file that was shared.
39
+ * @param {string} event_ts - A String of the file_shared event timestamp.
40
+ */
41
+ constructor(user, file_id, event_ts) {
42
+ super(user);
43
+ this.user = user;
44
+ this.file_id = file_id;
45
+ this.event_ts = event_ts;
46
+ }
47
+ }
48
+
49
+ class MeMessage extends TextMessage {
50
+
51
+ /**
52
+ * Represents a message generated by a /me invocation
53
+ *
54
+ * @constructor
55
+ * @param {User} user - A User instance that created the /me message
56
+ * @param {string} text - Text of the /me message
57
+ * @param {string} event_ts - event timestamp
58
+ */
59
+ constructor(user, text, event_ts) {
60
+ super(this.user, this.text, this.event_ts);
61
+ this.user = user;
62
+ this.text = text;
63
+ this.event_ts = event_ts;
64
+ }
65
+ }
66
+
67
+ class SlackTextMessage extends TextMessage {
68
+ /**
69
+ * Represents a TextMessage created from the Slack adapter
70
+ *
71
+ * @constructor
72
+ * @param {User} user - The User who sent this message
73
+ * @param {string|undefined} text - The parsed message text. Its no longer recommended to use this property.
74
+ * The `buildText()` method can be used to parse the raw text and populate the `text` property.
75
+ * @param {string|undefined} rawText - The unparsed message text. Its no longer recommended to use this property.
76
+ * The constructor will default to the `rawMessage.text` value.
77
+ * @param {Object} rawMessage - The Slack Message object
78
+ * @param {string} rawMessage.text
79
+ * @param {string} rawMessage.ts
80
+ * @param {string} [rawMessage.thread_ts] - the identifier for the thread the message is a part of
81
+ * @param {string} [rawMessage.attachments] - Slack message attachments
82
+ * @param {string} channel_id - The conversation where this message was sent.
83
+ * @param {string} robot_name - The Slack username for this robot
84
+ * @param {string} robot_alias - The alias for this robot
85
+ */
86
+ constructor(user, text, rawText, rawMessage, channel_id, robot_name, robot_alias) {
87
+ super(user, text, rawMessage.ts);
88
+ // private instance properties
89
+ this.rawMessage = rawMessage;
90
+ this._channel_id = channel_id;
91
+ this._robot_name = robot_name;
92
+ this._robot_alias = robot_alias;
93
+
94
+ // public instance property initialization
95
+ this.rawText = rawText || this.rawMessage.text;
96
+ if (this.rawMessage.thread_ts != null) { this.thread_ts = this.rawMessage.thread_ts; }
97
+ this.mentions = [];
98
+ }
99
+ static MESSAGE_REGEX = new RegExp(`\
100
+ <\
101
+ ([@#!])?\
102
+ ([^>|]+)\
103
+ (?:\\|\
104
+ ([^>]+)\
105
+ )?\
106
+ >\
107
+ `, 'g');
108
+ static MESSAGE_RESERVED_KEYWORDS = ["channel","group","everyone","here"];
109
+
110
+ /**
111
+ * Build the text property, a flat string representation of the contents of this message.
112
+ *
113
+ * @private
114
+ * @param {SlackClient} client - a client that can be used to get more data needed to build the text
115
+ * @param {function} cb - callback for the result
116
+ */
117
+ buildText(client, cb) {
118
+ // base text
119
+ let text = (this.rawMessage.text != null) ? this.rawMessage.text : "";
120
+
121
+ // flatten any attachments into text
122
+ if (this.rawMessage.attachments) {
123
+ const attachment_text = this.rawMessage.attachments.map(a => a.fallback).join("\n");
124
+ if (attachment_text.length > 0) { text = text + "\n" + attachment_text; }
125
+ }
126
+
127
+ // Replace links in text async to fetch user and channel info (if present)
128
+ const mentionFormatting = this.replaceLinks(client, text);
129
+ // Fetch conversation info
130
+ 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
+
139
+ // special handling for message text when inside a DM conversation
140
+ if (conversationInfo.is_im) {
141
+ const startOfText = text.indexOf("@") === 0 ? 1 : 0;
142
+ const robotIsNamed = (text.indexOf(this._robot_name) === startOfText) || (text.indexOf(this._robot_alias) === startOfText);
143
+ // Assume it was addressed to us even if it wasn't
144
+ if (!robotIsNamed) {
145
+ text = `${this._robot_name} ${text}`; // If this is a DM, pretend it was addressed to us
146
+ }
147
+ }
148
+
149
+ this.text = text;
150
+ return cb();
151
+ }).catch(error => {
152
+ client.robot.logger.error(`An error occurred while building text: ${error.message}`);
153
+ return cb(error);
154
+ });
155
+ }
156
+
157
+ /**
158
+ * Replace links inside of text
159
+ *
160
+ * @private
161
+ * @param {SlackClient} client - a client that can be used to get more data needed to build the text
162
+ * @returns {Promise<string>}
163
+ */
164
+ replaceLinks(client, text) {
165
+ let result;
166
+ const regex = SlackTextMessage.MESSAGE_REGEX;
167
+ regex.lastIndex = 0;
168
+ let cursor = 0;
169
+ const parts = [];
170
+
171
+ while (result = regex.exec(text)) {
172
+ var [m, type, link, label] = result;
173
+
174
+ switch (type) {
175
+ case "@":
176
+ if (label) {
177
+ parts.push(text.slice(cursor, result.index), `@${label}`);
178
+ this.mentions.push(new SlackMention(link, "user", undefined));
179
+ } else {
180
+ parts.push(text.slice(cursor, result.index), this.replaceUser(client, link, this.mentions));
181
+ }
182
+ break;
183
+
184
+ case "#":
185
+ if (label) {
186
+ parts.push(text.slice(cursor, result.index), `\#${label}`);
187
+ this.mentions.push(new SlackMention(link, "conversation", undefined));
188
+ } else {
189
+ parts.push(text.slice(cursor, result.index), this.replaceConversation(client, link, this.mentions));
190
+ }
191
+ break;
192
+
193
+ case "!":
194
+ if (SlackTextMessage.MESSAGE_RESERVED_KEYWORDS.includes(link)) {
195
+ parts.push(text.slice(cursor, result.index), `@${link}`);
196
+ } else if (label) {
197
+ parts.push(text.slice(cursor, result.index), label);
198
+ } else {
199
+ parts.push(text.slice(cursor, result.index), m);
200
+ }
201
+ break;
202
+
203
+ default:
204
+ link = link.replace(/^mailto:/, "");
205
+ if (label && (-1 === link.indexOf(label))) {
206
+ parts.push(text.slice(cursor, result.index), `${label} (${link})`);
207
+ } else {
208
+ parts.push(text.slice(cursor, result.index), link);
209
+ }
210
+ }
211
+
212
+ cursor = regex.lastIndex;
213
+ if (result[0].length === 0) {
214
+ regex.lastIndex++;
215
+ }
216
+ }
217
+
218
+ parts.push(text.slice(cursor));
219
+
220
+ return Promise.all(parts)
221
+ .then(substrings => substrings.join(""));
222
+ }
223
+
224
+ /**
225
+ * Creates a mention from a user ID
226
+ *
227
+ * @private
228
+ * @param {SlackClient} client - a client that can be used to get more data needed to build the text
229
+ * @param {string} id - the user ID
230
+ * @param {Array<Mention>} mentions - a mentions array that is updated to include this user mention
231
+ * @returns {Promise<string>} - a string that can be placed into the text for this mention
232
+ */
233
+ replaceUser(client, id, mentions) {
234
+ return client.fetchUser(id)
235
+ .then(res => {
236
+ mentions.push(new SlackMention(res.id, "user", res));
237
+ return `@${res.name}`;
238
+ }).catch(error => {
239
+ client.robot.logger.error(`Error getting user info ${id}: ${error.message}`);
240
+ return `<@${id}>`;
241
+ });
242
+ }
243
+
244
+ /**
245
+ * Creates a mention from a conversation ID
246
+ *
247
+ * @private
248
+ * @param {SlackClient} client - a client that can be used to get more data needed to build the text
249
+ * @param {string} id - the conversation ID
250
+ * @param {Array<Mention>} mentions - a mentions array that is updated to include this conversation mention
251
+ * @returns {Promise<string>} - a string that can be placed into the text for this mention
252
+ */
253
+ replaceConversation(client, id, mentions) {
254
+ return client.fetchConversation(id)
255
+ .then(conversation => {
256
+ if (conversation != null) {
257
+ mentions.push(new SlackMention(conversation.id, "conversation", conversation));
258
+ return `\#${conversation.name}`;
259
+ } else { return `<\#${id}>`; }
260
+ }).catch(error => {
261
+ client.robot.logger.error(`Error getting conversation info ${id}: ${error.message}`);
262
+ return `<\#${id}>`;
263
+ });
264
+ }
265
+
266
+ /**
267
+ * Factory method to construct SlackTextMessage
268
+ * @public
269
+ * @param {User} user - The User who sent this message
270
+ * @param {string|undefined} text - The parsed message text. Its no longer recommended to use this property.
271
+ * The `buildText()` method can be used to parse the raw text and populate the `text` property.
272
+ * @param {string|undefined} rawText - The unparsed message text. Its no longer recommended to use this property.
273
+ * The constructor will default to the `rawMessage.text` value.
274
+ * @param {Object} rawMessage - The Slack Message object
275
+ * @param {string} rawMessage.text
276
+ * @param {string} rawMessage.ts
277
+ * @param {string} [rawMessage.thread_ts] - the identifier for the thread the message is a part of
278
+ * @param {string} [rawMessage.attachments] - Slack message attachments
279
+ * @param {string} channel_id - The conversation where this message was sent.
280
+ * @param {string} robot_name - The Slack username for this robot
281
+ * @param {string} robot_alias - The alias for this robot
282
+ * @param {SlackClient} client - client used to fetch more data
283
+ * @param {function} cb - callback to return the result
284
+ */
285
+ static makeSlackTextMessage(user, text, rawText, rawMessage, channel_id, robot_name, robot_alias, client, cb) {
286
+ const message = new SlackTextMessage(user, text, rawText, rawMessage, channel_id, robot_name, robot_alias);
287
+
288
+ // creates a completion function that consistently calls the callback after this function has returned
289
+ const done = message => setImmediate(() => cb(null, message));
290
+
291
+ if ((message.text == null)) {
292
+ return message.buildText(client, function(error) {
293
+ if (error) {
294
+ return cb(error);
295
+ }
296
+ return done(message);
297
+ });
298
+ } else {
299
+ return done(message);
300
+ }
301
+ }
302
+ }
303
+
304
+ exports.SlackTextMessage = SlackTextMessage;
305
+ exports.ReactionMessage = ReactionMessage;
306
+ exports.FileSharedMessage = FileSharedMessage;
307
+ exports.MeMessage = MeMessage;
@@ -0,0 +1,24 @@
1
+ import { SocketModeClient, LogLevel } from '@slack/socket-mode'
2
+ import { WebClient } from '@slack/web-api'
3
+ const logLevel = LogLevel.DEBUG
4
+ const socketModeClient = new SocketModeClient({
5
+ appToken: process.env.SLACK_APP_TOKEN,
6
+ logLevel,
7
+ // pingPongLoggingEnabled: true,
8
+ // serverPingTimeout: 30000,
9
+ // clientPingTimeout: 5000,
10
+ })
11
+ const webClient = new WebClient(process.env.SLACK_BOT_TOKEN, {
12
+ logLevel,
13
+ })
14
+ const receive = async message => {
15
+ const { body, ack } = message
16
+ console.log('receiveing event', message)
17
+ try {
18
+ await ack()
19
+ } catch (e) {
20
+ console.error(e)
21
+ }
22
+ }
23
+ socketModeClient.on('message', receive)
24
+ await socketModeClient.start()