1. 'use strict';
  2. const fs = require('fs');
  3. const path = require('path');
  4. const mime = require('mime');
  5. const fetch = require('node-fetch');
  6. const { URL } = require('url');
  7. /**
  8. * Media attached to a message
  9. * @param {string} mimetype MIME type of the attachment
  10. * @param {string} data Base64-encoded data of the file
  11. * @param {?string} filename Document file name. Value can be null
  12. * @param {?number} filesize Document file size in bytes. Value can be null
  13. */
  14. class MessageMedia {
  15. constructor(mimetype, data, filename, filesize) {
  16. /**
  17. * MIME type of the attachment
  18. * @type {string}
  19. */
  20. this.mimetype = mimetype;
  21. /**
  22. * Base64 encoded data that represents the file
  23. * @type {string}
  24. */
  25. this.data = data;
  26. /**
  27. * Document file name. Value can be null
  28. * @type {?string}
  29. */
  30. this.filename = filename;
  31. /**
  32. * Document file size in bytes. Value can be null
  33. * @type {?number}
  34. */
  35. this.filesize = filesize;
  36. }
  37. /**
  38. * Creates a MessageMedia instance from a local file path
  39. * @param {string} filePath
  40. * @returns {MessageMedia}
  41. */
  42. static fromFilePath(filePath) {
  43. const b64data = fs.readFileSync(filePath, {encoding: 'base64'});
  44. const mimetype = mime.getType(filePath);
  45. const filename = path.basename(filePath);
  46. return new MessageMedia(mimetype, b64data, filename);
  47. }
  48. /**
  49. * Creates a MessageMedia instance from a URL
  50. * @param {string} url
  51. * @param {Object} [options]
  52. * @param {boolean} [options.unsafeMime=false]
  53. * @param {string} [options.filename]
  54. * @param {object} [options.client]
  55. * @param {object} [options.reqOptions]
  56. * @param {number} [options.reqOptions.size=0]
  57. * @returns {Promise<MessageMedia>}
  58. */
  59. static async fromUrl(url, options = {}) {
  60. const pUrl = new URL(url);
  61. let mimetype = mime.getType(pUrl.pathname);
  62. if (!mimetype && !options.unsafeMime)
  63. throw new Error('Unable to determine MIME type using URL. Set unsafeMime to true to download it anyway.');
  64. async function fetchData (url, options) {
  65. const reqOptions = Object.assign({ headers: { accept: 'image/* video/* text/* audio/*' } }, options);
  66. const response = await fetch(url, reqOptions);
  67. const mime = response.headers.get('Content-Type');
  68. const size = response.headers.get('Content-Length');
  69. const contentDisposition = response.headers.get('Content-Disposition');
  70. const name = contentDisposition ? contentDisposition.match(/((?<=filename=")(.*)(?="))/) : null;
  71. let data = '';
  72. if (response.buffer) {
  73. data = (await response.buffer()).toString('base64');
  74. } else {
  75. const bArray = new Uint8Array(await response.arrayBuffer());
  76. bArray.forEach((b) => {
  77. data += String.fromCharCode(b);
  78. });
  79. data = btoa(data);
  80. }
  81. return { data, mime, name, size };
  82. }
  83. const res = options.client
  84. ? (await options.client.pupPage.evaluate(fetchData, url, options.reqOptions))
  85. : (await fetchData(url, options.reqOptions));
  86. const filename = options.filename ||
  87. (res.name ? res.name[0] : (pUrl.pathname.split('/').pop() || 'file'));
  88. if (!mimetype)
  89. mimetype = res.mime;
  90. return new MessageMedia(mimetype, res.data, filename, res.size || null);
  91. }
  92. }
  93. module.exports = MessageMedia;