mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Initial commit
This commit is contained in:
194
public/scripts/extensions/regex/engine.js
Normal file
194
public/scripts/extensions/regex/engine.js
Normal file
@ -0,0 +1,194 @@
|
||||
import { substituteParams } from "../../../script.js";
|
||||
import { extension_settings } from "../../extensions.js";
|
||||
export {
|
||||
regex_placement,
|
||||
getRegexedString,
|
||||
runRegexScript
|
||||
}
|
||||
|
||||
const regex_placement = {
|
||||
// MD Display is deprecated. Do not use.
|
||||
MD_DISPLAY: 0,
|
||||
USER_INPUT: 1,
|
||||
AI_OUTPUT: 2,
|
||||
SLASH_COMMAND: 3
|
||||
}
|
||||
|
||||
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, isMarkdown } = {}) {
|
||||
let finalString = rawString;
|
||||
if (extension_settings.disabledExtensions.includes("regex") || !rawString || placement === undefined) {
|
||||
return finalString;
|
||||
}
|
||||
|
||||
extension_settings.regex.forEach((script) => {
|
||||
if ((script.markdownOnly && !isMarkdown) || (!script.markdownOnly && isMarkdown)) {
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
// TODO: Maybe move the for loops into a separate function?
|
||||
if (replaceStrategy === regex_replace_strategy.OVERLAY) {
|
||||
const splitReplace = finalString.split("{{match}}");
|
||||
|
||||
// There's a prefix
|
||||
if (splitReplace[0]) {
|
||||
// Fetch the prefix
|
||||
const splicedPrefix = spliceSymbols(splitReplace[0], false);
|
||||
|
||||
// Sequentially remove all occurrences of prefix from start of split
|
||||
const splitMatch = overlaidMatch.split(splicedPrefix);
|
||||
let sliceNum = 0;
|
||||
for (let index = 0; index < splitMatch.length; index++) {
|
||||
if (splitMatch[index].length === 0) {
|
||||
sliceNum++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
overlaidMatch = splitMatch.slice(sliceNum, splitMatch.length).join(splicedPrefix);
|
||||
}
|
||||
|
||||
// There's a suffix
|
||||
if (splitReplace[1]) {
|
||||
// Fetch the suffix
|
||||
const splicedSuffix = spliceSymbols(splitReplace[1], true);
|
||||
|
||||
// Sequential removal of all suffix occurrences from end of split
|
||||
const splitMatch = overlaidMatch.split(splicedSuffix);
|
||||
let sliceNum = 0;
|
||||
for (let index = splitMatch.length - 1; index >= 0; index--) {
|
||||
if (splitMatch[index].length === 0) {
|
||||
sliceNum++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
overlaidMatch = splitMatch.slice(0, splitMatch.length - sliceNum).join(splicedSuffix);
|
||||
}
|
||||
}
|
||||
|
||||
// Only one match is replaced. This is by design
|
||||
finalString = finalString.replace("{{match}}", overlaidMatch) || finalString.replace("{{match}}", regexMatch);
|
||||
|
||||
return finalString;
|
||||
}
|
||||
|
||||
// Splices common sentence 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);
|
||||
}
|
Reference in New Issue
Block a user