UNPKG

@ayanaware/bentocord

Version:

Bentocord is a Bento plugin designed to rapidly build fully functional Discord Bots.

254 lines 12.7 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SlashManager = void 0; const bento_1 = require("@ayanaware/bento"); const logger_api_1 = require("@ayanaware/logger-api"); const eris_1 = require("eris"); const BentocordInterface_1 = require("../BentocordInterface"); const Discord_1 = require("../discord/Discord"); const CommandManager_1 = require("./CommandManager"); const OptionType_1 = require("./constants/OptionType"); const { ApplicationCommandTypes, ApplicationCommandOptionTypes } = eris_1.Constants; const log = logger_api_1.Logger.get(); class SlashManager { constructor() { this.name = '@ayanaware/bentocord:SlashManager'; } /** * Sync Slash Commands with Discord * @param commandsIn Array of ApplicationCommand * @param opts SyncOptions * @returns Discord Slash Bulk Update Response Payload */ async syncCommands(commandsIn, opts) { opts = Object.assign({ delete: true, prefix: null }, opts); // want to avoid reference weirdness so we preform a deep copy of commands // has the added benifit of yeeting any properties / functions we dont care about const commands = JSON.parse(JSON.stringify(commandsIn)); const applicationId = this.discord.application.id; if (!applicationId) throw new Error('Failed to infer application_id'); // Get existing commands let existingCommands = []; if (opts.guildId) existingCommands = await this.discord.client.getGuildCommands(opts.guildId); else existingCommands = await this.discord.client.getCommands(); const commandIds = new Set(); const bulkOverwrite = []; for (const command of commands) { if (opts.guildId) command.guild_id = opts.guildId; // Update command if it already exists const existing = existingCommands.find(c => c.name === command.name); if (existing && existing.id) { command.id = existing.id; commandIds.add(existing.id); // consumed } bulkOverwrite.push(command); } // Delete is string... Only delete other commands starting with this string if (typeof opts.delete === 'string') { for (const existing of existingCommands) { if (!existing.name.startsWith(opts.delete)) bulkOverwrite.push(existing); } } else if ((typeof opts.delete === 'boolean' && !opts.delete) || typeof opts.delete !== 'boolean') { for (const existing of existingCommands) { if (!Array.from(commandIds).includes(existing.id)) { bulkOverwrite.push(existing); commandIds.add(existing.id); } } } // Bulk Update if (opts.guildId) return this.discord.client.bulkEditGuildCommands(opts.guildId, bulkOverwrite); return this.discord.client.bulkEditCommands(bulkOverwrite); } /** * Convert all slash supporting Bentocord commands to Discord ApplicationCommand * @returns Array of ApplicationCommand */ async convertCommands() { const collector = []; for (const [, details] of this.cm.getCommands()) { const command = details.command; const definition = command.definition; if (!definition || typeof definition.registerSlash === 'boolean' && !definition.registerSlash) continue; // don't register hidden commands if (definition.hidden ?? false) continue; const cmd = await this.convertCommand(command); collector.push(cmd); } return collector; } /** * Convert Bentocord Command into Discord ApplicationCommand * @param command */ async convertCommand(command) { const definition = command.definition; if (!definition) throw new Error('Command lacks definition'); // support translated names const names = await this.cm.getItemTranslations(definition.name, true); const primary = names[0][0]; // Since we only need one translated name for discord, we merge all // translation objects together, with accumulator overriding next object const translations = names.reduce((a, v) => Object.assign({}, v[1], a), {}); // support translated descriptions const [description] = await this.cm.getItemTranslations(definition.description); const appCommand = { type: ApplicationCommandTypes.CHAT_INPUT, name: primary, description: description[0], }; // dm_permission support // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access appCommand.dm_permission = definition.allowDM ?? true; // add name localizations (convert for discord needed) if (Object.keys(translations).length > 0) { // truncate to 32 chars const truncated = Object.fromEntries(Object.entries(translations).map(([l, t]) => [l, t.slice(0, 32)])); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access appCommand.name_localizations = await this.interface.convertTranslationMap(truncated); } // add description localizations (convert for discord needed) if (Object.keys(description[1]).length > 0) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access appCommand.description_localizations = await this.interface.convertTranslationMap(description[1]); } // convert options if (Array.isArray(definition.options)) appCommand.options = await this.convertOptions(definition.options); return appCommand; } /** * Convert Bentocord CommandOption to Discord ApplicationCommandOption Counterpart * @param options Array of CommandOptions * @returns ApplicationCommandOption Structure */ async convertOptions(options) { const collector = []; if (!options) options = []; for (const option of options) { // support translated names const names = await this.cm.getItemTranslations(option.name, true); const primary = names[0][0]; // Since we only need one translated name for discord, we merge all // translation objects together, with accumulator overriding next object const translations = names.reduce((a, v) => Object.assign({}, v[1], a), {}); // support translated descriptions const [description] = await this.cm.getItemTranslations(option.description); if (!description[0]) description[0] = primary; // Handle Special Subcommand & SubcommandGroup OptionTypes if (this.cm.isAnySubCommand(option)) { // don't register hidden subcommand/group if (option.hidden ?? false) continue; const subOption = { type: option.type === OptionType_1.OptionType.SUB_COMMAND ? ApplicationCommandOptionTypes.SUB_COMMAND : ApplicationCommandOptionTypes.SUB_COMMAND_GROUP, name: primary, description: description[0], }; // add name localizations (convert for discord needed) if (Object.keys(translations).length > 0) { // truncate to 32 chars const truncated = Object.fromEntries(Object.entries(translations).map(([l, t]) => [l, t.slice(0, 32)])); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access subOption.name_localizations = await this.interface.convertTranslationMap(truncated); } // add description localizations (convert for discord needed) if (Object.keys(description[1]).length > 0) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access subOption.description_localizations = await this.interface.convertTranslationMap(description[1]); } subOption.options = await this.convertOptions(option.options); collector.push(subOption); continue; } // Convert to Discord ApplicationCommandOptionType const resolver = this.cm.findResolver(option.type); // @ts-ignore const appOption = { type: resolver ? resolver.convert : ApplicationCommandOptionTypes.STRING, name: primary, description: description[0], }; // Prepend type information to description const typeInfo = this.cm.getTypePreview(option); appOption.description = `${typeInfo} ${appOption.description}`; // add name localizations (convert for discord needed) if (Object.keys(translations).length > 0) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access appOption.name_localizations = await this.interface.convertTranslationMap(translations); } // add description localizations (convert for discord needed) if (Object.keys(description[1]).length > 0) { // prepend type information to description description[1] = Object.fromEntries(Object.entries(description[1]).map(([key, value]) => [key, `${typeInfo} ${value}`])); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access appOption.description_localizations = await this.interface.convertTranslationMap(description[1]); } // Bentocord Array support if (option.array) appOption.type = ApplicationCommandOptionTypes.STRING; // TODO: Stop being lazy, and make this typesafe // choices support if ('choices' in option) { let choices = option.choices; if (typeof choices === 'function') choices = await choices(); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access appOption.choices = choices.map(c => ({ name: c.label, value: c.value })); } // min/max support // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if ('min' in option) appOption.min_value = option.min; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if ('max' in option) appOption.max_value = option.max; // channel_types support // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if ('channelTypes' in option) appOption.channel_types = option.channelTypes; // required support appOption.required = typeof option.required === 'boolean' ? option.required : true; // failed to convert option if (option.type === null) continue; collector.push(appOption); } return collector; } } __decorate([ (0, bento_1.Inject)(), __metadata("design:type", BentocordInterface_1.BentocordInterface) ], SlashManager.prototype, "interface", void 0); __decorate([ (0, bento_1.Inject)(), __metadata("design:type", Discord_1.Discord) ], SlashManager.prototype, "discord", void 0); __decorate([ (0, bento_1.Inject)(), __metadata("design:type", CommandManager_1.CommandManager) ], SlashManager.prototype, "cm", void 0); exports.SlashManager = SlashManager; //# sourceMappingURL=SlashManager.js.map