client.message.send, usando um objeto de conteúdo de mídia tipado. O builder preenche para você os campos gerenciados pelo protocolo (chaves de criptografia, digests SHA-256, direct path, upload) — você fornece a fonte e, opcionalmente, um mimetype.
Resolução de mimetype
mimetype é opcional. O builder resolve nesta ordem:
- O
mimetypeque você passa no objeto de conteúdo vence. - Se um
WaMediaProcessorcomdetectMimetypeestá configurado, o builder o chama (sniffing de magic bytes).@zapo-js/media-utilsimplementa isso em cima defile-type^19 — instalefile-typepara habilitar a detecção. - Caso contrário, o builder lança erro para mensagens
image/video/audio/document/ptv.
image/webp por padrão quando nenhum mimetype é definido. Entradas de stream Readable sem mimetype são staged para um arquivo temporário antes da detecção rodar.
Entrada de mídia
O campomedia aceita vários tipos de entrada:
Imagens
Vídeo
type: 'ptv' com o mesmo formato.
Áudio e notas de voz
Documentos
Figurinhas
type: 'sticker-pack' com stickers, um trayIcon e os metadados do pacote (stickerPackId, name, publisher).
Visualização única
Envolva imagem/vídeo/áudio como visualização única com a opção de envio:Pré-upload e reuso
Às vezes você quer encriptar e uploadar mídia uma vez e depois referenciar o mesmo descriptor em múltiplos envios — um asset de broadcast, uma resposta pronta, ou pra construir um payload proto bruto na mão.client.message.upload(source, options) roda o fluxo de encrypt / media_conn / upload CDN / parse que o send usa, mas retorna o descriptor sem enviar nada.
media_conn). Sources Uint8Array pegam o fast path de zero-temp-file; um path de arquivo ou stream Readable é encaminhado por temp file pra que o pass de encrypt consiga hashear deterministicamente.
WaUploadMediaOptions
WaMediaUploadResult
Crypto & transfer standalone
Pra fluxos que precisam rodar encrypt/decrypt fora de uma sessão — um worker em background processando ciphertext armazenado, um relay custom —WaMediaCrypto e WaMediaTransferClient (mais seus tipos de result e options) são re-exportados da raiz do package:
upload / download usam; o método do coordinator só as encapsula com o handshake media_conn amarrado à sessão.
Baixando mídia recebida
O coordinator de mensagens descriptografa e baixa a mídia de um evento recebido. Três variantes estão disponíveis — prefira as de streaming:WaIncomingMessageEvent quanto um Proto.IMessage bruto, mais WaDownloadMediaOptions opcional (por exemplo maxBytes para limitar downloadBytes).
Sem um client conectado
downloadMediaMessage é uma função livre que espelha o client.message.download mas não precisa de uma sessão pareada. O metadata de mídia encriptada viaja dentro da própria mensagem (já decifrada), então você consegue re-baixar mídia de um evento persistido muito tempo depois do socket original ter ido embora — útil para workers offline, replay de arquivo, ou qualquer coisa que processe mensagens armazenadas sem subir um WaClient.
WaIncomingMessageEvent ou um Proto.IMessage bruto, retorna um Readable cujo dono é você (pipe ou .destroy() — um stream não consumido vaza o socket). Verificação MAC + SHA-256 roda conforme os bytes são consumidos, mesma semântica do método do coordinator. Lança quando a mensagem não tem mídia para download.
Para acesso de mais baixo nível — quando você quer fazer o fetch ao CDN você mesmo, passar as chaves para outro processo, ou só inspecionar o que é baixável — o
resolveMediaPayload retorna as keys + hashes sem fazer I/O:
null quando a mensagem não tem mídia para download, ou quando o proto não carrega directPath / mediaKey. Desencapsula ephemeralMessage, viewOnceMessage / viewOnceMessageV2 e documentWithCaptionMessage antes de resolver. Tipos suportados: image, video (gif quando gifPlayback), audio (ptt quando ptt), document, sticker, ptv.
Pedindo um reupload
O CDN dropa blobs antigos de mídia — umdownload* numa mensagem que veio de history sync pode responder 404/410 bem depois do envio original. client.message.requestMediaReupload() roda o round-trip que o WhatsApp Web usa pra recuperar: encripta um recibo server-error com a media key da mensagem, envia como ack e espera a notificação mediaretry que o device primário do remetente responde. No success só o directPath muda — media key, hashes e length da mensagem original continuam válidos, então baixar com o novo path espalhado funciona.
WaMediaRetryRequest explícita ({ messageId, chatJid, mediaKey, fromMe, participant? }) quando você tem os ids e a media key mas não a mensagem decodada — a forma com evento extrai isso pra você e rejeita mensagens de newsletter mais mensagens sem mídia baixável. options.timeoutMs limita a espera pela notificação; o coordinator não lança em not_found / general_error (o remetente não tem mais o arquivo, ou o primário não conseguiu re-selar) — cheque result.result em 'success' | 'not_found' | 'decryption_error' | 'general_error'.
Requests são deduplicados por
messageId: chamadas concorrentes pra mesma mensagem compartilham um único round-trip e um único recibo server-error.Processamento de mídia
Para mídia adequada, use um processador de mídia. Instale@zapo-js/media-utils e passe um através da opção de client media — ele sonda e processa a mídia (dimensões, duração, miniaturas, waveforms, normalização de notas de voz) antes do upload. Sem ele, a mídia ainda faz upload mas sem esse processamento:
@zapo-js/media-utils invoca ffmpeg/ffprobe e usa sharp. Garanta que esses binários estejam disponíveis no seu ambiente.