Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 104 additions & 8 deletions src/structures/MessageMedia.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,26 +71,111 @@ class MessageMedia {
if (!mimetype && !options.unsafeMime)
throw new Error('Unable to determine MIME type using URL. Set unsafeMime to true to download it anyway.');

try {
const headResponse = await fetch(url, { method: 'HEAD' });
const contentLength = headResponse.headers.get('Content-Length');

if (contentLength) {
const fileSizeBytes = parseInt(contentLength);
const fileSizeMB = fileSizeBytes / (1024 * 1024);

const whatsappLimits = {
'image/': 16,
'video/': 64,
'audio/': 16,
'application/': 100,
'text/': 100,
'default': 64
};

const preliminaryMimetype = mimetype || headResponse.headers.get('Content-Type') || '';
let maxSizeMB = whatsappLimits.default;

for (const [type, limit] of Object.entries(whatsappLimits)) {
if (type !== 'default' && preliminaryMimetype.startsWith(type)) {
maxSizeMB = limit;
break;
}
}

if (fileSizeMB > maxSizeMB) {
const error = new Error(
`File size (${fileSizeMB.toFixed(2)}MB) exceeds WhatsApp limit for ${preliminaryMimetype} (${maxSizeMB}MB). ` +
`Maximum allowed: ${maxSizeMB}MB, Got: ${fileSizeMB.toFixed(2)}MB`
);
error.code = 'FILE_TOO_LARGE';
error.fileSize = fileSizeMB;
error.maxSize = maxSizeMB;
error.mimetype = preliminaryMimetype;
throw error;
}

if (fileSizeMB > 50) {
console.warn(`⚠️ Large file detected (${fileSizeMB.toFixed(2)}MB). This may take longer to process.`);
}
} else {
console.warn('⚠️ Could not determine file size from headers. Proceeding with download...');
}
} catch (headError) {
if (headError.code === 'FILE_TOO_LARGE') {
throw headError;
}
console.warn('⚠️ HEAD request failed, proceeding with download:', headError.message);
}

async function fetchData (url, options) {
const reqOptions = Object.assign({ headers: { accept: 'image/* video/* text/* audio/*' } }, options);
const response = await fetch(url, reqOptions);

if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}

const mime = response.headers.get('Content-Type');
const size = response.headers.get('Content-Length');

const contentDisposition = response.headers.get('Content-Disposition');
const name = contentDisposition ? contentDisposition.match(/((?<=filename=")(.*)(?="))/) : null;

let data = '';

if (response.buffer) {
data = (await response.buffer()).toString('base64');
const buffer = await response.buffer();

const actualSizeMB = buffer.length / (1024 * 1024);

if (actualSizeMB > 64) {
const error = new Error(
`Downloaded file size (${actualSizeMB.toFixed(2)}MB) exceeds WhatsApp general limit (64MB)`
);
error.code = 'DOWNLOADED_FILE_TOO_LARGE';
error.actualSize = actualSizeMB;
throw error;
}

data = buffer.toString('base64');
} else {
const bArray = new Uint8Array(await response.arrayBuffer());
bArray.forEach((b) => {
data += String.fromCharCode(b);
const arrayBuffer = await response.arrayBuffer();
const uint8Array = new Uint8Array(arrayBuffer);

const actualSizeMB = uint8Array.length / (1024 * 1024);

if (actualSizeMB > 64) {
const error = new Error(
`Downloaded file size (${actualSizeMB.toFixed(2)}MB) exceeds WhatsApp general limit (64MB)`
);
error.code = 'DOWNLOADED_FILE_TOO_LARGE';
error.actualSize = actualSizeMB;
throw error;
}

let binary = '';
uint8Array.forEach((byte) => {
binary += String.fromCharCode(byte);
});
data = btoa(data);
data = btoa(binary);
}

return { data, mime, name, size };
}

Expand All @@ -100,11 +185,22 @@ class MessageMedia {

const filename = options.filename ||
(res.name ? res.name[0] : (pUrl.pathname.split('/').pop() || 'file'));

if (!mimetype)
mimetype = res.mime;

return new MessageMedia(mimetype, res.data, filename, res.size || null);
const filesize = res.size ? parseInt(res.size) : null;

if (!res.data || !res.data.length) {
throw new Error('Downloaded file is empty or invalid');
}

const isValidBase64 = /^[A-Za-z0-9+/=]+$/.test(res.data);
if (!isValidBase64) {
throw new Error('Downloaded file data is not valid base64');
}

return new MessageMedia(mimetype, res.data, filename, filesize);
}
}

Expand Down