UNPKG

@sapphire/framework

Version:

Discord bot framework built for advanced and amazing bots.

412 lines (410 loc) • 18.1 kB
import { __name } from '../../../chunk-PAWJFY3S.mjs'; import { container } from '@sapphire/pieces'; import { isNullishOrEmpty } from '@sapphire/utilities'; import { ApplicationCommandType } from 'discord-api-types/v10'; import { Collection } from 'discord.js'; import { InternalRegistryAPIType, RegisterBehavior } from '../../types/Enums.mjs'; import { getDefaultBehaviorWhenNotIdentical, getDefaultGuildIds, allGuildIdsToFetchCommandsFor } from './ApplicationCommandRegistries.mjs'; import { getCommandDifferences, getCommandDifferencesFast } from './computeDifferences.mjs'; import { normalizeChatInputCommand, normalizeContextMenuCommand, convertApplicationCommandToApiData } from './normalizeInputs.mjs'; var _ApplicationCommandRegistry = class _ApplicationCommandRegistry { constructor(commandName) { /** * A set of all chat input command names and ids that point to this registry. * You should not use this field directly, but instead use {@link ApplicationCommandRegistry.globalChatInputCommandIds} */ this.chatInputCommands = /* @__PURE__ */ new Set(); /** * A set of all context menu command names and ids that point to this registry. * You should not use this field directly, but instead use {@link ApplicationCommandRegistry.globalContextMenuCommandIds} */ this.contextMenuCommands = /* @__PURE__ */ new Set(); /** * The guild ids that we need to fetch the commands for. */ this.guildIdsToFetch = /* @__PURE__ */ new Set(); /** * The global slash command id for this command. * @deprecated This field will only show the first global command id registered for this registry. * Use {@link ApplicationCommandRegistry.globalChatInputCommandIds} instead. */ this.globalCommandId = null; /** * A set of all registered and valid global chat input command ids that point to this registry. */ this.globalChatInputCommandIds = /* @__PURE__ */ new Set(); /** * A set of all registered and valid global context menu command ids that point to this registry. */ this.globalContextMenuCommandIds = /* @__PURE__ */ new Set(); /** * The guild command ids for this command. * @deprecated This field will only show the first guild command id registered for this registry per guild. * Use {@link ApplicationCommandRegistry.guildIdToChatInputCommandIds} and {@link ApplicationCommandRegistry.guildIdToContextMenuCommandIds} instead. */ this.guildCommandIds = new Collection(); /** * A map of guild ids to a set of registered and valid chat input command ids that point to this registry. */ this.guildIdToChatInputCommandIds = new Collection(); /** * A map of guild ids to a set of registered and valid context menu command ids that point to this registry. */ this.guildIdToContextMenuCommandIds = new Collection(); this.apiCalls = []; this.commandName = commandName; } get command() { return container.stores.get("commands").get(this.commandName); } registerChatInputCommand(command, options) { const builtData = normalizeChatInputCommand(command); this.chatInputCommands.add(builtData.name); const guildIdsToRegister = this.getGuildIdsToRegister(options); const registerOptions = { registerCommandIfMissing: true, behaviorWhenNotIdentical: getDefaultBehaviorWhenNotIdentical(), guildIds: guildIdsToRegister, ...options ?? {} }; this.apiCalls.push({ builtData, registerOptions, type: InternalRegistryAPIType.ChatInput }); if (options?.idHints) { for (const hint of options.idHints) { this.chatInputCommands.add(hint); } } this.processGuildIds(guildIdsToRegister); return this; } registerContextMenuCommand(command, options) { const builtData = normalizeContextMenuCommand(command); this.contextMenuCommands.add(builtData.name); const guildIdsToRegister = this.getGuildIdsToRegister(options); const registerOptions = { registerCommandIfMissing: true, behaviorWhenNotIdentical: getDefaultBehaviorWhenNotIdentical(), guildIds: guildIdsToRegister, ...options ?? {} }; this.apiCalls.push({ builtData, registerOptions, type: InternalRegistryAPIType.ContextMenu }); if (options?.idHints) { for (const hint of options.idHints) { this.contextMenuCommands.add(hint); } } this.processGuildIds(guildIdsToRegister); return this; } addChatInputCommandNames(...names) { const flattened = names.flat(Infinity); for (const command of flattened) { this.debug(`Registering name "${command}" to internal chat input map`); this.warn( `Registering the chat input command "${command}" using a name is not recommended.`, 'Please use the "addChatInputCommandIds" method instead with a command id.' ); this.chatInputCommands.add(command); } return this; } addContextMenuCommandNames(...names) { const flattened = names.flat(Infinity); for (const command of flattened) { this.debug(`Registering name "${command}" to internal context menu map`); this.warn( `Registering the context menu command "${command}" using a name is not recommended.`, 'Please use the "addContextMenuCommandIds" method instead with a command id.' ); this.contextMenuCommands.add(command); } return this; } addChatInputCommandIds(...commandIds) { const flattened = commandIds.flat(Infinity); for (const entry of flattened) { try { BigInt(entry); this.debug(`Registering id "${entry}" to internal chat input map`); } catch { this.debug(`Registering name "${entry}" to internal chat input map`); this.warn( `Registering the chat input command "${entry}" using a name *and* trying to bypass this warning by calling "addChatInputCommandIds" is not recommended.`, 'Please use the "addChatInputCommandIds" method with a valid command id instead.' ); } this.chatInputCommands.add(entry); } return this; } addContextMenuCommandIds(...commandIds) { const flattened = commandIds.flat(Infinity); for (const entry of flattened) { try { BigInt(entry); this.debug(`Registering id "${entry}" to internal context menu map`); } catch { this.debug(`Registering name "${entry}" to internal context menu map`); this.warn( `Registering the context menu command "${entry}" using a name *and* trying to bypass this warning by calling "addContextMenuCommandIds" is not recommended.`, 'Please use the "addContextMenuCommandIds" method with a valid command id instead.' ); } this.contextMenuCommands.add(entry); } return this; } async runAPICalls(applicationCommands, globalCommands, guildCommands) { if (this.apiCalls.length === 0) { this.trace("No API calls to run, and no command to register"); return; } if (getDefaultBehaviorWhenNotIdentical() === RegisterBehavior.BulkOverwrite) { throw new RangeError( `"runAPICalls" was called for "${this.commandName}" but the defaultBehaviorWhenNotIdentical is "BulkOverwrite". This should not happen.` ); } this.debug(`Preparing to process ${this.apiCalls.length} possible command registrations / updates...`); const results = await Promise.allSettled( this.apiCalls.map((call) => this.handleAPICall(applicationCommands, globalCommands, guildCommands, call)) ); const errored = results.filter((result) => result.status === "rejected"); if (errored.length) { this.error(`Received ${errored.length} errors while processing command registrations / updates`); for (const error of errored) { this.error(error.reason.stack ?? error.reason); } } } handleIdAddition(type, id, guildId) { switch (type) { case InternalRegistryAPIType.ChatInput: { this.addChatInputCommandIds(id); if (guildId) { this.guildIdToChatInputCommandIds.ensure(guildId, () => /* @__PURE__ */ new Set()).add(id); } else { this.globalChatInputCommandIds.add(id); } break; } case InternalRegistryAPIType.ContextMenu: { this.addContextMenuCommandIds(id); if (guildId) { this.guildIdToContextMenuCommandIds.ensure(guildId, () => /* @__PURE__ */ new Set()).add(id); } else { this.globalContextMenuCommandIds.add(id); } break; } } if (guildId) { if (!this.guildCommandIds.has(guildId)) { this.guildCommandIds.set(guildId, id); } } else { this.globalCommandId ??= id; } } getGuildIdsToRegister(options) { let guildIdsToRegister = void 0; if (!isNullishOrEmpty(options?.guildIds)) { guildIdsToRegister = options.guildIds; } else if (!isNullishOrEmpty(getDefaultGuildIds())) { guildIdsToRegister = getDefaultGuildIds(); } return guildIdsToRegister; } processGuildIds(guildIdsToRegister) { if (!isNullishOrEmpty(guildIdsToRegister)) { for (const id of guildIdsToRegister) { this.guildIdsToFetch.add(id); allGuildIdsToFetchCommandsFor.add(id); } } } async handleAPICall(commandsManager, globalCommands, allGuildsCommands, apiCall) { const { builtData, registerOptions } = apiCall; const commandName = builtData.name; const behaviorIfNotEqual = registerOptions.behaviorWhenNotIdentical ?? getDefaultBehaviorWhenNotIdentical(); const findCallback = /* @__PURE__ */ __name((entry) => { if (apiCall.type === InternalRegistryAPIType.ChatInput && entry.type !== ApplicationCommandType.ChatInput) return false; if (apiCall.type === InternalRegistryAPIType.ContextMenu) { if (entry.type === ApplicationCommandType.ChatInput) return false; if (apiCall.builtData.type !== entry.type) return false; } const isInIdHint = registerOptions.idHints?.includes(entry.id); return typeof isInIdHint === "boolean" ? isInIdHint || entry.name === commandName : entry.name === commandName; }, "findCallback"); let type; switch (apiCall.type) { case InternalRegistryAPIType.ChatInput: type = "chat input"; break; case InternalRegistryAPIType.ContextMenu: switch (apiCall.builtData.type) { case ApplicationCommandType.Message: type = "message context menu"; break; case ApplicationCommandType.User: type = "user context menu"; break; default: type = "unknown-type context menu"; } break; default: type = "unknown"; } if (!registerOptions.guildIds?.length) { const globalCommand = globalCommands.find(findCallback); if (globalCommand) { this.debug(`Checking if command "${commandName}" is identical with global ${type} command with id "${globalCommand.id}"`); this.handleIdAddition(apiCall.type, globalCommand.id); await this.handleCommandPresent(globalCommand, builtData, behaviorIfNotEqual, null); } else if (registerOptions.registerCommandIfMissing ?? true) { this.debug(`Creating new global ${type} command with name "${commandName}"`); await this.createMissingCommand(commandsManager, builtData, type); } else { this.debug(`Doing nothing about missing global ${type} command with name "${commandName}"`); } return; } for (const guildId of registerOptions.guildIds) { const guildCommands = allGuildsCommands.get(guildId); if (!guildCommands) { this.debug(`There are no commands for guild with id "${guildId}". Will create ${type} command "${commandName}".`); await this.createMissingCommand(commandsManager, builtData, type, guildId); continue; } const existingGuildCommand = guildCommands.find(findCallback); if (existingGuildCommand) { this.debug(`Checking if guild ${type} command "${commandName}" is identical to command "${existingGuildCommand.id}"`); this.handleIdAddition(apiCall.type, existingGuildCommand.id, guildId); await this.handleCommandPresent(existingGuildCommand, builtData, behaviorIfNotEqual, guildId); } else if (registerOptions.registerCommandIfMissing ?? true) { this.debug(`Creating new guild ${type} command with name "${commandName}" for guild "${guildId}"`); await this.createMissingCommand(commandsManager, builtData, type, guildId); } else { this.debug(`Doing nothing about missing guild ${type} command with name "${commandName}" for guild "${guildId}"`); } } } async handleCommandPresent(applicationCommand, apiData, behaviorIfNotEqual, guildId) { if (behaviorIfNotEqual === RegisterBehavior.BulkOverwrite) { this.debug( `Command "${this.commandName}" has the behaviorIfNotEqual set to "BulkOverwrite" which is invalid. Using defaultBehaviorWhenNotIdentical instead` ); behaviorIfNotEqual = getDefaultBehaviorWhenNotIdentical(); if (behaviorIfNotEqual === RegisterBehavior.BulkOverwrite) { throw new Error( `Invalid behaviorIfNotEqual value ("BulkOverwrite") for command "${this.commandName}", and defaultBehaviorWhenNotIdentical is also "BulkOverwrite". This should not happen.` ); } } let differences = []; if (behaviorIfNotEqual === RegisterBehavior.VerboseOverwrite) { const now = Date.now(); differences = [...getCommandDifferences(convertApplicationCommandToApiData(applicationCommand), apiData, guildId !== null)]; const later = Date.now() - now; this.debug(`Took ${later}ms to process differences via computing differences`); if (!differences.length) { this.debug( `${guildId ? "Guild command" : "Command"} "${apiData.name}" is identical to command "${applicationCommand.name}" (${applicationCommand.id})` ); return; } } if (behaviorIfNotEqual === RegisterBehavior.Overwrite || behaviorIfNotEqual === RegisterBehavior.LogToConsole) { const now = Date.now(); const areThereDifferences = getCommandDifferencesFast(convertApplicationCommandToApiData(applicationCommand), apiData, guildId !== null); const later = Date.now() - now; this.debug(`Took ${later}ms to process differences via fast compute differences`); if (!areThereDifferences) { this.debug( `${guildId ? "Guild command" : "Command"} "${apiData.name}" is identical to command "${applicationCommand.name}" (${applicationCommand.id})` ); return; } } this.logCommandDifferencesFound(applicationCommand, behaviorIfNotEqual === RegisterBehavior.LogToConsole, differences); if (behaviorIfNotEqual === RegisterBehavior.LogToConsole) { return; } try { await applicationCommand.edit(apiData); this.debug(`Updated command ${applicationCommand.name} (${applicationCommand.id}) with new api data`); } catch (error) { this.error(`Failed to update command ${applicationCommand.name} (${applicationCommand.id})`, error); } } logCommandDifferencesFound(applicationCommand, logAsWarn, differences) { const finalMessage = []; const pad = " ".repeat(5); for (const difference of differences) { finalMessage.push( [ `\u2514\u2500\u2500 At path: ${difference.key}`, // `${pad}\u251C\u2500\u2500 Received: ${difference.original}`, `${pad}\u2514\u2500\u2500 Expected: ${difference.expected}`, "" ].join("\n") ); } const finalMessageNewLine = finalMessage.length ? "\n" : ""; const header = `Found differences for command "${applicationCommand.name}" (${applicationCommand.id}) versus provided api data.${finalMessageNewLine}`; logAsWarn ? this.warn(header, ...finalMessage) : this.debug(header, ...finalMessage); } async createMissingCommand(commandsManager, apiData, type, guildId) { try { const result = await commandsManager.create(apiData, guildId); this.info( `Successfully created ${type}${guildId ? " guild" : ""} command "${apiData.name}" with id "${result.id}". You should add the id to the "idHints" property of the register method you used!` ); switch (apiData.type) { case void 0: case ApplicationCommandType.ChatInput: { this.handleIdAddition(InternalRegistryAPIType.ChatInput, result.id, guildId); break; } case ApplicationCommandType.Message: case ApplicationCommandType.User: { this.handleIdAddition(InternalRegistryAPIType.ContextMenu, result.id, guildId); break; } } } catch (err) { this.error( `Failed to register${guildId ? " guild" : ""} application command with name "${apiData.name}"${guildId ? ` for guild "${guildId}"` : ""}`, err ); } } info(message, ...other) { container.logger.info(`ApplicationCommandRegistry[${this.commandName}] ${message}`, ...other); } error(message, ...other) { container.logger.error(`ApplicationCommandRegistry[${this.commandName}] ${message}`, ...other); } warn(message, ...other) { container.logger.warn(`ApplicationCommandRegistry[${this.commandName}] ${message}`, ...other); } debug(message, ...other) { container.logger.debug(`ApplicationCommandRegistry[${this.commandName}] ${message}`, ...other); } trace(message, ...other) { container.logger.trace(`ApplicationCommandRegistry[${this.commandName}] ${message}`, ...other); } }; __name(_ApplicationCommandRegistry, "ApplicationCommandRegistry"); var ApplicationCommandRegistry = _ApplicationCommandRegistry; export { ApplicationCommandRegistry }; //# sourceMappingURL=ApplicationCommandRegistry.mjs.map //# sourceMappingURL=ApplicationCommandRegistry.mjs.map