Allow searching on /sendas via tag=

22d8a654d95763213a7d823adc2ae695ce6ca129

Wolfsblvt <wolfsblvt@gmail.com>

2 files changed, +56 -14Ignore whitespace
public/scripts/slash-commands.js+55 -13
@@ -187,11 +187,18 @@ export function initDefaultSlashCommands() {
187 }),187 }),
188 SlashCommandNamedArgument.fromProps({188 SlashCommandNamedArgument.fromProps({
189 name: 'avatar',189 name: 'avatar',
190 description: 'Optional character avatar override (Can be either avatar filename or just the character name to pull the avatar from)',190 description: 'Character avatar override (Can be either avatar filename or just the character name to pull the avatar from)',
191 typeList: [ARGUMENT_TYPE.STRING],191 typeList: [ARGUMENT_TYPE.STRING],
192 enumProvider: commonEnumProviders.characters('character'),192 enumProvider: commonEnumProviders.characters('character'),
193 }),193 }),
194 SlashCommandNamedArgument.fromProps({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 name: 'compact',202 name: 'compact',
196 description: 'Use compact layout',203 description: 'Use compact layout',
197 typeList: [ARGUMENT_TYPE.BOOLEAN],204 typeList: [ARGUMENT_TYPE.BOOLEAN],
@@ -3109,6 +3116,44 @@ async function setNarratorName(_, text) {
3109 return '';3116 return '';
3110}3117}
31113118
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 */
3129export 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
3112export async function sendMessageAs(args, text) {3157export async function sendMessageAs(args, text) {
3113 if (!text) {3158 if (!text) {
3114 return '';3159 return '';
@@ -3150,24 +3195,21 @@ export async function sendMessageAs(args, text) {
3150 const chatCharacter = this_chid !== undefined ? characters[this_chid] : null;3195 const chatCharacter = this_chid !== undefined ? characters[this_chid] : null;
3151 const isNeutralCharacter = !chatCharacter && name2 === neutralCharacterName && name === neutralCharacterName;3196 const isNeutralCharacter = !chatCharacter && name2 === neutralCharacterName && name === neutralCharacterName;
31523197
3153 const character = equalsIgnoreCaseAndAccents(chatCharacter.name, name) ? chatCharacter : characters.find(x => equalsIgnoreCaseAndAccents(x.name, name));3198 const character = findCharByName(name, { filteredByTags: args?.tags, preferCurrentChar: chatCharacter });
31543199
3155 let avatarChar = character;3200 const avatarCharacter = args.avatar ? findCharByName(args.avatar) : character;
3156 if (args.avatar) {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 }
31633205
3164 let force_avatar, original_avatar;3206 let force_avatar, original_avatar;
3165 if (chatCharacter === avatarChar || isNeutralCharacter) {3207 if (chatCharacter === avatarCharacter || isNeutralCharacter) {
3166 // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars3208 // If the targeted character is the currently selected one in a solo chat, we don't need to force any avatars
3167 }3209 }
3168 else if (avatarChar && avatarChar.avatar !== 'none') {3210 else if (avatarCharacter && avatarCharacter.avatar !== 'none') {
3169 force_avatar = getThumbnailUrl('avatar', avatarChar.avatar);3211 force_avatar = getThumbnailUrl('avatar', avatarCharacter.avatar);
3170 original_avatar = avatarChar.avatar;3212 original_avatar = avatarCharacter.avatar;
3171 }3213 }
3172 else {3214 else {
3173 force_avatar = default_avatar;3215 force_avatar = default_avatar;
public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js+1 -1
@@ -193,7 +193,7 @@ export const commonEnumProviders = {
193 if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures');193 if (charName instanceof SlashCommandClosure) throw new Error('Argument \'name\' does not support closures');
194 const key = searchCharByName(substituteParams(charName), { suppressLogging: true });194 const key = searchCharByName(substituteParams(charName), { suppressLogging: true });
195 const assigned = key ? getTagsList(key) : [];195 const assigned = key ? getTagsList(key) : [];
196 return tags.filter(it => !key || mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it))196 return tags.filter(it => mode === 'all' || mode === 'existing' && assigned.includes(it) || mode === 'not-existing' && !assigned.includes(it))
197 .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag));197 .map(tag => new SlashCommandEnumValue(tag.name, null, enumTypes.command, enumIcons.tag));
198 },198 },
199199