@ayanaware/bentocord
Version:
Bentocord is a Bento plugin designed to rapidly build fully functional Discord Bots.
254 lines • 12.7 kB
JavaScript
;
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