ctx.send
API de envio. Todo método retorna um MessageHandle (thenable) com ações encadeáveis.
// chat atual (runtime only)
await ctx.send.text("Olá!");
await ctx.send.text("https://exemplo.com", { linkPreview: false });
await ctx.send.text("Olá @user", { mentions: ["5511999999999@c.us"] });
await ctx.send.image("/tmp/foto.jpg", "legenda");
await ctx.send.image("/tmp/foto.jpg", "secreta", { viewOnce: true });
await ctx.send.video("/tmp/video.mp4", "legenda");
await ctx.send.audio("/tmp/audio.ogg"); // como voz (padrão)
await ctx.send.audio("/tmp/audio.ogg", { asVoice: false }); // como áudio normal
await ctx.send.sticker("/tmp/sticker.webp");
await ctx.send.sticker(bufferGeradoEmMemoria);
await ctx.send.file("/tmp/relatorio.pdf", "relatorio-2025.pdf");
await ctx.send.poll("Qual sabor?", ["Morango", "Chocolate"], { allowMultipleAnswers: true });
// qualquer chat, por ID (setup + runtime)
await ctx.send.to(chatId).text("notificação");
Opções por método
| Método | Opções |
|---|---|
text |
{ linkPreview?, mentions?: string[] } |
image / video |
{ viewOnce? } |
audio |
{ asVoice? (padrão true), viewOnce? } |
poll |
{ allowMultipleAnswers? } |
sticker / file |
sem opções extra |
Ações pós-envio (chaining)
await ctx.send.text("Pronto!").react("✅");
const handle = await ctx.send.image("/tmp/x.jpg"); // handle = Message enviada
await handle.delete();
delete(forEveryone? = true) e react(emoji) retornam Promise. Hoje, delete(false)
("apagar só pra mim") não faz nada — só delete()/delete(true) (apagar pra todos)
realmente tem efeito.
pin(duration?)também existe na interface, mas atualmente não tem efeito — o Baileys (a biblioteca por trás da conexão com o WhatsApp) ainda não suporta fixar mensagens, então chamar.pin()só registra um aviso no log e não faz nada. Deixamos o método aí pra quando isso mudar.
send vs reply:
ctx.send.*manda sem citar nada.ctx.msg.reply.*cita a mensagem que disparou o handler — prefirareplyem grupos.
ctx.send.to()dentro de um handler não aplicacooldown/jitterdoguardOptionsdo seu plugin (só o limite global de envios continua valendo) — é assim porque.to()normalmente serve pra notificar outro chat, não o atual. Já emsetup(),ctx.send.to()aplicacooldown/jitternormalmente (os padrões,true/true, já que não háguardOptionsde chat nenhum nesse ponto).
ctx.msg
Contexto da mensagem que disparou o handler (runtime only).
ctx.msg.body; // string — texto completo
ctx.msg.type; // "chat" | "image" | "video" | "audio" | ...
ctx.msg.fromMe; // true se enviada pelo próprio bot
ctx.msg.sender; // ID de quem enviou — ex: "5511999999999@c.us"
ctx.msg.senderName; // nome de exibição
ctx.msg.command; // primeira palavra, sem prefixo, minúscula
ctx.msg.args; // string[] — palavras após o comando
ctx.msg.hasMedia; // boolean
ctx.msg.isGif; // boolean
ctx.msg.hasReply; // true se é resposta a outra msg
ctx.msg.hasPrefix; // true se começa com CMD_PREFIX
Formato de ID: o ManyBot normaliza conversas privadas pro sufixo
@c.us(o formato que o ManyBot já usava antes de migrar pro Baileys) em tudo que expõe a plugins —ctx.msg.sender,ctx.chat.id, objetos de contato,CHATSnomanybot.toml, etc. Grupos continuam com@g.usnormalmente. Use sempre@c.usao montar ou comparar um ID de conversa privada manualmente — um valor com@s.whatsapp.net(o formato nativo do Baileys) não vai bater numa comparação===comctx.msg.sender.
Detectando comandos
// Mensagem: "!ping" (CMD_PREFIX = "!")
if (ctx.msg.is("ping")) {
await ctx.send.text("pong!");
}
Lendo argumentos
// Mensagem: "!video https://youtube.com/watch?v=..."
const url = ctx.msg.args[0];
if (!url) {
await ctx.msg.reply.text("Informe uma URL.");
return;
}
Respondendo com quote
ctx.msg.reply é um sender completo — mesmos métodos de ctx.send, mas citando a mensagem
original:
await ctx.msg.reply.text("Aqui está sua resposta!");
await ctx.msg.reply.image("/tmp/foto.jpg", "Sua imagem.");
Baixando mídia
if (ctx.msg.hasMedia) {
const media = await ctx.msg.downloadMedia(); // { mimetype, data(base64) } | null
if (!media) return void await ctx.msg.reply.text("Não consegui baixar.");
const buf = Buffer.from(media.data, "base64");
}
Mensagem citada
if (ctx.msg.hasReply) {
const quoted = await ctx.msg.getReply(); // objeto igual ao ctx.msg | null
console.log(quoted?.senderName, quoted?.hasMedia, quoted?.body);
}
getReply() devolve um objeto na mesma shape do ctx.msg (body, type, sender,
senderName, hasMedia, isGif, downloadMedia(), hasPrefix, command, args, reply,
etc.) — não é o formato bruto do Baileys. Isso quer dizer que dá sim pra usar
quoted.hasMedia/quoted.downloadMedia()/quoted.reply.text(...) direto:
if (ctx.msg.hasReply) {
const quoted = await ctx.msg.getReply();
if (quoted?.hasMedia) {
const media = await quoted.downloadMedia(); // baixa a mídia da mensagem citada
}
}
Não existe campo
quoted.pushNamenemquoted.message— usequoted.senderName(igual aoctx.msg.senderName) e, se precisar da estrutura crua do Baileys por algum motivo específico, não tem atalho público pra isso hoje.
getReply()não faz nenhuma chamada de rede — ele só reprocessa dados que a mensagem atual já trouxe consigo (a citação vem embutida emcontextInfo). Não tem delay associado a ele.
quoted.senderNamesempre cai pro número de telefone — o protocolo de citação do WhatsApp (contextInfo) não carrega opushNamede quem mandou a mensagem original, só o JID. Isso é diferente doctx.contacts.get()/ctx.msg.getContact(), que de fato aprende e guarda o nome real ao vivo (veja a nota em ctx.contacts) — masgetReply()não passa por esse caminho, então não se beneficia disso.
Contato do remetente
ctx.msg.getContact() resolve @lid automaticamente — prefira isso a
ctx.contacts.get(ctx.msg.sender) dentro de um handler. Mesma shape de
objeto de contato, incluindo o mesmo
comportamento de devolver null pra alguém que o bot ainda não "viu" mandar mensagem (veja a
nota em ctx.contacts) — trate esse caso antes de usar contact.mention
ou qualquer outro campo:
const contact = await ctx.msg.getContact();
if (!contact) return; // ainda não temos dados desse contato
await ctx.msg.reply.text(`oi ${contact.mention.text}`, contact.mention);
Evitando loops
O bot recebe as próprias mensagens também:
export default async function (ctx) {
if (ctx.msg.fromMe) return;
// ...
}