mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-03-01 18:11:12 +01:00
this also resizes the image down to that value and sends a small image to server move profileImageDimenstion & maximumProfileImageSize from edit to all profile pages, on harmony profile pic can be changed from any page by clicking the avatar change image.resize so it doesn't resize if image is already same width/height
186 lines
4.9 KiB
JavaScript
186 lines
4.9 KiB
JavaScript
'use strict';
|
|
|
|
const os = require('os');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const crypto = require('crypto');
|
|
const winston = require('winston');
|
|
|
|
const file = require('./file');
|
|
const plugins = require('./plugins');
|
|
const meta = require('./meta');
|
|
|
|
const image = module.exports;
|
|
|
|
function requireSharp() {
|
|
const sharp = require('sharp');
|
|
if (os.platform() === 'win32') {
|
|
// https://github.com/lovell/sharp/issues/1259
|
|
sharp.cache(false);
|
|
}
|
|
return sharp;
|
|
}
|
|
|
|
image.isFileTypeAllowed = async function (path) {
|
|
const plugins = require('./plugins');
|
|
if (plugins.hooks.hasListeners('filter:image.isFileTypeAllowed')) {
|
|
return await plugins.hooks.fire('filter:image.isFileTypeAllowed', path);
|
|
}
|
|
const sharp = require('sharp');
|
|
await sharp(path, {
|
|
failOnError: true,
|
|
}).metadata();
|
|
};
|
|
|
|
image.resizeImage = async function (data) {
|
|
if (plugins.hooks.hasListeners('filter:image.resize')) {
|
|
await plugins.hooks.fire('filter:image.resize', {
|
|
path: data.path,
|
|
target: data.target,
|
|
width: data.width,
|
|
height: data.height,
|
|
quality: data.quality,
|
|
});
|
|
} else {
|
|
const sharp = requireSharp();
|
|
const buffer = await fs.promises.readFile(data.path);
|
|
const sharpImage = sharp(buffer, {
|
|
failOnError: true,
|
|
animated: data.path.endsWith('gif'),
|
|
});
|
|
const metadata = await sharpImage.metadata();
|
|
|
|
sharpImage.rotate(); // auto-orients based on exif data
|
|
// don't resize if width/height not changing or not specificied
|
|
if ((data.width && metadata.width !== data.width) || (data.height && metadata.height !== data.height)) {
|
|
sharpImage.resize(data.width || null, data.height || null);
|
|
}
|
|
|
|
if (data.quality) {
|
|
switch (metadata.format) {
|
|
case 'jpeg': {
|
|
sharpImage.jpeg({
|
|
quality: data.quality,
|
|
mozjpeg: true,
|
|
});
|
|
break;
|
|
}
|
|
|
|
case 'png': {
|
|
sharpImage.png({
|
|
quality: data.quality,
|
|
compressionLevel: 9,
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
await sharpImage.toFile(data.target || data.path);
|
|
}
|
|
};
|
|
|
|
image.normalise = async function (path) {
|
|
if (plugins.hooks.hasListeners('filter:image.normalise')) {
|
|
await plugins.hooks.fire('filter:image.normalise', {
|
|
path: path,
|
|
});
|
|
} else {
|
|
const sharp = requireSharp();
|
|
await sharp(path, { failOnError: true }).png().toFile(`${path}.png`);
|
|
}
|
|
return `${path}.png`;
|
|
};
|
|
|
|
image.size = async function (path) {
|
|
let imageData;
|
|
if (plugins.hooks.hasListeners('filter:image.size')) {
|
|
imageData = await plugins.hooks.fire('filter:image.size', {
|
|
path: path,
|
|
});
|
|
} else {
|
|
const sharp = requireSharp();
|
|
imageData = await sharp(path, { failOnError: true }).metadata();
|
|
}
|
|
return imageData ? { width: imageData.width, height: imageData.height } : undefined;
|
|
};
|
|
|
|
image.stripEXIF = async function ({ path, type }) {
|
|
if (!meta.config.stripEXIFData || type === 'image/gif' || type === 'image/svg+xml') {
|
|
return;
|
|
}
|
|
try {
|
|
if (plugins.hooks.hasListeners('filter:image.stripEXIF')) {
|
|
await plugins.hooks.fire('filter:image.stripEXIF', {
|
|
path: path,
|
|
type: type,
|
|
});
|
|
return;
|
|
}
|
|
const buffer = await fs.promises.readFile(path);
|
|
const sharp = requireSharp();
|
|
await sharp(buffer, { failOnError: true, pages: -1 }).rotate().toFile(path);
|
|
} catch (err) {
|
|
winston.error(err.stack);
|
|
}
|
|
};
|
|
|
|
image.checkDimensions = async function (path) {
|
|
const result = await image.size(path);
|
|
|
|
if (result.width > meta.config.rejectImageWidth || result.height > meta.config.rejectImageHeight) {
|
|
throw new Error('[[error:invalid-image-dimensions]]');
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
image.convertImageToBase64 = async function (path) {
|
|
return await fs.promises.readFile(path, 'base64');
|
|
};
|
|
|
|
image.mimeFromBase64 = function (imageData) {
|
|
return imageData.slice(5, imageData.indexOf('base64') - 1);
|
|
};
|
|
|
|
image.extensionFromBase64 = function (imageData) {
|
|
return file.typeToExtension(image.mimeFromBase64(imageData));
|
|
};
|
|
|
|
image.writeImageDataToTempFile = async function (imageData) {
|
|
const filename = crypto.createHash('md5').update(imageData).digest('hex');
|
|
|
|
const type = image.mimeFromBase64(imageData);
|
|
const extension = file.typeToExtension(type);
|
|
|
|
const filepath = path.join(os.tmpdir(), filename + extension);
|
|
|
|
const buffer = Buffer.from(imageData.slice(imageData.indexOf('base64') + 7), 'base64');
|
|
|
|
await fs.promises.writeFile(filepath, buffer, { encoding: 'base64' });
|
|
return filepath;
|
|
};
|
|
|
|
image.sizeFromBase64 = function (imageData) {
|
|
return Buffer.from(imageData.slice(imageData.indexOf('base64') + 7), 'base64').length;
|
|
};
|
|
|
|
image.uploadImage = async function (filename, folder, imageData) {
|
|
if (plugins.hooks.hasListeners('filter:uploadImage')) {
|
|
return await plugins.hooks.fire('filter:uploadImage', {
|
|
image: imageData,
|
|
uid: imageData.uid,
|
|
folder: folder,
|
|
});
|
|
}
|
|
await image.isFileTypeAllowed(imageData.path);
|
|
const upload = await file.saveFileToLocal(filename, folder, imageData.path);
|
|
return {
|
|
url: upload.url,
|
|
path: upload.path,
|
|
name: imageData.name,
|
|
};
|
|
};
|
|
|
|
require('./promisify')(image);
|