@lilybird/handlers
Version:
Command handlers and more for lilybird
223 lines (222 loc) • 10.5 kB
JavaScript
export class ApplicationCommandStore {
#globalApplicationCommands = new Map();
#guildApplicationCommands = new Map();
#emit;
#assumeTransformed;
#customKeys;
constructor(handlerListener, options = { transformed: false }) {
this.#emit = handlerListener;
this.#assumeTransformed = options.transformed;
this.#customKeys = options.customKeys;
}
storeCommand(command) {
const { meta, options, handle, autocomplete, ...actualCommand } = command;
if (meta?.guild_command === true && !Array.isArray(meta.ids))
throw new Error("Invalid guild command. Lacking 'ids'");
const commands = meta?.guild_command === true ? this.#guildApplicationCommands : this.#globalApplicationCommands;
const isContinuingChain = (meta?.guild_command === true ? this.#guildApplicationCommands.size : this.#globalApplicationCommands.size) > 0;
if ((!Array.isArray(options) || options.length === 0) || (typeof options !== "undefined" && !this.#isCommandWrapper(options))) {
if (typeof handle === "undefined")
return this.#emit?.(5, actualCommand.name);
actualCommand.options = options;
const optsBody = this.#parseOptions(options);
const cmd = this.#makeCommandBase(actualCommand, { base_executor: handle, auto_executor: autocomplete }, isContinuingChain, optsBody);
commands.set(actualCommand.name, cmd);
return;
}
if (typeof handle !== "undefined")
this.#emit?.(6, actualCommand.name);
const optsBody = this.#parseOptions(options, true);
if (typeof optsBody === "undefined")
throw new Error("Something unexpected happened!");
const cmd = this.#compileCommand(actualCommand, options, isContinuingChain, optsBody);
commands.set(actualCommand.name, cmd);
return;
}
#parseOptions(options, appendSubCommandLogic = false) {
if (this.#assumeTransformed)
return undefined;
if (typeof options === "undefined")
return undefined;
const stack = ["const _obj = {"];
for (let i = 0, { length } = options; i < length; i++) {
const option = options[i];
stack.push(`${option.name}: undefined`, ",");
}
stack.pop();
stack.push("};");
if (appendSubCommandLogic) {
stack.push("const _opt = interaction.data.options[0];", "if ( _opt.type === 2) {", "sub_command_group = _opt.name;", "for (let i = 0, { length } = _opt.options; i < length; i++) {", "const _g_opt = _opt.options[i];", "if (_g_opt.type === 1) {sub_command = _g_opt.name; parseOpts(_g_opt.options, _obj);}", "}", "}", "else if (_opt.type === 1) {sub_command = _opt.name; parseOpts(_opt.options, _obj)}");
}
else
stack.push("parseOpts(interaction.data.options, _obj);");
return stack.join("");
}
#makeCommandBase(command, handler, useElse, optionsBody = undefined, matchTo = "interaction_name", name = command.name) {
const hasOptions = typeof optionsBody !== "undefined";
const names = [`handle_${name.replace("-", "_")}`];
const handlers = [handler.base_executor];
if (typeof handler.auto_executor !== "undefined") {
names.push(`auto_${name.replace("-", "_")}`);
handlers.push(handler.auto_executor);
}
let strArgs = this.#assumeTransformed ? "interaction" : "client, interaction";
if (hasOptions && !this.#assumeTransformed)
strArgs += ", _obj";
return {
body: {
command: `${useElse ? "else " : ""}if (${matchTo} === "${command.name}") {
${hasOptions && !this.#assumeTransformed ? optionsBody : ""}
return handle_${name.replace("-", "_")}(${strArgs}); }`,
autocomplete: typeof handler.auto_executor === "undefined"
? null
: `${useElse ? "else " : ""}if (${matchTo} === "${command.name}") {
${hasOptions && !this.#assumeTransformed ? optionsBody : ""}
return auto_${name.replace("-", "_")}(${strArgs}); }`
},
function: {
names,
handlers
},
json: command
};
}
#compileCommand(command, options, useElse, optionsBody, matchTo = "interaction_name", name = command.name) {
const fns = new Map();
const cmdArr = [`${useElse ? "else " : ""}if (${matchTo} === "${command.name}") {`];
const autoArr = [`${useElse ? "else " : ""}if (${matchTo} === "${command.name}") {`];
const temp = matchTo === "sub_command_group"
? []
: this.#assumeTransformed
? [
`const sub_command = interaction.${this.#customKeys?.sub_command};`,
`const sub_command_group = interaction.${this.#customKeys?.sub_command_group}`
]
: [
"let sub_command = undefined;",
"let sub_command_group = undefined;",
optionsBody
];
cmdArr.push(temp.join(""));
autoArr.push(temp.join(""));
for (let i = 0, { length } = options; i < length; i++) {
const subCommand = options[i];
if (subCommand.type === 2) {
if (!("options" in subCommand))
throw new Error("SubCommandGroup requires options to exist");
const { options: subOpts, ...realCommand } = subCommand;
const realOptions = this.#compileCommand(realCommand, subOpts, i > 0, `${name}_${subCommand.name}`, "sub_command_group");
cmdArr.push(realOptions.body.command);
if (realOptions.body.autocomplete !== null)
autoArr.push(realOptions.body.autocomplete);
for (let j = 0, len = realOptions.function.names.length; j < len; j++) {
const n = realOptions.function.names[j];
const h = realOptions.function.handlers[j];
fns.set(n, h);
}
if (!Array.isArray(command.options))
command.options = [];
command.options.push(realCommand);
}
else if (subCommand.type === 1) {
if (!("handle" in subCommand))
throw new Error("SubCommand requires 'handle' to exist");
const { handle, autocomplete, ...realCommand } = subCommand;
const cmd = this.#makeCommandBase(realCommand, { base_executor: handle, auto_executor: autocomplete }, i > 0, "", "sub_command", `${name}_${subCommand.name}`);
cmdArr.push(cmd.body.command);
if (cmd.body.autocomplete !== null)
autoArr.push(cmd.body.autocomplete);
for (let j = 0, len = cmd.function.names.length; j < len; j++) {
const n = cmd.function.names[j];
const h = cmd.function.handlers[j];
fns.set(n, h);
}
if (!Array.isArray(command.options))
command.options = [];
command.options.push(realCommand);
}
}
cmdArr.push("}");
autoArr.push("}");
return {
body: { command: cmdArr.join(""), autocomplete: autoArr.length > 4 ? autoArr.join("") : null },
function: { names: [...fns.keys()], handlers: [...fns.values()] },
json: command
};
}
#isCommandWrapper(options) {
for (let i = 0, { length } = options; i < length; i++) {
const { type } = options[i];
if (type === 2
|| type === 1)
return true;
}
return false;
}
getCompilationStack() {
const functions = new Map();
const cmdArr = [
this.#assumeTransformed ? `const interaction_name = interaction.${this.#customKeys?.name};` : "const interaction_name = interaction.data.name;",
`if (interaction.type === ${2}) {`
];
const autoArr = [`else if (interaction.type === ${4}) {`];
const commands = [...this.#globalApplicationCommands.values(), ...this.#guildApplicationCommands.values()];
for (let i = 0, { length } = commands; i < length; i++) {
const { body, function: fn } = commands[i];
cmdArr.push(body.command);
if (body.autocomplete !== null)
autoArr.push(body.autocomplete);
for (let j = 0, len = fn.names.length; j < len; j++) {
const n = fn.names[j];
const h = fn.handlers[j];
functions.set(n, h);
}
}
const arr = [];
if (cmdArr.length > 2)
arr.push(cmdArr.join(""), "}");
if (autoArr.length > 1) {
if (arr.length === 0)
autoArr[0] = autoArr[0].slice(5);
if (autoArr[1][0] === "e")
autoArr[1] = autoArr[1].slice(5);
arr.push(autoArr.join(""), "}");
}
if (arr.length === 0)
return null;
const fNames = functions.keys();
const fHandlers = functions.values();
const compiledListener = arr.join("");
return {
functionNames: fNames,
handlers: fHandlers,
stack: compiledListener
};
}
compile() {
const compiledResult = this.getCompilationStack();
if (compiledResult === null)
return null;
const { stack, functionNames, handlers } = compiledResult;
this.#emit?.(7, stack);
return new Function("parseOpts", ...functionNames, this.#assumeTransformed ? `return async (interaction) => { ${stack} }` : `return async (client, interaction) => { ${stack} }`)(innerOptionParser, ...handlers);
}
getStoredGlobalCommands() {
return [...this.#globalApplicationCommands.values()];
}
getStoredGuildCommands() {
return [...this.#guildApplicationCommands.values()];
}
clear() {
this.#globalApplicationCommands.clear();
this.#guildApplicationCommands.clear();
}
}
export function innerOptionParser(options, _obj) {
if (!options)
return;
for (let i = 0, { length } = options; i < length; i++) {
const opt = options[i];
_obj[opt.name] = opt.value;
}
}