Add support for characterFilter to getEntryField and setEntryField (#4185) * Add support for characterFilter to getEntryField and setEntryField * No more conflicts between lint and autoformat * Fix array type default values hint * Properly capitalize name * Fix search by name --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

116bef6c9ffbe2c912cd883a15aad7554ecbc4c6

Wandering Mouse <215439289+WanderingMouse@users.noreply.github.com>

Signed
3 files changed, +76 -5Showing whitespace changes
public/scripts/utils.js+1 -1
@@ -2367,7 +2367,7 @@ export function findChar({ name = null, allowAvatar = true, insensitive = true,
23672367
23682368 // If allowAvatar is true, search by avatar first
23692369 if (allowAvatar && name) {
23702370 const characterByAvatar = filteredCharacters.find(char => char.avatar === name || (!name.endsWith('.png') && char.avatar === `${name}.png`));
23712371 if (characterByAvatar) {
23722372 return characterByAvatar;
23732373 }
public/scripts/world-info.js+74 -2
@@ -1124,6 +1124,7 @@ function registerWorldInfoSlashCommands() {
11241124 async function getEntryFieldCallback(args, uid) {
11251125 const file = args.file;
11261126 const field = args.field || 'content';
1127+ const tags = getContext().tags;
11271128
11281129 const entries = await getEntriesFromFile(file);
11291130
@@ -1143,7 +1144,31 @@ function registerWorldInfoSlashCommands() {
11431144 return '';
11441145 }
11451146
1146- const fieldValue = entry[field];
1147+ // handle special cases, otherwise execute default logic
1148+ let fieldValue;
1149+ switch (field){
1150+ case 'characterFilterNames':
1151+ if (entry.characterFilter) {
1152+ fieldValue = entry.characterFilter.names;
1153+ }
1154+ break;
1155+ case 'characterFilterTags':
1156+ if (entry.characterFilter) {
1157+ if (!entry.characterFilter.tags) {
1158+ return '';
1159+ }
1160+ //Find the tag objects corresponding to each ID in the array, then return the names
1161+ fieldValue = tags.filter((tag) => entry.characterFilter.tags.includes(tag.id)).map((tag) => tag.name);
1162+ }
1163+ break;
1164+ case 'characterFilterExclude':
1165+ if (entry.characterFilter) {
1166+ fieldValue = entry.characterFilter.isExclude;
1167+ }
1168+ break;
1169+ default:
1170+ fieldValue = entry[field];
1171+ }
11471172
11481173 if (fieldValue === undefined) {
11491174 return '';
@@ -1189,6 +1214,23 @@ function registerWorldInfoSlashCommands() {
11891214 const file = args.file;
11901215 const uid = args.uid;
11911216 const field = args.field || 'content';
1217+ const tags = getContext().tags;
1218+
1219+ // characterFilter is an object with internal fields we need to access, which may also may be null and need to be populated
1220+ const createCharacterFilterFieldObjectIfNeeded = (currentEntry) => {
1221+ if (!currentEntry.characterFilter) {
1222+ Object.assign(
1223+ currentEntry,
1224+ {
1225+ characterFilter: {
1226+ isExclude: false,
1227+ names: [],
1228+ tags: [],
1229+ },
1230+ },
1231+ );
1232+ }
1233+ };
11921234
11931235 if (value === undefined) {
11941236 toastr.warning('Value is required');
@@ -1216,6 +1258,32 @@ function registerWorldInfoSlashCommands() {
12161258 return '';
12171259 }
12181260
1261+ // handle special cases, otherwise execute default logic
1262+ let tagNames;
1263+ let charNames;
1264+ switch (field){
1265+ case 'characterFilterNames':
1266+ createCharacterFilterFieldObjectIfNeeded(entry);
1267+ charNames = parseStringArray(value);
1268+ entry.characterFilter.names = charNames
1269+ .map((name) => getCharaFilename(null, { manualAvatarKey: findChar({ name, allowAvatar: true, preferCurrentChar: false, quiet: true })?.avatar }))
1270+ .filter(Boolean)
1271+ .filter(onlyUnique);
1272+ setWIOriginalDataValue(data, uid, 'character_filter', entry.characterFilter);
1273+ break;
1274+ case 'characterFilterTags':
1275+ createCharacterFilterFieldObjectIfNeeded(entry);
1276+ tagNames = parseStringArray(value);
1277+ //Find the tag objects corresponding to each name in the user array, then return an array of the corresponding IDs
1278+ entry.characterFilter.tags = tags.filter((tag) => tagNames.includes(tag.name)).map((tag) => tag.id);
1279+ setWIOriginalDataValue(data, uid, 'character_filter', entry.characterFilter);
1280+ break;
1281+ case 'characterFilterExclude':
1282+ createCharacterFilterFieldObjectIfNeeded(entry);
1283+ entry.characterFilter.isExclude = isTrueBoolean(value);
1284+ setWIOriginalDataValue(data, uid, 'character_filter', entry.characterFilter);
1285+ break;
1286+ default:
12191287 if (Array.isArray(entry[field])) {
12201288 entry[field] = parseStringArray(value);
12211289 } else if (typeof entry[field] === 'boolean') {
@@ -1229,6 +1297,7 @@ function registerWorldInfoSlashCommands() {
12291297 if (originalWIDataKeyMap[field]) {
12301298 setWIOriginalDataValue(data, uid, originalWIDataKeyMap[field], entry[field]);
12311299 }
1300+ }
12321301
12331302 await saveWorldInfo(file, data);
12341303 reloadEditor(file);
@@ -1349,7 +1418,7 @@ function registerWorldInfoSlashCommands() {
13491418 const localEnumProviders = {
13501419 /** All possible fields that can be set in a WI entry */
13511420 wiEntryFields: () => Object.entries(newWorldInfoEntryDefinition).map(([key, value]) =>
13521421 new SlashCommandEnumValue(key, `[${value.type}] default: ${(typeof value.default === 'string' ? `'${value.default}'` : JSON.stringify(value.default))}`,
13531422 enumTypes.enum, enumIcons.getDataTypeIcon(value.type))),
13541423
13551424 /** All existing UIDs based on the file argument as world name */
@@ -3578,6 +3647,9 @@ export const newWorldInfoEntryDefinition = {
35783647 sticky: { default: null, type: 'number?' },
35793648 cooldown: { default: null, type: 'number?' },
35803649 delay: { default: null, type: 'number?' },
3650+ characterFilterNames: { default: [], type: 'array' },
3651+ characterFilterTags: { default: [], type: 'array' },
3652+ characterFilterExclude: { default: false, type: 'boolean' },
35813653};
35823654
35833655export const newWorldInfoEntryTemplate = Object.fromEntries(
src/endpoints/chats.js+1 -2
@@ -863,8 +863,7 @@ router.post('/search', validateAvatarUrlMiddleware, function (request, response)
863863
864864 // Search through title and messages of the chat
865865 const fragments = query.trim().toLowerCase().split(/\s+/).filter(x => x);
866866 const text = [path.parse(chatFile.path).name, ...messages.map(message => message?.mes)].join('\n').toLowerCase();
867- ...messages.map(message => message?.mes)].join('\n').toLowerCase();
868867 const hasMatch = fragments.every(fragment => text.includes(fragment));
869868
870869 if (hasMatch) {