diff --git a/README.md b/README.md index 96ec12f..5f411dc 100644 --- a/README.md +++ b/README.md @@ -569,7 +569,7 @@ The presence expires after about 10 seconds. If you want to save the media you received ``` ts import { writeFile } from 'fs/promises' -import { downloadContentFromMessage } from '@adiwajshing/baileys' +import { downloadMediaMessage } from '@adiwajshing/baileys' sock.ev.on('messages.upsert', async ({ messages }) => { const m = messages[0] @@ -578,18 +578,29 @@ sock.ev.on('messages.upsert', async ({ messages }) => { const messageType = Object.keys (m.message)[0]// get what type of message it is -- text, image, video // if the message is an image if (messageType === 'imageMessage') { - // download stream - const stream = await downloadContentFromMessage(m.message.imageMessage, 'image') - let buffer = Buffer.from([]) - for await(const chunk of stream) { - buffer = Buffer.concat([buffer, chunk]) - } + // download the message + const buffer = await downloadMediaMessage( + m, + 'buffer', + { }, + { + logger, + // pass this so that baileys can request a reupload of media + // that has been deleted + reuploadRequest: sock.updateMediaMessage + } + ) // save to file await writeFile('./my-download.jpeg', buffer) } } ``` +**Note:** WhatsApp automatically removes old media from their servers, and so for the device to access said media -- a re-upload is required by another device that has the media. This can be accomplished using: +``` ts +const updatedMediaMsg = await sock.updateMediaMessage(msg) +``` + ## Deleting Messages ``` ts diff --git a/src/Utils/messages.ts b/src/Utils/messages.ts index 69761ec..b3107fe 100644 --- a/src/Utils/messages.ts +++ b/src/Utils/messages.ts @@ -1,5 +1,7 @@ import { Boom } from '@hapi/boom' +import axios from 'axios' import { promises as fs } from 'fs' +import { Logger } from 'pino' import { proto } from '../../WAProto' import { MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults' import { @@ -586,33 +588,67 @@ export const aggregateMessageKeysNotFromMe = (keys: proto.IMessageKey[]) => { return Object.values(keyMap) } +type DownloadMediaMessageContext = { + reuploadRequest: (msg: WAMessage) => Promise + logger: Logger +} + +const REUPLOAD_REQUIRED_STATUS = [410, 404] + /** * Downloads the given message. Throws an error if it's not a media message */ -export const downloadMediaMessage = async(message: WAMessage, type: 'buffer' | 'stream', options: MediaDownloadOptions) => { - const mContent = extractMessageContent(message.message) - if(!mContent) { - throw new Boom('No message present', { statusCode: 400, data: message }) - } - - const contentType = getContentType(mContent) - const mediaType = contentType.replace('Message', '') as MediaType - const media = mContent[contentType] - if(typeof media !== 'object' || !('url' in media)) { - throw new Boom(`"${contentType}" message is not a media message`) - } - - const stream = await downloadContentFromMessage(media, mediaType, options) - if(type === 'buffer') { - let buffer = Buffer.from([]) - for await (const chunk of stream) { - buffer = Buffer.concat([buffer, chunk]) +export const downloadMediaMessage = async( + message: WAMessage, + type: 'buffer' | 'stream', + options: MediaDownloadOptions, + ctx?: DownloadMediaMessageContext +) => { + try { + const result = await downloadMsg() + return result + } catch(error) { + if(ctx) { + if(axios.isAxiosError(error)) { + // check if the message requires a reupload + if(REUPLOAD_REQUIRED_STATUS.includes(error.response?.status)) { + ctx.logger.info({ key: message.key }, 'sending reupload media request...') + // request reupload + message = await ctx.reuploadRequest(message) + const result = await downloadMsg() + return result + } + } } - return buffer + throw error } - return stream + async function downloadMsg() { + const mContent = extractMessageContent(message.message) + if(!mContent) { + throw new Boom('No message present', { statusCode: 400, data: message }) + } + + const contentType = getContentType(mContent) + const mediaType = contentType.replace('Message', '') as MediaType + const media = mContent[contentType] + if(typeof media !== 'object' || !('url' in media)) { + throw new Boom(`"${contentType}" message is not a media message`) + } + + const stream = await downloadContentFromMessage(media, mediaType, options) + if(type === 'buffer') { + let buffer = Buffer.from([]) + for await (const chunk of stream) { + buffer = Buffer.concat([buffer, chunk]) + } + + return buffer + } + + return stream + } } /** Checks whether the given message is a media message; if it is returns the inner content */