-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
313 lines (271 loc) · 7.79 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
/**
* Dependencies:
* Dotenv, Express, BodyParser, Slack Web Client, Slack Events API
*/
require('dotenv').config();
const express = require('express');
const firebase = require('firebase');
const twilio = require('twilio');
const bodyParser = require('body-parser');
const WebClient = require('@slack/client').WebClient;
const createSlackEventAdapter = require('@slack/events-api').createSlackEventAdapter;
/**
* Tokens:
* Slack, Firebase, Twilio
*/
// Retrieve bot token from dotenv file
const bot_token = process.env.SLACK_BOT_TOKEN || '';
// Authorization token
const auth_token = process.env.SLACK_AUTH_TOKEN || '';
// Verification token for Events Adapter
const slackEvents = createSlackEventAdapter(process.env.SLACK_VERIFICATION_TOKEN);
// Twilio client authorization
const twilio_sid = process.env.TWILIO_SID || '';
const twilio_auth = process.env.TWILIO_AUTH_TOKEN || '';
/**
* App Configuration:
* Express, BodyParser, Firebase, Events API, Web Clients, Twilio
*/
// Creates our express app
const app = express();
// Use BodyParser for app
app.use(bodyParser.urlencoded({extended: true}));
app.use(bodyParser.json());
// Slack events client
app.use('/events', slackEvents.expressMiddleware());
// Slack web client
const web = new WebClient(auth_token);
const bot = new WebClient(bot_token);
// Firebase configuration
const config = {
apiKey: process.env.FB_API_KEY || '',
authDomain: process.env.FB_AUTH_DOMAIN || '',
databaseURL: process.env.FB_DB_URL || '',
projectId: process.env.FB_PROJECT_ID || '',
storageBucket: process.env.FB_STORAGE_BUCKET || '',
messagingSenderId: process.env.FB_MESSAGING_SENDER_ID || ''
};
// Firebase initialization
const db = firebase.initializeApp(config).database();
// Initialize Twilio client using our Twilio SID and Authorization token
const twilioClient = twilio(twilio_sid, twilio_auth);
/**
* App Variables
* Channel ID, Bot User ID, Port
*/
// Holds channel ID
let channel = process.env.SLACK_DEFAULT_CHANNEL
// Holds bot user ID
let botID;
// The port we'll be using for our Express server
const PORT = process.env.PORT || 3000;
// Starts our server
app.listen(PORT, function() {
console.log('TalkBot is listening on port ' + PORT);
// Get userID for bot
bot.auth.test()
.then((info) => {
if (info.user_id) {
botID = info.user_id;
}
})
.catch(console.error)
// Update channel
getChannel();
});
/**
* Slack Event Listeners
* Reaction added and message receieved
*/
slackEvents.on('reaction_added', (event) => {
// Check for white check mark emoji
if (event.reaction == 'white_check_mark') {
getNum(event.item.ts)
.then((num) => {deleteUser(num)})
.catch(console.error);
}
});
slackEvents.on('message', (event) => {
let trigger = '<@' + botID + '>';
// if message is addressed to bot
if (event.text.startsWith(trigger)) {
let msg = event.text.replace(trigger, '');
// if message is sent in a thread, send SMS to user in thread
if (event.thread_ts) {
sendSMS(msg, event.thread_ts);
}
// if message is not in a thread send a new SMS to a number retrieved
// in first parameter of the message
else {
// extract and remove number before sending SMS
let number = msg.split(' ')[1];
msg = msg.replace(number, '');
sendSMS(msg, null, number);
}
}
});
// Handles incoming SMS to Twilio number
app.post('/sms', function(req, res) {
// Gets message from Twilio req
let msg = req.body.Body ? req.body.Body : '';
// Gets phone number from sender without leading plus sign
let num = req.body.From ? req.body.From.slice(1) : '';
getID(num)
.then((id) => {
if (id) { // User exists in database
sendThread(msg, channel, id);
} else { // User doesn't exist -- send message and create user
sendMessage(msg, channel, num);
}
})
.catch(console.error);
});
app.post('/select', function(req, res) {
let body = JSON.parse(req.body.payload);
let id = body.actions[0].selected_options[0].value;
res.json({text: 'Great, I\'ll send incoming messages there', replace_original: false});
// Update Channel
updateChannel(id);
});
app.get('/auth', function(req, res) {
web.oauth.access(process.env.SLACK_CLIENT_ID, process.env.SLACK_CLIENT_SECRET, req.query.code, function(err, info) {
if (err) console.log(err);
if (!err) {
initBot(info.user_id);
res.redirect('http://slack.com');
}
});
});
function sendSMS(msg, id = null, number = null) {
// if an ID is present, message comes from a thread
// retrieve phone number from DB and send SMS to that number
if (id) {
getNum(id)
.then((num) => {
if (num) {
twilioClient.messages.create({
to: num,
from: process.env.TWILIO_PHONE_NUMBER,
body: msg
});
}
})
.catch(console.error)
} else if (number) {
// if a number is provided, send a message to that number
twilioClient.messages.create({
to: number,
from: process.env.TWILIO_PHONE_NUMBER,
body: msg
});
}
}
function sendMessage(msg, channel, num) {
const text = `Received SMS from ${num}`;
// format message to have number look nice
const attachments = {
attachments: JSON.stringify([
{
fallback: `${num}: ${msg}`,
fields: [
{
title: "From",
value: num,
short: true
},
{
title: "Message",
value: msg,
short: false
}
],
color: "#46A7F1"
}
])
};
// Send message using Slack Web Client
web.chat.postMessage(channel, text, attachments, function(err, info) {
if (err) {
console.log(err);
} else {
if (num) {
// Create user in database
createUser(num, info.ts);
}
}
});
}
function sendThread(text, channel, id) {
// Send message using Slack Web Client
var msg = {
thread_ts: id
};
web.chat.postMessage(channel, text, msg, function(err, info) {
if (err) console.log(err);
});
}
function initBot(id) {
// Open IM
web.im.open(id, function(err, info) {
if (err) console.log(err);
installConfig(info.channel.id);
});
}
function installConfig(id) {
let text = 'Hey there! I\'m TalkBot, a Slack bot you can use to send and receive messages from Twilio. Pick which channel you want me to send messages to. After you select the channel, make sure to invite me using the command `/invite @talkbot` in your selected channel.';
let msg = {
response_type: 'in_channel',
attachments: [{
fallback: 'Upgrade your Slack client to use messages like these.',
color: '#ed2e3b',
attachment_type: 'default',
callback_id: 'simple_select',
actions: [{
name: 'channels_list',
text: 'Which channel should I post to?',
type: 'select',
data_source: 'channels'
}]
}]
};
// Send initial IM to user
web.chat.postMessage(id, text, msg, function(err, info) {
if (err) console.log(err);
});
}
/**
* Firebase Access Methods:
* Channel manipulation, user manipulation, and user retrieval
*/
// Update channel
function updateChannel(id) {
db.ref('channel/id').set(id);
}
// Get channel
function getChannel() {
const ref = db.ref('channel/id');
ref.on('value', (snapshot) => {
channel = snapshot.val();
})
}
// Create user in Firebase
function createUser(num, id) {
db.ref('users/' + num).set(id);
}
// Delete user in Firebase
function deleteUser(num) {
db.ref('users/').child(num).remove();
}
// Get number from message ID in Firebase
function getNum(id) {
return db.ref('users/').orderByValue().equalTo(id).once('value').then((snapshot) => {
if (snapshot.val()) return Object.keys(snapshot.val())[0];
return null;
});
}
// Get thread ID from phone number in Firebase
function getID(num) {
return db.ref('users/').child(num).once('value').then((snapshot) => {
return snapshot.val();
});
}