Documentação/Componentes
COMPONENTES / MENSAGENS V2

Mensagens Components V2

Aprenda cada peça visual e combine containers, seções, mídia, action rows e selects em mensagens legíveis.

ContainerSectionGallerySelect

Mensagem básica com container#

O container cria um bloco visual e pode receber cor de destaque, texto e divisores.

TSstatus-card.ts
import {  ContainerBuilder,  MessageBuilder,  SeparatorBuilder,  TextDisplayBuilder} from '@ninenity/componentbuilder'export const statusCard = new MessageBuilder()  .addComponents(    new ContainerBuilder()      .setAccentColor(0xa571f4)      .addTextDisplayComponents(        new TextDisplayBuilder().setContent('## Status do serviço'),        new TextDisplayBuilder().setContent('Todos os sistemas estão operacionais.')      )      .addSeparatorComponents(        new SeparatorBuilder().setDivider(true).setSpacing('small')      )      .addTextDisplayComponents(        new TextDisplayBuilder().setContent('- API: online- Bot: online- Banco: online')      )  )  .template()

O conteúdo do TextDisplayBuilder aceita markdown suportado pelo Discord. Prefira blocos curtos e uma hierarquia clara em vez de uma única parede de texto.

Enviar pelo Core#

Converta o documento e combine as flags de Components V2 e resposta efêmera quando necessário.

TSDiscord/Commands/Status.ts
import { MessageFlags, SlashCommandBuilder } from '@ninenity/core'import { toDiscordMessagePayload } from '@ninenity/componentbuilder'import { statusCard } from '../Interactions/StatusCard'import Client from '../../Shared/Settings/Client'Client.slash({  data: new SlashCommandBuilder()    .setName('status')    .setDescription('Mostra o estado dos serviços.'),  execute: async interaction => {    await interaction.reply({      ...toDiscordMessagePayload(statusCard.document()),      flags: MessageFlags.IsComponentsV2 | MessageFlags.Ephemeral    })  }})

Seção com thumbnail ou botão#

Uma SectionBuilder combina texto com exatamente um acessório lateral.

TSproject-section.ts
const withThumbnail = new SectionBuilder()  .addTextDisplayComponents(    new TextDisplayBuilder().setContent(      '### Builder StudioCrie e exporte interfaces Components V2.'    )  )  .setThumbnailAccessory(    new ThumbnailBuilder()      .setURL('https://cdn.example.com/builder.png')      .setDescription('Logo do Builder Studio')  )const withButton = new SectionBuilder()  .setContent('### DocumentaçãoVeja todos os exemplos do componente.')  .setButtonAccessory(    new ButtonBuilder()      .setStyle(ButtonStyle.Link)      .setLabel('Abrir docs')      .setURL('https://ninenity.com/doc')  )const template = new MessageBuilder()  .addComponents(    new ContainerBuilder().addSectionComponents(withThumbnail, withButton)  )  .template()

Botões de link não possuem customId nem callback. Botões de ação precisam de customId, estilo diferente de Link e podem usar .setCallback().

Galeria e arquivo#

Use galeria para mídia visual e arquivo quando o anexo faz parte da composição.

TSmedia-card.ts
const gallery = new MediaGalleryBuilder()  .addItems(    item => item      .setURL('https://cdn.example.com/dashboard.png')      .setDescription('Dashboard do projeto'),    item => item      .setURL('https://cdn.example.com/components.png')      .setDescription('Componentes no Discord')  )const template = new MessageBuilder()  .addComponents(    new ContainerBuilder()      .addTextDisplayComponents(        new TextDisplayBuilder().setContent('## Visão do projeto')      )      .addMediaGalleryComponents(gallery)      .addFileComponents(        new FileBuilder().setURL('attachment://relatorio.pdf')      )  )  .template()
  • A galeria aceita no máximo 10 itens.
  • Use setSpoiler(true) em uma imagem ou arquivo que não deve aparecer imediatamente.
  • A URL attachment://nome.ext precisa corresponder a um arquivo realmente enviado no payload.
  • Sempre forneça descrição útil para imagens importantes.

Linha de botões#

Uma action row aceita até cinco botões. Use estilos para significado, não apenas decoração.

TSactions.ts
const actions = new ActionRowBuilder<MessageActionRowComponentBuilder>()  .addComponents(    new ButtonBuilder()      .setCustomId('project:approve')      .setLabel('Aprovar')      .setStyle(ButtonStyle.Success),    new ButtonBuilder()      .setCustomId('project:edit')      .setLabel('Editar')      .setStyle(ButtonStyle.Secondary),    new ButtonBuilder()      .setCustomId('project:delete')      .setLabel('Excluir')      .setStyle(ButtonStyle.Danger),    new ButtonBuilder()      .setLabel('Ver no HUB')      .setStyle(ButtonStyle.Link)      .setURL('https://hub.ninenity.com')  )const message = new MessageBuilder()  .addComponents(new ContainerBuilder().addActionRowComponents(actions))
EstiloUse para
PrimaryA ação principal daquela etapa
SecondaryAções neutras, navegação e alternativas
SuccessConfirmar, concluir ou ativar
DangerExcluir, revogar ou outra ação destrutiva
LinkAbrir URL; não recebe callback

Menus de seleção#

String select define opções; selects de usuário, cargo, canal e menção usam os seletores nativos do Discord.

TSstring-select.ts
const projectSelect = new StringSelectMenuBuilder()  .setCustomId('project:environment')  .setPlaceholder('Selecione um ambiente')  .setMinValues(1)  .setMaxValues(1)  .addOptions(    option => option      .setLabel('Produção')      .setValue('production')      .setDescription('Ambiente público'),    option => option      .setLabel('Desenvolvimento')      .setValue('development')      .setDescription('Ambiente de testes')  )const row = new ActionRowBuilder<MessageActionRowComponentBuilder>()  .addComponents(projectSelect)
TSseletores nativos
const userRow = new ActionRowBuilder<MessageActionRowComponentBuilder>()  .addComponents(    new UserSelectMenuBuilder()      .setCustomId('team:members')      .setPlaceholder('Selecione até 3 membros')      .setMinValues(1)      .setMaxValues(3)  )const roleRow = new ActionRowBuilder<MessageActionRowComponentBuilder>()  .addComponents(    new RoleSelectMenuBuilder()      .setCustomId('team:role')      .setPlaceholder('Selecione o cargo da equipe')  )

Variáveis de template#

Tokens deixam o mesmo layout renderizar dados diferentes sem reconstruir toda a árvore.

TSmember-template.ts
const memberTemplate = new MessageBuilder()  .addComponents(    new ContainerBuilder().addTextDisplayComponents(      new TextDisplayBuilder().setContent(        '## Olá, ${user.name}!Seu plano atual é **${account.plan}**.'      )    )  )  .template()const rendered = memberTemplate.render({  user: { name: interaction.user.displayName },  account: { plan: 'Pro' }})await interaction.reply({  ...toDiscordMessagePayload(rendered.document()),  flags: MessageFlags.IsComponentsV2})