SillyTavern/public/scripts/extensions/regex/engine.js

163 lines
6.0 KiB
JavaScript
Raw Normal View History

import { characters, substituteParams, this_chid } from '../../../script.js';
2023-12-02 19:04:51 +01:00
import { extension_settings } from '../../extensions.js';
import { regexFromString } from '../../utils.js';
2023-07-20 19:32:15 +02:00
export {
regex_placement,
getRegexedString,
2023-12-02 21:06:57 +01:00
runRegexScript,
2023-12-02 20:11:06 +01:00
};
2023-07-20 19:32:15 +02:00
/**
* @enum {number} Where the regex script should be applied
*/
2023-07-20 19:32:15 +02:00
const regex_placement = {
/**
* @deprecated MD Display is deprecated. Do not use.
*/
2023-07-20 19:32:15 +02:00
MD_DISPLAY: 0,
USER_INPUT: 1,
AI_OUTPUT: 2,
2023-12-02 21:06:57 +01:00
SLASH_COMMAND: 3,
2024-05-08 16:55:33 +02:00
// 4 - sendAs (legacy)
WORLD_INFO: 5,
2023-12-02 20:11:06 +01:00
};
2023-07-20 19:32:15 +02:00
function getScopedRegex() {
const isAllowed = extension_settings?.character_allowed_regex?.includes(characters?.[this_chid]?.avatar);
if (!isAllowed) {
return [];
}
const scripts = characters[this_chid]?.data?.extensions?.regex_scripts;
if (!Array.isArray(scripts)) {
return [];
}
return scripts;
}
/**
* Parent function to fetch a regexed version of a raw string
* @param {string} rawString The raw string to be regexed
* @param {regex_placement} placement The placement of the string
* @param {RegexParams} params The parameters to use for the regex script
* @returns {string} The regexed string
* @typedef {{characterOverride?: string, isMarkdown?: boolean, isPrompt?: boolean, isEdit?: boolean, depth?: number }} RegexParams The parameters to use for the regex script
*/
function getRegexedString(rawString, placement, { characterOverride, isMarkdown, isPrompt, isEdit, depth } = {}) {
// WTF have you passed me?
if (typeof rawString !== 'string') {
console.warn('getRegexedString: rawString is not a string. Returning empty string.');
return '';
}
2023-07-20 19:32:15 +02:00
let finalString = rawString;
2023-12-02 19:04:51 +01:00
if (extension_settings.disabledExtensions.includes('regex') || !rawString || placement === undefined) {
2023-07-20 19:32:15 +02:00
return finalString;
}
const allRegex = [...(extension_settings.regex ?? []), ...(getScopedRegex() ?? [])];
allRegex.forEach((script) => {
if (
// Script applies to Markdown and input is Markdown
(script.markdownOnly && isMarkdown) ||
// Script applies to Generate and input is Generate
(script.promptOnly && isPrompt) ||
// Script applies to all cases when neither "only"s are true, but there's no need to do it when `isMarkdown`, the as source (chat history) should already be changed beforehand
(!script.markdownOnly && !script.promptOnly && !isMarkdown)
) {
if (isEdit && !script.runOnEdit) {
console.debug(`getRegexedString: Skipping script ${script.scriptName} because it does not run on edit`);
return;
}
// Check if the depth is within the min/max depth
if (typeof depth === 'number' && depth >= 0) {
2024-02-04 20:34:17 +01:00
if (!isNaN(script.minDepth) && script.minDepth !== null && script.minDepth >= 0 && depth < script.minDepth) {
console.debug(`getRegexedString: Skipping script ${script.scriptName} because depth ${depth} is less than minDepth ${script.minDepth}`);
return;
}
2024-02-04 20:34:17 +01:00
if (!isNaN(script.maxDepth) && script.maxDepth !== null && script.maxDepth >= 0 && depth > script.maxDepth) {
console.debug(`getRegexedString: Skipping script ${script.scriptName} because depth ${depth} is greater than maxDepth ${script.maxDepth}`);
return;
}
}
if (script.placement.includes(placement)) {
finalString = runRegexScript(script, finalString, { characterOverride });
}
2023-07-20 19:32:15 +02:00
}
});
return finalString;
}
/**
* Runs the provided regex script on the given string
* @param {import('./index.js').RegexScript} regexScript The regex script to run
* @param {string} rawString The string to run the regex script on
* @param {RegexScriptParams} params The parameters to use for the regex script
* @returns {string} The new string
* @typedef {{characterOverride?: string}} RegexScriptParams The parameters to use for the regex script
*/
2023-07-20 19:32:15 +02:00
function runRegexScript(regexScript, rawString, { characterOverride } = {}) {
let newString = rawString;
if (!regexScript || !!(regexScript.disabled) || !regexScript?.findRegex || !rawString) {
return newString;
}
const findRegex = regexFromString(regexScript.substituteRegex ? substituteParams(regexScript.findRegex) : regexScript.findRegex);
// The user skill issued. Return with nothing.
if (!findRegex) {
return newString;
}
// Run replacement. Currently does not support the Overlay strategy
newString = rawString.replace(findRegex, function (match) {
const args = [...arguments];
2024-01-11 01:41:00 +01:00
const replaceString = regexScript.replaceString.replace(/{{match}}/gi, '$0');
2024-03-21 11:12:22 +01:00
const replaceWithGroups = replaceString.replaceAll(/\$(\d+)/g, (_, num) => {
2024-01-11 01:41:00 +01:00
// Get a full match or a capture group
const match = args[Number(num)];
2024-01-11 01:41:00 +01:00
// No match found - return the empty string
if (!match) {
return '';
}
// Remove trim strings from the match
const filteredMatch = filterString(match, regexScript.trimStrings, { characterOverride });
// TODO: Handle overlay here
return filteredMatch;
});
// Substitute at the end
return substituteParams(replaceWithGroups);
});
2023-07-20 19:32:15 +02:00
return newString;
}
/**
* Filters anything to trim from the regex match
* @param {string} rawString The raw string to filter
* @param {string[]} trimStrings The strings to trim
* @param {RegexScriptParams} params The parameters to use for the regex filter
* @returns {string} The filtered string
*/
2023-07-20 19:32:15 +02:00
function filterString(rawString, trimStrings, { characterOverride } = {}) {
let finalString = rawString;
trimStrings.forEach((trimString) => {
2023-07-20 19:32:15 +02:00
const subTrimString = substituteParams(trimString, undefined, characterOverride);
2023-12-02 19:04:51 +01:00
finalString = finalString.replaceAll(subTrimString, '');
});
2023-07-20 19:32:15 +02:00
return finalString;
}