This repository has been archived on 2025-11-10. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
CommandBot/src/CommandManager.ts
T

233 lines
9.2 KiB
TypeScript

import { CommandInteraction, Message } from "discord.js";
import { MissingParameterError } from "./errors.js";
import { BooleanParameter, InputParameter, NumberParameter, ObjectParameter, StringParameter } from "./Parameter.js";
import { Command } from "./Command.js";
import { CommandMessageStructure, PhraseOccurrenceData } from "./types.js";
/**
* @class Command manager
*/
export class CommandManager {
/**
* List of commands registered in the manager
* @type {Array} {@link Command}
*/
private readonly list: Command[];
/**
* Prefix used as a way to trigger the bot using messages
* @type {string}
*/
public readonly prefix?: string;
/**
* Separator used to split user input to a list of {@link InputParameter}s (applies to prefix interactions)
* @type {string}
*/
public readonly parameterSeparator: string;
/**
* @constructor
* @param {string} [prefix] - prefix used as a way to trigger the bot using messages
* @param {string} parameterSeparator - used to split user input to a list of {@link InputParameter}s (applies to prefix interactions)
*/
constructor(prefix?: string, parameterSeparator?: string) {
this.list = [];
this.prefix = prefix;
this.parameterSeparator = parameterSeparator || ",";
}
/**
* Retrieves the command by name, alias or keyword
* @param {string} phrase - command name, alias or keyword
* @returns {Command | null} Retrieved {@link Command} object from the manager or *null*
*/
get(phrase: string): Command | null {
let command: Command | null = null;
this.list.map((c) => {
if (c.name == phrase) {
command = c;
}
c.aliases &&
c.aliases.map((a) => {
if (a == phrase) {
command = c;
}
});
});
return command;
}
/**
* Get all commands registered in a manager
* @returns {Command[]} List of registered commands in a manager
*/
getList(): Command[] {
const list = this.list;
return list;
}
/**
* Adds the given {@link Command} to the instance manager with initial processing
* @param {Command} command - {@link Command} instance object
* @returns {boolean} Whether this command has been added successfully
*/
add(command: Command): boolean {
try {
if (!(command instanceof Command)) {
throw new TypeError("Inavlid argument type");
}
const nameOccurrence: PhraseOccurrenceData | null = this.findPhraseOccurrence(command.name);
if (nameOccurrence) {
throw new Error(`The name "${command.name}" has already been registered as ${nameOccurrence.type} in the "${nameOccurrence.command.name}" command.`);
}
command.aliases &&
command.aliases.map((a, i, ar) => {
const aliasOccurrence: PhraseOccurrenceData | null = this.findPhraseOccurrence(a);
if (aliasOccurrence) {
console.warn(
`[⚠ WARN] The name "${a}" is already registered as ${aliasOccurrence.type} in the "${aliasOccurrence.command.name}" command. It will be removed from the "${command.name}" command.`
);
ar.splice(i, 1);
}
});
command.parameters &&
command.parameters.map((p) => {
if (p.type != "string" && p.choices) {
throw new Error('Parameter with defined choices must have a "string" type');
}
});
this.list.push(command);
return true;
} catch (e) {
console.error(`[❌ ERROR] ${e.toString()}`);
return false;
}
}
/**
* Fetches {@link Command} and list of {@link InputParameter}s from the given {@link Message} object
* @param {Message} message - {@link Message} object
* @returns {CommandMessagesStructure | null} {@link CommandMessageStructure} or *null*
*/
fetchFromMessage(message: Message): CommandMessageStructure | null {
if (!this.prefix) return null;
if (!message.author.bot && message.content.startsWith(this.prefix)) {
const content = message.content.replace(this.prefix, "");
const name = content.split(" ")[0];
const command = this.get(name);
if (command) {
const argumentsText = content.replace(name, "");
const paramsList = argumentsText.split(this.parameterSeparator).map((a) => {
return a.replace(" ", "");
});
if ((paramsList[0] == "" || paramsList[0] == " ") && paramsList.length == 1) {
paramsList.splice(0, 1);
}
const parameters: InputParameter[] = [];
command.parameters.map((p, i) => {
if (!p.optional && !paramsList[i]) {
throw new MissingParameterError(p);
} else if (p.optional && !paramsList[i]) {
return;
}
switch (p.type) {
case "mentionable":
case "channel":
case "role":
case "user":
parameters.push(new ObjectParameter(p, paramsList[i]));
break;
case "string":
parameters.push(new StringParameter(p, paramsList[i]));
break;
case "boolean":
parameters.push(new BooleanParameter(p, paramsList[i]));
break;
case "number":
parameters.push(new NumberParameter(p, paramsList[i]));
break;
default:
parameters.push(new InputParameter(p, paramsList[i]));
break;
}
});
return {
command: command,
parameters: parameters,
};
} else {
return null;
}
} else {
return null;
}
}
/**
* Fetches command and parameters from the given {@link CommandInteraction} object
* @param {CommandInteraction} interaction - {@link CommandInteraction} object
* @returns {CommandMessagesStructure | null} {@link CommandMessageStructure} or *null*
*/
fetchFromInteraction(interaction: CommandInteraction): CommandMessageStructure | null {
const cmd = this.get(interaction.commandName);
if (cmd) {
if (interaction.options?.data) {
const paramsList = interaction.options.data;
const parameters: InputParameter[] = [];
cmd.parameters.map((p) => {
const inputParam = paramsList.find((d) => d.name == p.name);
if (!p.optional && !inputParam) {
throw new MissingParameterError(p);
} else if (p.optional && !inputParam) {
return;
}
switch (p.type) {
case "mentionable":
case "channel":
case "role":
case "user":
parameters.push(new ObjectParameter(p, inputParam?.value));
break;
case "string":
parameters.push(new StringParameter(p, inputParam?.value));
break;
case "boolean":
parameters.push(new BooleanParameter(p, inputParam?.value));
break;
case "number":
parameters.push(new NumberParameter(p, inputParam?.value));
break;
default:
parameters.push(new InputParameter(p, inputParam?.value));
break;
}
});
return {
command: cmd,
parameters: parameters,
};
}
} else {
return null;
}
return null;
}
private findPhraseOccurrence(phrase?: string): PhraseOccurrenceData | null {
let returnValue: PhraseOccurrenceData | null = null;
this.list.map((c) => {
if (phrase == c.name) {
returnValue = {
command: c,
type: "NAME",
};
} else if (c.aliases && c.aliases.indexOf(phrase || "") != -1) {
returnValue = {
command: c,
type: "ALIAS",
};
}
});
return returnValue;
}
}