'use strict';
const fs = require('fs');
const path = require('path');
const mimeModule = require('mime');
const { URL } = require('url');
const mime = mimeModule.default || mimeModule;
let fetchImplementationPromise;
const fetch = (...args) => {
fetchImplementationPromise ||= import('node-fetch').then(
({ default: implementation }) => implementation,
);
return fetchImplementationPromise.then((implementation) =>
implementation(...args),
);
};
/**
* Media attached to a message
* @param {string} mimetype MIME type of the attachment
* @param {string} data Base64-encoded data of the file
* @param {?string} filename Document file name. Value can be null
* @param {?number} filesize Document file size in bytes. Value can be null
*/
class MessageMedia {
constructor(mimetype, data, filename, filesize) {
/**
* MIME type of the attachment
* @type {string}
*/
this.mimetype = mimetype;
/**
* Base64 encoded data that represents the file
* @type {string}
*/
this.data = data;
/**
* Document file name. Value can be null
* @type {?string}
*/
this.filename = filename;
/**
* Document file size in bytes. Value can be null
* @type {?number}
*/
this.filesize = filesize;
}
/**
* Creates a MessageMedia instance from a local file path
* @param {string} filePath
* @returns {MessageMedia}
*/
static fromFilePath(filePath) {
const b64data = fs.readFileSync(filePath, { encoding: 'base64' });
const mimetype = mime.getType(filePath);
const filename = path.basename(filePath);
return new MessageMedia(mimetype, b64data, filename);
}
/**
* Creates a MessageMedia instance from a URL
* @param {string} url
* @param {Object} [options]
* @param {boolean} [options.unsafeMime=false]
* @param {string} [options.filename]
* @param {object} [options.client]
* @param {object} [options.reqOptions]
* @param {number} [options.reqOptions.size=0]
* @returns {Promise<MessageMedia>}
*/
static async fromUrl(url, options = {}) {
const pUrl = new URL(url);
let mimetype = mime.getType(pUrl.pathname);
if (!mimetype && !options.unsafeMime)
throw new Error(
'Unable to determine MIME type using URL. Set unsafeMime to true to download it anyway.',
);
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(
`Failed to fetch media (${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;
const bytes = new Uint8Array(await response.arrayBuffer());
let data;
if (typeof Buffer === 'function') {
data = Buffer.from(bytes).toString('base64');
} else {
let binary = '';
for (let offset = 0; offset < bytes.length; offset += 32768) {
binary += String.fromCharCode(
...bytes.subarray(offset, offset + 32768),
);
}
data = btoa(binary);
}
return { data, mime, name, size };
}
const res = options.client
? await options.client.pupPage.evaluate(
fetchData,
url,
options.reqOptions,
)
: await fetchData(url, options.reqOptions);
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);
}
}
module.exports = MessageMedia;