@whiskeysockets/baileys
Version:
A WebSockets library for interacting with WhatsApp Web
678 lines (677 loc) • 28.4 kB
JavaScript
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getStatusCodeForMediaRetry = exports.decryptMediaRetryData = exports.decodeMediaRetryNode = exports.encryptMediaRetryRequest = exports.getWAUploadToServer = exports.downloadEncryptedContent = exports.downloadContentFromMessage = exports.getUrlFromDirectPath = exports.encryptedStream = exports.getHttpStream = exports.getStream = exports.toBuffer = exports.toReadable = exports.mediaMessageSHA256B64 = exports.generateProfilePicture = exports.encodeBase64EncodedStringForUpload = exports.extractImageThumb = exports.hkdfInfoKey = void 0;
exports.getMediaKeys = getMediaKeys;
exports.getAudioDuration = getAudioDuration;
exports.getAudioWaveform = getAudioWaveform;
exports.generateThumbnail = generateThumbnail;
exports.extensionForMediaMessage = extensionForMediaMessage;
const boom_1 = require("@hapi/boom");
const axios_1 = __importDefault(require("axios"));
const child_process_1 = require("child_process");
const Crypto = __importStar(require("crypto"));
const events_1 = require("events");
const fs_1 = require("fs");
const os_1 = require("os");
const path_1 = require("path");
const stream_1 = require("stream");
const WAProto_1 = require("../../WAProto");
const Defaults_1 = require("../Defaults");
const WABinary_1 = require("../WABinary");
const crypto_1 = require("./crypto");
const generics_1 = require("./generics");
const getTmpFilesDirectory = () => (0, os_1.tmpdir)();
const getImageProcessingLibrary = async () => {
const [_jimp, sharp] = await Promise.all([
(async () => {
const jimp = await (Promise.resolve().then(() => __importStar(require('jimp'))).catch(() => { }));
return jimp;
})(),
(async () => {
const sharp = await (Promise.resolve().then(() => __importStar(require('sharp'))).catch(() => { }));
return sharp;
})()
]);
if (sharp) {
return { sharp };
}
const jimp = (_jimp === null || _jimp === void 0 ? void 0 : _jimp.default) || _jimp;
if (jimp) {
return { jimp };
}
throw new boom_1.Boom('No image processing library available');
};
const hkdfInfoKey = (type) => {
const hkdfInfo = Defaults_1.MEDIA_HKDF_KEY_MAPPING[type];
return `WhatsApp ${hkdfInfo} Keys`;
};
exports.hkdfInfoKey = hkdfInfoKey;
/** generates all the keys required to encrypt/decrypt & sign a media message */
async function getMediaKeys(buffer, mediaType) {
if (!buffer) {
throw new boom_1.Boom('Cannot derive from empty media key');
}
if (typeof buffer === 'string') {
buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64');
}
// expand using HKDF to 112 bytes, also pass in the relevant app info
const expandedMediaKey = await (0, crypto_1.hkdf)(buffer, 112, { info: (0, exports.hkdfInfoKey)(mediaType) });
return {
iv: expandedMediaKey.slice(0, 16),
cipherKey: expandedMediaKey.slice(16, 48),
macKey: expandedMediaKey.slice(48, 80),
};
}
/** Extracts video thumb using FFMPEG */
const extractVideoThumb = async (path, destPath, time, size) => new Promise((resolve, reject) => {
const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}`;
(0, child_process_1.exec)(cmd, (err) => {
if (err) {
reject(err);
}
else {
resolve();
}
});
});
const extractImageThumb = async (bufferOrFilePath, width = 32) => {
var _a, _b;
if (bufferOrFilePath instanceof stream_1.Readable) {
bufferOrFilePath = await (0, exports.toBuffer)(bufferOrFilePath);
}
const lib = await getImageProcessingLibrary();
if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
const img = lib.sharp.default(bufferOrFilePath);
const dimensions = await img.metadata();
const buffer = await img
.resize(width)
.jpeg({ quality: 50 })
.toBuffer();
return {
buffer,
original: {
width: dimensions.width,
height: dimensions.height,
},
};
}
else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
const { read, MIME_JPEG, RESIZE_BILINEAR, AUTO } = lib.jimp;
const jimp = await read(bufferOrFilePath);
const dimensions = {
width: jimp.getWidth(),
height: jimp.getHeight()
};
const buffer = await jimp
.quality(50)
.resize(width, AUTO, RESIZE_BILINEAR)
.getBufferAsync(MIME_JPEG);
return {
buffer,
original: dimensions
};
}
else {
throw new boom_1.Boom('No image processing library available');
}
};
exports.extractImageThumb = extractImageThumb;
const encodeBase64EncodedStringForUpload = (b64) => (encodeURIComponent(b64
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/\=+$/, '')));
exports.encodeBase64EncodedStringForUpload = encodeBase64EncodedStringForUpload;
const generateProfilePicture = async (mediaUpload) => {
var _a, _b;
let bufferOrFilePath;
if (Buffer.isBuffer(mediaUpload)) {
bufferOrFilePath = mediaUpload;
}
else if ('url' in mediaUpload) {
bufferOrFilePath = mediaUpload.url.toString();
}
else {
bufferOrFilePath = await (0, exports.toBuffer)(mediaUpload.stream);
}
const lib = await getImageProcessingLibrary();
let img;
if ('sharp' in lib && typeof ((_a = lib.sharp) === null || _a === void 0 ? void 0 : _a.default) === 'function') {
img = lib.sharp.default(bufferOrFilePath)
.resize(640, 640)
.jpeg({
quality: 50,
})
.toBuffer();
}
else if ('jimp' in lib && typeof ((_b = lib.jimp) === null || _b === void 0 ? void 0 : _b.read) === 'function') {
const { read, MIME_JPEG, RESIZE_BILINEAR } = lib.jimp;
const jimp = await read(bufferOrFilePath);
const min = Math.min(jimp.getWidth(), jimp.getHeight());
const cropped = jimp.crop(0, 0, min, min);
img = cropped
.quality(50)
.resize(640, 640, RESIZE_BILINEAR)
.getBufferAsync(MIME_JPEG);
}
else {
throw new boom_1.Boom('No image processing library available');
}
return {
img: await img,
};
};
exports.generateProfilePicture = generateProfilePicture;
/** gets the SHA256 of the given media message */
const mediaMessageSHA256B64 = (message) => {
const media = Object.values(message)[0];
return (media === null || media === void 0 ? void 0 : media.fileSha256) && Buffer.from(media.fileSha256).toString('base64');
};
exports.mediaMessageSHA256B64 = mediaMessageSHA256B64;
async function getAudioDuration(buffer) {
const musicMetadata = await Promise.resolve().then(() => __importStar(require('music-metadata')));
let metadata;
if (Buffer.isBuffer(buffer)) {
metadata = await musicMetadata.parseBuffer(buffer, undefined, { duration: true });
}
else if (typeof buffer === 'string') {
const rStream = (0, fs_1.createReadStream)(buffer);
try {
metadata = await musicMetadata.parseStream(rStream, undefined, { duration: true });
}
finally {
rStream.destroy();
}
}
else {
metadata = await musicMetadata.parseStream(buffer, undefined, { duration: true });
}
return metadata.format.duration;
}
/**
referenced from and modifying https://github.com/wppconnect-team/wa-js/blob/main/src/chat/functions/prepareAudioWaveform.ts
*/
async function getAudioWaveform(buffer, logger) {
try {
const { default: decoder } = await eval('import(\'audio-decode\')');
let audioData;
if (Buffer.isBuffer(buffer)) {
audioData = buffer;
}
else if (typeof buffer === 'string') {
const rStream = (0, fs_1.createReadStream)(buffer);
audioData = await (0, exports.toBuffer)(rStream);
}
else {
audioData = await (0, exports.toBuffer)(buffer);
}
const audioBuffer = await decoder(audioData);
const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data
const samples = 64; // Number of samples we want to have in our final data set
const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision
const filteredData = [];
for (let i = 0; i < samples; i++) {
const blockStart = blockSize * i; // the location of the first sample in the block
let sum = 0;
for (let j = 0; j < blockSize; j++) {
sum = sum + Math.abs(rawData[blockStart + j]); // find the sum of all the samples in the block
}
filteredData.push(sum / blockSize); // divide the sum by the block size to get the average
}
// This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
const multiplier = Math.pow(Math.max(...filteredData), -1);
const normalizedData = filteredData.map((n) => n * multiplier);
// Generate waveform like WhatsApp
const waveform = new Uint8Array(normalizedData.map((n) => Math.floor(100 * n)));
return waveform;
}
catch (e) {
logger === null || logger === void 0 ? void 0 : logger.debug('Failed to generate waveform: ' + e);
}
}
const toReadable = (buffer) => {
const readable = new stream_1.Readable({ read: () => { } });
readable.push(buffer);
readable.push(null);
return readable;
};
exports.toReadable = toReadable;
const toBuffer = async (stream) => {
const chunks = [];
for await (const chunk of stream) {
chunks.push(chunk);
}
stream.destroy();
return Buffer.concat(chunks);
};
exports.toBuffer = toBuffer;
const getStream = async (item, opts) => {
if (Buffer.isBuffer(item)) {
return { stream: (0, exports.toReadable)(item), type: 'buffer' };
}
if ('stream' in item) {
return { stream: item.stream, type: 'readable' };
}
if (item.url.toString().startsWith('http://') || item.url.toString().startsWith('https://')) {
return { stream: await (0, exports.getHttpStream)(item.url, opts), type: 'remote' };
}
return { stream: (0, fs_1.createReadStream)(item.url), type: 'file' };
};
exports.getStream = getStream;
/** generates a thumbnail for a given media, if required */
async function generateThumbnail(file, mediaType, options) {
var _a;
let thumbnail;
let originalImageDimensions;
if (mediaType === 'image') {
const { buffer, original } = await (0, exports.extractImageThumb)(file);
thumbnail = buffer.toString('base64');
if (original.width && original.height) {
originalImageDimensions = {
width: original.width,
height: original.height,
};
}
}
else if (mediaType === 'video') {
const imgFilename = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageIDV2)() + '.jpg');
try {
await extractVideoThumb(file, imgFilename, '00:00:00', { width: 32, height: 32 });
const buff = await fs_1.promises.readFile(imgFilename);
thumbnail = buff.toString('base64');
await fs_1.promises.unlink(imgFilename);
}
catch (err) {
(_a = options.logger) === null || _a === void 0 ? void 0 : _a.debug('could not generate video thumb: ' + err);
}
}
return {
thumbnail,
originalImageDimensions
};
}
const getHttpStream = async (url, options = {}) => {
const fetched = await axios_1.default.get(url.toString(), { ...options, responseType: 'stream' });
return fetched.data;
};
exports.getHttpStream = getHttpStream;
const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
var _a, _b;
const { stream, type } = await (0, exports.getStream)(media, opts);
logger === null || logger === void 0 ? void 0 : logger.debug('fetched media stream');
const mediaKey = Crypto.randomBytes(32);
const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType);
const encFilePath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageIDV2)() + '-enc');
const encFileWriteStream = (0, fs_1.createWriteStream)(encFilePath);
let originalFileStream;
let originalFilePath;
if (saveOriginalFileIfRequired) {
originalFilePath = (0, path_1.join)(getTmpFilesDirectory(), mediaType + (0, generics_1.generateMessageIDV2)() + '-original');
originalFileStream = (0, fs_1.createWriteStream)(originalFilePath);
}
let fileLength = 0;
const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv);
const hmac = Crypto.createHmac('sha256', macKey).update(iv);
const sha256Plain = Crypto.createHash('sha256');
const sha256Enc = Crypto.createHash('sha256');
const onChunk = (buff) => {
sha256Enc.update(buff);
hmac.update(buff);
encFileWriteStream.write(buff);
};
try {
for await (const data of stream) {
fileLength += data.length;
if (type === 'remote'
&& (opts === null || opts === void 0 ? void 0 : opts.maxContentLength)
&& fileLength + data.length > opts.maxContentLength) {
throw new boom_1.Boom(`content length exceeded when encrypting "${type}"`, {
data: { media, type }
});
}
if (originalFileStream) {
if (!originalFileStream.write(data)) {
await (0, events_1.once)(originalFileStream, 'drain');
}
}
sha256Plain.update(data);
onChunk(aes.update(data));
}
onChunk(aes.final());
const mac = hmac.digest().slice(0, 10);
sha256Enc.update(mac);
const fileSha256 = sha256Plain.digest();
const fileEncSha256 = sha256Enc.digest();
encFileWriteStream.write(mac);
encFileWriteStream.end();
(_a = originalFileStream === null || originalFileStream === void 0 ? void 0 : originalFileStream.end) === null || _a === void 0 ? void 0 : _a.call(originalFileStream);
stream.destroy();
logger === null || logger === void 0 ? void 0 : logger.debug('encrypted data successfully');
return {
mediaKey,
originalFilePath,
encFilePath,
mac,
fileEncSha256,
fileSha256,
fileLength
};
}
catch (error) {
// destroy all streams with error
encFileWriteStream.destroy();
(_b = originalFileStream === null || originalFileStream === void 0 ? void 0 : originalFileStream.destroy) === null || _b === void 0 ? void 0 : _b.call(originalFileStream);
aes.destroy();
hmac.destroy();
sha256Plain.destroy();
sha256Enc.destroy();
stream.destroy();
try {
await fs_1.promises.unlink(encFilePath);
if (originalFilePath) {
await fs_1.promises.unlink(originalFilePath);
}
}
catch (err) {
logger === null || logger === void 0 ? void 0 : logger.error({ err }, 'failed deleting tmp files');
}
throw error;
}
};
exports.encryptedStream = encryptedStream;
const DEF_HOST = 'mmg.whatsapp.net';
const AES_CHUNK_SIZE = 16;
const toSmallestChunkSize = (num) => {
return Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE;
};
const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`;
exports.getUrlFromDirectPath = getUrlFromDirectPath;
const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
const downloadUrl = url || (0, exports.getUrlFromDirectPath)(directPath);
const keys = await getMediaKeys(mediaKey, type);
return (0, exports.downloadEncryptedContent)(downloadUrl, keys, opts);
};
exports.downloadContentFromMessage = downloadContentFromMessage;
/**
* Decrypts and downloads an AES256-CBC encrypted file given the keys.
* Assumes the SHA256 of the plaintext is appended to the end of the ciphertext
* */
const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
let bytesFetched = 0;
let startChunk = 0;
let firstBlockIsIV = false;
// if a start byte is specified -- then we need to fetch the previous chunk as that will form the IV
if (startByte) {
const chunk = toSmallestChunkSize(startByte || 0);
if (chunk) {
startChunk = chunk - AES_CHUNK_SIZE;
bytesFetched = chunk;
firstBlockIsIV = true;
}
}
const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined;
const headers = {
...(options === null || options === void 0 ? void 0 : options.headers) || {},
Origin: Defaults_1.DEFAULT_ORIGIN,
};
if (startChunk || endChunk) {
headers.Range = `bytes=${startChunk}-`;
if (endChunk) {
headers.Range += endChunk;
}
}
// download the message
const fetched = await (0, exports.getHttpStream)(downloadUrl, {
...options || {},
headers,
maxBodyLength: Infinity,
maxContentLength: Infinity,
});
let remainingBytes = Buffer.from([]);
let aes;
const pushBytes = (bytes, push) => {
if (startByte || endByte) {
const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0);
const end = bytesFetched + bytes.length < endByte ? undefined : Math.max(endByte - bytesFetched, 0);
push(bytes.slice(start, end));
bytesFetched += bytes.length;
}
else {
push(bytes);
}
};
const output = new stream_1.Transform({
transform(chunk, _, callback) {
let data = Buffer.concat([remainingBytes, chunk]);
const decryptLength = toSmallestChunkSize(data.length);
remainingBytes = data.slice(decryptLength);
data = data.slice(0, decryptLength);
if (!aes) {
let ivValue = iv;
if (firstBlockIsIV) {
ivValue = data.slice(0, AES_CHUNK_SIZE);
data = data.slice(AES_CHUNK_SIZE);
}
aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue);
// if an end byte that is not EOF is specified
// stop auto padding (PKCS7) -- otherwise throws an error for decryption
if (endByte) {
aes.setAutoPadding(false);
}
}
try {
pushBytes(aes.update(data), b => this.push(b));
callback();
}
catch (error) {
callback(error);
}
},
final(callback) {
try {
pushBytes(aes.final(), b => this.push(b));
callback();
}
catch (error) {
callback(error);
}
},
});
return fetched.pipe(output, { end: true });
};
exports.downloadEncryptedContent = downloadEncryptedContent;
function extensionForMediaMessage(message) {
const getExtension = (mimetype) => mimetype.split(';')[0].split('/')[1];
const type = Object.keys(message)[0];
let extension;
if (type === 'locationMessage' ||
type === 'liveLocationMessage' ||
type === 'productMessage') {
extension = '.jpeg';
}
else {
const messageContent = message[type];
extension = getExtension(messageContent.mimetype);
}
return extension;
}
const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
var _a, _b;
// send a query JSON to obtain the url & auth token to upload our media
let uploadInfo = await refreshMediaConn(false);
let urls;
const hosts = [...customUploadHosts, ...uploadInfo.hosts];
fileEncSha256B64 = (0, exports.encodeBase64EncodedStringForUpload)(fileEncSha256B64);
for (const { hostname } of hosts) {
logger.debug(`uploading to "${hostname}"`);
const auth = encodeURIComponent(uploadInfo.auth); // the auth token
const url = `https://${hostname}${Defaults_1.MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let result;
try {
const body = await axios_1.default.post(url, (0, fs_1.createReadStream)(filePath), {
...options,
maxRedirects: 0,
headers: {
...options.headers || {},
'Content-Type': 'application/octet-stream',
'Origin': Defaults_1.DEFAULT_ORIGIN
},
httpsAgent: fetchAgent,
timeout: timeoutMs,
responseType: 'json',
maxBodyLength: Infinity,
maxContentLength: Infinity,
});
result = body.data;
if ((result === null || result === void 0 ? void 0 : result.url) || (result === null || result === void 0 ? void 0 : result.directPath)) {
urls = {
mediaUrl: result.url,
directPath: result.direct_path
};
break;
}
else {
uploadInfo = await refreshMediaConn(true);
throw new Error(`upload failed, reason: ${JSON.stringify(result)}`);
}
}
catch (error) {
if (axios_1.default.isAxiosError(error)) {
result = (_a = error.response) === null || _a === void 0 ? void 0 : _a.data;
}
const isLast = hostname === ((_b = hosts[uploadInfo.hosts.length - 1]) === null || _b === void 0 ? void 0 : _b.hostname);
logger.warn({ trace: error.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`);
}
}
if (!urls) {
throw new boom_1.Boom('Media upload failed on all hosts', { statusCode: 500 });
}
return urls;
};
};
exports.getWAUploadToServer = getWAUploadToServer;
const getMediaRetryKey = (mediaKey) => {
return (0, crypto_1.hkdf)(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' });
};
/**
* Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
*/
const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
const recp = { stanzaId: key.id };
const recpBuffer = WAProto_1.proto.ServerErrorReceipt.encode(recp).finish();
const iv = Crypto.randomBytes(12);
const retryKey = await getMediaRetryKey(mediaKey);
const ciphertext = (0, crypto_1.aesEncryptGCM)(recpBuffer, retryKey, iv, Buffer.from(key.id));
const req = {
tag: 'receipt',
attrs: {
id: key.id,
to: (0, WABinary_1.jidNormalizedUser)(meId),
type: 'server-error'
},
content: [
// this encrypt node is actually pretty useless
// the media is returned even without this node
// keeping it here to maintain parity with WA Web
{
tag: 'encrypt',
attrs: {},
content: [
{ tag: 'enc_p', attrs: {}, content: ciphertext },
{ tag: 'enc_iv', attrs: {}, content: iv }
]
},
{
tag: 'rmr',
attrs: {
jid: key.remoteJid,
'from_me': (!!key.fromMe).toString(),
// @ts-ignore
participant: key.participant || undefined
}
}
]
};
return req;
};
exports.encryptMediaRetryRequest = encryptMediaRetryRequest;
const decodeMediaRetryNode = (node) => {
const rmrNode = (0, WABinary_1.getBinaryNodeChild)(node, 'rmr');
const event = {
key: {
id: node.attrs.id,
remoteJid: rmrNode.attrs.jid,
fromMe: rmrNode.attrs.from_me === 'true',
participant: rmrNode.attrs.participant
}
};
const errorNode = (0, WABinary_1.getBinaryNodeChild)(node, 'error');
if (errorNode) {
const errorCode = +errorNode.attrs.code;
event.error = new boom_1.Boom(`Failed to re-upload media (${errorCode})`, { data: errorNode.attrs, statusCode: (0, exports.getStatusCodeForMediaRetry)(errorCode) });
}
else {
const encryptedInfoNode = (0, WABinary_1.getBinaryNodeChild)(node, 'encrypt');
const ciphertext = (0, WABinary_1.getBinaryNodeChildBuffer)(encryptedInfoNode, 'enc_p');
const iv = (0, WABinary_1.getBinaryNodeChildBuffer)(encryptedInfoNode, 'enc_iv');
if (ciphertext && iv) {
event.media = { ciphertext, iv };
}
else {
event.error = new boom_1.Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 });
}
}
return event;
};
exports.decodeMediaRetryNode = decodeMediaRetryNode;
const decryptMediaRetryData = async ({ ciphertext, iv }, mediaKey, msgId) => {
const retryKey = await getMediaRetryKey(mediaKey);
const plaintext = (0, crypto_1.aesDecryptGCM)(ciphertext, retryKey, iv, Buffer.from(msgId));
return WAProto_1.proto.MediaRetryNotification.decode(plaintext);
};
exports.decryptMediaRetryData = decryptMediaRetryData;
const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code];
exports.getStatusCodeForMediaRetry = getStatusCodeForMediaRetry;
const MEDIA_RETRY_STATUS_MAP = {
[WAProto_1.proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
[WAProto_1.proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
[WAProto_1.proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
[WAProto_1.proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
};
;