2024-05-26 16:19:00 +02:00
|
|
|
import { callPopup, characters, eventSource, event_types, getCurrentChatId, reloadCurrentChat, saveSettingsDebounced, this_chid } from '../../../script.js';
|
|
|
|
import { extension_settings, renderExtensionTemplateAsync, writeExtensionField } from '../../extensions.js';
|
|
|
|
import { selected_group } from '../../group-chats.js';
|
2024-05-12 21:15:05 +02:00
|
|
|
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
|
|
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
2024-06-21 20:04:55 +02:00
|
|
|
import { enumIcons } from '../../slash-commands/SlashCommandCommonEnumsProvider.js';
|
|
|
|
import { SlashCommandEnumValue, enumTypes } from '../../slash-commands/SlashCommandEnumValue.js';
|
2024-05-12 21:15:05 +02:00
|
|
|
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
2024-02-10 20:57:22 +01:00
|
|
|
import { download, getFileText, getSortableDelay, uuidv4 } from '../../utils.js';
|
2023-12-02 19:04:51 +01:00
|
|
|
import { resolveVariable } from '../../variables.js';
|
|
|
|
import { regex_placement, runRegexScript } from './engine.js';
|
2023-07-20 19:32:15 +02:00
|
|
|
|
2024-06-17 07:04:10 +02:00
|
|
|
/**
|
|
|
|
* @typedef {object} RegexScript
|
|
|
|
* @property {string} scriptName - The name of the script
|
|
|
|
* @property {boolean} disabled - Whether the script is disabled
|
|
|
|
* @property {string} replaceString - The replace string
|
|
|
|
* @property {string[]} trimStrings - The trim strings
|
|
|
|
* @property {string?} findRegex - The find regex
|
|
|
|
* @property {string?} substituteRegex - The substitute regex
|
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieves the list of regex scripts by combining the scripts from the extension settings and the character data
|
|
|
|
*
|
|
|
|
* @return {RegexScript[]} An array of regex scripts, where each script is an object containing the necessary information.
|
|
|
|
*/
|
|
|
|
export function getRegexScripts() {
|
|
|
|
return [...(extension_settings.regex ?? []), ...(characters[this_chid]?.data?.extensions?.regex_scripts ?? [])];
|
|
|
|
}
|
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
/**
|
|
|
|
* Saves a regex script to the extension settings or character data.
|
|
|
|
* @param {import('../../char-data.js').RegexScriptData} regexScript
|
|
|
|
* @param {number} existingScriptIndex Index of the existing script
|
|
|
|
* @param {boolean} isScoped Is the script scoped to a character?
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
|
|
|
async function saveRegexScript(regexScript, existingScriptIndex, isScoped) {
|
2023-07-20 19:32:15 +02:00
|
|
|
// If not editing
|
2024-05-26 16:19:00 +02:00
|
|
|
const array = (isScoped ? characters[this_chid]?.data?.extensions?.regex_scripts : extension_settings.regex) ?? [];
|
|
|
|
|
|
|
|
// Assign a UUID if it doesn't exist
|
|
|
|
if (!regexScript.id) {
|
|
|
|
regexScript.id = uuidv4();
|
|
|
|
}
|
2023-07-20 19:32:15 +02:00
|
|
|
|
2023-07-25 05:21:54 +02:00
|
|
|
// Is the script name undefined or empty?
|
|
|
|
if (!regexScript.scriptName) {
|
2023-12-02 19:04:51 +01:00
|
|
|
toastr.error('Could not save regex script: The script name was undefined or empty!');
|
2023-07-25 05:21:54 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-07-20 19:32:15 +02:00
|
|
|
// Is a find regex present?
|
|
|
|
if (regexScript.findRegex.length === 0) {
|
2023-12-02 19:04:51 +01:00
|
|
|
toastr.warning('This regex script will not work, but was saved anyway: A find regex isn\'t present.');
|
2023-07-20 19:32:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Is there someplace to place results?
|
|
|
|
if (regexScript.placement.length === 0) {
|
2023-12-02 19:04:51 +01:00
|
|
|
toastr.warning('This regex script will not work, but was saved anyway: One "Affects" checkbox must be selected!');
|
2023-07-20 19:32:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (existingScriptIndex !== -1) {
|
2024-05-26 16:19:00 +02:00
|
|
|
array[existingScriptIndex] = regexScript;
|
2023-07-20 19:32:15 +02:00
|
|
|
} else {
|
2024-05-26 16:19:00 +02:00
|
|
|
array.push(regexScript);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isScoped) {
|
|
|
|
await writeExtensionField(this_chid, 'regex_scripts', array);
|
|
|
|
|
|
|
|
// Add the character to the allowed list
|
|
|
|
if (!extension_settings.character_allowed_regex.includes(characters[this_chid].avatar)) {
|
|
|
|
extension_settings.character_allowed_regex.push(characters[this_chid].avatar);
|
|
|
|
}
|
2023-07-20 19:32:15 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
saveSettingsDebounced();
|
|
|
|
await loadRegexScripts();
|
|
|
|
|
|
|
|
// Reload the current chat to undo previous markdown
|
|
|
|
const currentChatId = getCurrentChatId();
|
|
|
|
if (currentChatId !== undefined && currentChatId !== null) {
|
|
|
|
await reloadCurrentChat();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
async function deleteRegexScript({ id, isScoped }) {
|
|
|
|
const array = (isScoped ? characters[this_chid]?.data?.extensions?.regex_scripts : extension_settings.regex) ?? [];
|
2023-07-20 19:32:15 +02:00
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
const existingScriptIndex = array.findIndex((script) => script.id === id);
|
2023-07-20 19:32:15 +02:00
|
|
|
if (!existingScriptIndex || existingScriptIndex !== -1) {
|
2024-05-26 16:19:00 +02:00
|
|
|
array.splice(existingScriptIndex, 1);
|
|
|
|
|
|
|
|
if (isScoped) {
|
|
|
|
await writeExtensionField(this_chid, 'regex_scripts', array);
|
|
|
|
}
|
2023-07-20 19:32:15 +02:00
|
|
|
|
|
|
|
saveSettingsDebounced();
|
|
|
|
await loadRegexScripts();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function loadRegexScripts() {
|
2023-12-02 19:04:51 +01:00
|
|
|
$('#saved_regex_scripts').empty();
|
2024-05-26 16:19:00 +02:00
|
|
|
$('#saved_scoped_scripts').empty();
|
2023-07-20 19:32:15 +02:00
|
|
|
|
2024-04-11 22:38:44 +02:00
|
|
|
const scriptTemplate = $(await renderExtensionTemplateAsync('regex', 'scriptTemplate'));
|
2023-07-20 19:32:15 +02:00
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
/**
|
|
|
|
* Renders a script to the UI.
|
|
|
|
* @param {string} container Container to render the script to
|
|
|
|
* @param {import('../../char-data.js').RegexScriptData} script Script data
|
|
|
|
* @param {boolean} isScoped Script is scoped to a character
|
|
|
|
* @param {number} index Index of the script in the array
|
|
|
|
*/
|
|
|
|
function renderScript(container, script, isScoped, index) {
|
2023-07-20 19:32:15 +02:00
|
|
|
// Have to clone here
|
|
|
|
const scriptHtml = scriptTemplate.clone();
|
2024-05-26 16:19:00 +02:00
|
|
|
const save = () => saveRegexScript(script, index, isScoped);
|
|
|
|
|
|
|
|
if (!script.id) {
|
|
|
|
script.id = uuidv4();
|
|
|
|
}
|
|
|
|
|
|
|
|
scriptHtml.attr('id', script.id);
|
2023-07-20 19:32:15 +02:00
|
|
|
scriptHtml.find('.regex_script_name').text(script.scriptName);
|
2023-12-02 19:04:51 +01:00
|
|
|
scriptHtml.find('.disable_regex').prop('checked', script.disabled ?? false)
|
2024-05-26 16:19:00 +02:00
|
|
|
.on('input', async function () {
|
2023-12-02 19:04:51 +01:00
|
|
|
script.disabled = !!$(this).prop('checked');
|
2024-05-26 16:19:00 +02:00
|
|
|
await save();
|
2023-10-26 04:48:19 +02:00
|
|
|
});
|
2023-11-05 22:40:43 +01:00
|
|
|
scriptHtml.find('.regex-toggle-on').on('click', function () {
|
2023-12-02 19:04:51 +01:00
|
|
|
scriptHtml.find('.disable_regex').prop('checked', true).trigger('input');
|
2023-11-05 22:40:43 +01:00
|
|
|
});
|
|
|
|
scriptHtml.find('.regex-toggle-off').on('click', function () {
|
2023-12-02 19:04:51 +01:00
|
|
|
scriptHtml.find('.disable_regex').prop('checked', false).trigger('input');
|
2023-11-05 22:40:43 +01:00
|
|
|
});
|
|
|
|
scriptHtml.find('.edit_existing_regex').on('click', async function () {
|
2024-05-26 16:19:00 +02:00
|
|
|
await onRegexEditorOpenClick(scriptHtml.attr('id'), isScoped);
|
|
|
|
});
|
|
|
|
scriptHtml.find('.move_to_global').on('click', async function () {
|
|
|
|
const confirm = await callPopup('Are you sure you want to move this regex script to global?', 'confirm');
|
|
|
|
|
|
|
|
if (!confirm) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await deleteRegexScript({ id: script.id, isScoped: true });
|
|
|
|
await saveRegexScript(script, -1, false);
|
|
|
|
});
|
|
|
|
scriptHtml.find('.move_to_scoped').on('click', async function () {
|
|
|
|
if (this_chid === undefined) {
|
|
|
|
toastr.error('No character selected.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selected_group) {
|
|
|
|
toastr.error('Cannot edit scoped scripts in group chats.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const confirm = await callPopup('Are you sure you want to move this regex script to scoped?', 'confirm');
|
|
|
|
|
|
|
|
if (!confirm) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
await deleteRegexScript({ id: script.id, isScoped: false });
|
|
|
|
await saveRegexScript(script, -1, true);
|
2023-07-20 19:32:15 +02:00
|
|
|
});
|
2024-02-10 20:57:22 +01:00
|
|
|
scriptHtml.find('.export_regex').on('click', async function () {
|
2024-04-26 18:05:10 +02:00
|
|
|
const fileName = `${script.scriptName.replace(/[\s.<>:"/\\|?*\x00-\x1F\x7F]/g, '_').toLowerCase()}.json`;
|
2024-02-10 20:57:22 +01:00
|
|
|
const fileData = JSON.stringify(script, null, 4);
|
|
|
|
download(fileData, fileName, 'application/json');
|
|
|
|
});
|
2023-11-05 22:40:43 +01:00
|
|
|
scriptHtml.find('.delete_regex').on('click', async function () {
|
2023-12-02 19:04:51 +01:00
|
|
|
const confirm = await callPopup('Are you sure you want to delete this regex script?', 'confirm');
|
2023-11-05 22:44:28 +01:00
|
|
|
|
|
|
|
if (!confirm) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
await deleteRegexScript({ id: script.id, isScoped });
|
|
|
|
await reloadCurrentChat();
|
2023-07-20 19:32:15 +02:00
|
|
|
});
|
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
$(container).append(scriptHtml);
|
|
|
|
}
|
|
|
|
|
|
|
|
extension_settings?.regex?.forEach((script, index, array) => renderScript('#saved_regex_scripts', script, false, index, array));
|
|
|
|
characters[this_chid]?.data?.extensions?.regex_scripts?.forEach((script, index, array) => renderScript('#saved_scoped_scripts', script, true, index, array));
|
|
|
|
|
|
|
|
const isAllowed = extension_settings?.character_allowed_regex?.includes(characters?.[this_chid]?.avatar);
|
|
|
|
$('#regex_scoped_toggle').prop('checked', isAllowed);
|
2023-07-20 19:32:15 +02:00
|
|
|
}
|
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
/**
|
|
|
|
* Opens the regex editor.
|
|
|
|
* @param {string|boolean} existingId Existing ID
|
|
|
|
* @param {boolean} isScoped Is the script scoped to a character?
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
|
|
|
async function onRegexEditorOpenClick(existingId, isScoped) {
|
2024-04-11 22:38:44 +02:00
|
|
|
const editorHtml = $(await renderExtensionTemplateAsync('regex', 'editor'));
|
2024-05-26 16:19:00 +02:00
|
|
|
const array = (isScoped ? characters[this_chid]?.data?.extensions?.regex_scripts : extension_settings.regex) ?? [];
|
2023-07-20 19:32:15 +02:00
|
|
|
|
|
|
|
// If an ID exists, fill in all the values
|
|
|
|
let existingScriptIndex = -1;
|
|
|
|
if (existingId) {
|
2024-05-26 16:19:00 +02:00
|
|
|
existingScriptIndex = array.findIndex((script) => script.id === existingId);
|
2023-07-20 19:32:15 +02:00
|
|
|
if (existingScriptIndex !== -1) {
|
2024-05-26 16:19:00 +02:00
|
|
|
const existingScript = array[existingScriptIndex];
|
2023-07-20 19:32:15 +02:00
|
|
|
if (existingScript.scriptName) {
|
2023-12-02 19:04:51 +01:00
|
|
|
editorHtml.find('.regex_script_name').val(existingScript.scriptName);
|
2023-07-20 19:32:15 +02:00
|
|
|
} else {
|
2023-12-02 20:11:06 +01:00
|
|
|
toastr.error('This script doesn\'t have a name! Please delete it.');
|
2023-07-20 19:32:15 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-12-02 19:04:51 +01:00
|
|
|
editorHtml.find('.find_regex').val(existingScript.findRegex || '');
|
|
|
|
editorHtml.find('.regex_replace_string').val(existingScript.replaceString || '');
|
|
|
|
editorHtml.find('.regex_trim_strings').val(existingScript.trimStrings?.join('\n') || []);
|
2024-01-24 21:48:58 +01:00
|
|
|
editorHtml.find('input[name="disabled"]').prop('checked', existingScript.disabled ?? false);
|
|
|
|
editorHtml.find('input[name="only_format_display"]').prop('checked', existingScript.markdownOnly ?? false);
|
|
|
|
editorHtml.find('input[name="only_format_prompt"]').prop('checked', existingScript.promptOnly ?? false);
|
|
|
|
editorHtml.find('input[name="run_on_edit"]').prop('checked', existingScript.runOnEdit ?? false);
|
|
|
|
editorHtml.find('input[name="substitute_regex"]').prop('checked', existingScript.substituteRegex ?? false);
|
|
|
|
editorHtml.find('input[name="min_depth"]').val(existingScript.minDepth ?? '');
|
|
|
|
editorHtml.find('input[name="max_depth"]').val(existingScript.maxDepth ?? '');
|
2023-07-20 19:32:15 +02:00
|
|
|
|
|
|
|
existingScript.placement.forEach((element) => {
|
|
|
|
editorHtml
|
|
|
|
.find(`input[name="replace_position"][value="${element}"]`)
|
2023-12-02 19:04:51 +01:00
|
|
|
.prop('checked', true);
|
2023-07-20 19:32:15 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
editorHtml
|
2023-12-02 19:04:51 +01:00
|
|
|
.find('input[name="only_format_display"]')
|
|
|
|
.prop('checked', true);
|
2023-07-20 19:32:15 +02:00
|
|
|
|
|
|
|
editorHtml
|
2023-12-02 19:04:51 +01:00
|
|
|
.find('input[name="run_on_edit"]')
|
|
|
|
.prop('checked', true);
|
2023-07-20 19:32:15 +02:00
|
|
|
|
|
|
|
editorHtml
|
2023-12-02 19:04:51 +01:00
|
|
|
.find('input[name="replace_position"][value="1"]')
|
|
|
|
.prop('checked', true);
|
2023-07-20 19:32:15 +02:00
|
|
|
}
|
|
|
|
|
2024-01-04 03:34:38 +01:00
|
|
|
editorHtml.find('#regex_test_mode_toggle').on('click', function () {
|
|
|
|
editorHtml.find('#regex_test_mode').toggleClass('displayNone');
|
|
|
|
updateTestResult();
|
|
|
|
});
|
|
|
|
|
|
|
|
function updateTestResult() {
|
|
|
|
if (!editorHtml.find('#regex_test_mode').is(':visible')) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const testScript = {
|
2024-05-26 16:19:00 +02:00
|
|
|
id: uuidv4(),
|
2024-01-04 03:34:38 +01:00
|
|
|
scriptName: editorHtml.find('.regex_script_name').val(),
|
|
|
|
findRegex: editorHtml.find('.find_regex').val(),
|
|
|
|
replaceString: editorHtml.find('.regex_replace_string').val(),
|
|
|
|
trimStrings: String(editorHtml.find('.regex_trim_strings').val()).split('\n').filter((e) => e.length !== 0) || [],
|
|
|
|
substituteRegex: editorHtml.find('input[name="substitute_regex"]').prop('checked'),
|
|
|
|
};
|
|
|
|
const rawTestString = String(editorHtml.find('#regex_test_input').val());
|
|
|
|
const result = runRegexScript(testScript, rawTestString);
|
|
|
|
editorHtml.find('#regex_test_output').text(result);
|
|
|
|
}
|
|
|
|
|
|
|
|
editorHtml.find('input, textarea, select').on('input', updateTestResult);
|
|
|
|
|
2023-12-02 19:04:51 +01:00
|
|
|
const popupResult = await callPopup(editorHtml, 'confirm', undefined, { okButton: 'Save' });
|
2023-07-20 19:32:15 +02:00
|
|
|
if (popupResult) {
|
|
|
|
const newRegexScript = {
|
2024-05-26 16:19:00 +02:00
|
|
|
id: existingId ? String(existingId) : uuidv4(),
|
|
|
|
scriptName: String(editorHtml.find('.regex_script_name').val()),
|
|
|
|
findRegex: String(editorHtml.find('.find_regex').val()),
|
|
|
|
replaceString: String(editorHtml.find('.regex_replace_string').val()),
|
2023-12-02 19:04:51 +01:00
|
|
|
trimStrings: editorHtml.find('.regex_trim_strings').val().split('\n').filter((e) => e.length !== 0) || [],
|
2023-07-20 19:32:15 +02:00
|
|
|
placement:
|
|
|
|
editorHtml
|
2023-12-02 19:04:51 +01:00
|
|
|
.find('input[name="replace_position"]')
|
|
|
|
.filter(':checked')
|
2023-12-02 20:11:06 +01:00
|
|
|
.map(function () { return parseInt($(this).val()); })
|
2023-07-20 19:32:15 +02:00
|
|
|
.get()
|
2023-12-02 15:19:35 +01:00
|
|
|
.filter((e) => !isNaN(e)) || [],
|
2024-01-24 21:48:58 +01:00
|
|
|
disabled: editorHtml.find('input[name="disabled"]').prop('checked'),
|
|
|
|
markdownOnly: editorHtml.find('input[name="only_format_display"]').prop('checked'),
|
|
|
|
promptOnly: editorHtml.find('input[name="only_format_prompt"]').prop('checked'),
|
|
|
|
runOnEdit: editorHtml.find('input[name="run_on_edit"]').prop('checked'),
|
|
|
|
substituteRegex: editorHtml.find('input[name="substitute_regex"]').prop('checked'),
|
|
|
|
minDepth: parseInt(String(editorHtml.find('input[name="min_depth"]').val())),
|
|
|
|
maxDepth: parseInt(String(editorHtml.find('input[name="max_depth"]').val())),
|
2023-07-20 19:32:15 +02:00
|
|
|
};
|
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
saveRegexScript(newRegexScript, existingScriptIndex, isScoped);
|
2023-07-20 19:32:15 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Common settings migration function. Some parts will eventually be removed
|
|
|
|
// TODO: Maybe migrate placement to strings?
|
|
|
|
function migrateSettings() {
|
|
|
|
let performSave = false;
|
|
|
|
|
|
|
|
// Current: If MD Display is present in placement, remove it and add new placements/MD option
|
|
|
|
extension_settings.regex.forEach((script) => {
|
2024-05-26 16:19:00 +02:00
|
|
|
if (!script.id) {
|
|
|
|
script.id = uuidv4();
|
|
|
|
performSave = true;
|
|
|
|
}
|
|
|
|
|
2023-07-20 19:32:15 +02:00
|
|
|
if (script.placement.includes(regex_placement.MD_DISPLAY)) {
|
|
|
|
script.placement = script.placement.length === 1 ?
|
|
|
|
Object.values(regex_placement).filter((e) => e !== regex_placement.MD_DISPLAY) :
|
|
|
|
script.placement = script.placement.filter((e) => e !== regex_placement.MD_DISPLAY);
|
|
|
|
|
2023-12-02 20:11:06 +01:00
|
|
|
script.markdownOnly = true;
|
|
|
|
script.promptOnly = true;
|
2023-07-20 19:32:15 +02:00
|
|
|
|
|
|
|
performSave = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Old system and sendas placement migration
|
|
|
|
// 4 - sendAs
|
|
|
|
if (script.placement.includes(4)) {
|
|
|
|
script.placement = script.placement.length === 1 ?
|
|
|
|
[regex_placement.SLASH_COMMAND] :
|
|
|
|
script.placement = script.placement.filter((e) => e !== 4);
|
|
|
|
|
|
|
|
performSave = true;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
if (!extension_settings.character_allowed_regex) {
|
|
|
|
extension_settings.character_allowed_regex = [];
|
|
|
|
performSave = true;
|
|
|
|
}
|
|
|
|
|
2023-07-20 19:32:15 +02:00
|
|
|
if (performSave) {
|
|
|
|
saveSettingsDebounced();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-30 21:59:04 +01:00
|
|
|
/**
|
|
|
|
* /regex slash command callback
|
|
|
|
* @param {object} args Named arguments
|
|
|
|
* @param {string} value Unnamed argument
|
|
|
|
* @returns {string} The regexed string
|
|
|
|
*/
|
|
|
|
function runRegexCallback(args, value) {
|
|
|
|
if (!args.name) {
|
2023-12-02 19:04:51 +01:00
|
|
|
toastr.warning('No regex script name provided.');
|
2023-11-30 21:59:04 +01:00
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
const scriptName = String(resolveVariable(args.name));
|
2024-06-17 07:04:10 +02:00
|
|
|
const scripts = getRegexScripts();
|
2023-11-30 21:59:04 +01:00
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
for (const script of scripts) {
|
2023-11-30 21:59:04 +01:00
|
|
|
if (String(script.scriptName).toLowerCase() === String(scriptName).toLowerCase()) {
|
|
|
|
if (script.disabled) {
|
|
|
|
toastr.warning(`Regex script "${scriptName}" is disabled.`);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.debug(`Running regex callback for ${scriptName}`);
|
|
|
|
return runRegexScript(script, value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
toastr.warning(`Regex script "${scriptName}" not found.`);
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
2024-02-10 20:57:22 +01:00
|
|
|
/**
|
|
|
|
* Performs the import of the regex file.
|
|
|
|
* @param {File} file Input file
|
2024-05-26 16:19:00 +02:00
|
|
|
* @param {boolean} isScoped Is the script scoped to a character?
|
2024-02-10 20:57:22 +01:00
|
|
|
*/
|
2024-05-26 16:19:00 +02:00
|
|
|
async function onRegexImportFileChange(file, isScoped) {
|
2024-02-10 20:57:22 +01:00
|
|
|
if (!file) {
|
|
|
|
toastr.error('No file provided.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
const fileText = await getFileText(file);
|
|
|
|
const regexScript = JSON.parse(fileText);
|
|
|
|
if (!regexScript.scriptName) {
|
|
|
|
throw new Error('No script name provided.');
|
|
|
|
}
|
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
// Assign a new UUID
|
|
|
|
regexScript.id = uuidv4();
|
|
|
|
|
|
|
|
const array = (isScoped ? characters[this_chid]?.data?.extensions?.regex_scripts : extension_settings.regex) ?? [];
|
|
|
|
array.push(regexScript);
|
|
|
|
|
|
|
|
if (isScoped) {
|
|
|
|
await writeExtensionField(this_chid, 'regex_scripts', array);
|
|
|
|
}
|
2024-02-10 20:57:22 +01:00
|
|
|
|
|
|
|
saveSettingsDebounced();
|
|
|
|
await loadRegexScripts();
|
|
|
|
toastr.success(`Regex script "${regexScript.scriptName}" imported.`);
|
|
|
|
} catch (error) {
|
|
|
|
console.log(error);
|
|
|
|
toastr.error('Invalid JSON file.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
function purgeEmbeddedRegexScripts( { character }){
|
|
|
|
const avatar = character?.avatar;
|
|
|
|
|
|
|
|
if (avatar && extension_settings.character_allowed_regex?.includes(avatar)) {
|
|
|
|
const index = extension_settings.character_allowed_regex.indexOf(avatar);
|
|
|
|
if (index !== -1) {
|
|
|
|
extension_settings.character_allowed_regex.splice(index, 1);
|
|
|
|
saveSettingsDebounced();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function checkEmbeddedRegexScripts() {
|
|
|
|
const chid = this_chid;
|
|
|
|
|
|
|
|
if (chid !== undefined && !selected_group) {
|
|
|
|
const avatar = characters[chid]?.avatar;
|
|
|
|
const scripts = characters[chid]?.data?.extensions?.regex_scripts;
|
|
|
|
|
|
|
|
if (Array.isArray(scripts) && scripts.length > 0) {
|
|
|
|
if (avatar && !extension_settings.character_allowed_regex.includes(avatar)) {
|
|
|
|
const checkKey = `AlertRegex_${characters[chid].avatar}`;
|
|
|
|
|
|
|
|
if (!localStorage.getItem(checkKey)) {
|
|
|
|
localStorage.setItem(checkKey, 'true');
|
|
|
|
const template = await renderExtensionTemplateAsync('regex', 'embeddedScripts', {});
|
|
|
|
const result = await callPopup(template, 'confirm', '', { okButton: 'Yes' });
|
|
|
|
|
|
|
|
if (result) {
|
|
|
|
extension_settings.character_allowed_regex.push(avatar);
|
|
|
|
await reloadCurrentChat();
|
|
|
|
saveSettingsDebounced();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
loadRegexScripts();
|
|
|
|
}
|
|
|
|
|
2023-07-20 19:32:15 +02:00
|
|
|
// Workaround for loading in sequence with other extensions
|
|
|
|
// NOTE: Always puts extension at the top of the list, but this is fine since it's static
|
|
|
|
jQuery(async () => {
|
|
|
|
if (extension_settings.regex) {
|
|
|
|
migrateSettings();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Manually disable the extension since static imports auto-import the JS file
|
2023-12-02 19:04:51 +01:00
|
|
|
if (extension_settings.disabledExtensions.includes('regex')) {
|
2023-07-20 19:32:15 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-04-11 22:38:44 +02:00
|
|
|
const settingsHtml = $(await renderExtensionTemplateAsync('regex', 'dropdown'));
|
2023-12-02 19:04:51 +01:00
|
|
|
$('#extensions_settings2').append(settingsHtml);
|
|
|
|
$('#open_regex_editor').on('click', function () {
|
2024-05-26 16:19:00 +02:00
|
|
|
onRegexEditorOpenClick(false, false);
|
|
|
|
});
|
|
|
|
$('#open_scoped_editor').on('click', function () {
|
|
|
|
if (this_chid === undefined) {
|
|
|
|
toastr.error('No character selected.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selected_group) {
|
|
|
|
toastr.error('Cannot edit scoped scripts in group chats.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
onRegexEditorOpenClick(false, true);
|
2024-02-10 20:57:22 +01:00
|
|
|
});
|
|
|
|
$('#import_regex_file').on('change', async function () {
|
2024-05-26 16:19:00 +02:00
|
|
|
let target = 'global';
|
|
|
|
const template = $(await renderExtensionTemplateAsync('regex', 'importTarget'));
|
|
|
|
template.find('#regex_import_target_global').on('input', () => target = 'global');
|
|
|
|
template.find('#regex_import_target_scoped').on('input', () => target = 'scoped');
|
|
|
|
|
|
|
|
await callPopup(template, 'text');
|
|
|
|
|
2024-02-10 20:57:22 +01:00
|
|
|
const inputElement = this instanceof HTMLInputElement && this;
|
2024-05-17 00:06:00 +02:00
|
|
|
for (const file of inputElement.files) {
|
2024-05-26 16:19:00 +02:00
|
|
|
await onRegexImportFileChange(file, target === 'scoped');
|
2024-05-17 00:06:00 +02:00
|
|
|
}
|
2024-02-10 20:57:22 +01:00
|
|
|
inputElement.value = '';
|
|
|
|
});
|
|
|
|
$('#import_regex').on('click', function () {
|
|
|
|
$('#import_regex_file').trigger('click');
|
2023-07-20 19:32:15 +02:00
|
|
|
});
|
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
let sortableDatas = [
|
|
|
|
{
|
|
|
|
selector: '#saved_regex_scripts',
|
|
|
|
setter: x => extension_settings.regex = x,
|
|
|
|
getter: () => extension_settings.regex ?? [],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
selector: '#saved_scoped_scripts',
|
|
|
|
setter: x => writeExtensionField(this_chid, 'regex_scripts', x),
|
|
|
|
getter: () => characters[this_chid]?.data?.extensions?.regex_scripts ?? [],
|
|
|
|
},
|
|
|
|
];
|
|
|
|
for (const { selector, setter, getter } of sortableDatas) {
|
|
|
|
$(selector).sortable({
|
|
|
|
delay: getSortableDelay(),
|
|
|
|
stop: async function () {
|
|
|
|
const oldScripts = getter();
|
|
|
|
const newScripts = [];
|
|
|
|
$(selector).children().each(function () {
|
|
|
|
const id = $(this).attr('id');
|
|
|
|
const existingScript = oldScripts.find((e) => e.id === id);
|
|
|
|
if (existingScript) {
|
|
|
|
newScripts.push(existingScript);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
await setter(newScripts);
|
|
|
|
saveSettingsDebounced();
|
2023-07-20 19:32:15 +02:00
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
console.debug(`Regex scripts in ${selector} reordered`);
|
|
|
|
await loadRegexScripts();
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
2023-07-20 19:32:15 +02:00
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
$('#regex_scoped_toggle').on('input', function () {
|
|
|
|
if (this_chid === undefined) {
|
|
|
|
toastr.error('No character selected.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (selected_group) {
|
|
|
|
toastr.error('Cannot edit scoped scripts in group chats.');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const isEnable = !!$(this).prop('checked');
|
|
|
|
const avatar = characters[this_chid].avatar;
|
|
|
|
|
|
|
|
if (isEnable) {
|
|
|
|
if (!extension_settings.character_allowed_regex.includes(avatar)) {
|
|
|
|
extension_settings.character_allowed_regex.push(avatar);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const index = extension_settings.character_allowed_regex.indexOf(avatar);
|
|
|
|
if (index !== -1) {
|
|
|
|
extension_settings.character_allowed_regex.splice(index, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
saveSettingsDebounced();
|
|
|
|
reloadCurrentChat();
|
2023-07-20 19:32:15 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
await loadRegexScripts();
|
2023-12-02 19:04:51 +01:00
|
|
|
$('#saved_regex_scripts').sortable('enable');
|
2023-11-30 21:59:04 +01:00
|
|
|
|
2024-06-21 20:04:55 +02:00
|
|
|
const localEnumProviders = {
|
|
|
|
regexScripts: () => getRegexScripts().map(script => {
|
|
|
|
const isGlobal = extension_settings.regex?.some(x => x.scriptName === script.scriptName);
|
|
|
|
return new SlashCommandEnumValue(script.scriptName, `${enumIcons.getStateIcon(!script.disabled)} [${isGlobal ? 'global' : 'scoped'}] ${script.findRegex}`,
|
|
|
|
isGlobal ? enumTypes.enum : enumTypes.name, isGlobal ? 'G' : 'S');
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
|
|
|
name: 'regex',
|
2024-05-12 21:15:05 +02:00
|
|
|
callback: runRegexCallback,
|
|
|
|
returns: 'replaced text',
|
|
|
|
namedArgumentList: [
|
2024-06-17 07:04:10 +02:00
|
|
|
SlashCommandNamedArgument.fromProps({
|
|
|
|
name: 'name',
|
|
|
|
description: 'script name',
|
|
|
|
typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.VARIABLE_NAME],
|
|
|
|
isRequired: true,
|
2024-06-21 20:04:55 +02:00
|
|
|
enumProvider: localEnumProviders.regexScripts,
|
2024-06-17 07:04:10 +02:00
|
|
|
}),
|
2024-05-12 21:15:05 +02:00
|
|
|
],
|
|
|
|
unnamedArgumentList: [
|
|
|
|
new SlashCommandArgument(
|
|
|
|
'input', [ARGUMENT_TYPE.STRING], false,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
helpString: 'Runs a Regex extension script by name on the provided string. The script must be enabled.',
|
|
|
|
}));
|
|
|
|
|
2024-05-26 16:19:00 +02:00
|
|
|
eventSource.on(event_types.CHAT_CHANGED, checkEmbeddedRegexScripts);
|
|
|
|
eventSource.on(event_types.CHARACTER_DELETED, purgeEmbeddedRegexScripts);
|
2023-07-20 19:32:15 +02:00
|
|
|
});
|