| | import fs from 'node:fs'; |
| | import path from 'node:path'; |
| |
|
| | import express from 'express'; |
| | import mime from 'mime-types'; |
| | import sanitize from 'sanitize-filename'; |
| | import { sync as writeFileAtomicSync } from 'write-file-atomic'; |
| |
|
| | import { getImageBuffers } from '../util.js'; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | function getSpritesPath(directories, name, isSubfolder) { |
| | if (isSubfolder) { |
| | const nameParts = name.split('/'); |
| | const characterName = sanitize(nameParts[0]); |
| | const subfolderName = sanitize(nameParts[1]); |
| |
|
| | if (!characterName || !subfolderName) { |
| | return null; |
| | } |
| |
|
| | return path.join(directories.characters, characterName, subfolderName); |
| | } |
| |
|
| | name = sanitize(name); |
| |
|
| | if (!name) { |
| | return null; |
| | } |
| |
|
| | return path.join(directories.characters, name); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | export function importRisuSprites(directories, data) { |
| | try { |
| | const name = data?.data?.name; |
| | const risuData = data?.data?.extensions?.risuai; |
| |
|
| | |
| | if (!risuData || !name) { |
| | return; |
| | } |
| |
|
| | let images = []; |
| |
|
| | if (Array.isArray(risuData.additionalAssets)) { |
| | images = images.concat(risuData.additionalAssets); |
| | } |
| |
|
| | if (Array.isArray(risuData.emotions)) { |
| | images = images.concat(risuData.emotions); |
| | } |
| |
|
| | |
| | if (images.length === 0) { |
| | return; |
| | } |
| |
|
| | |
| | const spritesPath = getSpritesPath(directories, name, false); |
| |
|
| | |
| | if (!spritesPath) { |
| | return; |
| | } |
| |
|
| | |
| | if (!fs.existsSync(spritesPath)) { |
| | fs.mkdirSync(spritesPath, { recursive: true }); |
| | } |
| |
|
| | |
| | if (!fs.statSync(spritesPath).isDirectory()) { |
| | return; |
| | } |
| |
|
| | console.info(`RisuAI: Found ${images.length} sprites for ${name}. Writing to disk.`); |
| | const files = fs.readdirSync(spritesPath); |
| |
|
| | outer: for (const [label, fileBase64] of images) { |
| | |
| | for (const file of files) { |
| | if (path.parse(file).name === label) { |
| | console.warn(`RisuAI: The sprite ${label} for ${name} already exists. Skipping.`); |
| | continue outer; |
| | } |
| | } |
| |
|
| | const filename = label + '.png'; |
| | const pathToFile = path.join(spritesPath, sanitize(filename)); |
| | writeFileAtomicSync(pathToFile, fileBase64, { encoding: 'base64' }); |
| | } |
| |
|
| | |
| | delete data.data.extensions.risuai.additionalAssets; |
| | delete data.data.extensions.risuai.emotions; |
| | } catch (error) { |
| | console.error(error); |
| | } |
| | } |
| |
|
| | export const router = express.Router(); |
| |
|
| | router.get('/get', function (request, response) { |
| | const name = String(request.query.name); |
| | const isSubfolder = name.includes('/'); |
| | const spritesPath = getSpritesPath(request.user.directories, name, isSubfolder); |
| | let sprites = []; |
| |
|
| | try { |
| | if (spritesPath && fs.existsSync(spritesPath) && fs.statSync(spritesPath).isDirectory()) { |
| | sprites = fs.readdirSync(spritesPath) |
| | .filter(file => { |
| | const mimeType = mime.lookup(file); |
| | return mimeType && mimeType.startsWith('image/'); |
| | }) |
| | .map((file) => { |
| | const pathToSprite = path.join(spritesPath, file); |
| | const mtime = fs.statSync(pathToSprite).mtime?.toISOString().replace(/[^0-9]/g, '').slice(0, 14); |
| |
|
| | const fileName = path.parse(pathToSprite).name.toLowerCase(); |
| | |
| | |
| | const label = fileName.match(/^(.+?)(?:[-\\.].*?)?$/)?.[1] ?? fileName; |
| |
|
| | return { |
| | label: label, |
| | path: `/characters/${name}/${file}` + (mtime ? `?t=${mtime}` : ''), |
| | }; |
| | }); |
| | } |
| | } |
| | catch (err) { |
| | console.error(err); |
| | } |
| | return response.send(sprites); |
| | }); |
| |
|
| | router.post('/delete', async (request, response) => { |
| | const label = request.body.label; |
| | const name = String(request.body.name); |
| | const isSubfolder = name.includes('/'); |
| | const spriteName = request.body.spriteName || label; |
| |
|
| | if (!spriteName || !name) { |
| | return response.sendStatus(400); |
| | } |
| |
|
| | try { |
| | const spritesPath = getSpritesPath(request.user.directories, name, isSubfolder); |
| |
|
| | |
| | if (!spritesPath || !fs.existsSync(spritesPath) || !fs.statSync(spritesPath).isDirectory()) { |
| | return response.sendStatus(404); |
| | } |
| |
|
| | const files = fs.readdirSync(spritesPath); |
| |
|
| | |
| | for (const file of files) { |
| | if (path.parse(file).name === spriteName) { |
| | fs.unlinkSync(path.join(spritesPath, file)); |
| | } |
| | } |
| |
|
| | return response.sendStatus(200); |
| | } catch (error) { |
| | console.error(error); |
| | return response.sendStatus(500); |
| | } |
| | }); |
| |
|
| | router.post('/upload-zip', async (request, response) => { |
| | const file = request.file; |
| | const name = String(request.body.name); |
| | const isSubfolder = name.includes('/'); |
| |
|
| | if (!file || !name) { |
| | return response.sendStatus(400); |
| | } |
| |
|
| | try { |
| | const spritesPath = getSpritesPath(request.user.directories, name, isSubfolder); |
| |
|
| | |
| | if (!spritesPath) { |
| | return response.sendStatus(400); |
| | } |
| |
|
| | |
| | if (!fs.existsSync(spritesPath)) { |
| | fs.mkdirSync(spritesPath, { recursive: true }); |
| | } |
| |
|
| | |
| | if (!fs.statSync(spritesPath).isDirectory()) { |
| | return response.sendStatus(404); |
| | } |
| |
|
| | const spritePackPath = path.join(file.destination, file.filename); |
| | const sprites = await getImageBuffers(spritePackPath); |
| | const files = fs.readdirSync(spritesPath); |
| |
|
| | for (const [filename, buffer] of sprites) { |
| | |
| | const existingFile = files.find(file => path.parse(file).name === path.parse(filename).name); |
| |
|
| | if (existingFile) { |
| | fs.unlinkSync(path.join(spritesPath, existingFile)); |
| | } |
| |
|
| | |
| | const pathToSprite = path.join(spritesPath, sanitize(filename)); |
| | writeFileAtomicSync(pathToSprite, buffer); |
| | } |
| |
|
| | |
| | fs.unlinkSync(spritePackPath); |
| | return response.send({ ok: true, count: sprites.length }); |
| | } catch (error) { |
| | console.error(error); |
| | return response.sendStatus(500); |
| | } |
| | }); |
| |
|
| | router.post('/upload', async (request, response) => { |
| | const file = request.file; |
| | const label = request.body.label; |
| | const name = String(request.body.name); |
| | const isSubfolder = name.includes('/'); |
| | const spriteName = request.body.spriteName || label; |
| |
|
| | if (!file || !label || !name) { |
| | return response.sendStatus(400); |
| | } |
| |
|
| | try { |
| | const spritesPath = getSpritesPath(request.user.directories, name, isSubfolder); |
| |
|
| | |
| | if (!spritesPath) { |
| | return response.sendStatus(400); |
| | } |
| |
|
| | |
| | if (!fs.existsSync(spritesPath)) { |
| | fs.mkdirSync(spritesPath, { recursive: true }); |
| | } |
| |
|
| | |
| | if (!fs.statSync(spritesPath).isDirectory()) { |
| | return response.sendStatus(404); |
| | } |
| |
|
| | const files = fs.readdirSync(spritesPath); |
| |
|
| | |
| | for (const file of files) { |
| | if (path.parse(file).name === spriteName) { |
| | fs.unlinkSync(path.join(spritesPath, file)); |
| | } |
| | } |
| |
|
| | const filename = spriteName + path.parse(file.originalname).ext; |
| | const spritePath = path.join(file.destination, file.filename); |
| | const pathToFile = path.join(spritesPath, sanitize(filename)); |
| | |
| | fs.cpSync(spritePath, pathToFile); |
| | |
| | fs.unlinkSync(spritePath); |
| | return response.send({ ok: true }); |
| | } catch (error) { |
| | console.error(error); |
| | return response.sendStatus(500); |
| | } |
| | }); |
| |
|