Comandos
Crie comandos slash, prefixados e menus de contexto com um contrato de execução consistente.
Um contrato, três entradas#
Todo registro combina os dados visíveis ao Discord com opções de runtime e uma função execute.
| Registro | Entrada | Builder |
|---|---|---|
Client.slash() | Comando nativo /nome | SlashCommandBuilder |
Client.prefix() | Mensagem como !nome | PrefixCommandBuilder |
Client.contextMenu() | Menu sobre usuário ou mensagem | ContextMenuCommandBuilder |
data
Nome, descrição, opções, aliases ou tipo do comando.
runtime
Cooldown, fila, escopo de DM e outras regras antes da execução.
execute
A lógica chamada com a interação já normalizada pelo Core.
Comando slash#
Use slash para descoberta nativa, validação de opções e autocomplete do Discord.
import { SlashCommandBuilder, type SlashInputCommandInteraction} from '@ninenity/core'import Client from '../../Shared/Settings/Client'Client.slash({ data: new SlashCommandBuilder() .setName('ping') .setDescription('Responde com a latência.'), cooldown: '5s', queue: 'user', execute: async (interaction: SlashInputCommandInteraction) => { const sent = await interaction.reply({ content: 'Calculando...', withResponse: true }) const latency = sent.resource?.message?.createdTimestamp ? sent.resource.message.createdTimestamp - interaction.createdTimestamp : 0 await interaction.editReply('Latência: ' + latency + 'ms') }})Opções tipadas#
O builder declara as opções e o objeto interaction.options entrega os valores validados.
import { ChannelType, SlashCommandBuilder, type SlashInputCommandInteraction} from '@ninenity/core'import Client from '../../Shared/Settings/Client'Client.slash({ data: new SlashCommandBuilder() .setName('inspect') .setDescription('Inspeciona dados informados.') .addStringOption(option => option .setName('texto') .setDescription('Texto obrigatório') .setRequired(true)) .addIntegerOption(option => option .setName('quantidade') .setDescription('Entre 1 e 5') .setMinValue(1) .setMaxValue(5)) .addUserOption(option => option .setName('usuario') .setDescription('Usuário opcional')) .addChannelOption(option => option .setName('canal') .setDescription('Apenas canais de texto') .addChannelTypes(ChannelType.GuildText)), config: { Dm: false }, execute: async (interaction: SlashInputCommandInteraction) => { const text = interaction.options.getString('texto', true) const count = interaction.options.getInteger('quantidade') ?? 1 const user = interaction.options.getUser('usuario') const channel = interaction.options.getChannel('canal') await interaction.reply([ 'Texto: ' + text.repeat(count), 'Usuário: ' + (user?.tag ?? 'não informado'), 'Canal: ' + (channel?.name ?? 'não informado') ].join('')) }})| Opção | Leitura |
|---|---|
| String | getString(nome, obrigatório?) |
| Integer / Number | getInteger() / getNumber() |
| Boolean | getBoolean() |
| User / Member | getUser() / getMember() |
| Channel / Role | getChannel() / getRole() |
| Attachment | getAttachment() |
Slash e prefixo com o mesmo executor#
Quando a regra é igual, extraia uma função que aceite a interação normalizada e registre duas entradas.
import { PrefixCommandBuilder, SlashCommandBuilder, type InputCommandInteraction} from '@ninenity/core'import Client from '../../Shared/Settings/Client'const execute = async (interaction: InputCommandInteraction) => { await interaction.reply('Olá, ' + interaction.user.username + '!')}Client.slash({ data: new SlashCommandBuilder() .setName('starter') .setDescription('Mostra a resposta inicial.'), cooldown: '3s', queue: 'user', execute})Client.prefix({ data: new PrefixCommandBuilder() .setName('starter') .setAliases(['start']), cooldown: '3s', queue: 'user', execute})Prefixo, aliases e subcomandos#
O PrefixCommandBuilder oferece a mesma leitura estruturada de opções, sem depender do parser manual de message.content.
import { PrefixCommandBuilder, type PrefixInputCommandInteraction} from '@ninenity/core'import Client from '../../Shared/Settings/Client'Client.prefix({ data: new PrefixCommandBuilder() .setName('manage') .setAliases(['m']) .addSubcommand(subcommand => subcommand .setName('status') .setDescription('Mostra o status')) .addSubcommand(subcommand => subcommand .setName('set') .setDescription('Salva um valor') .addStringOption(option => option .setName('value') .setDescription('Novo valor') .setRequired(true))) .addSubcommandGroup(group => group .setName('config') .setDescription('Configuração') .addSubcommand(subcommand => subcommand .setName('show') .setDescription('Exibe a configuração'))), execute: async (interaction: PrefixInputCommandInteraction) => { const group = interaction.options.getSubcommandGroup() const command = interaction.options.getSubcommand() ?? 'status' const value = interaction.options.getString('value') await interaction.reply( 'grupo=' + (group ?? 'geral') + ' comando=' + command + ' valor=' + (value ?? 'nenhum') ) }})Com prefixo !, os exemplos seriam !manage status, !m set produção e !manage config show. O Core resolve aliases, grupo, subcomando e opções antes de chamar execute.
Menus de contexto#
Menus de contexto aparecem no clique direito sobre um usuário ou uma mensagem.
import { ApplicationCommandType, ContextMenuCommandBuilder, type ContextMenuInputCommandInteraction} from '@ninenity/core'import Client from '../../Shared/Settings/Client'Client.contextMenu({ data: new ContextMenuCommandBuilder() .setName('Ver usuário') .setType(ApplicationCommandType.User), execute: async (interaction: ContextMenuInputCommandInteraction) => { const raw = interaction.interaction const target = raw.isUserContextMenuCommand() ? raw.targetUser : null await interaction.reply({ content: target ? 'Selecionado: ' + target.tag : 'Usuário indisponível.', flags: 64 }) }})Client.contextMenu({ data: new ContextMenuCommandBuilder() .setName('Citar mensagem') .setType(ApplicationCommandType.Message), execute: async interaction => { const raw = interaction.interaction const target = raw.isMessageContextMenuCommand() ? raw.targetMessage : null await interaction.reply( target ? target.author.tag + ': ' + target.content : 'Mensagem indisponível.' ) }})Cooldown e filas#
Cooldown limita frequência; fila evita duas execuções concorrentes sobre o mesmo recurso.
Client.slash({ data: new SlashCommandBuilder() .setName('sincronizar') .setDescription('Sincroniza os dados do usuário.'), cooldown: '30s', queue: 'user', execute: async interaction => { await syncUser(interaction.user.id) await interaction.reply({ content: 'Sincronizado.', flags: 64 }) }})await Client.queue('billing:' + interaction.user.id, async () => { const invoice = await createInvoice(interaction.user.id) await interaction.reply('Fatura criada: ' + invoice.id)})