From bbe52886da664cd730018ec7ec12a6c820bff5d5 Mon Sep 17 00:00:00 2001
From: Cohee <18619528+Cohee1207@users.noreply.github.com>
Date: Thu, 7 Sep 2023 17:48:12 +0300
Subject: [PATCH] Slash command to set sprite / emote. Also allow to do it per
click even in online mode
---
.../scripts/extensions/expressions/index.js | 144 +++++++++---------
1 file changed, 68 insertions(+), 76 deletions(-)
diff --git a/public/scripts/extensions/expressions/index.js b/public/scripts/extensions/expressions/index.js
index 4bb69f91a..3fa0a33b2 100644
--- a/public/scripts/extensions/expressions/index.js
+++ b/public/scripts/extensions/expressions/index.js
@@ -2,6 +2,7 @@ import { callPopup, eventSource, event_types, getRequestHeaders, saveSettingsDeb
import { dragElement, isMobile } from "../../RossAscends-mods.js";
import { getContext, getApiUrl, modules, extension_settings, ModuleWorkerWrapper, doExtrasFetch, renderExtensionTemplate } from "../../extensions.js";
import { loadMovingUIState, power_user } from "../../power-user.js";
+import { registerSlashCommand } from "../../slash-commands.js";
import { onlyUnique, debounce, getCharaFilename } from "../../utils.js";
export { MODULE_NAME };
@@ -125,15 +126,7 @@ async function visualNovelSetCharacterSprites(container, name, expression) {
continue;
}
- let spriteFolderName = character.name;
- const avatarFileName = getSpriteFolderName({ original_avatar: character.avatar });
- const expressionOverride = extension_settings.expressionOverrides.find((e) =>
- e.name == avatarFileName
- );
-
- if (expressionOverride && expressionOverride.path) {
- spriteFolderName = expressionOverride.path;
- }
+ const spriteFolderName = getSpriteFolderName({ original_avatar: character.avatar }, character.name);
// download images if not downloaded yet
if (spriteCache[spriteFolderName] === undefined) {
@@ -270,16 +263,7 @@ async function setLastMessageSprite(img, avatar, labels) {
if (lastMessage) {
const text = lastMessage.mes || '';
- let spriteFolderName = lastMessage.name;
- const avatarFileName = getSpriteFolderName(lastMessage);
- const expressionOverride = extension_settings.expressionOverrides.find((e) =>
- e.name == avatarFileName
- );
-
- if (expressionOverride && expressionOverride.path) {
- spriteFolderName = expressionOverride.path;
- }
-
+ const spriteFolderName = getSpriteFolderName(lastMessage);
const sprites = spriteCache[spriteFolderName] || [];
const label = await getExpressionLabel(text);
const path = labels.includes(label) ? sprites.find(x => x.label === label)?.path : '';
@@ -365,7 +349,7 @@ async function setImage(img, path) {
expressionClone.removeClass('default');
expressionClone.off('error');
expressionClone.on('error', function () {
- console.debug('Expression image error', sprite.path);
+ console.debug('Expression image error', path);
$(this).attr('src', '');
$(this).off('error');
resolve();
@@ -419,17 +403,7 @@ async function loadLiveChar() {
return;
}
- const context = getContext();
- let spriteFolderName = context.name2;
- const message = getLastCharacterMessage();
- const avatarFileName = getSpriteFolderName(message);
- const expressionOverride = extension_settings.expressionOverrides.find((e) =>
- e.name == avatarFileName
- );
-
- if (expressionOverride && expressionOverride.path) {
- spriteFolderName = expressionOverride.path;
- }
+ const spriteFolderName = getSpriteFolderName();
const talkingheadPath = `/characters/${encodeURIComponent(spriteFolderName)}/talkinghead.png`;
@@ -468,7 +442,7 @@ async function loadLiveChar() {
function handleImageChange() {
const imgElement = document.querySelector('img#expression-image.expression');
- if (!imgElement) {
+ if (!imgElement || !(imgElement instanceof HTMLImageElement)) {
console.log("Cannot find addExpressionImage()");
return;
}
@@ -480,7 +454,7 @@ function handleImageChange() {
if (imgElement.src !== talkingheadResultFeedSrc) {
const expressionImageElement = document.querySelector('.expression_list_image');
- if (expressionImageElement) {
+ if (expressionImageElement && expressionImageElement instanceof HTMLImageElement) {
doExtrasFetch(expressionImageElement.src, {
method: 'HEAD',
})
@@ -516,12 +490,14 @@ async function moduleWorker() {
//clear expression
let imgElement = document.getElementById('expression-image');
- imgElement.src = "";
+ if (imgElement && imgElement instanceof HTMLImageElement) {
+ imgElement.src = "";
+ }
//set checkbox to global var
$('#image_type_toggle').prop('checked', extension_settings.expressions.talkinghead);
if (extension_settings.expressions.talkinghead) {
- settalkingheadState(extension_settings.expressions.talkinghead);
+ setTalkingHeadState(extension_settings.expressions.talkinghead);
}
}
@@ -545,15 +521,7 @@ async function moduleWorker() {
}
const currentLastMessage = getLastCharacterMessage();
- let spriteFolderName = currentLastMessage.name;
- const avatarFileName = getSpriteFolderName(currentLastMessage);
- const expressionOverride = extension_settings.expressionOverrides.find((e) =>
- e.name == avatarFileName
- );
-
- if (expressionOverride && expressionOverride.path) {
- spriteFolderName = expressionOverride.path;
- }
+ let spriteFolderName = getSpriteFolderName(currentLastMessage, currentLastMessage.name);
// character has no expressions or it is not loaded
if (Object.keys(spriteCache).length === 0) {
@@ -627,18 +595,8 @@ async function moduleWorker() {
}
}
-async function talkingheadcheck() {
- const context = getContext();
- let spriteFolderName = context.name2;
- const message = getLastCharacterMessage();
- const avatarFileName = getSpriteFolderName(message);
- const expressionOverride = extension_settings.expressionOverrides.find((e) =>
- e.name == avatarFileName
- );
-
- if (expressionOverride && expressionOverride.path) {
- spriteFolderName = expressionOverride.path;
- }
+async function talkingHeadCheck() {
+ let spriteFolderName = getSpriteFolderName();
try {
await validateImages(spriteFolderName);
@@ -659,11 +617,25 @@ async function talkingheadcheck() {
}
}
-function settalkingheadState(switch_var) {
+function getSpriteFolderName(characterMessage = null, characterName = null) {
+ const context = getContext();
+ let spriteFolderName = characterName ?? context.name2;
+ const message = characterMessage ?? getLastCharacterMessage();
+ const avatarFileName = getFolderNameByMessage(message);
+ const expressionOverride = extension_settings.expressionOverrides.find(e => e.name == avatarFileName);
+
+ if (expressionOverride && expressionOverride.path) {
+ spriteFolderName = expressionOverride.path;
+ }
+
+ return spriteFolderName;
+}
+
+function setTalkingHeadState(switch_var) {
extension_settings.expressions.talkinghead = switch_var; // Store setting
saveSettingsDebounced();
- talkingheadcheck().then(result => {
+ talkingHeadCheck().then(result => {
if (result) {
//console.log("talkinghead exists!");
@@ -672,7 +644,7 @@ function settalkingheadState(switch_var) {
} else {
unloadLiveChar();
}
- handleImageChange(switch_var); // Change image as needed
+ handleImageChange(); // Change image as needed
} else {
@@ -681,7 +653,7 @@ function settalkingheadState(switch_var) {
});
}
-function getSpriteFolderName(message) {
+function getFolderNameByMessage(message) {
const context = getContext();
let avatarPath = '';
@@ -712,6 +684,31 @@ async function sendExpressionCall(name, expression, force, vnMode) {
}
}
+async function setSpriteSlashCommand(_, spriteId) {
+ if (!spriteId) {
+ console.log('No sprite id provided');
+ return;
+ }
+
+ spriteId = spriteId.trim().toLowerCase();
+
+ const spriteFolderName = getSpriteFolderName();
+ await validateImages(spriteFolderName);
+
+ // Fuzzy search for sprite
+ const fuse = new Fuse(spriteCache[spriteFolderName], { keys: ['label'] });
+ const results = fuse.search(spriteId);
+ const spriteItem = results[0]?.item;
+
+ if (!spriteItem) {
+ console.log('No sprite found for search term ' + spriteId);
+ return;
+ }
+
+ const vnMode = isVisualNovelMode();
+ await sendExpressionCall(spriteFolderName, spriteItem.label, true, vnMode);
+}
+
async function getExpressionLabel(text) {
// Return if text is undefined, saving a costly fetch request
if (!modules.includes('classify') || !text) {
@@ -968,12 +965,12 @@ async function setExpression(character, expression, force) {
} else {
- talkingheadcheck().then(result => {
+ talkingHeadCheck().then(result => {
if (result) {
// Find the element with id="expression-image" and class="expression"
const imgElement = document.querySelector('img#expression-image.expression');
//console.log("searching");
- if (imgElement) {
+ if (imgElement && imgElement instanceof HTMLImageElement) {
//console.log("setting value");
imgElement.src = getApiUrl() + '/api/talkinghead/result_feed';
}
@@ -988,18 +985,10 @@ async function setExpression(character, expression, force) {
}
function onClickExpressionImage() {
- // online mode doesn't need force set
- if (modules.includes('classify')) {
- return;
- }
-
const expression = $(this).attr('id');
- const name = getLastCharacterMessage().name;
-
- if ($(this).find('.failure').length === 0) {
- setExpression(name, expression, true);
- }
+ setSpriteSlashCommand({}, expression);
}
+
async function handleFileUpload(url, formData) {
try {
const data = await jQuery.ajax({
@@ -1057,7 +1046,7 @@ async function onClickExpressionUpload(event) {
async function onClickExpressionOverrideButton() {
const context = getContext();
const currentLastMessage = getLastCharacterMessage();
- const avatarFileName = getSpriteFolderName(currentLastMessage);
+ const avatarFileName = getFolderNameByMessage(currentLastMessage);
// If the avatar name couldn't be found, abort.
if (!avatarFileName) {
@@ -1066,7 +1055,7 @@ async function onClickExpressionOverrideButton() {
return;
}
- const overridePath = $("#expression_override").val();
+ const overridePath = String($("#expression_override").val());
const existingOverrideIndex = extension_settings.expressionOverrides.findIndex((e) =>
e.name == avatarFileName
);
@@ -1191,7 +1180,7 @@ async function onClickExpressionDelete(event) {
function setExpressionOverrideHtml(forceClear = false) {
const currentLastMessage = getLastCharacterMessage();
- const avatarFileName = getSpriteFolderName(currentLastMessage);
+ const avatarFileName = getFolderNameByMessage(currentLastMessage);
if (!avatarFileName) {
return;
}
@@ -1249,7 +1238,9 @@ function setExpressionOverrideHtml(forceClear = false) {
$('.expression_settings').hide();
$('#image_type_toggle').on('click', function () {
- settalkingheadState(this.checked);
+ if (this instanceof HTMLInputElement) {
+ setTalkingHeadState(this.checked);
+ }
});
}
@@ -1270,4 +1261,5 @@ function setExpressionOverrideHtml(forceClear = false) {
});
eventSource.on(event_types.MOVABLE_PANELS_RESET, updateVisualNovelModeDebounced);
eventSource.on(event_types.GROUP_UPDATED, updateVisualNovelModeDebounced);
+ registerSlashCommand('sprite', setSpriteSlashCommand, ['emote'], 'spriteId – force sets the sprite for the current character', true, true);
})();