@nortex/handler
Version:
The easy to use, all-in-one command and event handler.
198 lines (197 loc) • 8.94 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.CommandHandler = void 0;
const discord_js_1 = require("discord.js");
const BaseHandler_1 = require("./BaseHandler");
const ExecutionError_1 = require("../errors/ExecutionError");
const Verificators_1 = require("../util/Verificators");
const Command_1 = require("../structures/Command");
class CommandHandler extends BaseHandler_1.BaseHandler {
constructor(options) {
super(options);
if (!options.client)
throw new ReferenceError("CommandHandler(): options.client is required.");
this.client = options.client;
this.commands = [];
if (options.autoLoad === undefined || options.autoLoad === false)
this.loadCommands();
return this;
}
loadCommands() {
return new Promise(async (res, rej) => {
const files = await this.load(false /*emitReady*/).catch(rej);
files.forEach((cmd) => this.registerCommand(cmd));
return res(this.commands);
});
}
registerCommand(cmd) {
if (!(cmd instanceof Command_1.Command))
return;
if (this.commands.find((c) => c.name === cmd.name))
throw new Error(`Command ${cmd.name} cannot be registered twice.`);
// Verify & define defaults for optional fields
if (!cmd.name) {
throw new Error("registerCommand(): Can't register command that does not have a name. Define the command name with the @Name decorator.");
}
if (!cmd.description) {
throw new Error("registerCommand(): Can't register command that does not have a description. Define the command description with the @Description decorator.");
}
if (!cmd.options)
cmd.options = [];
if (!cmd.disabled)
cmd.disabled = false;
// Define handler and client properties on class
Object.defineProperty(cmd, "handler", { value: this });
Object.defineProperty(cmd, "client", { value: this.client });
this.commands.push(cmd);
this.debug(`Loaded command "${cmd.name}".`);
this.emit("load", cmd);
return;
}
runCommand(interaction /*TODO: temporary typing fix*/, ...additionalOptions) {
return new Promise((res, rej) => {
if (interaction.user.bot)
return rej("Bot users can't run interactions.");
if (interaction.type === discord_js_1.InteractionType.ApplicationCommand) {
this.handleCommandRun(interaction, ...additionalOptions)
.then(res)
.catch(rej);
}
else if (interaction.type === discord_js_1.InteractionType.ApplicationCommandAutocomplete) {
// Polyfill for autocomplete interactions
this.handleAutocomplete(interaction, ...additionalOptions)
.then(res)
.catch(rej);
}
else {
throw new Error("CommandHandler#runCommand(): Unsupported interaction type. This only supports commands. You should check the type beforehand, or refer to ComponentHandler() to handle component interactions.");
}
});
}
async updateInteractions(force = false) {
return new Promise(async (res, rej) => {
if (!this.client.application)
return rej(new Error("updateInteractions(): client.application is undefined. Make sure you are executing updateInteractions() after the client has emitted the 'ready' event."));
let changesMade = false;
if (force) {
// Forcing update, automatically assume changes were made
this.debug("Skipping checks and updating interactions.");
changesMade = true;
}
else {
// Fetch existing interactions and compare to loaded
this.debug("Checking for differences.");
const fetchedInteractions = await this.client.application.commands.fetch().catch((err) => {
return rej(new Error(`Can't fetch client commands: ${err.message}.\nMake sure you are executing updateInteractions() after the client has emitted the 'ready' event and 'this.client.application' is populated.`));
});
if (!fetchedInteractions)
return rej(new Error("Interactions weren't fetched."));
changesMade = this.checkDiff(fetchedInteractions);
}
if (changesMade) {
this.formatAndSend(this.commands).then(res).catch(rej);
}
else {
this.debug("No changes in interactions - not refreshing.");
res(false); // Result with false (no changes)
}
});
}
formatAndSend(commands) {
return new Promise(async (res, rej) => {
let interactionsToSend = [];
commands.forEach((cmd) => {
if (!(cmd instanceof Command_1.Command))
return this.debug(`Skipping ${JSON.stringify(cmd)} - class does not extend Command.`);
const data = {
type: discord_js_1.ApplicationCommandType.ChatInput,
application_id: this.client.application.id,
name: cmd.name,
description: cmd.description,
options: cmd.options,
default_member_permissions: cmd.defaultMemberPermissions,
};
interactionsToSend.push(data);
});
await this.client
.application.commands.set(interactionsToSend)
.then((returned) => {
this.debug(`Updated interactions (${returned.size} returned). Updates should be visible momentarily.`);
res(true); // Result with true (updated)
})
.catch((err) => {
return rej(new Error(`Can't update client commands: ${err}`));
});
});
}
handleCommandRun(interaction, ...additionalOptions) {
return new Promise(async (res, rej) => {
const cmd = this.commands.find((i) => i.name === interaction.commandName.toLowerCase());
if (!cmd)
return;
if (!(cmd instanceof Command_1.Command)) {
throw new ExecutionError_1.ExecutionError("Attempting to run non-command class with runCommand().", "INVALID_CLASS");
}
const failedReason = await Verificators_1.default.verifyCommand(interaction, cmd);
if (failedReason) {
rej(failedReason);
return;
}
try {
cmd.run(interaction, ...additionalOptions);
res(cmd);
}
catch (ex) {
console.error(ex);
rej(ex);
}
});
}
handleAutocomplete(interaction, ...additionalOptions) {
return new Promise(async (res, rej) => {
const cmd = this.commands.find((cmd) => cmd.name === interaction.commandName.toLowerCase());
if (!cmd)
return;
if (!(cmd instanceof Command_1.Command)) {
throw new ExecutionError_1.ExecutionError("Attempting to call autocomplete on non-command class.", "INVALID_CLASS");
}
if (!cmd["autocomplete"])
return;
try {
cmd.autocomplete(interaction, ...additionalOptions);
res(cmd);
}
catch (ex) {
console.error(ex);
rej(ex);
}
});
}
checkDiff(interactions) {
const fetched = Array.from(interactions.values()); // Collection to array conversion
// Assume no changes made
let changesMade = false;
for (let localCmd of this.commands) {
const remoteCmd = fetched.find((f) => f.name === localCmd.name);
if (!remoteCmd) {
// Handle created commands
this.debug("Commands match check failed because there are new files created in the filesystem. Updating...");
changesMade = true;
break;
}
// Handle changed commands
// @ts-ignore
changesMade = !remoteCmd.equals(localCmd);
}
// Handle deleted commands
for (let remoteCmd of fetched) {
if (!this.commands.find((i) => i.name === remoteCmd.name)) {
this.debug("Commands match check failed because local command files are missing from the fetched command list. Updating...");
changesMade = true;
break;
}
}
return changesMade;
}
}
exports.CommandHandler = CommandHandler;