mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Multiple scripts were not running due to improper variable assingment. For efficiency's sake, do not do a string comparison before returning and instead do another variable assignment in the parent function. Doing this reduces the length of regex hooks in the parent calls, but also removes the need for unnecessary O(n) complexity of comparing two string variables. If there are errors, it would be advisable to add string comparison and revert back to the old logic in parent function calls. Signed-off-by: kingbri <bdashore3@proton.me>
164 lines
5.2 KiB
JavaScript
164 lines
5.2 KiB
JavaScript
import { substituteParams } from "../../../script.js";
|
|
import { extension_settings } from "../../extensions.js";
|
|
export {
|
|
regex_placement,
|
|
getRegexedString,
|
|
runRegexScript
|
|
}
|
|
|
|
const regex_placement = {
|
|
MD_DISPLAY: 0,
|
|
USER_INPUT: 1,
|
|
AI_OUTPUT: 2,
|
|
SYSTEM: 3,
|
|
SENDAS: 4
|
|
}
|
|
|
|
const regex_replace_strategy = {
|
|
REPLACE: 0,
|
|
OVERLAY: 1
|
|
}
|
|
|
|
// Originally from: https://github.com/IonicaBizau/regex-parser.js/blob/master/lib/index.js
|
|
function regexFromString(input) {
|
|
try {
|
|
// Parse input
|
|
var m = input.match(/(\/?)(.+)\1([a-z]*)/i);
|
|
|
|
// Invalid flags
|
|
if (m[3] && !/^(?!.*?(.).*?\1)[gmixXsuUAJ]+$/.test(m[3])) {
|
|
return RegExp(input);
|
|
}
|
|
|
|
// Create the regular expression
|
|
return new RegExp(m[2], m[3]);
|
|
} catch {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Parent function to fetch a regexed version of a raw string
|
|
function getRegexedString(rawString, placement, { characterOverride } = {}) {
|
|
let finalString = rawString;
|
|
if (extension_settings.disabledExtensions.includes("regex") || !rawString || placement === undefined) {
|
|
return finalString;
|
|
}
|
|
|
|
extension_settings.regex.forEach((script) => {
|
|
if (script.placement.includes(placement)) {
|
|
finalString = runRegexScript(script, finalString, { characterOverride });
|
|
}
|
|
});
|
|
|
|
return finalString;
|
|
}
|
|
|
|
// Runs the provided regex script on the given string
|
|
function runRegexScript(regexScript, rawString, { characterOverride } = {}) {
|
|
let newString = rawString;
|
|
if (!regexScript || !!(regexScript.disabled) || !regexScript?.findRegex || !rawString) {
|
|
return newString;
|
|
}
|
|
|
|
let match;
|
|
const findRegex = regexFromString(regexScript.substituteRegex ? substituteParams(regexScript.findRegex) : regexScript.findRegex);
|
|
|
|
// The user skill issued. Return with nothing.
|
|
if (!findRegex) {
|
|
return newString;
|
|
}
|
|
|
|
while ((match = findRegex.exec(rawString)) !== null) {
|
|
const fencedMatch = match[0];
|
|
const capturedMatch = match[1];
|
|
|
|
let trimCapturedMatch;
|
|
let trimFencedMatch;
|
|
if (capturedMatch) {
|
|
const tempTrimCapture = filterString(capturedMatch, regexScript.trimStrings, { characterOverride });
|
|
trimFencedMatch = fencedMatch.replaceAll(capturedMatch, tempTrimCapture);
|
|
trimCapturedMatch = tempTrimCapture;
|
|
} else {
|
|
trimFencedMatch = filterString(fencedMatch, regexScript.trimStrings, { characterOverride });
|
|
}
|
|
|
|
// TODO: Use substrings for replacement. But not necessary at this time.
|
|
// A substring is from match.index to match.index + match[0].length or fencedMatch.length
|
|
const subReplaceString = substituteRegexParams(
|
|
regexScript.replaceString,
|
|
trimCapturedMatch ?? trimFencedMatch,
|
|
{
|
|
characterOverride,
|
|
replaceStrategy: regexScript.replaceStrategy ?? regex_replace_strategy.REPLACE
|
|
}
|
|
);
|
|
if (!newString) {
|
|
newString = rawString.replace(fencedMatch, subReplaceString);
|
|
} else {
|
|
newString = newString.replace(fencedMatch, subReplaceString);
|
|
}
|
|
|
|
// If the regex isn't global, break out of the loop
|
|
if (!findRegex.flags.includes('g')) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return newString;
|
|
}
|
|
|
|
// Filters anything to trim from the regex match
|
|
function filterString(rawString, trimStrings, { characterOverride } = {}) {
|
|
let finalString = rawString;
|
|
trimStrings.forEach((trimString) => {
|
|
const subTrimString = substituteParams(trimString, undefined, characterOverride);
|
|
finalString = finalString.replaceAll(subTrimString, "");
|
|
});
|
|
|
|
return finalString;
|
|
}
|
|
|
|
// Substitutes regex-specific and normal parameters
|
|
function substituteRegexParams(rawString, regexMatch, { characterOverride, replaceStrategy } = {}) {
|
|
let finalString = rawString;
|
|
finalString = substituteParams(finalString, undefined, characterOverride);
|
|
|
|
let overlaidMatch = regexMatch;
|
|
if (replaceStrategy === regex_replace_strategy.OVERLAY) {
|
|
const splitReplace = finalString.split("{{match}}");
|
|
|
|
// There's a prefix
|
|
if (splitReplace[0]) {
|
|
const splicedPrefix = spliceSymbols(splitReplace[0], false);
|
|
overlaidMatch = overlaidMatch.replace(splicedPrefix, "").trim();
|
|
}
|
|
|
|
// There's a suffix
|
|
if (splitReplace[1]) {
|
|
const splicedSuffix = spliceSymbols(splitReplace[1], true);
|
|
overlaidMatch = overlaidMatch.replace(new RegExp(`${splicedSuffix}$`), "").trim();
|
|
}
|
|
}
|
|
|
|
// Only one match is replaced. This is by design
|
|
finalString = finalString.replace("{{match}}", overlaidMatch) || finalString.replace("{{match}}", regexMatch);
|
|
|
|
return finalString;
|
|
}
|
|
|
|
// Splices symbols and whitespace from the beginning and end of a string
|
|
// Using a for loop due to sequential ordering
|
|
function spliceSymbols(rawString, isSuffix) {
|
|
let offset = 0;
|
|
|
|
for (const ch of isSuffix ? rawString.split('').reverse() : rawString) {
|
|
if (ch.match(/[^\w.,?'!]/)) {
|
|
offset++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return isSuffix ? rawString.substring(0, rawString.length - offset) : rawString.substring(offset);;
|
|
}
|