| 987 | 986 | } |
| 988 | 987 | |
| 989 | 988 | /** |
| 989 | + * Returns the sprite folder name (including override) for a character. |
| 990 | + * @param {object} char Character object |
| 991 | + * @param {string} char.avatar Avatar filename with extension |
| 992 | + * @returns {string} Sprite folder name |
| 993 | + * @throws {Error} If character not found or avatar not set |
| 994 | + */ |
| 995 | +function spriteFolderNameFromCharacter(char) { |
| 996 | + const avatarFileName = char.avatar.replace(/\.[^/.]+$/, ''); |
| 997 | + const expressionOverride = extension_settings.expressionOverrides.find(e => e.name === avatarFileName); |
| 998 | + return expressionOverride?.path ? expressionOverride.path : avatarFileName; |
| 999 | +} |
| 1000 | + |
| 1001 | +/** |
| 1002 | + * Slash command callback for /uploadsprite |
| 1003 | + * |
| 1004 | + * label= is required |
| 1005 | + * if name= is provided, it will be used as a findChar lookup |
| 1006 | + * if name= is not provided, the last character's name will be used |
| 1007 | + * if folder= is a full path, it will be used as the folder |
| 1008 | + * if folder= is a partial path, it will be appended to the character's name |
| 1009 | + * if folder= is not provided, the character's override folder will be used, if set |
| 1010 | + * |
| 1011 | + * @param {object} args |
| 1012 | + * @param {string} args.name Character name or avatar key, passed through findChar |
| 1013 | + * @param {string} args.label Expression label |
| 1014 | + * @param {string} args.folder Sprite folder path, processed using backslash rules |
| 1015 | + * @param {string} imageUrl Image URI to fetch and upload |
| 1016 | + * @returns {Promise<void>} |
| 1017 | + */ |
| 1018 | +async function uploadSpriteCommand({ name, label, folder }, imageUrl) { |
| 1019 | + if (!imageUrl) throw new Error('Image URL is required'); |
| 1020 | + if (!label || typeof label !== 'string') throw new Error('Expression label is required'); |
| 1021 | + |
| 1022 | + label = label.replace(/[^a-z]/gi, '').toLowerCase().trim(); |
| 1023 | + if (!label) throw new Error('Expression label must contain at least one letter'); |
| 1024 | + |
| 1025 | + name = name || getLastCharacterMessage().original_avatar || getLastCharacterMessage().name; |
| 1026 | + const char = findChar({ name }); |
| 1027 | + |
| 1028 | + if (!folder) { |
| 1029 | + folder = spriteFolderNameFromCharacter(char); |
| 1030 | + } else if (folder.startsWith('/') || folder.startsWith('\\')) { |
| 1031 | + const subfolder = folder.slice(1); |
| 1032 | + folder = `${char.name}/${subfolder}`; |
| 1033 | + } |
| 1034 | + |
| 1035 | + try { |
| 1036 | + const response = await fetch(imageUrl); |
| 1037 | + const blob = await response.blob(); |
| 1038 | + const file = new File([blob], 'image.png', { type: 'image/png' }); |
| 1039 | + |
| 1040 | + const formData = new FormData(); |
| 1041 | + formData.append('name', folder); // this is the folder or character name |
| 1042 | + formData.append('label', label); // this is the expression label |
| 1043 | + formData.append('avatar', file); // this is the image file |
| 1044 | + |
| 1045 | + await handleFileUpload('/api/sprites/upload', formData); |
| 1046 | + console.debug(`[${MODULE_NAME}] Upload of ${imageUrl} completed for ${name} with label ${label}`); |
| 1047 | + } catch (error) { |
| 1048 | + console.error(`[${MODULE_NAME}] Error uploading file:`, error); |
| 1049 | + throw error; |
| 1050 | + } |
| 1051 | +} |
| 1052 | + |
| 1053 | +/** |
| 990 | 1054 | * Processes the classification text to reduce the amount of text sent to the API. |
| 991 | 1055 | * Quotes and asterisks are to be removed. If the text is less than 300 characters, it is returned as is. |
| 992 | 1056 | * If the text is more than 300 characters, the first and last 150 characters are returned. |