diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js index a10e66c6b..0363a3ae4 100644 --- a/public/scripts/extensions/expressions/index.js +++ b/public/scripts/extensions/expressions/index.js @@ -716,6 +716,13 @@ async function setSpriteSetCommand(_, folder) { folder = ''; } + if (folder.startsWith('/') || folder.startsWith('\\')) { + folder = folder.slice(1); + + const currentLastMessage = getLastCharacterMessage(); + folder = `${currentLastMessage.name}/${folder}`; + } + $("#expression_override").val(folder.trim()); onClickExpressionOverrideButton(); removeExpression(); @@ -1372,5 +1379,5 @@ function setExpressionOverrideHtml(forceClear = false) { eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced); eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced); registerSlashCommand('sprite', setSpriteSlashCommand, ['emote'], 'spriteId – force sets the sprite for the current character', true, true); - registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], 'folder – sets an override sprite folder for the current character. Empty value to reset to default.', true, true); + registerSlashCommand('spriteoverride', setSpriteSetCommand, ['costume'], 'folder – sets an override sprite folder for the current character. If the name starts with a slash or a backslash, selects a sub-folder in the character-named folder. Empty value to reset to default.', true, true); })(); diff --git a/server.js b/server.js index b1051b50c..4818a951f 100644 --- a/server.js +++ b/server.js @@ -2941,13 +2941,42 @@ app.get('/discover_extensions', jsonParser, function (_, response) { return response.send(extensions); }); +/** + * Gets the path to the sprites folder for the provided character name + * @param {string} name - The name of the character + * @param {boolean} isSubfolder - Whether the name contains a subfolder + * @returns {string | null} The path to the sprites folder. Null if the name is invalid. + */ +function getSpritesPath(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); +} + app.get('/get_sprites', jsonParser, function (request, response) { const name = String(request.query.name); - const spritesPath = path.join(directories.characters, name); + const isSubfolder = name.includes('/'); + const spritesPath = getSpritesPath(name, isSubfolder); let sprites = []; try { - if (fs.existsSync(spritesPath) && fs.statSync(spritesPath).isDirectory()) { + if (spritesPath && fs.existsSync(spritesPath) && fs.statSync(spritesPath).isDirectory()) { sprites = fs.readdirSync(spritesPath) .filter(file => { const mimeType = mime.lookup(file);