-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.js
407 lines (346 loc) · 12 KB
/
app.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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
require('dotenv').config()
const fetch = require('node-fetch');
const Discord = require('discord.js');
const client = new Discord.Client();
const express = require('express')
const app = express()
app.use(express.json());
const appApi = require('./app_api/AppApi');
const discordApi = require('./discord_api/DiscordApi');
const config = {
WELCOME_NEW_MEMBERS: true,
DISCORD_GUILD_ID: process.env.DISCORD_GUILD_ID,
ADB_SECRET: process.env.ADB_SECRET,
PORT: process.env.PORT || 6070,
DISCORD_TOKEN: process.env.DISCORD_TOKEN,
}
if (config.DISCORD_GUILD_ID === "" || config.ADB_SECRET === "" || config.DISCORD_TOKEN === "") {
console.log("ERROR: MISSING CONFIG!")
process.exit(1)
}
// Store state in a single variable for convenient dependency injection in tests
const state = {
DISCORD_GUILD_ID: config.DISCORD_GUILD_ID,
client,
discordApi,
};
const msgCharLimit = 1900
const authorizePostRequests = function (req, res, next) {
if (req.method === "POST" && req.headers["auth"] !== config.ADB_SECRET) {
res.status(400);
return res.json({"result": "not authorized"});
}
next();
}
app.use(authorizePostRequests);
function validateEmail(email) {
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}
client.on('ready', () => {
console.log(`Logged in as ${client.user.tag}!`);
});
client.on('message', msg => {
if (msg.author.bot) return // don't reply to bots (i.e. yourself)
if (msg.content.startsWith("!users")) {
console.log("!users command called")
// fetch all users
client.guilds.fetch(config.DISCORD_GUILD_ID)
.then(guild => {
guild.members.fetch()
.then(users => {
let allUsers = new Map()
users.forEach((value, key) => {
allUsers[key] = {
"username": value.user.username,
"adb": null,
}
})
// get users from ADB
const paramsForStatus = new URLSearchParams();
paramsForStatus.append('auth', config.ADB_SECRET);
fetch('https://adb.dxe.io/discord/list', {method: 'POST', body: paramsForStatus})
.then( res => {
return res.json()
})
.then( json => {
json.activists.forEach(activist => {
// add to allUsers where key matches ID
if (allUsers[activist.DiscordID]) allUsers[activist.DiscordID].adb = activist.Name
})
let newMessage = "These users do not have their Discord ID associated with an activist in the ADB. Note that this list does not exclude people who filled out the new Discord form.";
for (const [key, value] of Object.entries(allUsers)) {
if (!value.adb) newMessage += key + "\t" + value.username + "\n";
}
for(let i = 0; i < newMessage.length; i += msgCharLimit) {
const msgToSend = newMessage.substring(i, Math.min(newMessage.length, i + msgCharLimit));
msg.reply(msgToSend)
}
})
})
.catch(err => {
res.status(500);
return res.json({"result": "error"});
})
})
// request list of users from ADB to compare to
return
}
if (msg.content.startsWith("!events")) {
msg.reply("Event functionality is coming soon.")
return
}
if (msg.content.startsWith("!set")) {
console.log("!set command called")
const moderatorRoleId = "748440668850094141"
let isModerator = false;
client.guilds.fetch(config.DISCORD_GUILD_ID)
.then(guild => {
guild.members.fetch(msg.author)
.then(user => {
roles = user._roles
// get names of roles that the user has
roles.forEach(role => {
if (role === moderatorRoleId) {
isModerator = true
}
})
if (!isModerator) {
msg.reply("Sorry, only moderators may use this command.")
return
}
const key = msg.content.substring(5).split(" ")[0].toLowerCase()
const value = msg.content.substring(msg.content.indexOf(" ", 5) + 1)
if (key === "users" || key === "events") {
msg.reply("Sorry, that is a reserved word.")
return
}
if (!/^[a-zA-Z]+$/.test(key)) {
msg.reply("Sorry, the word must only contain letters.")
return
}
if (key.length > 2000) {
msg.reply("Sorry, the message length must be <= 2000 characters.")
return
}
const params = new URLSearchParams();
params.append('auth', config.ADB_SECRET);
params.append('user', msg.author.id)
params.append('text', value)
console.log("params:" + params.toString())
fetch('https://adb.dxe.io/discord/set_message/' + key, {method: 'POST', body: params})
.then( res => { return res.json() } )
.then( json => {
msg.reply(json.status)
})
})
})
return
}
if (msg.content.startsWith("!")) {
console.log("! command called")
const chapterMemberRoleID = "748440285410754601"
let isChapterMember = false;
client.guilds.fetch(config.DISCORD_GUILD_ID)
.then(guild => {
guild.members.fetch(msg.author)
.then(user => {
roles = user._roles
// get names of roles that the user has
roles.forEach(role => {
if (role === chapterMemberRoleID) {
isChapterMember = true
}
})
if (!isChapterMember) {
// don't say anything if error since they may be calling a different bot
return
}
const key = msg.content.substring(1).toLowerCase()
if (!/^[a-zA-Z]+$/.test(key)) {
return
}
const params = new URLSearchParams();
params.append('auth', config.ADB_SECRET);
fetch('https://adb.dxe.io/discord/get_message/' + key, {method: 'POST', body: params})
.then( res => { return res.json() } )
.then( json => {
let status = json.status
let message = json.message
if (status === 'success') {
msg.reply(message)
return
}
})
})
})
return
}
// reply if message sent directly to you
if (msg.channel.type === "dm") {
// get the user's ID
userDiscordID = msg.author.id
console.log(userDiscordID);
// get status of user id in ADB
const paramsForStatus = new URLSearchParams();
paramsForStatus.append('auth', config.ADB_SECRET);
paramsForStatus.append('id', userDiscordID);
fetch('https://adb.dxe.io/discord/status', {method: 'POST', body: paramsForStatus})
.then( res => { return res.json() } )
.then( json => {
status = json.status
// if user status is confirmed, just say g'day & that you regret to inform them that there is nothing else you can do for them at this time
if (status == 'confirmed') {
msg.reply("Your identify has been confirmed. There's nothing else that I can do for you at this time. Have a good day!")
return
}
// if pending, then tell them that they may enter another email address if they'd like
if (status == 'pending' && !validateEmail(msg.content)) {
msg.reply("I've already sent you an email to confirm your identity, but I can send another if you enter your email address again.")
return
}
// confirm email is valid
if (!validateEmail(msg.content)) {
msg.reply("Sorry, that is not a valid email address.")
msg.reply("I need to verify your identity to add you to our exclusive channels. What is your email address?");
return
}
userEmail = msg.content
// make "generate" request to ADB - it will return "invalid email" if not found or "success" if email belongs to an activist
const paramsForGenerate = new URLSearchParams();
paramsForGenerate.append('auth', config.ADB_SECRET);
paramsForGenerate.append('id', userDiscordID);
paramsForGenerate.append('email', userEmail);
fetch('https://adb.dxe.io/discord/generate', {method: 'POST', body: paramsForGenerate})
.then( res => { return res.json() } )
.then( json => {
status = json.status
if (status === 'invalid email') {
msg.reply("Sorry, I could not find any activists associated with that email address. Please enter another email address to check, or email [email protected] for assistance.")
return
}
if (status === 'too many activists') {
msg.reply("Sorry, there are multiple activists associated with that email address. Please email [email protected] for assistance.")
return
}
if (status === 'success') {
msg.reply("I just sent an email to you to confirm your email address. Please click the confirmation link in the email. (If you can't find it, then be sure to check your spam folder.)")
return
}
})
})
}
});
// event listener for new guild members
if (config.WELCOME_NEW_MEMBERS) {
client.on('guildMemberAdd', member => {
console.log("guild member added.");
const channel = member.guild.channels.cache.find(ch => ch.name === '🔑verify');
channel.send(`Welcome, ${member}! Please verify your email by replying to the direct message sent by <@768973756411674644>. (If you have any trouble, please email [email protected].)`);
// logging for now to make sure everything goes okay
console.log("New member joined the server:")
console.log(JSON.stringify(member));
member.send(`Hi, ${member}! I need to verify your identity to add you to our exclusive channels. What is your email address?`)
});
}
client.login(config.DISCORD_TOKEN);
function getUserRoles() {}
app.get('/health', (req, res) => {
res.status(200);
return res.json({"status": "healthy"});
})
app.get('/roles/get', (req, res) => {
// TODO: check that request is authorized with our ADB/bot shared secret
// (not super important b/c it is only accessible internally on server itself)
// fetch the guild's roles so that we know the names
client.guilds.fetch(config.DISCORD_GUILD_ID)
.then(guild => {
guild.roles.fetch()
.then(roles => {
let allRoles = new Map()
roles.cache.forEach(role => {
allRoles[role.id] = role.name
})
// fetch the user's roles
client.guilds.fetch(config.DISCORD_GUILD_ID)
.then(guild => {
guild.members.fetch(req.query.user)
.then(user => {
roles = user._roles
// get names of roles that the user has
let userRoles = new Map()
roles.forEach(role => {
userRoles[role] = allRoles[role]
})
res.json(userRoles);
})
.catch(err => {
res.status(500);
return res.json({"result": "error"});
})
})
})
})
})
app.post('/roles/add', appApi.addRole(state))
app.post('/roles/remove', appApi.removeRole(state))
app.post('/send_message', async (req, res) => {
let recipient = req.body.recipient
let message = req.body.message
if (typeof(recipient) == 'undefined' || recipient.length == 0) {
res.status(400);
return res.json({"result": "error: no recipient provided"});
}
if (typeof(message) == 'undefined' || message.length == 0) {
res.status(400);
return res.json({"result": "error: no message provided"});
}
client.guilds.fetch(config.DISCORD_GUILD_ID)
.then(guild => {
guild.members.fetch(recipient)
.then(recipient => {
for(let i = 0; i < message.length; i += msgCharLimit) {
const msgToSend = message.substring(i, Math.min(message.length, i + msgCharLimit));
recipient.send(msgToSend)
}
})
.then(result => {
return res.json({"result": "sent"});
})
.catch(err => {
res.status(500);
return res.json({"result": "error"});
})
})
})
app.post('/update_nickname', (req, res) => {
// note that this will not work if the user is a moderator
let user = req.body.user
let name = req.body.name
console.log("Updating nickname of " + user + " to " + name);
if (typeof(user) == 'undefined' || user.length == 0) {
res.status(400);
return res.json({"result": "error: no user provided"});
}
if (typeof(name) == 'undefined' || name.length == 0) {
res.status(400);
return res.json({"result": "error: no name provided"});
}
client.guilds.fetch(config.DISCORD_GUILD_ID)
.then(guild => {
guild.members.fetch(user)
.then(user => {
user.setNickname(name)
.then(result => {
return res.json({"result": "updated"});
})
.catch(err => {
res.status(500);
return res.json({"result": "error: " + err.message});
})
})
})
})
app.listen(config.PORT, () => {
console.log(`App listening at http://localhost:${config.PORT}`)
})