mirror of
				https://github.com/SillyTavern/SillyTavern.git
				synced 2025-06-05 21:59:27 +02:00 
			
		
		
		
	Extract API endpoints for images
This commit is contained in:
		| @@ -29,7 +29,7 @@ let galleryMaxRows = 3; | |||||||
|  * @returns {Promise<Array>} - Resolves with an array of gallery item objects, rejects on error. |  * @returns {Promise<Array>} - Resolves with an array of gallery item objects, rejects on error. | ||||||
|  */ |  */ | ||||||
| async function getGalleryItems(url) { | async function getGalleryItems(url) { | ||||||
|     const response = await fetch(`/listimgfiles/${url}`, { |     const response = await fetch(`/api/images/list/${url}`, { | ||||||
|         method: 'POST', |         method: 'POST', | ||||||
|         headers: getRequestHeaders(), |         headers: getRequestHeaders(), | ||||||
|     }); |     }); | ||||||
| @@ -201,7 +201,7 @@ async function uploadFile(file, url) { | |||||||
|                 'Content-Type': 'application/json', |                 'Content-Type': 'application/json', | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|             const response = await fetch('/uploadimage', { |             const response = await fetch('/api/images/upload', { | ||||||
|                 method: 'POST', |                 method: 'POST', | ||||||
|                 headers: headers, |                 headers: headers, | ||||||
|                 body: JSON.stringify(payload), |                 body: JSON.stringify(payload), | ||||||
|   | |||||||
| @@ -996,7 +996,7 @@ export async function saveBase64AsFile(base64Data, characterName, filename = '', | |||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     // Send the data URL to your backend using fetch |     // Send the data URL to your backend using fetch | ||||||
|     const response = await fetch('/uploadimage', { |     const response = await fetch('/api/images/upload', { | ||||||
|         method: 'POST', |         method: 'POST', | ||||||
|         body: JSON.stringify(requestBody), |         body: JSON.stringify(requestBody), | ||||||
|         headers: { |         headers: { | ||||||
|   | |||||||
							
								
								
									
										93
									
								
								server.js
									
									
									
									
									
								
							
							
						
						
									
										93
									
								
								server.js
									
									
									
									
									
								
							| @@ -42,9 +42,6 @@ const { | |||||||
|     getVersion, |     getVersion, | ||||||
|     getConfigValue, |     getConfigValue, | ||||||
|     color, |     color, | ||||||
|     clientRelativePath, |  | ||||||
|     removeFileExtension, |  | ||||||
|     getImages, |  | ||||||
|     forwardFetchResponse, |     forwardFetchResponse, | ||||||
| } = require('./src/util'); | } = require('./src/util'); | ||||||
| const { ensureThumbnailCache } = require('./src/endpoints/thumbnails'); | const { ensureThumbnailCache } = require('./src/endpoints/thumbnails'); | ||||||
| @@ -244,89 +241,6 @@ app.post('/savemovingui', jsonParser, (request, response) => { | |||||||
|     return response.sendStatus(200); |     return response.sendStatus(200); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Ensure the directory for the provided file path exists. |  | ||||||
|  * If not, it will recursively create the directory. |  | ||||||
|  * |  | ||||||
|  * @param {string} filePath - The full path of the file for which the directory should be ensured. |  | ||||||
|  */ |  | ||||||
| function ensureDirectoryExistence(filePath) { |  | ||||||
|     const dirname = path.dirname(filePath); |  | ||||||
|     if (fs.existsSync(dirname)) { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|     ensureDirectoryExistence(dirname); |  | ||||||
|     fs.mkdirSync(dirname); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Endpoint to handle image uploads. |  | ||||||
|  * The image should be provided in the request body in base64 format. |  | ||||||
|  * Optionally, a character name can be provided to save the image in a sub-folder. |  | ||||||
|  * |  | ||||||
|  * @route POST /uploadimage |  | ||||||
|  * @param {Object} request.body - The request payload. |  | ||||||
|  * @param {string} request.body.image - The base64 encoded image data. |  | ||||||
|  * @param {string} [request.body.ch_name] - Optional character name to determine the sub-directory. |  | ||||||
|  * @returns {Object} response - The response object containing the path where the image was saved. |  | ||||||
|  */ |  | ||||||
| app.post('/uploadimage', jsonParser, async (request, response) => { |  | ||||||
|     // Check for image data |  | ||||||
|     if (!request.body || !request.body.image) { |  | ||||||
|         return response.status(400).send({ error: 'No image data provided' }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     try { |  | ||||||
|         // Extracting the base64 data and the image format |  | ||||||
|         const splitParts = request.body.image.split(','); |  | ||||||
|         const format = splitParts[0].split(';')[0].split('/')[1]; |  | ||||||
|         const base64Data = splitParts[1]; |  | ||||||
|         const validFormat = ['png', 'jpg', 'webp', 'jpeg', 'gif'].includes(format); |  | ||||||
|         if (!validFormat) { |  | ||||||
|             return response.status(400).send({ error: 'Invalid image format' }); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Constructing filename and path |  | ||||||
|         let filename; |  | ||||||
|         if (request.body.filename) { |  | ||||||
|             filename = `${removeFileExtension(request.body.filename)}.${format}`; |  | ||||||
|         } else { |  | ||||||
|             filename = `${Date.now()}.${format}`; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // if character is defined, save to a sub folder for that character |  | ||||||
|         let pathToNewFile = path.join(DIRECTORIES.userImages, sanitize(filename)); |  | ||||||
|         if (request.body.ch_name) { |  | ||||||
|             pathToNewFile = path.join(DIRECTORIES.userImages, sanitize(request.body.ch_name), sanitize(filename)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         ensureDirectoryExistence(pathToNewFile); |  | ||||||
|         const imageBuffer = Buffer.from(base64Data, 'base64'); |  | ||||||
|         await fs.promises.writeFile(pathToNewFile, imageBuffer); |  | ||||||
|         response.send({ path: clientRelativePath(pathToNewFile) }); |  | ||||||
|     } catch (error) { |  | ||||||
|         console.log(error); |  | ||||||
|         response.status(500).send({ error: 'Failed to save the image' }); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| app.post('/listimgfiles/:folder', (req, res) => { |  | ||||||
|     const directoryPath = path.join(process.cwd(), 'public/user/images/', sanitize(req.params.folder)); |  | ||||||
|  |  | ||||||
|     if (!fs.existsSync(directoryPath)) { |  | ||||||
|         fs.mkdirSync(directoryPath, { recursive: true }); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     try { |  | ||||||
|         const images = getImages(directoryPath); |  | ||||||
|         return res.send(images); |  | ||||||
|     } catch (error) { |  | ||||||
|         console.error(error); |  | ||||||
|         return res.status(500).send({ error: 'Unable to retrieve files' }); |  | ||||||
|     } |  | ||||||
| }); |  | ||||||
|  |  | ||||||
|  |  | ||||||
| function cleanUploads() { | function cleanUploads() { | ||||||
|     try { |     try { | ||||||
|         if (fs.existsSync(UPLOADS_PATH)) { |         if (fs.existsSync(UPLOADS_PATH)) { | ||||||
| @@ -422,6 +336,13 @@ redirect('/uploaduseravatar', '/api/avatars/upload'); | |||||||
| redirect('/deletequickreply', '/api/quick-replies/delete'); | redirect('/deletequickreply', '/api/quick-replies/delete'); | ||||||
| redirect('/savequickreply', '/api/quick-replies/save'); | redirect('/savequickreply', '/api/quick-replies/save'); | ||||||
|  |  | ||||||
|  | // Redirect deprecated image endpoints | ||||||
|  | redirect('/uploadimage', '/api/images/upload'); | ||||||
|  | redirect('/listimgfiles/:folder', '/api/images/list/:folder'); | ||||||
|  |  | ||||||
|  | // Image management | ||||||
|  | app.use('/api/images', require('./src/endpoints/images').router); | ||||||
|  |  | ||||||
| // Quick reply management | // Quick reply management | ||||||
| app.use('/api/quick-replies', require('./src/endpoints/quick-replies').router); | app.use('/api/quick-replies', require('./src/endpoints/quick-replies').router); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										94
									
								
								src/endpoints/images.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/endpoints/images.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,94 @@ | |||||||
|  | const fs = require('fs'); | ||||||
|  | const path = require('path'); | ||||||
|  | const express = require('express'); | ||||||
|  | const sanitize = require('sanitize-filename'); | ||||||
|  |  | ||||||
|  | const { jsonParser } = require('../express-common'); | ||||||
|  | const { DIRECTORIES } = require('../constants'); | ||||||
|  | const { clientRelativePath, removeFileExtension, getImages } = require('../util'); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Ensure the directory for the provided file path exists. | ||||||
|  |  * If not, it will recursively create the directory. | ||||||
|  |  * | ||||||
|  |  * @param {string} filePath - The full path of the file for which the directory should be ensured. | ||||||
|  |  */ | ||||||
|  | function ensureDirectoryExistence(filePath) { | ||||||
|  |     const dirname = path.dirname(filePath); | ||||||
|  |     if (fs.existsSync(dirname)) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |     ensureDirectoryExistence(dirname); | ||||||
|  |     fs.mkdirSync(dirname); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const router = express.Router(); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Endpoint to handle image uploads. | ||||||
|  |  * The image should be provided in the request body in base64 format. | ||||||
|  |  * Optionally, a character name can be provided to save the image in a sub-folder. | ||||||
|  |  * | ||||||
|  |  * @route POST /api/images/upload | ||||||
|  |  * @param {Object} request.body - The request payload. | ||||||
|  |  * @param {string} request.body.image - The base64 encoded image data. | ||||||
|  |  * @param {string} [request.body.ch_name] - Optional character name to determine the sub-directory. | ||||||
|  |  * @returns {Object} response - The response object containing the path where the image was saved. | ||||||
|  |  */ | ||||||
|  | router.post('/upload', jsonParser, async (request, response) => { | ||||||
|  |     // Check for image data | ||||||
|  |     if (!request.body || !request.body.image) { | ||||||
|  |         return response.status(400).send({ error: 'No image data provided' }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |         // Extracting the base64 data and the image format | ||||||
|  |         const splitParts = request.body.image.split(','); | ||||||
|  |         const format = splitParts[0].split(';')[0].split('/')[1]; | ||||||
|  |         const base64Data = splitParts[1]; | ||||||
|  |         const validFormat = ['png', 'jpg', 'webp', 'jpeg', 'gif'].includes(format); | ||||||
|  |         if (!validFormat) { | ||||||
|  |             return response.status(400).send({ error: 'Invalid image format' }); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Constructing filename and path | ||||||
|  |         let filename; | ||||||
|  |         if (request.body.filename) { | ||||||
|  |             filename = `${removeFileExtension(request.body.filename)}.${format}`; | ||||||
|  |         } else { | ||||||
|  |             filename = `${Date.now()}.${format}`; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // if character is defined, save to a sub folder for that character | ||||||
|  |         let pathToNewFile = path.join(DIRECTORIES.userImages, sanitize(filename)); | ||||||
|  |         if (request.body.ch_name) { | ||||||
|  |             pathToNewFile = path.join(DIRECTORIES.userImages, sanitize(request.body.ch_name), sanitize(filename)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         ensureDirectoryExistence(pathToNewFile); | ||||||
|  |         const imageBuffer = Buffer.from(base64Data, 'base64'); | ||||||
|  |         await fs.promises.writeFile(pathToNewFile, imageBuffer); | ||||||
|  |         response.send({ path: clientRelativePath(pathToNewFile) }); | ||||||
|  |     } catch (error) { | ||||||
|  |         console.log(error); | ||||||
|  |         response.status(500).send({ error: 'Failed to save the image' }); | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | router.post('/list/:folder', (req, res) => { | ||||||
|  |     const directoryPath = path.join(process.cwd(), DIRECTORIES.userImages, sanitize(req.params.folder)); | ||||||
|  |  | ||||||
|  |     if (!fs.existsSync(directoryPath)) { | ||||||
|  |         fs.mkdirSync(directoryPath, { recursive: true }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     try { | ||||||
|  |         const images = getImages(directoryPath); | ||||||
|  |         return res.send(images); | ||||||
|  |     } catch (error) { | ||||||
|  |         console.error(error); | ||||||
|  |         return res.status(500).send({ error: 'Unable to retrieve files' }); | ||||||
|  |     } | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | module.exports = { router }; | ||||||
		Reference in New Issue
	
	Block a user