Media
One rule governs all media in this package: wherever a WAMediaUpload is accepted, you can pass { url }, a Buffer, or a readable stream โ the library fetches, encrypts, and uploads it for you.
Sending โ per type
| Type | Content shape | Per-type options that exist |
|---|---|---|
| Image | { image: upload, caption?, jpegThumbnail? } | width/height are read from the image; thumbnails auto-generated |
| Video | { video: upload, caption? } | gifPlayback: true sends it as a looping GIF-style video; ptv: true sends a round video note |
| Audio | { audio: upload, mimetype } | ptt: true = voice note (waveform + duration computed); set seconds to override duration |
| Document | { document: upload, mimetype, fileName } | caption supported; fileLength optional |
| Sticker | { sticker: upload } | WebP expected (512ร512 is the platform norm). For whole packs, use StickerPack |
| Location | { location: { degreesLatitude, degreesLongitude }, name?, address? } | Live location uses liveLocation instead |
| Contact | { contacts: { displayName, contacts: [{ vcard }] } } | vCard string per contact |
| Link preview | { text, ... } with a URL inside | Set generateHighQualityLinkPreview: true in the socket config for rich thumbnails, or pass a manual thumbnail/matchedText via extended text options |
| View once | any media + viewOnce: true | Works on image/video/document sends |
| Disappearing | any content + option ephemeralExpiration | Seconds: 86400 / 604800 / 7776000 โ pass in the send options, not the content |
Downloading what people send you
import { downloadMediaMessage } from 'aunubaileys'
import fs from 'node:fs'
sock.ev.on('messages.upsert', async ({ messages, type }) => {
if (type !== 'notify') return
for (const m of messages) {
const img = m.message?.imageMessage
if (!img) continue
const buffer = await downloadMediaMessage(m, 'buffer', {})
fs.writeFileSync('./inbox/' + m.key.id + '.jpg', buffer)
}
})
downloadMediaMessage(message, 'buffer' | 'stream', options) decrypts and returns the media. For big files use 'stream' and pipe it โ buffering a 2 GB video into RAM is how bots die.
When a download fails: re-upload requests
Old media can fail to decrypt because the sender's device no longer serves it. The recovery path built into the socket:
// Ask the sender's client to re-upload the media
await sock.requestPlaceholderResend(m.key)
// The answer arrives as an event, not a return value:
sock.ev.on('messages.media-update', (updates) => {
for (const { key, media, error } of updates) {
if (media) console.log('Media refreshed for', key.id) // retry the download now
else console.log('Re-upload failed:', error)
}
})
Practical notes from the field
- Hosts are per-message. Since v1.0.4, media downloads honour the host embedded in the message itself โ if your downloads of older media suddenly work after updating, that's why.
- Upload once, send many. Passing the same URL repeatedly re-uploads every time unless you supply a
mediaCachein the socket config. - Voice notes need the right mimetype (
audio/ogg; codecs=opus) andptt: true, or clients show a plain audio file. - Newsletter media is special โ channels use their own upload paths since v1.0.2; see Newsletters.
- Quoted media: when replying to media, pass
{ quoted: m }in options โ the original message must be resolvable, which is another reason to keep the store wired.