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.
| Campo | Tipo | Limite |
|---|---|---|
title | texto | 256 caracteres |
description | texto | 4096 caracteres |
url | link https que torna o título clicável | 2048 caracteres |
color | #rrggbb (cor da barra lateral) | formato exato |
fields | lista 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 grande | https |
thumbnail | { url } imagem pequena | https |
timestamp | milissegundos Unix | até 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 com400). - 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>emdescriptione novaluedos 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
bodyda mensagem. descriptionevaluedos campos aceitam a formatação de markdown descrita abaixo, menções, canais e emojis personalizados.title,author,footere onamedos campos são texto simples (tokens viram texto literal).- Campos com
inline: trueficam lado a lado. - Só contas de bot podem enviar embeds, e a mensagem com embed pode ter
bodyvazio.
Exemplo renderizável:
{
"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:
| Efeito | Sintaxe |
|---|---|
| Negrito | **texto** |
| Itálico | *texto* ou _texto_ |
| Sublinhado | __texto__ |
| Tachado | ~~texto~~ |
| Código | uma crase antes e outra depois do texto |
| Bloco de código | três crases, linguagem opcional na primeira linha, e três crases para fechar |
| Spoiler | duas 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:
# 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.
| Token | O que faz | Observações |
|---|---|---|
<@idDaConta> | Menciona uma pessoa e a notifica | Use o id da conta (author.id nos eventos, id em GET /guilds/:g) |
<@&idDoCargo> | Menciona um cargo | Precisa 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 canal | Não notifica ninguém |
@everyone e @here | Menção geral | Só notificam com a permissão MENTION_EVERYONE |
<:nome:id> | Emoji personalizado estático | Veja Emojis personalizados |
<a:nome:id> | Emoji personalizado animado | O 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 \\.
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:
| Emoji | Token |
|---|---|
| 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:
- Preparar:
POST /attachments/initcom{ name, size, contentType?, scope: { guildId, channelId } }. A resposta201trazid,uploadUrl,uploadHeaderseexpiresAt(a URL de envio vale 15 minutos). - Enviar os bytes:
PUTnauploadUrl, com os cabeçalhos deuploadHeaders(hojeContent-Type: application/octet-stream) e exatamentesizebytes no corpo. SemAuthorizationnesse passo: a URL já está assinada. - Concluir:
POST /attachments/:id/complete. A API confere o tamanho, detecta o tipo pelo conteúdo (não pelo nome nem pelocontentTypedeclarado) e devolve{ id, name, size, kind, mime, warn }.kindéimage,video,audiooufile;warnmarca nomes e conteúdos executáveis. - Usar: mande a mensagem com
attachments: ["att:<id>"]emPOST /guilds/:g/channels/:c/messages(obodypode ficar vazio) ou numa resposta de interação. A mensagem volta comattachments(as referências) efiles(os metadados de cada arquivo).
Regras:
- Permissões: o bot precisa ser membro do servidor e ter
SEND_MESSAGESeATTACH_FILESno canal (as sobrescritas do canal valem, como para qualquer membro). Sem isso,inite o envio da mensagem respondem403 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:
scopecomdmUserIdougroupIdresponde403. - Limite por arquivo: 100 MB (fixo para bots, igual ao plano grátis; não há upgrade). Acima disso,
413com{ error, maxBytes, tier: "bot" }.GET /attachments/limitscom 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 responde400 Anexo inválido.. - Envios em andamento: até 10 por bot ao mesmo tempo (
429), e até 20initpor 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(ouPOST /attachments/resolvecom{ 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) eGET .../messagestrazem 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
imageouthumbnailde um embed.
# 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):
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/callbackcom{ "type": "message", "data": { "body": "...", "attachments": ["att:..."] } }.- Follow-ups:
POST /webhooks/:appId/:tokencom{ attachments: [...] }(pode ser só o anexo, sembody). - Edição:
PATCH /webhooks/:appId/:token/messages/@originalcomattachmentssubstitui 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.semATTACH_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).