Allow searching on /sendas via tag=
| @@ -187,11 +187,18 @@ export function initDefaultSlashCommands() { | ||
| 187 | 187 | }), |
| 188 | 188 | SlashCommandNamedArgument.fromProps({ |
| 189 | 189 | name: 'avatar', |
| 190 | 190 | description: 'Optional characterCharacter avatar override (Can be either avatar filename or just the character name to pull the avatar from)', |
| 191 | 191 | typeList: [ARGUMENT_TYPE.STRING], |
| 192 | 192 | enumProvider: commonEnumProviders.characters('character'), |
| 193 | 193 | }), |
| 194 | 194 | SlashCommandNamedArgument.fromProps({ |
| 195 | + name: 'tag', | |
| 196 | + description: 'Supply one or more tags to filter down to the correct character for the provided name, if multiple characters have the same name. (does not apply to the avatar argument)', | |
| 197 | + typeList: [ARGUMENT_TYPE.STRING], | |
| 198 | + enumProvider: commonEnumProviders.tagsForChar('all'), | |
| 199 | + acceptsMultiple: true, | |
| 200 | + }), | |
| 201 | + SlashCommandNamedArgument.fromProps({ | |
| 195 | 202 | name: 'compact', |
| 196 | 203 | description: 'Use compact layout', |
| 197 | 204 | typeList: [ARGUMENT_TYPE.BOOLEAN], |
| @@ -3109,6 +3116,44 @@ async function setNarratorName(_, text) { | ||
| 3109 | 3116 | return ''; |
| 3110 | 3117 | } |
| 3111 | 3118 | |
| 3119 | +/** | |
| 3120 | + * Finds a character by name, with optional filtering and precedence for avatars | |
| 3121 | + * @param {string} name - The name to search for | |
| 3122 | + * @param {object} [options={}] - The options for the search | |
| 3123 | + * @param {boolean} [options.allowAvatar=false] - Whether to allow searching by avatar | |
| 3124 | + * @param {boolean} [options.insensitive=true] - Whether the search should be case insensitive | |
| 3125 | + * @param {string[]?} [options.filteredByTags=null] - Tags to filter characters by | |
| 3126 | + * @param {any?} [options.preferCurrentChar=null] - The current character to prefer | |
| 3127 | + * @returns {any?} - The found character or null if not found | |
| 3128 | + */ | |
| 3129 | +export function findCharByName(name, { allowAvatar = false, insensitive = true, filteredByTags = null, preferCurrentChar = null } = {}) { | |
| 3130 | + const matches = (char) => (allowAvatar && char.avatar === name) || insensitive ? equalsIgnoreCaseAndAccents(char.name, name) : char.name === name; | |
| 3131 | + | |
| 3132 | + // If we have a current char and prefer it, return that if it matches - unless tags are provided, they have precedence | |
| 3133 | + if (preferCurrentChar && !filteredByTags && matches(preferCurrentChar)) { | |
| 3134 | + return preferCurrentChar; | |
| 3135 | + } | |
| 3136 | + | |
| 3137 | + // Filter characters by tags if provided | |
| 3138 | + let filteredCharacters = characters; | |
| 3139 | + if (filteredByTags) { | |
| 3140 | + filteredCharacters = characters.filter(char => filteredByTags.every(tag => char.tags.includes(tag))); | |
| 3141 | + } | |
| 3142 | + | |
| 3143 | + // If allowAvatar is true, search by avatar first | |
| 3144 | + if (allowAvatar) { | |
| 3145 | + const characterByAvatar = filteredCharacters.find(char => char.avatar === name); | |
| 3146 | + if (characterByAvatar) { | |
| 3147 | + return characterByAvatar; | |
| 3148 | + } | |
| 3149 | + } | |
| 3150 | + | |
| 3151 | + // Search for a matching character by name | |
| 3152 | + let character = filteredCharacters.find(matches); | |
| 3153 | + | |
| 3154 | + return character; | |
| 3155 | +} | |
| 3156 | + | |
| 3112 | 3157 | export async function sendMessageAs(args, text) { |
| 3113 | 3158 | if (!text) { |
| 3114 | 3159 | return ''; |
| @@ -3150,24 +3195,21 @@ export async function sendMessageAs(args, text) { | ||
| 3150 | 3195 | const chatCharacter = this_chid !== undefined ? characters[this_chid] : null; |
| 3151 | 3196 | const isNeutralCharacter = !chatCharacter && name2 === neutralCharacterName && name === neutralCharacterName; |
| 3152 | 3197 | |
| 3153 | 3198 | const character = equalsIgnoreCaseAndAccentsfindCharByName(chatCharacter.name, name) ? chatCharacter{ filteredByTags: charactersargs?.find(xtags, =>preferCurrentChar: equalsIgnoreCaseAndAccents(x.name,chatCharacter name)}); |
| 3154 | 3199 | |
| 3155 | - let avatarChar = character; | |
| 3200 | + const avatarCharacter = args.avatar ? findCharByName(args.avatar) : character; | |
| 3156 | 3201 | if (args.avatar && !avatarCharacter) { |
| 3157 | - avatarChar = characters.find(x => x.avatar == args.avatar) ?? characters.find(x => equalsIgnoreCaseAndAccents(x.name, args.avatar)); | |
| 3202 | + toastr.warning(`Character for avatar ${args.avatar} not found`); | |
| 3158 | - if (!avatarChar) { | |
| 3203 | + return ''; | |
| 3159 | - toastr.warning(`Character for avatar ${args.avatar} not found`); | |
| 3160 | - return ''; | |
| 3161 | - } | |
| 3162 | 3204 | } |
| 3163 | 3205 | |
| 3164 | 3206 | let force_avatar, original_avatar; |
| 3165 | 3207 | if (chatCharacter === avatarCharavatarCharacter || isNeutralCharacter) { |
| 3166 | 3208 | // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars |
| 3167 | 3209 | } |
| 3168 | 3210 | else if (avatarCharavatarCharacter && avatarCharavatarCharacter.avatar !== 'none') { |
| 3169 | 3211 | force_avatar = getThumbnailUrl('avatar', avatarCharavatarCharacter.avatar); |
| 3170 | 3212 | original_avatar = avatarCharavatarCharacter.avatar; |
| 3171 | 3213 | } |
| 3172 | 3214 | else { |
| 3173 | 3215 | force_avatar = default_avatar; |
| @@ -193,7 +193,7 @@ export const commonEnumProviders = { | ||
| 193 | 193 | if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures'); |
| 194 | 194 | const key = searchCharByName(substituteParams(charName), { suppressLogging: true }); |
| 195 | 195 | const assigned = key ? getTagsList(key) : []; |
| 196 | 196 | return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it)) |
| 197 | 197 | .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag)); |
| 198 | 198 | }, |
| 199 | 199 | |