-
Notifications
You must be signed in to change notification settings - Fork 1
/
bot.js
436 lines (381 loc) · 17.1 KB
/
bot.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
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
/*
* @Author: BanderDragon
* @Date: 2019-03-10 02:54:40
* @Last Modified by: BanderDragon
* @Last Modified time: 2021-06-03 03:31:15
*/
// Configure the Discord bot client
const Discord = require('discord.js');
const config = require('./config.json');
const configSecret = require('./config-secret.json');
const db = require('./db');
const fs = require('fs');
const cmdLog = './cmdExec.log';
// Set up the library functions
const library = require('./library');
// Set up global access so that all modules can access the library
// TODO - there has to be a better way of doing this:
// - maybe move library into the client? (but this doesn't work for functions who cannot access, unless the client is passed)
// Back-compatibility - leave definition of library variable above, and in other areas of the code.
// The main problem being encountered is when one library module requires functions from other library modules.... I have been
// referencing them individually, as doing require('../../library') is not working
global.library = library;
// Set up the logger for debug/info
const logger = require('winston');
const { WebPush } = require('./web-push/web-push');
global.webPushApp = new WebPush();
global.webPushApp.initialise();
//anotherWebApp.sendTest();
logger.remove(logger.transports.Console);
logger.add(new logger.transports.Console, {
colorize: true
});
logger.level = config.debugLevel;
// Initialize Discord Bot
// token needs to be added to config.json
const client = new Discord.Client({
token: config.token,
autorun: true
});
// Initialise the commands module
client.commands = library.Commands.initialiseCommands(client);
// initialise the cooldowns collection
const cooldowns = new Discord.Collection();
client.ignoreBots = true;
client.ignoreMyself = true;
client.myselfCount = 0;
client.myselfMaximum = 0;
var me = false;
try {
client.deleteCallingCommand = config.deleteCallingCommand
} catch (error) {
logger.info(`Failed to access config.deleteCallingCommand; update your config.json with this member. Error: ${JSON.stringify(error)}`);
client.deleteCallingCommand = false;
}
try {
// Initialise the settings for individual servers - these
// will be stored in the database, but a cached version in the client
// would be much more efficient...
client.myGuildSettings = [];
} catch (error) {
logger.error(`Failed to initialise guild settings, ${JSON.stringify(error)}`);
}
try {
var stream = fs.createWriteStream(cmdLog, {flags:'a'});
client.cmdLogStream = stream;
if(stream == null) {
logger.info(`Unable to initialise file stream for cmdExec.log`);
} else {
stream.write(new Date().toISOString() + ` bot started`) + `/n`;
}
} catch (error) {
logger.info(`Unable to write ${cmdLog}`);
}
//
// Set up the callback functions
//
//
// bot.on ready - used when the bot comes online
//
client.on("ready", () => {
logger.info('Connected');
logger.info('Logged in as: ');
logger.info(client.user.username + ' - (' + client.user.id + ')');
// Replace templated parameters in help text with real data
library.Commands.resolveCommandDescriptions(client);
// Update the bot activity text to reflect the connections status
client.user.setActivity(`${client.guilds.cache.size} guilds | ${config.prefix}datahelp`, { type: 'WATCHING' });
logger.info(`${client.user.username} Bot has started, with ${client.users.cache.size} users, in ${client.channels.size} channels of ${client.guilds.cache.size} guilds.`);
/* We want to ensure our database is created when the bot comes online
* and configure it if they don't exist...
*
* Create the users and guilds tables first, then the dependant tables...
*/
db.users.exists()
.then(data => {
if (data.rows[0].exists == false) {
// Database does not exist, lets create it...
logger.debug(`No database users table found! Creating...`);
db.users.create();
logger.debug(`Users configured.`)
}
});
db.guilds.exists()
.then(data => {
if (data.rows[0].exists == false) {
// Database does not exist, lets create it...
logger.debug(`No database users table found! Creating...`);
db.guilds.create();
logger.debug(`Guilds configured.`)
}
});
db.scores.exists()
.then(data => {
if (data.rows[0].exists == false) {
// Database does not exist, lets create it...
logger.debug(`No database score table found! Creating...`);
db.scores.create();
logger.debug(`Scores configured.`)
}
});
db.userGlobalSettings.exists()
.then(data => {
if(data.rows[0].exists == false) {
// Table does not exists, lets create it...
logger.debug(`No userGlobalSettings table found! Creating...`);
db.userGlobalSettings.create();
logger.debug(`userGlobalSettings configured.`)
}
});
db.userGuildSettings.exists()
.then(data => {
if(data.rows[0].exists == false) {
// Table does not exists, lets create it...
logger.debug(`No userGuildSettings table found! Creating...`);
db.userGuildSettings.create();
logger.debug(`userGuildSettings configured.`)
}
});
db.guildSettings.exists()
.then(data => {
if(data.rows[0].exists == false) {
// Table does not exists, lets create it...
logger.debug(`No guildSettings table found! Creating...`);
db.guildSettings.create();
logger.debug(`guildSettings configured.`)
}
});
/* Populate the database with the guilds we are online in. */
//for (x = 0; x < client.guilds.cache.size; x++) {
client.guilds.cache.forEach((guild) => {
logger.info(`Adding ${guild.name}, id: ${guild.id} to the database`);
db.guilds.add(guild.id).then((guild_record) => {
if (guild_record == null) {
logger.info(`Guild already exists.`);
} else {
logger.info(`Added id: ${guild_record.id} guild id: ${guild_record.guild_id} into the guilds table.`);
}
});
/* Cache the settings for this guild - and upgrade them to the latest version if required */
var settings = library.System.getGuildSettings(guild.id, client)
.then(settings => {
var oldVersion = settings.version;
settings = library.Settings.upgradeGuildSettings(settings);
var newVersion = settings.version;
client.myGuildSettings[`${guild.id}`] = settings;
if (settings.modified) {
library.System.saveGuildSettings(guild.id, settings)
.then(result => {
logger.info(`Upgraded settings JSON for ${guild.name}, ${guild.id}, result: ${!(result == null)}. Old version: ${oldVersion}, new version: ${newVersion}`);
});
}
});
}); // end for
});
//
// guildCreate & guildDelete
// These two events are triggered when the bot joins and leaves
// a guild server...
//
client.on("guildCreate", guild => {
// This event triggers when the bot joins a guild.
logger.info(`New guild joined: ${guild.name} (id: ${guild.id}). This guild has ${guild.memberCount} members!`);
// Update the bot activity text to reflect the new stat
client.user.setActivity(`${client.guilds.cache.size} guilds | ${config.prefix}datahelp`, { type: 'WATCHING' });
db.guilds.add(guild.id)
.then(guild_record => {
if(guild_record) {
logger.info(`Added id: ${guild_record.id} guild id: ${guild_record.guild_id} into the guilds table. ${guild_record.count} records added.`);
} else {
logger.info(`Added a guild - db.guilds.add() has returned a null value - perhaps this record already exists in my Db?`);
}
});
// Notify my owner that there is a new guild
client.users.fetch(library.Admin.botOwnerId())
// client.fetchUser(library.Admin.botOwnerId())
.then(user => {
user.send(`Hey, ${user.username}, ${guild.name} just added me to their server. Their owner is ${guild.owner}, ${guild.owner.user.tag} whose Id is ${guild.owner.id}`);
});
});
client.on("guildDelete", guild => {
// this event triggers when the bot is removed from a guild.
logger.info(`I have been removed from: ${guild.name} (id: ${guild.id})`);
client.users.fetch(library.Admin.botOwnerId())
.then(user => {
user.send(`Hey, ${user.username}, ${guild.name} just removed me from the server. Their owner is ${guild.owner}. ${guild.owner.user.tag}, Id ${guild.owner.id}`);
});
// Update the bot activity text to reflect the new stat
client.user.setActivity(`${client.guilds.cache.size} guilds | ${config.prefix}datahelp`, { type: 'WATCHING' });
// TODO - Remove the database entry for this guild - and all the users for this guild
});
client.on('message', async message => {
// Ignore other bots!
if (client.user.id == message.author.id) {
// I sent this message
me = true;
if (client.ignoreMyself) {
return;
} else if (message.channel.type == 'dm') {
// Always ignore myself in a DM!
return;
} else {
logger.debug(`Not ignoring myself for ${client.myselfCount + 1} command, a maximum of ${client.myselfMaximum}`)
}
} else {
me = false;
if (client.ignoreBots) {
if (message.author.bot) {
return;
}
}
}
// Settings should be cached in the client
var prefix = library.Config.getPrefix();
if (message.guild) {
prefix = await library.System.getPrefix(message.guild.id);
}
if (!(message.content && message.content.startsWith(prefix))) {
// Hack to ensure !showprefix works, regardless of prefix specified
var found = false
if (prefix != config.prefix) {
if(!message.content.startsWith(config.prefix + 'showprefix')) {
var prefixCmd = client.commands.get('showprefix');
for(var i=0; i < prefixCmd.aliases.length; i++) {
if(message.content.startsWith(config.prefix + prefixCmd.aliases[i])) {
found = true;
break;
}
}
} else {
found = true;
}
if(!found && !message.content.startsWith(config.prefix + 'datahelp')) {
var helpCmd = client.commands.get('datahelp');
for(let i=0; i < helpCmd.aliases.length; i++) {
if(message.content.startsWith(config.prefix + helpCmd.aliases[i])) {
found = true;
break;
}
}
} else {
found = true;
}
if(found) {
prefix = config.prefix;
}
}
if(!found) {
if(message.channel.type == 'dm') {
// Messages sent in DM cannot use custom command prefix
// so we must feedback this to the user
return library.Helper.sendErrorMessage(`${message.author}, I cannot understand your request. Please note, when sending commands to ${library.Config.botName(message.client)} via direct messages, you cannot use your server's custom command prefix.\n\nDuring direct messages you should use my default prefix, which is **${library.Config.getPrefix()}**\nFor example: *${library.Config.getPrefix()}datahelp*`, message.channel);
}
// Not prefixed, do not continue
return;
}
}
// split up the message into the command word and any additional arguements
const args = message.content.slice(prefix.length).trim().split(/ +/g);
const cmdName = args.shift().toLowerCase();
// get the specified command name
const cmd = client.commands.get(cmdName)
|| client.commands.find(command => command.aliases && command.aliases.includes(cmdName));
// If no command exists, simply exit
if (!cmd) return;
// Some commands are not meant to be executed inside DMs
if (cmd.guildOnly && message.channel.type !== 'text') {
return message.reply('I cannot execute that command inside a direct message!');
}
// If the command requires arguments yet there are none, provide
// the user with a correct explanation.
if (cmd.args && !args.length) {
let reply = `You didn't provide any arguments, ${message.author}!`;
if (cmd.usage) {
reply += `\nThe proper usage would be: \`${prefix}${cmd.name} ${cmd.usage}\``;
}
return library.Helper.sendErrorMessage(reply, message.channel);
}
// Check if a specified role is required
if(cmd.role) {
let allowedRole = message.guild.roles.find("name", cmd.role);
if(!message.member.roles.has(allowedRole.id)) {
return message.channel.send(`You cannot use this command, only members of *${cmd.role}* can use this command.`)
}
}
// Check if a specified channel is required
if(cmd.channel) {
if(message.channel.name != cmd.channel) {
return message.channel.send(`You can only use this command in the *${cmd.channel}* channel.`);
}
}
// Check for cooldowns status
if (!cooldowns.has(cmd.name)) {
cooldowns.set(cmd.name, new Discord.Collection());
}
const now = Date.now();
const timestamps = cooldowns.get(cmd.name);
const cooldownAmount = (cmd.cooldown || 3) * 1000;
if (timestamps.has(message.author.id)) {
const expirationTime = timestamps.get(message.author.id) + cooldownAmount;
if (now < expirationTime) {
//const timeLeft = (expirationTime - now) / 1000;
//return message.reply(`please wait ${timeLeft.toFixed(1)} more second(s) before reusing the \`${cmd.name}\` command.`);
}
}
timestamps.set(message.author.id, now);
setTimeout(() => timestamps.delete(message.author.id), cooldownAmount);
// try to execute the command
// and exit gracefully on error
try {
if (me) {
client.myselfCount++;
if (client.myselfCount == client.myselfMaximum) {
client.ignoreMyself = true;
client.myselfCount = 0;
client.myselfMaximum = 0;
}
}
if(client.cmdLogStream == null) {
logger.info("No open logfile stream");
client.cmdLogStream = fs.createWriteStream(cmdLog, {flags:'a'});
}
try {
var nowDateTimeStr = new Date().toISOString();
logger.debug(`${nowDateTimeStr} - Writing to log file...`);
client.cmdLogStream.write(nowDateTimeStr + ':' + "\n");
client.cmdLogStream.write(" " + `executing ${cmd.name}` + "\n");
client.cmdLogStream.write(" " + `called by ${message.author.username}` + "\n\n");
} catch (e) {
logger.error(e);
}
cmd.execute(message, args);
if (client.deleteCallingCommand) {
message.delete;
}
} catch (error) {
let errorJSON = library.Format.stringifyError(error);
let stack = error.stack.toString();
logger.error(errorJSON);
//message.reply('there was an error trying to execute that command!');
if(logger.level === 'silly' || logger.level === 'debug' || logger.level === 'error') {
const embed = new Discord.MessageEmbed()
.setTitle(`${error.name} : Unexpected Error (${error.name}) in the bagging area`)
.setAuthor(message.client.user.username, message.client.user.avatarURL())
.setDescription(`An unexpected error occurred : ${error.message}`)
.setColor(config.hostilestotaldamageColor);
embed.addField("Stack trace:", `length: ${stack.length} characters`);
for(let i = 0; i < Math.ceil(stack.length / 255); i++) {
let start = i * 256;
let end = start + 256;
if (end > stack.length) {
end = stack.length;
}
embed.addField(`${stack.substring(start, end)}`, `${start}, ${end}`);
}
message.channel.send({ embed });
}
}
});
// Start the client!
//client.login(process.env.BOT_TOKEN);
client.login(configSecret.token);