@lilybird/handlers
Version:
Command handlers and more for lilybird
250 lines (249 loc) • 10.7 kB
JavaScript
import { ApplicationCommandStore, innerOptionParser } from "./application-command-store.js";
import { MessageComponentStore } from "./message-component-store.js";
import { join } from "node:path";
export class Handler {
#acs = new ApplicationCommandStore();
#mcs = new MessageComponentStore();
#listeners = new Map();
#customKeys;
#emit;
#cachePath;
constructor(options) {
this.#cachePath = options.cachePath;
this.#emit = options.handlerListener;
if (options.enableDynamicComponents)
this.#mcs = new MessageComponentStore(options.handlerListener, options.enableDynamicComponents);
if (typeof options.acsOptions !== "undefined") {
this.#customKeys = options.acsOptions.customKeys;
this.#acs = new ApplicationCommandStore(options.handlerListener, options.acsOptions);
}
}
buttonCollector(component) {
this.#mcs.addDynamicComponent(component);
}
storeCommand(data) {
const { components, ...command } = data;
if (typeof components !== "undefined")
for (let i = 0, { length } = components; i < length; i++)
this.#mcs.storeComponent(components[i]);
this.#acs.storeCommand(command);
}
subCommandMock(data) { return data; }
storeListener(data) {
this.#listeners.set(data.event, data.handle);
}
clearStores() {
this.#acs.clear();
this.#mcs.clear();
}
#differOption(incoming, cached) {
if (incoming.type !== cached.type)
return true;
const differentName = incoming.name !== cached.name;
const differentDescription = incoming.description !== cached.description;
const differentRequired = incoming.required !== cached.required;
const base = differentName || differentDescription || differentRequired;
switch (incoming.type) {
case 1:
case 2: {
if (incoming.options?.length !== cached.options?.length)
return true;
if (typeof incoming.options !== "undefined" && typeof cached.options !== "undefined") {
for (let i = 0, { length } = incoming.options; i < length; i++) {
const option = incoming.options[i];
const cachedIndex = cached.options.findIndex((op) => op.name === option.name);
if (cachedIndex === -1)
return true;
if (!this.#differOption(option, cached.options[cachedIndex]))
continue;
return true;
}
}
return base;
}
case 10:
case 4: {
const differentMinValue = incoming.min_value !== cached.min_value;
const differentMaxValue = incoming.max_value !== cached.max_value;
return base || differentMinValue || differentMaxValue;
}
case 3: {
const differentMinLength = incoming.min_length !== cached.min_length;
const differentMaxLength = incoming.max_length !== cached.max_length;
return base || differentMinLength || differentMaxLength;
}
case 7: {
const differentChannelTypes = incoming.channel_types?.length !== cached.channel_types?.length;
return base || differentChannelTypes;
}
case 5:
case 6:
case 8:
case 9:
case 11: {
return base;
}
}
}
#differ(incoming, cached) {
if (incoming.options?.length !== cached.options?.length)
return true;
const differentName = incoming.name !== cached.name;
const differentDescription = incoming.description !== cached.description;
const differentDefaultPermissions = incoming.default_member_permissions !== cached.default_member_permissions;
const differentDMpermission = incoming.dm_permission !== cached.dm_permission;
const differentType = incoming.type !== cached.type;
const differentNSFW = incoming.nsfw !== cached.nsfw;
if (typeof incoming.options !== "undefined" && typeof cached.options !== "undefined") {
for (let i = 0, { length } = incoming.options; i < length; i++) {
const option = incoming.options[i];
const cachedIndex = cached.options.findIndex((op) => op.name === option.name);
if (cachedIndex === -1)
return true;
if (!this.#differOption(option, cached.options[cachedIndex]))
continue;
return true;
}
}
return differentType
|| differentName
|| differentDescription
|| differentNSFW
|| differentDMpermission
|| differentDefaultPermissions;
}
#getStackBody(commandsBody, componentsBody) {
const realStack = [];
if (commandsBody.length > 0)
realStack.push("const interaction_name = interaction.data.name;");
if (componentsBody.length > 0)
realStack.push("const custom_id = interaction.data.custom_id;");
if (commandsBody.length > 0 && commandsBody[0] === "c") {
const trimLength = "const interaction_name = interaction.data.name;".length;
realStack.push(commandsBody.slice(trimLength));
}
if (componentsBody.length > 0 && componentsBody[0] === "c") {
const trimLength = "const custom_id = interaction.data.custom_id;".length;
if (realStack.length > 2)
realStack.push("else ");
realStack.push(componentsBody.slice(trimLength));
}
return realStack.join("");
}
getCompilationStack() {
const maybeAcs = this.#acs.getCompilationStack();
const acs = maybeAcs ?? { functionNames: [], handlers: [], stack: "" };
const maybeMcs = this.#mcs.getCompilationStack();
const mcs = maybeMcs ?? { functionNames: [], handlers: [], stack: "" };
const body = this.#getStackBody(acs.stack, mcs.stack);
if (body.length === 0)
return null;
return {
functionNames: [...acs.functionNames, ...mcs.functionNames],
handlers: [...acs.handlers, ...mcs.handlers],
stack: body
};
}
compileCommands() {
const compiledResult = this.getCompilationStack();
if (compiledResult === null)
return null;
const { stack, functionNames, handlers } = compiledResult;
this.#emit?.(7, stack);
return new Function("parseOpts", ...functionNames, typeof this.#customKeys !== "undefined" ? `return async (interaction) => { ${stack} }` : `return async (client, interaction) => { ${stack} }`)(innerOptionParser, ...handlers);
}
getListenersObject(includeCommands = true) {
const obj = {};
for (let i = 0, entries = [...this.#listeners.entries()], { length } = entries; i < length; i++) {
const [key, value] = entries[i];
obj[key] = value;
}
if (includeCommands) {
const listener = this.compileCommands();
if (listener === null)
return obj;
if ("interactionCreate" in obj) {
obj.interactionCreate = typeof this.#customKeys !== "undefined"
? (interaction) => {
obj.interactionCreate(interaction);
listener(interaction);
}
: (client, interaction) => {
obj.interactionCreate(client, interaction);
listener(client, interaction);
};
}
else
obj.interactionCreate = listener;
}
return obj;
}
async loadGlobalCommands(client) {
const path = join(this.#cachePath ?? process.cwd(), "global.json");
const file = Bun.file(path);
const commands = this.#acs.getStoredGlobalCommands();
const commandsJSON = commands.map((cmd) => {
const { __meta, ...obj } = cmd.json;
return obj;
});
if (!await file.exists()) {
this.#emit?.(0, undefined);
await Bun.write(file, JSON.stringify(commandsJSON));
await client.rest.bulkOverwriteGlobalApplicationCommand(client.user.id, commandsJSON);
return;
}
const cachedCommands = await file.json();
const toPublish = [];
for (let i = 0, { length } = commands; i < length; i++) {
const { __meta, ...command } = commands[i].json;
const cachedIndex = cachedCommands.findIndex((c) => c.name === command.name);
if (cachedIndex === -1) {
toPublish.push(command);
cachedCommands.push(command);
continue;
}
if (!this.#differ(command, cachedCommands[cachedIndex]))
continue;
toPublish.push(command);
cachedCommands[cachedIndex] = command;
}
if (toPublish.length < 1) {
this.#emit?.(1, undefined);
return;
}
this.#emit?.(2, toPublish);
for (let i = 0, { length } = toPublish; i < length; i++)
await client.rest.createGlobalApplicationCommand(client.user.id, toPublish[i]);
await Bun.write(file, JSON.stringify(cachedCommands));
}
getStoredData() {
return {
commands: {
global: this.#acs.getStoredGlobalCommands(),
guild: this.#acs.getStoredGuildCommands()
},
components: this.#mcs.getStoredComponents(),
listeners: [...this.#listeners.entries()]
};
}
addDebugListener(listener) {
this.#emit = listener;
}
set cachePath(path) {
this.#cachePath = path;
}
get cachePath() {
return this.#cachePath;
}
set enableDynamicComponents(bool) {
this.#mcs.attachDynamicComponentListener = bool;
}
get enableDynamicComponents() {
return this.#mcs.attachDynamicComponentListener;
}
}
export const handler = new Handler({});
export const $applicationCommand = handler.storeCommand.bind(handler);
export const $subCommand = handler.subCommandMock.bind(handler);
export const $listener = handler.storeListener.bind(handler);
export const $component = handler.buttonCollector.bind(handler);