forked from Galactic/galactic-bot
264 lines
7.9 KiB
JavaScript
264 lines
7.9 KiB
JavaScript
const { stripIndents } = require('common-tags');
|
|
const { escapeRegex } = require('escape-string-regexp');
|
|
|
|
const { Observer } = require('../../../interfaces/');
|
|
|
|
class CommandHandler extends Observer {
|
|
|
|
constructor(client) {
|
|
|
|
super(client, {
|
|
name: 'commandHandler',
|
|
priority: 5,
|
|
guarded: true
|
|
});
|
|
|
|
this.client = client;
|
|
|
|
this.hooks = [
|
|
['message', this.handleMessage.bind(this)]
|
|
];
|
|
|
|
|
|
}
|
|
|
|
async handleMessage(message) {
|
|
|
|
if(!this.client._built
|
|
|| message.webhookID
|
|
|| message.author.bot
|
|
|| (message.guild && !message.guild.available)) return undefined;
|
|
|
|
if(message.guild && !message.member) {
|
|
await message.guild.members.fetch(message.author.id);
|
|
}
|
|
|
|
const content = message.content;
|
|
const args = content.split(' ');
|
|
const { command, newArgs } = await this._getCommand(message, args);
|
|
if(!command) return undefined;
|
|
|
|
message.command = command;
|
|
|
|
return await this.handleCommand(message, newArgs);
|
|
|
|
}
|
|
|
|
async _getCommand(message, [arg1, arg2, ...args]) {
|
|
|
|
const prefix = this.client._options.bot.prefix; //Change this for guild prefix settings.
|
|
let command = null;
|
|
let remains = [];
|
|
|
|
if(arg1 && arg1.startsWith(prefix)) {
|
|
const commandName = arg1.slice(1);
|
|
command = await this._matchCommand(message, commandName);
|
|
remains = [arg2, ...args];
|
|
} else if(arg1 && arg2 && arg1.startsWith('<@')){
|
|
const pattern = new RegExp(`^(<@!?${this.client.user.id}>)`, 'i');
|
|
if(arg2 && pattern.test(arg1)) {
|
|
command = await this._matchCommand(message, arg2);
|
|
}
|
|
remains = args
|
|
}
|
|
|
|
return { command, newArgs: remains };
|
|
|
|
}
|
|
|
|
|
|
async _matchCommand(message, commandName) {
|
|
|
|
const command = this.client.resolver.components(commandName, 'command', true)[0];
|
|
if(!command) return null;
|
|
|
|
//Eventually search for custom commands here.
|
|
|
|
return command;
|
|
|
|
}
|
|
|
|
/* Command Handling */
|
|
|
|
async handleCommand(message, args) {
|
|
|
|
const inhibitor = await this._handleInhibitors(message);
|
|
if(inhibitor.error) {
|
|
message.channel.send(`${inhibitor.resolveable} failed to pass.`);
|
|
return undefined;
|
|
}
|
|
|
|
const parsedArguments = await this._parseArguments(message, args);
|
|
|
|
|
|
|
|
}
|
|
|
|
async _handleInhibitors(message) {
|
|
|
|
const inhibitors = this.client.registry.components.filter(c=>c.type === 'inhibitor' && !c.disabled);
|
|
if(inhibitors.size === 0) return { error: false };
|
|
|
|
const promises = [];
|
|
for(const inhibitor of inhibitors.values()) {
|
|
if(inhibitor.guild && !message.guild) continue;
|
|
promises.push((async () => {
|
|
let inhibited = inhibitor.execute(message);
|
|
if(inhibited instanceof Promise) inhibited = await inhibited;
|
|
return inhibited;
|
|
})());
|
|
}
|
|
|
|
const reasons = (await Promise.all(promises)).filter(p=>p.error);
|
|
if(reasons.length === 0) return { error: false };
|
|
|
|
reasons.sort((a, b) => b.inhibitor.priority - a.inhibitor.priority);
|
|
return reasons[0];
|
|
|
|
}
|
|
|
|
async _parseArguments(message, args = []) {
|
|
|
|
const command = message.command;
|
|
let parsedArguments = [];
|
|
const parsedFlags = {};
|
|
|
|
const { shortFlags, longFlags, keys } = await this._createFlags(command.arguments);
|
|
|
|
// -prune 50 -c #test
|
|
// -prune 50 -b -c #test
|
|
|
|
let currentArgument = null;
|
|
for(const word of args) {
|
|
const [one,two,...chars] = word.split('');
|
|
if(one === '-' && two !== '-') { //short flag maybe?
|
|
const name = [ two, ...chars ].join('');
|
|
if(currentArgument) {
|
|
if(currentArgument.required) {
|
|
//Cannot use another flag after a required flag.
|
|
//Handle requirement error for currentArgument.
|
|
}
|
|
}
|
|
if(!keys.includes(name)) continue;
|
|
currentArgument = shortFlags[name];
|
|
|
|
} else if(one === '-' && two === '-') { //long flag maybe?
|
|
if(currentArgument.required) {
|
|
//Cannot use another flag after a required flag.
|
|
//Handle requirement error for currentArgument.
|
|
}
|
|
const name = chars.join('');
|
|
if(!keys.includes(name)) continue;
|
|
currentArgument = longFlags[name];
|
|
|
|
} else {
|
|
const regex = new RegExp('([0-9]*)([a-zA-Z]+)([0-9]*)', 'g');
|
|
let match = regex.exec(word);
|
|
if(currentArgument && !match) {
|
|
await this._handleTypeParsing(currentArgument, word);
|
|
if(!currentArgument.infinite) currentArgument = null;
|
|
}
|
|
if(!keys[match[2]]) {
|
|
//do sometihng there
|
|
}
|
|
currentArgument = longFlags[match[2]];
|
|
const value = match[1] || match[3];
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
async _handleTypeParsing(argument, string) {
|
|
|
|
const { error, value } = await this.constructor.parseType(argument, string); //Cannot access static functions through "this".
|
|
if(error) {
|
|
//Failed to parse correctly. prompt.invalid
|
|
}
|
|
|
|
if(['INTEGER', 'FLOAT'].includes(argument.type)) {
|
|
const { min, max } = argument;
|
|
if(value > max) {
|
|
//Failed to parse correctly. prompt.invalid
|
|
}
|
|
if(value < min) {
|
|
//Failed to parse correctly. prompt.invalid
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
async _createFlags(args) {
|
|
|
|
let shortFlags = {};
|
|
let longFlags = {};
|
|
let keys = [];
|
|
|
|
for(const arg of args) {
|
|
|
|
let letters = [];
|
|
let names = [ arg.name, ...arg.aliases ];
|
|
keys = [...keys, ...names];
|
|
|
|
for(const name of names) {
|
|
longFlags[name] = arg;
|
|
|
|
if(!arg.types.includes('FLAG')) continue;
|
|
|
|
let letter = name.slice(0, 1);
|
|
if(letters.includes(letter)) continue;
|
|
if(keys.includes(letter)) letter = letter.toUpperCase();
|
|
if(keys.includes(letter)) break;
|
|
|
|
keys.push(letter);
|
|
letters.push(letter);
|
|
|
|
shortFlags[letter] = arg;
|
|
}
|
|
|
|
}
|
|
|
|
return { shortFlags, longFlags, keys };
|
|
|
|
}
|
|
|
|
static async parseType(type, str) { //this is in the class for a reason, will soon reference to a user resolver etc.
|
|
|
|
//INTEGER AND FLOAT ARE SAME FUNCTION
|
|
|
|
const types = {
|
|
STRING: (str) => {
|
|
return { error: false, value: `${str}` };
|
|
},
|
|
INTEGER: (str) => {
|
|
const int = parseInt(str);
|
|
if(Number.isNaN(int)) return { error: true }
|
|
return { error: false, value: int };
|
|
},
|
|
FLOAT: (str) => {
|
|
const float = parseInt(str);
|
|
if(Number.isNaN(float)) return { error: true }
|
|
return { error: false, value: float };
|
|
},
|
|
BOOLEAN: (str) => {
|
|
const truthy = ['yes', 'y', 'true', 't', 'on', 'enable'];
|
|
const falsey = ['no', 'n', 'false', 'f', 'off', 'disable'];
|
|
if(typeof str === 'boolean') return { error: false, value: str };
|
|
|
|
if(typeof str === 'string') str = str.toLowerCase();
|
|
if(truthy.includes(str)) return { error: false, value: true };
|
|
if(falsey.includes(str)) return { error: false, value: false };
|
|
return { error: true };
|
|
}
|
|
}
|
|
|
|
return await types[type](str);
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
module.exports = CommandHandler; |