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

- Área: Base do Core
- URL humana: https://ninenity.vercel.app/doc/comandos
- URL Markdown: https://ninenity.vercel.app/doc/markdown/comandos
- Pacotes e conceitos: Slash, Prefix, Context 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`.

| 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.

### Discord/Commands/Ping.ts

```text

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')
  }
})
```


> **Responda em até três segundos:** Se o trabalho pode demorar, chame `interaction.deferReply()` primeiro e finalize com `editReply()`. Isso evita a mensagem de interação expirada.

## Opções tipadas

O builder declara as opções e o objeto `interaction.options` entrega os valores validados.

### Discord/Commands/Inspect.ts

```text

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.

### Discord/Commands/Starter.ts

```text

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
})
```


> **Use apenas a superfície comum:** Dentro do executor compartilhado, evite APIs exclusivas de slash ou prefixo. Quando precisar delas, estreite o tipo ou mantenha executores separados.

## Prefixo, aliases e subcomandos

O PrefixCommandBuilder oferece a mesma leitura estruturada de opções, sem depender do parser manual de `message.content`.

### Discord/Commands/Manage.ts

```text

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.

### Discord/Commands/UserContext.ts

```text

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
    })
  }
})
```


### Discord/Commands/MessageContext.ts

```text

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.

### opções declarativas

```text

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 })
  }
})
```


### fila manual dentro de um handler

```text

await Client.queue('billing:' + interaction.user.id, async () => {
  const invoice = await createInvoice(interaction.user.id)
  await interaction.reply('Fatura criada: ' + invoice.id)
})
```


> **Isolamento automático:** Quando `Client.queue()` roda dentro do contexto de uma guild, o Core inclui essa guild na chave interna. A mesma ação em dois servidores não bloqueia uma à outra.
