Update /ask with new char find functionality
| @@ -247,7 +247,7 @@ export function initDefaultSlashCommands() { | |||
| 247 | namedArgumentList: [ | 247 | namedArgumentList: [ |
| 248 | SlashCommandNamedArgument.fromProps({ | 248 | SlashCommandNamedArgument.fromProps({ |
| 249 | name: 'name', | 249 | name: 'name', |
| 250 | description: 'Character name', | 250 | description: 'Character name - or unique character identifier (avatar key)', |
| 251 | typeList: [ARGUMENT_TYPE.STRING], | 251 | typeList: [ARGUMENT_TYPE.STRING], |
| 252 | isRequired: true, | 252 | isRequired: true, |
| 253 | enumProvider: commonEnumProviders.characters('character'), | 253 | enumProvider: commonEnumProviders.characters('character'), |
| @@ -255,7 +255,7 @@ export function initDefaultSlashCommands() { | |||
| 255 | }), | 255 | }), |
| 256 | SlashCommandNamedArgument.fromProps({ | 256 | SlashCommandNamedArgument.fromProps({ |
| 257 | name: 'avatar', | 257 | name: 'avatar', |
| 258 | description: 'Character avatar override (Can be either avatar filename or just the character name to pull the avatar from)', | 258 | description: 'Character avatar override (Can be either avatar key or just the character name to pull the avatar from)', |
| 259 | typeList: [ARGUMENT_TYPE.STRING], | 259 | typeList: [ARGUMENT_TYPE.STRING], |
| 260 | enumProvider: commonEnumProviders.characters('character'), | 260 | enumProvider: commonEnumProviders.characters('character'), |
| 261 | }), | 261 | }), |
| @@ -288,6 +288,9 @@ export function initDefaultSlashCommands() { | |||
| 288 | <pre><code>/sendas name="Chloe" Hello, guys!</code></pre> | 288 | <pre><code>/sendas name="Chloe" Hello, guys!</code></pre> |
| 289 | will send "Hello, guys!" from "Chloe". | 289 | will send "Hello, guys!" from "Chloe". |
| 290 | </li> | 290 | </li> |
| 291 | <li> | ||
| 292 | <pre><code>/sendas name="Chloe" avatar="BigBadBoss" Hehehe, I am the big bad evil, fear me.</code></pre> | ||
| 293 | will send a message as the character "Chloe", but utilizing the avatar from a character named "BigBadBoss". | ||
| 291 | </ul> | 294 | </ul> |
| 292 | </div> | 295 | </div> |
| 293 | <div> | 296 | <div> |
| @@ -509,7 +512,7 @@ export function initDefaultSlashCommands() { | |||
| 509 | namedArgumentList: [ | 512 | namedArgumentList: [ |
| 510 | SlashCommandNamedArgument.fromProps({ | 513 | SlashCommandNamedArgument.fromProps({ |
| 511 | name: 'name', | 514 | name: 'name', |
| 512 | description: 'character name', | 515 | description: 'Character name - or unique character identifier (avatar key)', |
| 513 | typeList: [ARGUMENT_TYPE.STRING], | 516 | typeList: [ARGUMENT_TYPE.STRING], |
| 514 | isRequired: true, | 517 | isRequired: true, |
| 515 | enumProvider: commonEnumProviders.characters('character'), | 518 | enumProvider: commonEnumProviders.characters('character'), |
| @@ -2558,26 +2561,22 @@ async function askCharacter(args, text) { | |||
| 2558 | return ''; | 2561 | return ''; |
| 2559 | } | 2562 | } |
| 2560 | 2563 | ||
| 2561 | let name = ''; | 2564 | if (!args.name) { |
| 2562 | 2565 | toastr.warning('You must specify a name of the character to ask.'); | |
| 2563 | if (args?.name) { | 2566 | return ''; |
| 2564 | name = args.name.trim(); | ||
| 2565 | |||
| 2566 | if (!name) { | ||
| 2567 | toastr.warning('You must specify a name of the character to ask.'); | ||
| 2568 | return ''; | ||
| 2569 | } | ||
| 2570 | } | 2567 | } |
| 2571 | 2568 | ||
| 2572 | const prevChId = this_chid; | 2569 | const prevChId = this_chid; |
| 2573 | 2570 | ||
| 2574 | // Find the character | 2571 | // Find the character |
| 2575 | const chId = characters.findIndex((e) => e.name === name || e.avatar === name); | 2572 | const character = findChar({ name: args?.name }); |
| 2576 | if (!characters[chId] || chId === -1) { | 2573 | if (!character) { |
| 2577 | toastr.error('Character not found.'); | 2574 | toastr.error('Character not found.'); |
| 2578 | return ''; | 2575 | return ''; |
| 2579 | } | 2576 | } |
| 2580 | 2577 | ||
| 2578 | const chId = getCharIndex(character); | ||
| 2579 | |||
| 2581 | if (text) { | 2580 | if (text) { |
| 2582 | const mesText = getRegexedString(text.trim(), regex_placement.SLASH_COMMAND); | 2581 | const mesText = getRegexedString(text.trim(), regex_placement.SLASH_COMMAND); |
| 2583 | // Sending a message implicitly saves the chat, so this needs to be done before changing the character | 2582 | // Sending a message implicitly saves the chat, so this needs to be done before changing the character |
| @@ -2588,19 +2587,9 @@ async function askCharacter(args, text) { | |||
| 2588 | // Override character and send a user message | 2587 | // Override character and send a user message |
| 2589 | setCharacterId(String(chId)); | 2588 | setCharacterId(String(chId)); |
| 2590 | 2589 | ||
| 2591 | const character = characters[chId]; | 2590 | const { name, force_avatar, original_avatar } = getNameAndAvatarForMessage(character, args?.name); |
| 2592 | let force_avatar, original_avatar; | ||
| 2593 | 2591 | ||
| 2594 | if (character && character.avatar !== 'none') { | 2592 | setCharacterName(name); |
| 2595 | force_avatar = getThumbnailUrl('avatar', character.avatar); | ||
| 2596 | original_avatar = character.avatar; | ||
| 2597 | } | ||
| 2598 | else { | ||
| 2599 | force_avatar = default_avatar; | ||
| 2600 | original_avatar = default_avatar; | ||
| 2601 | } | ||
| 2602 | |||
| 2603 | setCharacterName(character.name); | ||
| 2604 | 2593 | ||
| 2605 | const restoreCharacter = () => { | 2594 | const restoreCharacter = () => { |
| 2606 | if (String(this_chid) !== String(chId)) { | 2595 | if (String(this_chid) !== String(chId)) { |
| @@ -2613,7 +2602,7 @@ async function askCharacter(args, text) { | |||
| 2613 | // Only force the new avatar if the character name is the same | 2602 | // Only force the new avatar if the character name is the same |
| 2614 | // This skips if an error was fired | 2603 | // This skips if an error was fired |
| 2615 | const lastMessage = chat[chat.length - 1]; | 2604 | const lastMessage = chat[chat.length - 1]; |
| 2616 | if (lastMessage && lastMessage?.name === character.name) { | 2605 | if (lastMessage && lastMessage?.name === name) { |
| 2617 | lastMessage.force_avatar = force_avatar; | 2606 | lastMessage.force_avatar = force_avatar; |
| 2618 | lastMessage.original_avatar = original_avatar; | 2607 | lastMessage.original_avatar = original_avatar; |
| 2619 | } | 2608 | } |
| @@ -2624,7 +2613,7 @@ async function askCharacter(args, text) { | |||
| 2624 | // Run generate and restore previous character | 2613 | // Run generate and restore previous character |
| 2625 | try { | 2614 | try { |
| 2626 | eventSource.once(event_types.MESSAGE_RECEIVED, restoreCharacter); | 2615 | eventSource.once(event_types.MESSAGE_RECEIVED, restoreCharacter); |
| 2627 | toastr.info(`Asking ${character.name} something...`); | 2616 | toastr.info(`Asking ${name} something...`); |
| 2628 | askResult = await Generate('ask_command'); | 2617 | askResult = await Generate('ask_command'); |
| 2629 | } catch (error) { | 2618 | } catch (error) { |
| 2630 | restoreCharacter(); | 2619 | restoreCharacter(); |
| @@ -3215,6 +3204,41 @@ export function validateArrayArg(arg, name, { allowUndefined = true } = {}) { | |||
| 3215 | return arg; | 3204 | return arg; |
| 3216 | } | 3205 | } |
| 3217 | 3206 | ||
| 3207 | |||
| 3208 | /** | ||
| 3209 | * Retrieves the name and avatar information for a message | ||
| 3210 | * | ||
| 3211 | * The name of the character will always have precendence over the one given as argument. If you want to specify a different name for the message, | ||
| 3212 | * explicitly implement this in the code using this. | ||
| 3213 | * | ||
| 3214 | * @param {object?} character - The character object to get the avatar data for | ||
| 3215 | * @param {string?} name - The name to get the avatar data for | ||
| 3216 | * @returns {{name: string, force_avatar: string, original_avatar: string}} An object containing the name for the message, forced avatar URL, and original avatar | ||
| 3217 | */ | ||
| 3218 | export function getNameAndAvatarForMessage(character, name = null) { | ||
| 3219 | const isNeutralCharacter = !character && name2 === neutralCharacterName && name === neutralCharacterName; | ||
| 3220 | const currentChar = characters[this_chid]; | ||
| 3221 | |||
| 3222 | let force_avatar, original_avatar; | ||
| 3223 | if (character?.avatar === currentChar?.avatar || isNeutralCharacter) { | ||
| 3224 | // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars | ||
| 3225 | } | ||
| 3226 | else if (character && character.avatar !== 'none') { | ||
| 3227 | force_avatar = getThumbnailUrl('avatar', character.avatar); | ||
| 3228 | original_avatar = character.avatar; | ||
| 3229 | } | ||
| 3230 | else { | ||
| 3231 | force_avatar = default_avatar; | ||
| 3232 | original_avatar = default_avatar; | ||
| 3233 | } | ||
| 3234 | |||
| 3235 | return { | ||
| 3236 | name: character?.name || name, | ||
| 3237 | force_avatar: force_avatar, | ||
| 3238 | original_avatar: original_avatar, | ||
| 3239 | }; | ||
| 3240 | } | ||
| 3241 | |||
| 3218 | /** | 3242 | /** |
| 3219 | * Finds a character by name, with optional filtering and precedence for avatars | 3243 | * Finds a character by name, with optional filtering and precedence for avatars |
| 3220 | * @param {object} [options={}] - The options for the search | 3244 | * @param {object} [options={}] - The options for the search |
| @@ -3273,6 +3297,19 @@ export function findChar({ name = null, allowAvatar = true, insensitive = true, | |||
| 3273 | return matchingCharacters[0] || null; | 3297 | return matchingCharacters[0] || null; |
| 3274 | } | 3298 | } |
| 3275 | 3299 | ||
| 3300 | /** | ||
| 3301 | * Gets the index of a character based on the character object | ||
| 3302 | * @param {object} char - The character object to find the index for | ||
| 3303 | * @throws {Error} If the character is not found | ||
| 3304 | * @returns {number} The index of the character in the characters array | ||
| 3305 | */ | ||
| 3306 | export function getCharIndex(char) { | ||
| 3307 | if (!char) throw new Error('Character is undefined'); | ||
| 3308 | const index = characters.findIndex(c => c.avatar === char.avatar); | ||
| 3309 | if (index === -1) throw new Error(`Character not found: ${char.avatar}`); | ||
| 3310 | return index; | ||
| 3311 | } | ||
| 3312 | |||
| 3276 | export async function sendMessageAs(args, text) { | 3313 | export async function sendMessageAs(args, text) { |
| 3277 | if (!text) { | 3314 | if (!text) { |
| 3278 | return ''; | 3315 | return ''; |
| @@ -3319,23 +3356,10 @@ export async function sendMessageAs(args, text) { | |||
| 3319 | return ''; | 3356 | return ''; |
| 3320 | } | 3357 | } |
| 3321 | 3358 | ||
| 3322 | const isNeutralCharacter = !character && name2 === neutralCharacterName && name === neutralCharacterName; | 3359 | const { name: nameForMessage, force_avatar, original_avatar } = getNameAndAvatarForMessage(avatarCharacter, name); |
| 3323 | |||
| 3324 | let force_avatar, original_avatar; | ||
| 3325 | if (character === avatarCharacter || isNeutralCharacter) { | ||
| 3326 | // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars | ||
| 3327 | } | ||
| 3328 | else if (avatarCharacter && avatarCharacter.avatar !== 'none') { | ||
| 3329 | force_avatar = getThumbnailUrl('avatar', avatarCharacter.avatar); | ||
| 3330 | original_avatar = avatarCharacter.avatar; | ||
| 3331 | } | ||
| 3332 | else { | ||
| 3333 | force_avatar = default_avatar; | ||
| 3334 | original_avatar = default_avatar; | ||
| 3335 | } | ||
| 3336 | 3360 | ||
| 3337 | const message = { | 3361 | const message = { |
| 3338 | name: character?.name || name, | 3362 | name: nameForMessage, |
| 3339 | is_user: false, | 3363 | is_user: false, |
| 3340 | is_system: isSystem, | 3364 | is_system: isSystem, |
| 3341 | send_date: getMessageTimeStamp(), | 3365 | send_date: getMessageTimeStamp(), |