Documentação/Base do Core
BASE DO CORE / COMANDOS

Comandos

Crie comandos slash, prefixados e menus de contexto com um contrato de execução consistente.

SlashPrefixContext menu

Um contrato, três entradas#

Todo registro combina os dados visíveis ao Discord com opções de runtime e uma função execute.

RegistroEntradaBuilder
Client.slash()Comando nativo /nomeSlashCommandBuilder
Client.prefix()Mensagem como !nomePrefixCommandBuilder
Client.contextMenu()Menu sobre usuário ou mensagemContextMenuCommandBuilder
D

data

Nome, descrição, opções, aliases ou tipo do comando.

R

runtime

Cooldown, fila, escopo de DM e outras regras antes da execução.

E

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.

TSDiscord/Commands/Ping.ts
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.

TSDiscord/Commands/Inspect.ts
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çãoLeitura
StringgetString(nome, obrigatório?)
Integer / NumbergetInteger() / getNumber()
BooleangetBoolean()
User / MembergetUser() / getMember()
Channel / RolegetChannel() / getRole()
AttachmentgetAttachment()

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.

TSDiscord/Commands/Starter.ts
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.

TSDiscord/Commands/Manage.ts
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.

TSDiscord/Commands/UserContext.ts
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    })  }})
TSDiscord/Commands/MessageContext.ts
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.

TSopções declarativas
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 })  }})
TSfila manual dentro de um handler
await Client.queue('billing:' + interaction.user.id, async () => {  const invoice = await createInvoice(interaction.user.id)  await interaction.reply('Fatura criada: ' + invoice.id)})