Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
387 lines (249 sloc) 9.41 KB
import * as Discord from 'discord.js';
import { GuildMember, RichEmbed } from 'discord.js';
import * as dotenv from 'dotenv';
import * as glob from 'glob';
import { ReplaySubject } from 'rxjs';
import { CommandBase } from './CommandBase';
import { CommandParser, MESSAGE_TYPE } from './CommandParser';
import { DB } from './DB';
import { Event } from './Event';
import { EVENT_OBJECT } from './EventObjectType';
import { Logger } from './Logger';
//
// Load .env into process.env
//
dotenv.config();
class Bot {
public readonly EVENT_DISCORD_CONNECTED = 'EVENT_DISCORD_CONNECTED';
public readonly EVENT_DISCORD_DISCONNECTED = 'EVENT_DISCORD_DISCONNECTED';
/**
* Used for passing events to/from the bot context.
*/
public events$: ReplaySubject<{ name: string, payload?: any }> = new ReplaySubject(1);
/**
* Discord.js Client
*/
public client: Discord.Client = new Discord.Client();
/**
* Array of Command Class References
*/
public commands: Array<CommandBase> = [];
/**
* Array of Database Entities
*/
private entities: Array<any> = [];
/**
* Current working directory where the bot started.
*/
public currentPath: string;
/**
* Called by the @Command decorated classes
*
* @param commandRef Command class reference.
*/
public register(commandRef: CommandBase): void {
if (commandRef.config) {
this.commands.push(commandRef);
//
// Check for required .env variables.
//
if (commandRef.config.requiredEnvVars) {
const errors: Array<string> = [];
commandRef.config.requiredEnvVars.forEach(env => {
if (!process.env[ env ]) {
errors.push(env);
}
});
if (errors.length > 0) {
throw `The following environment variables are missing: ${ errors.join(', ') }! Add them to your .env file!`;
}
}
if (commandRef.config.entities) {
this.entities = [ ...this.entities, commandRef.config.entities ];
}
Logger.log(`Command Registered: ${ commandRef.config.name } (${ commandRef.config.description })`);
}
}
/**
* Start the bot.
*/
public async start(currentPath: string) {
this.currentPath = currentPath;
//
// Bind discord.js events
//
this.client.on('message', (message: MESSAGE_TYPE) => this.handleMessage(Event.MESSAGE, message));
this.client.on('guildMemberAdd', (guildMember: GuildMember) => this.handleEvent(Event.GUILD_MEMBER_ADD, guildMember));
Logger.log('Logging into discord');
this.client.login(process.env.TOKEN).then(() => {
Logger.log('Connected to discord');
this.events$.next({
name: this.EVENT_DISCORD_CONNECTED
});
});
//
// Load Module Classes
//
await glob(`${ currentPath }/node_modules/${ process.env.NAMESPACE }/${ process.env.MODULE_PATTERN }`, (err: any, commands: any) => {
commands.map((command: any) => {
Logger.log(`Bootstrapping ${ command }`);
commands.map((command: CommandBase) => require(command.toString()));
});
//
// (optionaly) Connect to database
//
if (process.env.MYSQL_HOST && this.entities.length > 0) {
DB.connect();
}
Logger.log('Bot Started');
});
}
/**
* Handle an incoming event.
*
* @param event Event type.
* @param eventobject Event Object from discord.js.
*/
public handleEvent(event: Event, eventobject: EVENT_OBJECT): void {
this.runEvent(event, eventobject);
}
/*
* Handle an incoming message.
*
* @param event Event type.
* @param Message Discord message obj.
*
*/
public handleMessage(event: Event, message: MESSAGE_TYPE): void {
//
// Prevent the bot from talking to itself (this would cause an endless loop).
//
if (!message.author.bot) {
const command = new CommandParser(message);
//
// Run the preCommand validation checks before executing runCommand.
//
if (this.preCommand(event, command)) {
this.runCommand(event, command);
}
}
}
/*
* Perform validation checks.
*
* @param CommandParser commandParser The parsed command.
*
*/
public preCommand(event: Event, parsedCommand: CommandParser): boolean {
const command: CommandBase = this.getCommandByName(parsedCommand.command);
if (command) {
let errors: string[] = [];
//
// Roles Validation
//
if (command.config.roles) {
let roleMatched = false;
for (let i = 0; i < command.config.roles.length; i++) {
if (parsedCommand.obj.member.roles.find(role => role.name === command.config.roles[ i ])) {
roleMatched = true;
break;
}
}
if (!roleMatched) {
errors.push('You do not have the required role to run this command.');
}
}
//
// Parameter Validation
//
if (command.config.params) {
for (let i = 0; i < command.config.params.length; i++) {
const argument = parsedCommand.getArgumentByName(command.config.params[ i ].name);
if (command.config.params[ i ].required && !argument) {
errors.push(`The parameter "${ command.config.params[ i ].name }" (${ command.config.params[ i ].description }) is required.`);
} else {
//
// Check to see if the argument passed exists in the commands accetpable parameters.
//
if (argument) {
const regex = new RegExp(command.config.params[ i ].pattern);
if (!argument.value.match(regex)) {
errors.push(`The parameter "${ command.config.params[ i ].name }" with the value "${ argument.value }" is invalid (Acceptable pattern: ${ command.config.params[ i ].pattern }).`);
}
}
}
}
}
//
// Send the error messages back to the user.
//
if (errors.length > 0) {
parsedCommand.obj.reply(new RichEmbed().setTitle('The following errors ocurred:')
.setDescription(errors.join("\n\t")));
return false;
} else {
return true;
}
}
//
// Return true by default which means the command did not have any validations.
//
return true;
}
/*
* Run a command
*
* @param string commandName Name of the command to run.
*
*/
public runCommand(event: Event, command: CommandParser): void {
for (let i = 0; i < this.commands.length; i++) {
if (this.commands[ i ].config.event === event && this.commands[ i ].config.pattern) {
if (command.command.match(new RegExp(this.commands[ i ].config.pattern))) {
console.log(`Running Pattern Command: ${ this.commands[ i ].config.pattern }`);
this.commands[ i ].run(command);
}
} else {
if (this.commands[ i ].config.event === event && this.commands[ i ].config.name === command.command || this.commands[ i ].config.name === '*') {
console.log(`Running Command: ${ this.commands[ i ].config.name }`);
this.commands[ i ].run(command);
}
}
}
}
/**
* Run a command based on an event.
*
* @param event Event type.
* @param eventobject Event object.
*/
public runEvent(event: Event, eventobject: EVENT_OBJECT): void {
const command = this.getCommandByEvent(event);
if (command) {
command.run(eventobject);
}
}
/**
* Retrieves a registered command by it's name.
*
* @param name Name of the command.
*/
public getCommandByName(name: string): CommandBase {
for (let i = 0; i < this.commands.length; i++) {
if (this.commands[ i ].config.name === name) {
return this.commands[ i ];
}
}
}
/**
* Retrieves a registered command by it's event type.
*/
public getCommandByEvent(event: Event): CommandBase {
for (let i = 0; i < this.commands.length; i++) {
if (this.commands[ i ].config.event === event) {
return this.commands[ i ];
}
}
}
}
export const BOT = new Bot();
You can’t perform that action at this time.