Extract API endpoints for user avatars

This commit is contained in:
Cohee 2024-03-20 00:39:48 +02:00
parent 41528d0423
commit d448d4f65b
4 changed files with 76 additions and 58 deletions

View File

@ -5552,7 +5552,7 @@ function changeMainAPI() {
* @returns {Promise<string[]>} List of avatar file names
*/
export async function getUserAvatars(doRender = true, openPageAt = '') {
const response = await fetch('/getuseravatars', {
const response = await fetch('/api/avatars/get', {
method: 'POST',
headers: getRequestHeaders(),
});
@ -5699,7 +5699,7 @@ async function uploadUserAvatar(e) {
const formData = new FormData($('#form_upload_avatar').get(0));
const dataUrl = await getBase64Async(file);
let url = '/uploaduseravatar';
let url = '/api/avatars/upload';
if (!power_user.never_resize_avatars) {
$('#dialogue_popup').addClass('large_dialogue_popup wide_dialogue_popup');

View File

@ -46,7 +46,7 @@ async function uploadUserAvatar(url, name) {
return jQuery.ajax({
type: 'POST',
url: '/uploaduseravatar',
url: '/api/avatars/upload',
data: formData,
beforeSend: () => { },
cache: false,
@ -355,7 +355,7 @@ async function deleteUserAvatar(e) {
return;
}
const request = await fetch('/deleteuseravatar', {
const request = await fetch('/api/avatars/delete', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({

View File

@ -29,9 +29,6 @@ const net = require('net');
const dns = require('dns');
const fetch = require('node-fetch').default;
// image processing related library imports
const jimp = require('jimp');
// Unrestrict console logs display limit
util.inspect.defaultOptions.maxArrayLength = null;
util.inspect.defaultOptions.maxStringLength = null;
@ -39,13 +36,12 @@ util.inspect.defaultOptions.maxStringLength = null;
// local library imports
const basicAuthMiddleware = require('./src/middleware/basicAuth');
const whitelistMiddleware = require('./src/middleware/whitelist');
const { jsonParser, urlencodedParser } = require('./src/express-common.js');
const { jsonParser } = require('./src/express-common.js');
const contentManager = require('./src/endpoints/content-manager');
const {
getVersion,
getConfigValue,
color,
tryParse,
clientRelativePath,
removeFileExtension,
getImages,
@ -106,7 +102,7 @@ const server_port = process.env.SILLY_TAVERN_PORT || getConfigValue('port', 8000
const autorun = (getConfigValue('autorun', false) || cliArguments.autorun) && !cliArguments.ssl;
const listen = getConfigValue('listen', false);
const { DIRECTORIES, UPLOADS_PATH, AVATAR_WIDTH, AVATAR_HEIGHT } = require('./src/constants');
const { DIRECTORIES, UPLOADS_PATH } = require('./src/constants');
// CORS Settings //
const CORS = cors({
@ -237,30 +233,6 @@ app.get('/version', async function (_, response) {
response.send(data);
});
app.post('/getuseravatars', jsonParser, function (request, response) {
var images = getImages('public/User Avatars');
response.send(JSON.stringify(images));
});
app.post('/deleteuseravatar', jsonParser, function (request, response) {
if (!request.body) return response.sendStatus(400);
if (request.body.avatar !== sanitize(request.body.avatar)) {
console.error('Malicious avatar name prevented');
return response.sendStatus(403);
}
const fileName = path.join(DIRECTORIES.avatars, sanitize(request.body.avatar));
if (fs.existsSync(fileName)) {
fs.rmSync(fileName);
return response.send({ result: 'ok' });
}
return response.sendStatus(404);
});
app.post('/savemovingui', jsonParser, (request, response) => {
if (!request.body || !request.body.name) {
return response.sendStatus(400);
@ -297,30 +269,6 @@ app.post('/deletequickreply', jsonParser, (request, response) => {
});
app.post('/uploaduseravatar', urlencodedParser, async (request, response) => {
if (!request.file) return response.sendStatus(400);
try {
const pathToUpload = path.join(UPLOADS_PATH, request.file.filename);
const crop = tryParse(request.query.crop);
let rawImg = await jimp.read(pathToUpload);
if (typeof crop == 'object' && [crop.x, crop.y, crop.width, crop.height].every(x => typeof x === 'number')) {
rawImg = rawImg.crop(crop.x, crop.y, crop.width, crop.height);
}
const image = await rawImg.cover(AVATAR_WIDTH, AVATAR_HEIGHT).getBufferAsync(jimp.MIME_PNG);
const filename = request.body.overwrite_name || `${Date.now()}.png`;
const pathToNewFile = path.join(DIRECTORIES.avatars, filename);
writeFileAtomicSync(pathToNewFile, image);
fs.rmSync(pathToUpload);
return response.send({ path: filename });
} catch (err) {
return response.status(400).send('Is not a valid image');
}
});
/**
* Ensure the directory for the provided file path exists.
@ -491,6 +439,14 @@ redirect('/downloadbackground', '/api/backgrounds/upload'); // yes, the download
// Redirect deprecated theme API endpoints
redirect('/savetheme', '/api/themes/save');
// Redirect deprecated avatar API endpoints
redirect('/getuseravatars', '/api/avatars/get');
redirect('/deleteuseravatar', '/api/avatars/delete');
redirect('/uploaduseravatar', '/api/avatars/upload');
// Avatar management
app.use('/api/avatars', require('./src/endpoints/avatars').router);
// Theme management
app.use('/api/themes', require('./src/endpoints/themes').router);

62
src/endpoints/avatars.js Normal file
View File

@ -0,0 +1,62 @@
const express = require('express');
const path = require('path');
const fs = require('fs');
const sanitize = require('sanitize-filename');
const writeFileAtomicSync = require('write-file-atomic').sync;
const { jsonParser, urlencodedParser } = require('../express-common');
const { DIRECTORIES, AVATAR_WIDTH, AVATAR_HEIGHT, UPLOADS_PATH } = require('../constants');
const { getImages, tryParse } = require('../util');
// image processing related library imports
const jimp = require('jimp');
const router = express.Router();
router.post('/get', jsonParser, function (request, response) {
var images = getImages(DIRECTORIES.avatars);
response.send(JSON.stringify(images));
});
router.post('/delete', jsonParser, function (request, response) {
if (!request.body) return response.sendStatus(400);
if (request.body.avatar !== sanitize(request.body.avatar)) {
console.error('Malicious avatar name prevented');
return response.sendStatus(403);
}
const fileName = path.join(DIRECTORIES.avatars, sanitize(request.body.avatar));
if (fs.existsSync(fileName)) {
fs.rmSync(fileName);
return response.send({ result: 'ok' });
}
return response.sendStatus(404);
});
router.post('/upload', urlencodedParser, async (request, response) => {
if (!request.file) return response.sendStatus(400);
try {
const pathToUpload = path.join(UPLOADS_PATH, request.file.filename);
const crop = tryParse(request.query.crop);
let rawImg = await jimp.read(pathToUpload);
if (typeof crop == 'object' && [crop.x, crop.y, crop.width, crop.height].every(x => typeof x === 'number')) {
rawImg = rawImg.crop(crop.x, crop.y, crop.width, crop.height);
}
const image = await rawImg.cover(AVATAR_WIDTH, AVATAR_HEIGHT).getBufferAsync(jimp.MIME_PNG);
const filename = request.body.overwrite_name || `${Date.now()}.png`;
const pathToNewFile = path.join(DIRECTORIES.avatars, filename);
writeFileAtomicSync(pathToNewFile, image);
fs.rmSync(pathToUpload);
return response.send({ path: filename });
} catch (err) {
return response.status(400).send('Is not a valid image');
}
});
module.exports = { router };