2017-09-30 01:21:48 +02:00
|
|
|
// Check for environmental variables.
|
|
|
|
require('checkenv').check();
|
|
|
|
|
2020-05-02 07:35:33 +02:00
|
|
|
import discord = require('discord.js');
|
|
|
|
import path = require('path');
|
|
|
|
// const schedule = require('node-schedule');
|
|
|
|
import fs = require('fs');
|
2016-12-08 04:52:37 +01:00
|
|
|
|
2020-05-02 07:35:33 +02:00
|
|
|
import logger from './logging';
|
|
|
|
import state from './state';
|
|
|
|
import * as data from './data';
|
2020-05-24 08:22:06 +02:00
|
|
|
import { IModule, ITrigger } from './models/interfaces';
|
2016-12-08 04:52:37 +01:00
|
|
|
|
2018-04-04 01:28:33 +02:00
|
|
|
state.responses = require('./responses.json');
|
2017-09-30 01:21:48 +02:00
|
|
|
|
2020-05-02 07:35:33 +02:00
|
|
|
interface IModuleMap {
|
2020-05-24 08:22:06 +02:00
|
|
|
[name: string]: IModule;
|
2020-05-02 07:35:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
let cachedModules: IModuleMap = {};
|
2020-05-24 08:22:06 +02:00
|
|
|
let cachedTriggers: ITrigger[] = [];
|
2019-07-22 02:56:54 +02:00
|
|
|
const client = new discord.Client();
|
2020-05-03 04:17:51 +02:00
|
|
|
const rulesTrigger = process.env.DISCORD_RULES_TRIGGER;
|
|
|
|
const rluesRole = process.env.DISCORD_RULES_ROLE;
|
2020-05-02 07:35:33 +02:00
|
|
|
const mediaUsers = new Map();
|
2019-07-22 02:55:55 +02:00
|
|
|
|
2018-04-04 01:28:33 +02:00
|
|
|
logger.info('Application startup. Configuring environment.');
|
2020-05-03 04:17:51 +02:00
|
|
|
if (!rulesTrigger) {
|
|
|
|
throw new Error('DISCORD_RULES_TRIGGER somehow became undefined.');
|
|
|
|
}
|
|
|
|
if (!rluesRole) {
|
|
|
|
throw new Error('DISCORD_RULES_ROLE somehow became undefined.');
|
|
|
|
}
|
2018-04-04 01:28:33 +02:00
|
|
|
|
2020-05-02 07:35:33 +02:00
|
|
|
function findArray(haystack: string | any[], arr: any[]) {
|
|
|
|
return arr.some(function (v: any) {
|
2017-09-30 01:38:00 +02:00
|
|
|
return haystack.indexOf(v) >= 0;
|
|
|
|
});
|
|
|
|
}
|
2016-12-08 04:52:37 +01:00
|
|
|
|
2020-05-02 07:35:33 +02:00
|
|
|
function IsIgnoredCategory(categoryName: string) {
|
2019-08-16 08:51:51 +02:00
|
|
|
const IgnoredCategory = ['welcome', 'team', 'website-team'];
|
|
|
|
return IgnoredCategory.includes(categoryName);
|
2019-08-12 09:44:22 +02:00
|
|
|
}
|
|
|
|
|
2020-05-02 07:35:33 +02:00
|
|
|
client.on('ready', async () => {
|
2019-07-22 02:56:54 +02:00
|
|
|
// Initialize app channels.
|
2020-05-03 04:17:51 +02:00
|
|
|
if (!process.env.DISCORD_LOG_CHANNEL || !process.env.DISCORD_MSGLOG_CHANNEL) {
|
|
|
|
throw new Error('DISCORD_LOG_CHANNEL or DISCORD_MSGLOG_CHANNEL not defined.');
|
|
|
|
}
|
2020-05-02 07:35:33 +02:00
|
|
|
let logChannel = await client.channels.fetch(process.env.DISCORD_LOG_CHANNEL) as discord.TextChannel;
|
|
|
|
let msglogChannel = await client.channels.fetch(process.env.DISCORD_MSGLOG_CHANNEL) as discord.TextChannel;
|
|
|
|
if (!logChannel.send) throw new Error('DISCORD_LOG_CHANNEL is not a text channel!');
|
|
|
|
if (!msglogChannel.send) throw new Error('DISCORD_MSGLOG_CHANNEL is not a text channel!');
|
|
|
|
state.logChannel = logChannel;
|
|
|
|
state.msglogChannel = msglogChannel;
|
2016-12-08 04:52:37 +01:00
|
|
|
|
2017-03-31 03:17:21 +02:00
|
|
|
logger.info('Bot is now online and connected to server.');
|
2016-12-08 04:52:37 +01:00
|
|
|
});
|
|
|
|
|
2018-09-07 03:10:21 +02:00
|
|
|
client.on('error', (x) => {
|
2019-07-22 02:56:54 +02:00
|
|
|
logger.error(x);
|
|
|
|
logger.error('Restarting process.');
|
|
|
|
process.exit(1);
|
|
|
|
});
|
2018-09-07 03:10:21 +02:00
|
|
|
client.on('warn', (x) => {
|
2019-07-22 02:56:54 +02:00
|
|
|
logger.warn(x);
|
|
|
|
});
|
2018-09-07 03:10:21 +02:00
|
|
|
|
2019-07-22 02:56:54 +02:00
|
|
|
client.on('debug', (x) => null);
|
2018-09-07 03:10:21 +02:00
|
|
|
|
|
|
|
client.on('disconnect', () => {
|
2019-07-22 02:56:54 +02:00
|
|
|
logger.warn('Disconnected from Discord server.');
|
|
|
|
});
|
2018-09-07 03:10:21 +02:00
|
|
|
|
2017-09-30 01:38:00 +02:00
|
|
|
client.on('guildMemberAdd', (member) => {
|
2020-05-03 04:17:51 +02:00
|
|
|
if (process.env.DISCORD_RULES_ROLE)
|
|
|
|
member.roles.add(process.env.DISCORD_RULES_ROLE);
|
2017-07-20 03:45:58 +02:00
|
|
|
});
|
|
|
|
|
2019-08-09 20:26:55 +02:00
|
|
|
client.on('messageDelete', message => {
|
2020-05-02 07:35:33 +02:00
|
|
|
let parent = (message.channel as discord.TextChannel).parent;
|
|
|
|
if (parent && IsIgnoredCategory(parent.name) === false) {
|
2020-05-03 04:17:51 +02:00
|
|
|
if (message.content && message.content.startsWith('.') === false && message.author?.bot === false) {
|
2020-05-02 07:35:33 +02:00
|
|
|
const deletionEmbed = new discord.MessageEmbed()
|
2020-05-03 04:17:51 +02:00
|
|
|
.setAuthor(message.author?.tag, message.author?.displayAvatarURL())
|
2020-05-02 09:23:12 +02:00
|
|
|
.setDescription(`Message deleted in ${message.channel.toString()}`)
|
2019-08-12 09:44:22 +02:00
|
|
|
.addField('Content', message.cleanContent, false)
|
|
|
|
.setTimestamp()
|
|
|
|
.setColor('RED');
|
|
|
|
|
|
|
|
state.msglogChannel.send(deletionEmbed);
|
2020-07-27 05:42:40 +02:00
|
|
|
logger.info(`${message.author?.username} ${message.author} deleted message: ${message.cleanContent}.`);
|
2019-08-12 09:44:22 +02:00
|
|
|
}
|
2019-08-09 20:26:55 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
client.on('messageUpdate', (oldMessage, newMessage) => {
|
2019-08-16 08:51:51 +02:00
|
|
|
const AllowedRoles = ['Administrators', 'Moderators', 'Team', 'VIP'];
|
2020-05-03 04:17:51 +02:00
|
|
|
let authorRoles = oldMessage.member?.roles?.cache?.map(x => x.name);
|
|
|
|
if (!authorRoles) {
|
|
|
|
logger.error(`Unable to get the roles for ${oldMessage.author}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!findArray(authorRoles, AllowedRoles)) {
|
2020-05-02 07:35:33 +02:00
|
|
|
let parent = (oldMessage.channel as discord.TextChannel).parent;
|
|
|
|
if (parent && IsIgnoredCategory(parent.name) === false) {
|
2019-08-16 08:51:51 +02:00
|
|
|
const oldM = oldMessage.cleanContent;
|
|
|
|
const newM = newMessage.cleanContent;
|
2020-05-02 07:35:33 +02:00
|
|
|
if (oldMessage.content !== newMessage.content && oldM && newM) {
|
|
|
|
const editedEmbed = new discord.MessageEmbed()
|
2020-05-03 04:17:51 +02:00
|
|
|
.setAuthor(oldMessage.author?.tag, oldMessage.author?.displayAvatarURL())
|
2020-05-02 09:23:12 +02:00
|
|
|
.setDescription(`Message edited in ${oldMessage.channel.toString()} [Jump To Message](${newMessage.url})`)
|
2019-08-16 08:51:51 +02:00
|
|
|
.addField('Before', oldM, false)
|
|
|
|
.addField('After', newM, false)
|
|
|
|
.setTimestamp()
|
|
|
|
.setColor('GREEN');
|
|
|
|
|
|
|
|
state.msglogChannel.send(editedEmbed);
|
2020-05-03 04:17:51 +02:00
|
|
|
logger.info(`${oldMessage.author?.username} ${oldMessage.author} edited message from: ${oldM} to: ${newM}.`);
|
2019-08-16 08:51:51 +02:00
|
|
|
}
|
2019-08-12 09:44:22 +02:00
|
|
|
}
|
2019-08-09 20:26:55 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2016-12-08 04:52:37 +01:00
|
|
|
client.on('message', message => {
|
2017-09-30 01:38:00 +02:00
|
|
|
if (message.author.bot && message.content.startsWith('.ban') === false) { return; }
|
2016-12-08 04:52:37 +01:00
|
|
|
|
2018-01-20 05:29:33 +01:00
|
|
|
if (message.guild == null && state.responses.pmReply) {
|
2016-12-08 04:52:37 +01:00
|
|
|
// We want to log PM attempts.
|
|
|
|
logger.info(`${message.author.username} ${message.author} [PM]: ${message.content}`);
|
2020-05-02 09:23:12 +02:00
|
|
|
state.logChannel.send(`${message.author.toString()} [PM]: ${message.content}`);
|
2018-01-20 05:29:33 +01:00
|
|
|
message.reply(state.responses.pmReply);
|
2016-12-08 04:52:37 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-05-02 07:35:33 +02:00
|
|
|
logger.verbose(`${message.author.username} ${message.author} [Channel: ${(message.channel as discord.TextChannel).name} ${message.channel}]: ${message.content}`);
|
2016-12-08 04:52:37 +01:00
|
|
|
|
2020-05-03 04:17:51 +02:00
|
|
|
let authorRoles = message.member?.roles?.cache?.map(x => x.name);
|
|
|
|
|
2019-07-22 02:55:55 +02:00
|
|
|
if (message.channel.id === process.env.DISCORD_MEDIA_CHANNEL && !message.author.bot) {
|
|
|
|
const AllowedMediaRoles = ['Administrators', 'Moderators', 'Team', 'VIP'];
|
2020-05-03 04:17:51 +02:00
|
|
|
if (!authorRoles) {
|
|
|
|
logger.error(`Unable to get the roles for ${message.author}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (!findArray(authorRoles, AllowedMediaRoles)) {
|
2020-07-27 05:47:27 +02:00
|
|
|
const urlRegex = new RegExp(/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,4}\b([-a-zA-Z0-9@:%_+.~#?&\/=]*)/gi);
|
2019-07-22 02:55:55 +02:00
|
|
|
if (message.attachments.size > 0 || message.content.match(urlRegex)) {
|
|
|
|
mediaUsers.set(message.author.id, true);
|
|
|
|
} else if (mediaUsers.get(message.author.id)) {
|
|
|
|
mediaUsers.set(message.author.id, false);
|
|
|
|
} else {
|
|
|
|
message.delete();
|
|
|
|
mediaUsers.set(message.author.id, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-28 01:19:14 +01:00
|
|
|
// Check if the channel is #rules, if so we want to follow a different logic flow.
|
|
|
|
if (message.channel.id === process.env.DISCORD_RULES_CHANNEL) {
|
2020-05-03 04:17:51 +02:00
|
|
|
if (message.content.toLowerCase().includes(rulesTrigger)) {
|
2018-02-28 01:19:14 +01:00
|
|
|
// We want to remove the 'Unauthorized' role from them once they agree to the rules.
|
|
|
|
logger.verbose(`${message.author.username} ${message.author} has accepted the rules, removing role ${process.env.DISCORD_RULES_ROLE}.`);
|
2020-05-03 04:17:51 +02:00
|
|
|
message.member?.roles.remove(rluesRole, 'Accepted the rules.');
|
2018-02-28 01:19:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Delete the message in the channel to force a cleanup.
|
|
|
|
message.delete();
|
|
|
|
} else if (message.content.startsWith('.') && message.content.startsWith('..') === false) {
|
|
|
|
// We want to make sure it's an actual command, not someone '...'-ing.
|
2020-05-02 07:35:33 +02:00
|
|
|
const cmd = message.content.split(' ', 1)[0].slice(1);
|
2016-12-08 04:52:37 +01:00
|
|
|
|
|
|
|
// Check by the name of the command.
|
2020-05-03 01:15:06 +02:00
|
|
|
let cachedModule = cachedModules[`${cmd.toLowerCase()}`];
|
2020-05-02 22:40:16 +02:00
|
|
|
let quoteResponse = null;
|
2016-12-08 04:52:37 +01:00
|
|
|
// Check by the quotes in the configuration.
|
2020-05-02 22:40:16 +02:00
|
|
|
if (!cachedModule) quoteResponse = state.responses.quotes[cmd];
|
|
|
|
if (!cachedModule && !quoteResponse) return; // Not a valid command.
|
2016-12-08 04:52:37 +01:00
|
|
|
|
2020-05-02 07:35:33 +02:00
|
|
|
// Check access permissions.
|
2020-05-03 04:17:51 +02:00
|
|
|
if (!authorRoles) {
|
|
|
|
logger.error(`Unable to get the roles for ${message.author}`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (cachedModule && cachedModule.roles && !findArray(authorRoles, cachedModule.roles)) {
|
2020-05-02 09:23:12 +02:00
|
|
|
state.logChannel.send(`${message.author.toString()} attempted to use admin command: ${message.content}`);
|
2020-05-02 07:35:33 +02:00
|
|
|
logger.info(`${message.author.username} ${message.author} attempted to use admin command: ${message.content}`);
|
2020-05-02 22:40:16 +02:00
|
|
|
return;
|
2020-05-02 07:35:33 +02:00
|
|
|
}
|
2016-12-08 04:52:37 +01:00
|
|
|
|
2020-05-02 07:35:33 +02:00
|
|
|
logger.info(`${message.author.username} ${message.author} [Channel: ${message.channel}] executed command: ${message.content}`);
|
|
|
|
message.delete();
|
|
|
|
|
|
|
|
try {
|
2020-05-02 22:40:16 +02:00
|
|
|
if (!!cachedModule) {
|
2020-05-02 07:35:33 +02:00
|
|
|
cachedModule.command(message);
|
2020-05-03 01:15:06 +02:00
|
|
|
} else if (cachedModules['quote']) {
|
2020-05-03 04:17:51 +02:00
|
|
|
cachedModules['quote'].command(message, quoteResponse?.reply);
|
2020-05-02 07:35:33 +02:00
|
|
|
}
|
|
|
|
} catch (err) { logger.error(err); }
|
|
|
|
|
2020-05-24 08:22:06 +02:00
|
|
|
} else if (message.author.bot === false) {
|
|
|
|
// This is a normal channel message.
|
|
|
|
cachedTriggers.forEach(function (trigger) {
|
|
|
|
if (!trigger.roles || authorRoles && findArray(authorRoles, trigger.roles)) {
|
|
|
|
if (trigger.trigger(message) === true) {
|
|
|
|
logger.debug(`${message.author.username} ${message.author} [Channel: ${message.channel}] triggered: ${message.content}`);
|
|
|
|
try {
|
|
|
|
trigger.execute(message);
|
|
|
|
} catch (err) { logger.error(err); }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2016-12-08 04:52:37 +01:00
|
|
|
}
|
2017-03-31 03:17:21 +02:00
|
|
|
});
|
2016-12-08 04:52:37 +01:00
|
|
|
|
2017-03-31 03:17:21 +02:00
|
|
|
// Cache all command modules.
|
2020-05-02 07:35:33 +02:00
|
|
|
cachedModules = {};
|
|
|
|
fs.readdirSync('./commands/').forEach(function (file) {
|
2017-03-31 03:17:21 +02:00
|
|
|
// Load the module if it's a script.
|
2017-09-30 01:38:00 +02:00
|
|
|
if (path.extname(file) === '.js') {
|
2017-04-08 01:36:45 +02:00
|
|
|
if (file.includes('.disabled')) {
|
|
|
|
logger.info(`Did not load disabled module: ${file}`);
|
|
|
|
} else {
|
2020-05-03 01:15:06 +02:00
|
|
|
const moduleName = path.basename(file, '.js').toLowerCase();
|
|
|
|
logger.info(`Loaded module: ${moduleName} from ${file}`);
|
|
|
|
cachedModules[moduleName] = require(`./commands/${file}`);
|
2017-04-08 01:36:45 +02:00
|
|
|
}
|
2017-03-31 03:17:21 +02:00
|
|
|
}
|
2016-12-08 04:52:37 +01:00
|
|
|
});
|
|
|
|
|
2020-05-24 08:22:06 +02:00
|
|
|
// Cache all triggers.
|
|
|
|
cachedTriggers = [];
|
|
|
|
fs.readdirSync('./triggers/').forEach(function (file) {
|
|
|
|
// Load the module if it's a script.
|
|
|
|
if (path.extname(file) === '.js') {
|
|
|
|
if (file.includes('.disabled')) {
|
|
|
|
logger.info(`Did not load disabled trigger: ${file}`);
|
|
|
|
} else {
|
|
|
|
const moduleName = path.basename(file, '.js').toLowerCase();
|
|
|
|
logger.info(`Loaded trigger: ${moduleName} from ${file}`);
|
|
|
|
try {
|
|
|
|
cachedTriggers.push(require(`./triggers/${file}`));
|
|
|
|
} catch (e) {
|
|
|
|
logger.error(`Could not load trigger ${moduleName}: ${e}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-04-04 01:28:33 +02:00
|
|
|
data.readWarnings();
|
|
|
|
data.readBans();
|
|
|
|
|
|
|
|
// Load custom responses
|
|
|
|
if (process.env.DATA_CUSTOM_RESPONSES) {
|
|
|
|
data.readCustomResponses();
|
|
|
|
}
|
|
|
|
|
2017-09-30 01:21:48 +02:00
|
|
|
client.login(process.env.DISCORD_LOGIN_TOKEN);
|
2017-09-30 01:38:00 +02:00
|
|
|
logger.info('Startup completed. Established connection to Discord.');
|