UNPKG

@lilybird/handlers

Version:
250 lines (249 loc) 10.7 kB
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);