vsyncDOCUMENTAÇÃO

Fundamentos de Apps

Mensagens e embeds

Formatação de mensagens, embeds, menções de pessoas, cargos e canais, emojis personalizados, como escapar símbolos e como o bot envia imagens e arquivos.

Bots enviam mensagens com POST /guilds/:g/channels/:c/messages (veja a Referência REST). O texto do body aceita markdown, menções e emojis; embeds são cartões ricos anexados à mensagem. Para enviar arquivos, prepare um anexo e inclua sua referência em attachments, conforme o fluxo de anexos.

Schema do embed#

Um embed é um cartão rico anexado a uma mensagem do bot. Uma mensagem aceita até 10 embeds, e a soma de title, description, fields, author.name e footer.text de todos não pode passar de 6000 caracteres. Todos os campos são opcionais; embeds vazios são descartados e chaves desconhecidas são ignoradas.

CampoTipoLimite
titletexto256 caracteres
descriptiontexto4096 caracteres
urllink https que torna o título clicável2048 caracteres
color#rrggbb (cor da barra lateral)formato exato
fieldslista de { name, value, inline? }até 25; name 256, value 1024; ambos obrigatórios
author{ name, iconUrl?, url? }name 256 (obrigatório)
footer{ text, iconUrl? }text 2048 (obrigatório)
image{ url } imagem grandehttps
thumbnail{ url } imagem pequenahttps
timestampmilissegundos Unixaté o ano 2100

Regras importantes:

  • Somente https em todos os links e imagens (http:, javascript: e links com usuário e senha são recusados com 400).
  • Os caracteres < e > são trocados por ‹ e › (não há HTML). A exceção são os tokens <@id>, <@&id>, <#id> e <:nome:id> em description e no value dos campos, que são mantidos e aparecem como chips/emoji, como no Discord.
  • Menções dentro de embeds não notificam ninguém: elas são só visuais (sem badge, som ou contagem de menções). Para pingar alguém, coloque a menção no body da mensagem.
  • description e value dos campos aceitam a formatação de markdown descrita abaixo, menções, canais e emojis personalizados. title, author, footer e o name dos campos são texto simples (tokens viram texto literal).
  • Campos com inline: true ficam lado a lado.
  • Só contas de bot podem enviar embeds, e a mensagem com embed pode ter body vazio.

Exemplo renderizável:

JSON
{
  "body": "Relatório do dia",
  "embeds": [
    {
      "title": "Resumo do servidor",
      "url": "https://vsync.lat",
      "description": "Veja os **números** de hoje.\n> Tudo funcionando",
      "color": "#7c5cff",
      "author": { "name": "vsync Bot", "iconUrl": "https://exemplo.com/icone.png" },
      "fields": [
        { "name": "Mensagens", "value": "1.240", "inline": true },
        { "name": "Membros novos", "value": "17", "inline": true },
        { "name": "Observação", "value": "Pico às 21h, `0` erros." }
      ],
      "thumbnail": { "url": "https://exemplo.com/miniatura.png" },
      "footer": { "text": "Atualizado agora" },
      "timestamp": 1791420369298
    }
  ]
}

Markdown nas mensagens#

O texto de body (e a description e os valores de campos dos embeds) aceita:

EfeitoSintaxe
Negrito**texto**
Itálico*texto* ou _texto_
Sublinhado__texto__
Tachado~~texto~~
Códigouma crase antes e outra depois do texto
Bloco de códigotrês crases, linguagem opcional na primeira linha, e três crases para fechar
Spoilerduas barras verticais antes e depois do texto
Título# Título, ## Título, ### Título no começo da linha
Texto pequeno-# texto no começo da linha
Citação> texto (uma linha ou várias) ou >>> texto (até o fim)
Lista- item ou 1. item (um nível de sub-lista com dois espaços)
Link[texto](https://site) ou o endereço solto
Menção a pessoa<@idDaConta>
Menção a cargo<@&idDoCargo> (veja Menções, emojis e escape)
Menção a canal<#idDoCanal>
Menção geral@everyone e @here (só notificam com a permissão MENTION_EVERYONE)
Emoji personalizado<:nome:id> ou <a:nome:id>
Escapar\ antes do símbolo, por exemplo \*, para mostrar o caractere; também vale para menções e emojis (\<@id>, \<:nome:id>)

Exemplo de body:

Texto
# Bem-vindo!
Veja o canal <#a8c57340-...> e leia as **regras**.
- Seja gentil
- Spoiler: ||surpresa||

O limite é de 2000 caracteres por mensagem. Menções usam o id (author.id em eventos ou id em GET /guilds/:g), não o nome. Cargos, emojis e escape estão na próxima seção.

Menções, emojis e escape#

Alguns símbolos especiais dentro do body viram menções ou emojis. Todos usam o id (de conta, de cargo, de canal ou de emoji), nunca o nome.

TokenO que fazObservações
<@idDaConta>Menciona uma pessoa e a notificaUse o id da conta (author.id nos eventos, id em GET /guilds/:g)
<@&idDoCargo>Menciona um cargoPrecisa da permissão MENTION_EVERYONE ou que o cargo seja mencionável. No máximo 10 cargos por mensagem
<#idDoCanal>Mostra um link para o canalNão notifica ninguém
@everyone e @hereMenção geralSó notificam com a permissão MENTION_EVERYONE
<:nome:id>Emoji personalizado estáticoVeja Emojis personalizados
<a:nome:id>Emoji personalizado animadoO id define o emoji; o nome é só informativo

Escapar: uma barra invertida (\) antes de um token envia o texto literal, sem mencionar ninguém e sem virar emoji. Por exemplo, \<@id> aparece como o próprio texto <@id>, e \<:nome:id> como <:nome:id>. Para mostrar uma barra invertida de verdade, escreva \\.

Texto
Menção de verdade: <@bc86cc02-...> e <@&efb6b62a-...>
Texto literal, sem notificar: \<@bc86cc02-...> e \<:festa:3f2a9c1e-...>
Uma barra invertida: \\

Emojis personalizados#

Cada servidor pode ter até 50 emojis próprios sem Pulse; o limite sobe com o nível de Pulse do servidor (100 no nível 1, 150 no nível 2 e 250 no nível 3). O bot lista os do servidor em emojis (em GET /guilds/:g, GET /guilds/:g/emojis e no GUILD_CREATE), cada um com id, name, url e animated. Para usar um emoji no body, escreva o token:

EmojiToken
Estático<:nome:id>
Animado<a:nome:id>

Exemplo: Valeu <:festa:3f2a9c1e-5b7d-4c0e-8a11-9d6e2b7a4f10>. A exibição usa o id; o nome do token é só informativo. O bot só pode usar emojis de servidores em que ele está: um token de emoji inexistente ou de outro servidor vira o texto :nome:. A mesma regra vale para reações (emoji no endpoint de reações recebe o token); emoji inválido responde 400 Emoji inválido.. Em embeds, os tokens funcionam em description e no value dos campos (com a mesma regra: emoji que o bot não pode usar vira :nome:).

Enviar imagens e arquivos#

Bots enviam imagens, vídeos, áudios e arquivos quaisquer como anexos de verdade, pelo mesmo fluxo que as pessoas usam no app: os bytes vão direto para o armazenamento privado da vsync por uma URL de envio temporária, e a mensagem só referencia o arquivo. A API nunca recebe o conteúdo, e os arquivos só são lidos por quem pode ver a mensagem, por URLs assinadas de curta duração.

O fluxo tem três passos e depois a mensagem:

  1. Preparar: POST /attachments/init com { name, size, contentType?, scope: { guildId, channelId } }. A resposta 201 traz id, uploadUrl, uploadHeaders e expiresAt (a URL de envio vale 15 minutos).
  2. Enviar os bytes: PUT na uploadUrl, com os cabeçalhos de uploadHeaders (hoje Content-Type: application/octet-stream) e exatamente size bytes no corpo. Sem Authorization nesse passo: a URL já está assinada.
  3. Concluir: POST /attachments/:id/complete. A API confere o tamanho, detecta o tipo pelo conteúdo (não pelo nome nem pelo contentType declarado) e devolve { id, name, size, kind, mime, warn }. kind é image, video, audio ou file; warn marca nomes e conteúdos executáveis.
  4. Usar: mande a mensagem com attachments: ["att:<id>"] em POST /guilds/:g/channels/:c/messages (o body pode ficar vazio) ou numa resposta de interação. A mensagem volta com attachments (as referências) e files (os metadados de cada arquivo).

Regras:

  • Permissões: o bot precisa ser membro do servidor e ter SEND_MESSAGES e ATTACH_FILES no canal (as sobrescritas do canal valem, como para qualquer membro). Sem isso, init e o envio da mensagem respondem 403 O bot não pode anexar arquivos aqui..
  • Só canais de servidor (texto e voz). Canais de voz têm chat de texto e aceitam anexos como os de texto; categorias não. Bots não têm mensagens diretas: scope com dmUserId ou groupId responde 403.
  • Limite por arquivo: 100 MB (fixo para bots, igual ao plano grátis; não há upgrade). Acima disso, 413 com { error, maxBytes, tier: "bot" }. GET /attachments/limits com o token do bot devolve { tier: "bot", maxBytes, limits, available, maxPending }.
  • Anexos por mensagem: o limite é o do servidor (4 no nível 0 de Pulse, até 10 nos níveis mais altos), o mesmo das pessoas. Acima, 400 Envie até N anexos por mensagem..
  • Um anexo, uma mensagem. A referência precisa estar concluída, ser do próprio bot e do mesmo canal do scope; depois de usada não serve de novo. Qualquer outra situação responde 400 Anexo inválido..
  • Envios em andamento: até 10 por bot ao mesmo tempo (429), e até 20 init por minuto. Um envio não concluído some em 1 hora; um arquivo concluído e não usado em mensagem some em 24 horas. Para desistir antes, DELETE /attachments/:id.
  • Ler arquivos: GET /attachments/:id (ou POST /attachments/resolve com { ids }) devolve { url, downloadUrl, expiresAt } com URLs de 5 minutos, para arquivos de mensagens em canais que o bot vê. Os eventos do gateway (MESSAGE_CREATE, MESSAGE_UPDATE) e GET .../messages trazem só files (metadados), nunca URLs assinadas.
  • Não existe envio por URL (a API não baixa imagens de outros sites). Para mostrar uma imagem hospedada fora, continue usando image ou thumbnail de um embed.
Bash
# 1) preparar
INIT=$(curl -s -X POST $API/attachments/init \
  -H "Authorization: Bot $TOKEN" -H "Content-Type: application/json" \
  -d "{\"name\":\"grafico.png\",\"size\":$(wc -c < grafico.png),\"contentType\":\"image/png\",\"scope\":{\"guildId\":\"$G\",\"channelId\":\"$C\"}}")
ID=$(echo "$INIT" | jq -r .id)
# 2) enviar os bytes (sem Authorization)
curl -s -X PUT "$(echo "$INIT" | jq -r .uploadUrl)" -H "Content-Type: application/octet-stream" --data-binary @grafico.png
# 3) concluir
curl -s -X POST $API/attachments/$ID/complete -H "Authorization: Bot $TOKEN"
# 4) mandar a mensagem
curl -s -X POST $API/guilds/$G/channels/$C/messages \
  -H "Authorization: Bot $TOKEN" -H "Content-Type: application/json" \
  -d "{\"body\":\"Gráfico de hoje\",\"attachments\":[\"att:$ID\"]}"

Em Node (20 ou mais novo, só fetch):

JavaScript
import { readFile } from "node:fs/promises";

const API = "https://api.vsync.lat";
const headers = { Authorization: `Bot ${process.env.VSYNC_BOT_TOKEN}`, "Content-Type": "application/json" };

async function sendFile(guildId, channelId, path, name, body = "") {
  const bytes = await readFile(path);
  const init = await fetch(`${API}/attachments/init`, {
    method: "POST", headers,
    body: JSON.stringify({ name, size: bytes.byteLength, scope: { guildId, channelId } }),
  }).then((r) => r.json());
  if (!init.id) throw new Error(init.error);
  const put = await fetch(init.uploadUrl, { method: "PUT", headers: init.uploadHeaders, body: bytes });
  if (!put.ok) throw new Error(`upload falhou: ${put.status}`);
  const file = await fetch(`${API}/attachments/${init.id}/complete`, { method: "POST", headers }).then((r) => r.json());
  if (!file.id) throw new Error(file.error);
  return fetch(`${API}/guilds/${guildId}/channels/${channelId}/messages`, {
    method: "POST", headers,
    body: JSON.stringify({ body, attachments: [`att:${file.id}`] }),
  }).then((r) => r.json());
}

Anexos em respostas de interação#

As respostas a comandos, botões e modais aceitam o mesmo campo attachments (referências att:<id> preparadas no canal da interação):

  • POST /interactions/:id/:token/callback com { "type": "message", "data": { "body": "...", "attachments": ["att:..."] } }.
  • Follow-ups: POST /webhooks/:appId/:token com { attachments: [...] } (pode ser só o anexo, sem body).
  • Edição: PATCH /webhooks/:appId/:token/messages/@original com attachments substitui a lista: os que já estavam na mensagem ficam, os novos são vinculados e os que saíram da lista são apagados do armazenamento. "attachments": [] remove todos (a mensagem precisa continuar com texto, embed ou componente).
  • Respostas efêmeras não aceitam anexos (400 Respostas efêmeras não aceitam anexos.): elas não são guardadas, então o arquivo nunca viraria mensagem.
  • Valem as mesmas regras de permissão (403 O bot não pode anexar arquivos aqui. sem ATTACH_FILES; a interação continua pendente para você responder sem o anexo) e de quantidade por mensagem.

Como uma interação precisa do primeiro retorno em 3 segundos, prepare o arquivo antes (ou responda com defer, faça o envio e conclua com o follow-up).