|
|
import { characters, saveSettingsDebounced, substituteParams, substituteParamsExtended, this_chid } from '../../../script.js'; |
|
|
import { extension_settings, writeExtensionField } from '../../extensions.js'; |
|
|
import { getPresetManager } from '../../preset-manager.js'; |
|
|
import { regexFromString } from '../../utils.js'; |
|
|
import { lodash } from '../../../lib.js'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const SCRIPT_TYPES = { |
|
|
|
|
|
GLOBAL: 0, |
|
|
PRESET: 2, |
|
|
SCOPED: 1, |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const SCRIPT_TYPE_UNKNOWN = -1; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const DEFAULT_GET_REGEX_SCRIPTS_OPTIONS = Object.freeze({ allowedOnly: false }); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getRegexScripts(options = DEFAULT_GET_REGEX_SCRIPTS_OPTIONS) { |
|
|
return [...Object.values(SCRIPT_TYPES).flatMap(type => getScriptsByType(type, options))]; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getScriptsByType(scriptType, { allowedOnly } = DEFAULT_GET_REGEX_SCRIPTS_OPTIONS) { |
|
|
switch (scriptType) { |
|
|
case SCRIPT_TYPE_UNKNOWN: |
|
|
return []; |
|
|
case SCRIPT_TYPES.GLOBAL: |
|
|
return extension_settings.regex ?? []; |
|
|
case SCRIPT_TYPES.SCOPED: { |
|
|
if (allowedOnly && !extension_settings?.character_allowed_regex?.includes(characters?.[this_chid]?.avatar)) { |
|
|
return []; |
|
|
} |
|
|
const scopedScripts = characters[this_chid]?.data?.extensions?.regex_scripts; |
|
|
return Array.isArray(scopedScripts) ? scopedScripts : []; |
|
|
} |
|
|
case SCRIPT_TYPES.PRESET: { |
|
|
if (allowedOnly && !extension_settings?.preset_allowed_regex?.[getCurrentPresetAPI()]?.includes(getCurrentPresetName())) { |
|
|
return []; |
|
|
} |
|
|
const presetManager = getPresetManager(); |
|
|
const presetScripts = presetManager?.readPresetExtensionField({ path: 'regex_scripts' }); |
|
|
return Array.isArray(presetScripts) ? presetScripts : []; |
|
|
} |
|
|
default: |
|
|
console.warn(`getScriptsByType: Invalid script type ${scriptType}`); |
|
|
return []; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function saveScriptsByType(scripts, scriptType) { |
|
|
switch (scriptType) { |
|
|
case SCRIPT_TYPES.GLOBAL: |
|
|
extension_settings.regex = scripts; |
|
|
saveSettingsDebounced(); |
|
|
break; |
|
|
case SCRIPT_TYPES.SCOPED: |
|
|
await writeExtensionField(this_chid, 'regex_scripts', scripts); |
|
|
break; |
|
|
case SCRIPT_TYPES.PRESET: { |
|
|
const presetManager = getPresetManager(); |
|
|
await presetManager.writePresetExtensionField({ path: 'regex_scripts', value: scripts }); |
|
|
break; |
|
|
} |
|
|
default: |
|
|
console.warn(`saveScriptsByType: Invalid script type ${scriptType}`); |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function isScopedScriptsAllowed(character) { |
|
|
return !!extension_settings?.character_allowed_regex?.includes(character?.avatar); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function allowScopedScripts(character) { |
|
|
const avatar = character?.avatar; |
|
|
if (!avatar) { |
|
|
return; |
|
|
} |
|
|
if (!Array.isArray(extension_settings?.character_allowed_regex)) { |
|
|
extension_settings.character_allowed_regex = []; |
|
|
} |
|
|
if (!extension_settings.character_allowed_regex.includes(avatar)) { |
|
|
extension_settings.character_allowed_regex.push(avatar); |
|
|
saveSettingsDebounced(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function disallowScopedScripts(character) { |
|
|
const avatar = character?.avatar; |
|
|
if (!avatar) { |
|
|
return; |
|
|
} |
|
|
if (!Array.isArray(extension_settings?.character_allowed_regex)) { |
|
|
return; |
|
|
} |
|
|
const index = extension_settings.character_allowed_regex.indexOf(avatar); |
|
|
if (index !== -1) { |
|
|
extension_settings.character_allowed_regex.splice(index, 1); |
|
|
saveSettingsDebounced(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function isPresetScriptsAllowed(apiId, presetName) { |
|
|
if (!apiId || !presetName) { |
|
|
return false; |
|
|
} |
|
|
return !!extension_settings?.preset_allowed_regex?.[apiId]?.includes(presetName); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function allowPresetScripts(apiId, presetName) { |
|
|
if (!apiId || !presetName) { |
|
|
return; |
|
|
} |
|
|
if (!Array.isArray(extension_settings?.preset_allowed_regex?.[apiId])) { |
|
|
lodash.set(extension_settings, ['preset_allowed_regex', apiId], []); |
|
|
} |
|
|
if (!extension_settings.preset_allowed_regex[apiId].includes(presetName)) { |
|
|
extension_settings.preset_allowed_regex[apiId].push(presetName); |
|
|
saveSettingsDebounced(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function disallowPresetScripts(apiId, presetName) { |
|
|
if (!apiId || !presetName) { |
|
|
return; |
|
|
} |
|
|
if (!Array.isArray(extension_settings?.preset_allowed_regex?.[apiId])) { |
|
|
return; |
|
|
} |
|
|
const index = extension_settings.preset_allowed_regex[apiId].indexOf(presetName); |
|
|
if (index !== -1) { |
|
|
extension_settings.preset_allowed_regex[apiId].splice(index, 1); |
|
|
saveSettingsDebounced(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getCurrentPresetAPI() { |
|
|
return getPresetManager()?.apiId ?? null; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getCurrentPresetName() { |
|
|
return getPresetManager()?.getSelectedPresetName() ?? null; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const regex_placement = { |
|
|
|
|
|
|
|
|
|
|
|
MD_DISPLAY: 0, |
|
|
USER_INPUT: 1, |
|
|
AI_OUTPUT: 2, |
|
|
SLASH_COMMAND: 3, |
|
|
|
|
|
WORLD_INFO: 5, |
|
|
REASONING: 6, |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const substitute_find_regex = { |
|
|
NONE: 0, |
|
|
RAW: 1, |
|
|
ESCAPED: 2, |
|
|
}; |
|
|
|
|
|
function sanitizeRegexMacro(x) { |
|
|
return (x && typeof x === 'string') ? |
|
|
x.replaceAll(/[\n\r\t\v\f\0.^$*+?{}[\]\\/|()]/gs, function (s) { |
|
|
switch (s) { |
|
|
case '\n': |
|
|
return '\\n'; |
|
|
case '\r': |
|
|
return '\\r'; |
|
|
case '\t': |
|
|
return '\\t'; |
|
|
case '\v': |
|
|
return '\\v'; |
|
|
case '\f': |
|
|
return '\\f'; |
|
|
case '\0': |
|
|
return '\\0'; |
|
|
default: |
|
|
return '\\' + s; |
|
|
} |
|
|
}) : x; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function getRegexedString(rawString, placement, { characterOverride, isMarkdown, isPrompt, isEdit, depth } = {}) { |
|
|
|
|
|
if (typeof rawString !== 'string') { |
|
|
console.warn('getRegexedString: rawString is not a string. Returning empty string.'); |
|
|
return ''; |
|
|
} |
|
|
|
|
|
let finalString = rawString; |
|
|
if (extension_settings.disabledExtensions.includes('regex') || !rawString || placement === undefined) { |
|
|
return finalString; |
|
|
} |
|
|
|
|
|
const allRegex = getRegexScripts({ allowedOnly: true }); |
|
|
allRegex.forEach((script) => { |
|
|
if ( |
|
|
|
|
|
(script.markdownOnly && isMarkdown) || |
|
|
|
|
|
(script.promptOnly && isPrompt) || |
|
|
|
|
|
(!script.markdownOnly && !script.promptOnly && !isMarkdown && !isPrompt) |
|
|
) { |
|
|
if (isEdit && !script.runOnEdit) { |
|
|
console.debug(`getRegexedString: Skipping script ${script.scriptName} because it does not run on edit`); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
if (typeof depth === 'number') { |
|
|
if (!isNaN(script.minDepth) && script.minDepth !== null && script.minDepth >= -1 && depth < script.minDepth) { |
|
|
console.debug(`getRegexedString: Skipping script ${script.scriptName} because depth ${depth} is less than minDepth ${script.minDepth}`); |
|
|
return; |
|
|
} |
|
|
|
|
|
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 }); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
return finalString; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function runRegexScript(regexScript, rawString, { characterOverride } = {}) { |
|
|
let newString = rawString; |
|
|
if (!regexScript || !!(regexScript.disabled) || !regexScript?.findRegex || !rawString) { |
|
|
return newString; |
|
|
} |
|
|
|
|
|
const getRegexString = () => { |
|
|
switch (Number(regexScript.substituteRegex)) { |
|
|
case substitute_find_regex.NONE: |
|
|
return regexScript.findRegex; |
|
|
case substitute_find_regex.RAW: |
|
|
return substituteParamsExtended(regexScript.findRegex); |
|
|
case substitute_find_regex.ESCAPED: |
|
|
return substituteParamsExtended(regexScript.findRegex, {}, sanitizeRegexMacro); |
|
|
default: |
|
|
console.warn(`runRegexScript: Unknown substituteRegex value ${regexScript.substituteRegex}. Using raw regex.`); |
|
|
return regexScript.findRegex; |
|
|
} |
|
|
}; |
|
|
const regexString = getRegexString(); |
|
|
const findRegex = regexFromString(regexString); |
|
|
|
|
|
|
|
|
if (!findRegex) { |
|
|
return newString; |
|
|
} |
|
|
|
|
|
|
|
|
newString = rawString.replace(findRegex, function (match) { |
|
|
const args = [...arguments]; |
|
|
const replaceString = regexScript.replaceString.replace(/{{match}}/gi, '$0'); |
|
|
const replaceWithGroups = replaceString.replaceAll(/\$(\d+)|\$<([^>]+)>/g, (_, num, groupName) => { |
|
|
if (num) { |
|
|
|
|
|
match = args[Number(num)]; |
|
|
} else if (groupName) { |
|
|
|
|
|
const groups = args[args.length - 1]; |
|
|
match = groups && typeof groups === 'object' && groups[groupName]; |
|
|
} |
|
|
|
|
|
|
|
|
if (!match) { |
|
|
return ''; |
|
|
} |
|
|
|
|
|
|
|
|
const filteredMatch = filterString(match, regexScript.trimStrings, { characterOverride }); |
|
|
|
|
|
return filteredMatch; |
|
|
}); |
|
|
|
|
|
|
|
|
return substituteParams(replaceWithGroups); |
|
|
}); |
|
|
|
|
|
return newString; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function filterString(rawString, trimStrings, { characterOverride } = {}) { |
|
|
let finalString = rawString; |
|
|
trimStrings.forEach((trimString) => { |
|
|
const subTrimString = substituteParams(trimString, undefined, characterOverride); |
|
|
finalString = finalString.replaceAll(subTrimString, ''); |
|
|
}); |
|
|
|
|
|
return finalString; |
|
|
} |
|
|
|