import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from '../script.js'; import { extension_settings, saveMetadataDebounced } from './extensions.js'; import { executeSlashCommandsWithOptions } from './slash-commands.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js'; import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js'; import { SlashCommandBreakController } from './slash-commands/SlashCommandBreakController.js'; import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; import { SlashCommandClosureResult } from './slash-commands/SlashCommandClosureResult.js'; import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; import { PARSER_FLAG, SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { SlashCommandScope } from './slash-commands/SlashCommandScope.js'; import { isFalseBoolean } from './utils.js'; /** @typedef {import('./slash-commands/SlashCommandParser.js').NamedArguments} NamedArguments */ /** @typedef {import('./slash-commands/SlashCommand.js').UnnamedArguments} UnnamedArguments */ const MAX_LOOPS = 100; function getLocalVariable(name, args = {}) { if (!chat_metadata.variables) { chat_metadata.variables = {}; } let localVariable = chat_metadata?.variables[args.key ?? name]; if (args.index !== undefined) { try { localVariable = JSON.parse(localVariable); const numIndex = Number(args.index); if (Number.isNaN(numIndex)) { localVariable = localVariable[args.index]; } else { localVariable = localVariable[Number(args.index)]; } if (typeof localVariable == 'object') { localVariable = JSON.stringify(localVariable); } } catch { // that didn't work } } return (localVariable?.trim?.() === '' || isNaN(Number(localVariable))) ? (localVariable || '') : Number(localVariable); } function setLocalVariable(name, value, args = {}) { if (!chat_metadata.variables) { chat_metadata.variables = {}; } if (args.index !== undefined) { try { let localVariable = JSON.parse(chat_metadata.variables[name] ?? 'null'); const numIndex = Number(args.index); if (Number.isNaN(numIndex)) { if (localVariable === null) { localVariable = {}; } localVariable[args.index] = value; } else { if (localVariable === null) { localVariable = []; } localVariable[numIndex] = value; } chat_metadata.variables[name] = JSON.stringify(localVariable); } catch { // that didn't work } } else { chat_metadata.variables[name] = value; } saveMetadataDebounced(); return value; } function getGlobalVariable(name, args = {}) { let globalVariable = extension_settings.variables.global[args.key ?? name]; if (args.index !== undefined) { try { globalVariable = JSON.parse(globalVariable); const numIndex = Number(args.index); if (Number.isNaN(numIndex)) { globalVariable = globalVariable[args.index]; } else { globalVariable = globalVariable[Number(args.index)]; } if (typeof globalVariable == 'object') { globalVariable = JSON.stringify(globalVariable); } } catch { // that didn't work } } return (globalVariable?.trim?.() === '' || isNaN(Number(globalVariable))) ? (globalVariable || '') : Number(globalVariable); } function setGlobalVariable(name, value, args = {}) { if (args.index !== undefined) { try { let globalVariable = JSON.parse(extension_settings.variables.global[name] ?? 'null'); const numIndex = Number(args.index); if (Number.isNaN(numIndex)) { if (globalVariable === null) { globalVariable = {}; } globalVariable[args.index] = value; } else { if (globalVariable === null) { globalVariable = []; } globalVariable[numIndex] = value; } extension_settings.variables.global[name] = JSON.stringify(globalVariable); } catch { // that didn't work } } else { extension_settings.variables.global[name] = value; } saveSettingsDebounced(); return value; } function addLocalVariable(name, value) { const currentValue = getLocalVariable(name) || 0; try { const parsedValue = JSON.parse(currentValue); if (Array.isArray(parsedValue)) { parsedValue.push(value); setLocalVariable(name, JSON.stringify(parsedValue)); return parsedValue; } } catch { // ignore non-array values } const increment = Number(value); if (isNaN(increment) || isNaN(Number(currentValue))) { const stringValue = String(currentValue || '') + value; setLocalVariable(name, stringValue); return stringValue; } const newValue = Number(currentValue) + increment; if (isNaN(newValue)) { return ''; } setLocalVariable(name, newValue); return newValue; } function addGlobalVariable(name, value) { const currentValue = getGlobalVariable(name) || 0; try { const parsedValue = JSON.parse(currentValue); if (Array.isArray(parsedValue)) { parsedValue.push(value); setGlobalVariable(name, JSON.stringify(parsedValue)); return parsedValue; } } catch { // ignore non-array values } const increment = Number(value); if (isNaN(increment) || isNaN(Number(currentValue))) { const stringValue = String(currentValue || '') + value; setGlobalVariable(name, stringValue); return stringValue; } const newValue = Number(currentValue) + increment; if (isNaN(newValue)) { return ''; } setGlobalVariable(name, newValue); return newValue; } function incrementLocalVariable(name) { return addLocalVariable(name, 1); } function incrementGlobalVariable(name) { return addGlobalVariable(name, 1); } function decrementLocalVariable(name) { return addLocalVariable(name, -1); } function decrementGlobalVariable(name) { return addGlobalVariable(name, -1); } /** * Resolves a variable name to its value or returns the string as is if the variable does not exist. * @param {string} name Variable name * @param {SlashCommandScope} scope Scope * @returns {string} Variable value or the string literal */ export function resolveVariable(name, scope = null) { if (scope?.existsVariable(name)) { return scope.getVariable(name); } if (existsLocalVariable(name)) { return getLocalVariable(name); } if (existsGlobalVariable(name)) { return getGlobalVariable(name); } return name; } export function replaceVariableMacros(input) { const lines = input.split('\n'); for (let i = 0; i < lines.length; i++) { let line = lines[i]; // Skip lines without macros if (!line || !line.includes('{{')) { continue; } // Replace {{getvar::name}} with the value of the variable name line = line.replace(/{{getvar::([^}]+)}}/gi, (_, name) => { name = name.trim(); return getLocalVariable(name); }); // Replace {{setvar::name::value}} with empty string and set the variable name to value line = line.replace(/{{setvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { name = name.trim(); setLocalVariable(name, value); return ''; }); // Replace {{addvar::name::value}} with empty string and add value to the variable value line = line.replace(/{{addvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { name = name.trim(); addLocalVariable(name, value); return ''; }); // Replace {{incvar::name}} with empty string and increment the variable name by 1 line = line.replace(/{{incvar::([^}]+)}}/gi, (_, name) => { name = name.trim(); return incrementLocalVariable(name); }); // Replace {{decvar::name}} with empty string and decrement the variable name by 1 line = line.replace(/{{decvar::([^}]+)}}/gi, (_, name) => { name = name.trim(); return decrementLocalVariable(name); }); // Replace {{getglobalvar::name}} with the value of the global variable name line = line.replace(/{{getglobalvar::([^}]+)}}/gi, (_, name) => { name = name.trim(); return getGlobalVariable(name); }); // Replace {{setglobalvar::name::value}} with empty string and set the global variable name to value line = line.replace(/{{setglobalvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { name = name.trim(); setGlobalVariable(name, value); return ''; }); // Replace {{addglobalvar::name::value}} with empty string and add value to the global variable value line = line.replace(/{{addglobalvar::([^:]+)::([^}]+)}}/gi, (_, name, value) => { name = name.trim(); addGlobalVariable(name, value); return ''; }); // Replace {{incglobalvar::name}} with empty string and increment the global variable name by 1 line = line.replace(/{{incglobalvar::([^}]+)}}/gi, (_, name) => { name = name.trim(); return incrementGlobalVariable(name); }); // Replace {{decglobalvar::name}} with empty string and decrement the global variable name by 1 line = line.replace(/{{decglobalvar::([^}]+)}}/gi, (_, name) => { name = name.trim(); return decrementGlobalVariable(name); }); lines[i] = line; } return lines.join('\n'); } function listVariablesCallback() { if (!chat_metadata.variables) { chat_metadata.variables = {}; } const localVariables = Object.entries(chat_metadata.variables).map(([name, value]) => `${name}: ${value}`); const globalVariables = Object.entries(extension_settings.variables.global).map(([name, value]) => `${name}: ${value}`); const localVariablesString = localVariables.length > 0 ? localVariables.join('\n\n') : 'No local variables'; const globalVariablesString = globalVariables.length > 0 ? globalVariables.join('\n\n') : 'No global variables'; const chatName = getCurrentChatId(); const converter = new showdown.Converter(); const message = `### Local variables (${chatName}):\n${localVariablesString}\n\n### Global variables:\n${globalVariablesString}`; const htmlMessage = DOMPurify.sanitize(converter.makeHtml(message)); sendSystemMessage(system_message_types.GENERIC, htmlMessage); return ''; } /** * * @param {NamedArguments} args * @param {(string|SlashCommandClosure)[]} value */ async function whileCallback(args, value) { if (args.guard instanceof SlashCommandClosure) throw new Error('argument \'guard\' cannot be a closure for command /while'); const isGuardOff = isFalseBoolean(args.guard); const iterations = isGuardOff ? Number.MAX_SAFE_INTEGER : MAX_LOOPS; /**@type {string|SlashCommandClosure} */ let command; if (value) { if (value[0] instanceof SlashCommandClosure) { command = value[0]; } else { command = value.join(' '); } } let commandResult; for (let i = 0; i < iterations; i++) { const { a, b, rule } = parseBooleanOperands(args); const result = evalBoolean(rule, a, b); if (result && command) { if (command instanceof SlashCommandClosure) { command.breakController = new SlashCommandBreakController(); commandResult = await command.execute(); } else { commandResult = await executeSubCommands(command, args._scope, args._parserFlags, args._abortController); } if (commandResult.isAborted) break; if (commandResult.isBreak) break; } else { break; } } if (commandResult) { return commandResult.pipe; } return ''; } /** * * @param {NamedArguments} args * @param {UnnamedArguments} value * @returns */ async function timesCallback(args, value) { if (args.guard instanceof SlashCommandClosure) throw new Error('argument \'guard\' cannot be a closure for command /while'); let repeats; let command; if (Array.isArray(value)) { [repeats, ...command] = value; if (command[0] instanceof SlashCommandClosure) { command = command[0]; } else { command = command.join(' '); } } else { [repeats, ...command] = /**@type {string}*/(value).split(' '); command = command.join(' '); } const isGuardOff = isFalseBoolean(args.guard); const iterations = Math.min(Number(repeats), isGuardOff ? Number.MAX_SAFE_INTEGER : MAX_LOOPS); let result; for (let i = 0; i < iterations; i++) { if (command instanceof SlashCommandClosure) { command.breakController = new SlashCommandBreakController(); command.scope.setMacro('timesIndex', i); result = await command.execute(); } else { result = await executeSubCommands(command.replace(/\{\{timesIndex\}\}/g, i.toString()), args._scope, args._parserFlags, args._abortController); } if (result.isAborted) break; if (result.isBreak) break; } return result?.pipe ?? ''; } /** * * @param {NamedArguments} args * @param {(string|SlashCommandClosure)[]} value */ async function ifCallback(args, value) { const { a, b, rule } = parseBooleanOperands(args); const result = evalBoolean(rule, a, b); /**@type {string|SlashCommandClosure} */ let command; if (value) { if (value[0] instanceof SlashCommandClosure) { command = value[0]; } else { command = value.join(' '); } } let commandResult; if (result && command) { if (command instanceof SlashCommandClosure) return (await command.execute()).pipe; commandResult = await executeSubCommands(command, args._scope, args._parserFlags, args._abortController); } else if (!result && args.else && ((typeof args.else === 'string' && args.else !== '') || args.else instanceof SlashCommandClosure)) { if (args.else instanceof SlashCommandClosure) return (await args.else.execute()).pipe; commandResult = await executeSubCommands(args.else, args._scope, args._parserFlags, args._abortController); } if (commandResult) { return commandResult.pipe; } return ''; } /** * Checks if a local variable exists. * @param {string} name Local variable name * @returns {boolean} True if the local variable exists, false otherwise */ function existsLocalVariable(name) { return chat_metadata.variables && chat_metadata.variables[name] !== undefined; } /** * Checks if a global variable exists. * @param {string} name Global variable name * @returns {boolean} True if the global variable exists, false otherwise */ function existsGlobalVariable(name) { return extension_settings.variables.global && extension_settings.variables.global[name] !== undefined; } /** * Parses boolean operands from command arguments. * @param {object} args Command arguments * @returns {{a: string | number, b: string | number, rule: string}} Boolean operands */ export function parseBooleanOperands(args) { // Resolution order: numeric literal, local variable, global variable, string literal /** * @param {string} operand Boolean operand candidate */ function getOperand(operand) { if (operand === undefined) { return ''; } const operandNumber = Number(operand); if (!isNaN(operandNumber)) { return operandNumber; } if (args._scope.existsVariable(operand)) { const operandVariable = args._scope.getVariable(operand); return operandVariable ?? ''; } if (existsLocalVariable(operand)) { const operandLocalVariable = getLocalVariable(operand); return operandLocalVariable ?? ''; } if (existsGlobalVariable(operand)) { const operandGlobalVariable = getGlobalVariable(operand); return operandGlobalVariable ?? ''; } const stringLiteral = String(operand); return stringLiteral || ''; } const left = getOperand(args.a || args.left || args.first || args.x); const right = getOperand(args.b || args.right || args.second || args.y); const rule = args.rule; return { a: left, b: right, rule }; } /** * Evaluates a boolean comparison rule. * @param {string} rule Boolean comparison rule * @param {string|number} a The left operand * @param {string|number} b The right operand * @returns {boolean} True if the rule yields true, false otherwise */ export function evalBoolean(rule, a, b) { if (!rule) { toastr.warning('The rule must be specified for the boolean comparison.', 'Invalid command'); throw new Error('Invalid command.'); } let result = false; if (typeof a === 'number' && typeof b === 'number') { // only do numeric comparison if both operands are numbers const aNumber = Number(a); const bNumber = Number(b); switch (rule) { case 'not': result = !aNumber; break; case 'gt': result = aNumber > bNumber; break; case 'gte': result = aNumber >= bNumber; break; case 'lt': result = aNumber < bNumber; break; case 'lte': result = aNumber <= bNumber; break; case 'eq': result = aNumber === bNumber; break; case 'neq': result = aNumber !== bNumber; break; default: toastr.error('Unknown boolean comparison rule for type number.', 'Invalid command'); throw new Error('Invalid command.'); } } else { // otherwise do case-insensitive string comparsion, stringify non-strings let aString; let bString; if (typeof a == 'string') { aString = a.toLowerCase(); } else { aString = JSON.stringify(a).toLowerCase(); } if (typeof b == 'string') { bString = b.toLowerCase(); } else { bString = JSON.stringify(b).toLowerCase(); } switch (rule) { case 'in': result = aString.includes(bString); break; case 'nin': result = !aString.includes(bString); break; case 'eq': result = aString === bString; break; case 'neq': result = aString !== bString; break; default: toastr.error('Unknown boolean comparison rule for type string.', 'Invalid /if command'); throw new Error('Invalid command.'); } } return result; } /** * Executes a slash command from a string (may be enclosed in quotes) and returns the result. * @param {string} command Command to execute. May contain escaped macro and batch separators. * @param {SlashCommandScope} [scope] The scope to use. * @param {{[id:PARSER_FLAG]:boolean}} [parserFlags] The parser flags to use. * @param {SlashCommandAbortController} [abortController] The abort controller to use. * @returns {Promise<SlashCommandClosureResult>} Closure execution result */ async function executeSubCommands(command, scope = null, parserFlags = null, abortController = null) { if (command.startsWith('"') && command.endsWith('"')) { command = command.slice(1, -1); } const result = await executeSlashCommandsWithOptions(command, { handleExecutionErrors: false, handleParserErrors: false, parserFlags, scope, abortController: abortController ?? new SlashCommandAbortController(), }); return result; } /** * Deletes a local variable. * @param {string} name Variable name to delete * @returns {string} Empty string */ function deleteLocalVariable(name) { if (!existsLocalVariable(name)) { console.warn(`The local variable "${name}" does not exist.`); return ''; } delete chat_metadata.variables[name]; saveMetadataDebounced(); return ''; } /** * Deletes a global variable. * @param {string} name Variable name to delete * @returns {string} Empty string */ function deleteGlobalVariable(name) { if (!existsGlobalVariable(name)) { console.warn(`The global variable "${name}" does not exist.`); return ''; } delete extension_settings.variables.global[name]; saveSettingsDebounced(); return ''; } /** * Parses a series of numeric values from a string. * @param {string} value A space-separated list of numeric values or variable names * @param {SlashCommandScope} scope Scope * @returns {number[]} An array of numeric values */ function parseNumericSeries(value, scope = null) { if (typeof value === 'number') { return [value]; } const array = value .split(' ') .map(i => i.trim()) .filter(i => i !== '') .map(i => isNaN(Number(i)) ? Number(resolveVariable(i, scope)) : Number(i)) .filter(i => !isNaN(i)); return array; } function performOperation(value, operation, singleOperand = false, scope = null) { if (!value) { return 0; } const array = parseNumericSeries(value, scope); if (array.length === 0) { return 0; } const result = singleOperand ? operation(array[0]) : operation(array); if (isNaN(result) || !isFinite(result)) { return 0; } return result; } function addValuesCallback(args, value) { return performOperation(value, (array) => array.reduce((a, b) => a + b, 0), false, args._scope); } function mulValuesCallback(args, value) { return performOperation(value, (array) => array.reduce((a, b) => a * b, 1), false, args._scope); } function minValuesCallback(args, value) { return performOperation(value, (array) => Math.min(...array), false, args._scope); } function maxValuesCallback(args, value) { return performOperation(value, (array) => Math.max(...array), false, args._scope); } function subValuesCallback(args, value) { return performOperation(value, (array) => array[0] - array[1], false, args._scope); } function divValuesCallback(args, value) { return performOperation(value, (array) => { if (array[1] === 0) { console.warn('Division by zero.'); return 0; } return array[0] / array[1]; }, false, args._scope); } function modValuesCallback(args, value) { return performOperation(value, (array) => { if (array[1] === 0) { console.warn('Division by zero.'); return 0; } return array[0] % array[1]; }, false, args._scope); } function powValuesCallback(args, value) { return performOperation(value, (array) => Math.pow(array[0], array[1]), false, args._scope); } function sinValuesCallback(args, value) { return performOperation(value, Math.sin, true, args._scope); } function cosValuesCallback(args, value) { return performOperation(value, Math.cos, true, args._scope); } function logValuesCallback(args, value) { return performOperation(value, Math.log, true, args._scope); } function roundValuesCallback(args, value) { return performOperation(value, Math.round, true, args._scope); } function absValuesCallback(args, value) { return performOperation(value, Math.abs, true, args._scope); } function sqrtValuesCallback(args, value) { return performOperation(value, Math.sqrt, true, args._scope); } function lenValuesCallback(value) { let parsedValue = value; try { parsedValue = JSON.parse(value); } catch { // could not parse } if (Array.isArray(parsedValue)) { return parsedValue.length; } switch (typeof parsedValue) { case 'string': return parsedValue.length; case 'object': return Object.keys(parsedValue).length; case 'number': return String(parsedValue).length; default: return 0; } } function randValuesCallback(from, to, args) { const range = to - from; const value = from + Math.random() * range; if (args.round == 'round') { return Math.round(value); } if (args.round == 'ceil') { return Math.ceil(value); } if (args.round == 'floor') { return Math.floor(value); } return value; } /** * Declare a new variable in the current scope. * @param {NamedArguments} args Named arguments. * @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]} value Name and optional value for the variable. * @returns The variable's value */ function letCallback(args, value) { if (!Array.isArray(value)) value = [value]; if (args.key !== undefined) { const key = args.key; if (typeof key != 'string') throw new Error('Key must be a string'); if (args._hasUnnamedArgument) { const val = typeof value[0] == 'string' ? value.join(' ') : value[0]; args._scope.letVariable(key, val); return val; } else { args._scope.letVariable(key); return ''; } } const key = value.shift(); if (typeof key != 'string') throw new Error('Key must be a string'); if (value.length > 0) { const val = typeof value[0] == 'string' ? value.join(' ') : value[0]; args._scope.letVariable(key, val); return val; } else { args._scope.letVariable(key); return ''; } } /** * Set or retrieve a variable in the current scope or nearest ancestor scope. * @param {NamedArguments} args Named arguments. * @param {string|SlashCommandClosure|(string|SlashCommandClosure)[]} value Name and optional value for the variable. * @returns The variable's value */ function varCallback(args, value) { if (!Array.isArray(value)) value = [value]; if (args.key !== undefined) { const key = args.key; if (typeof key != 'string') throw new Error('Key must be a string'); if (args._hasUnnamedArgument) { const val = typeof value[0] == 'string' ? value.join(' ') : value[0]; args._scope.setVariable(key, val, args.index); return val; } else { return args._scope.getVariable(key, args.index); } } const key = value.shift(); if (typeof key != 'string') throw new Error('Key must be a string'); if (value.length > 0) { const val = typeof value[0] == 'string' ? value.join(' ') : value[0]; args._scope.setVariable(key, val, args.index); return val; } else { return args._scope.getVariable(key, args.index); } } /** * @param {NamedArguments} args * @param {SlashCommandClosure} value * @returns {string} */ function closureSerializeCallback(args, value) { if (!(value instanceof SlashCommandClosure)) { throw new Error('unnamed argument must be a closure'); } return value.rawText; } /** * @param {NamedArguments} args * @param {UnnamedArguments} value * @returns {SlashCommandClosure} */ function closureDeserializeCallback(args, value) { const parser = new SlashCommandParser(); const closure = parser.parse(value, true, args._parserFlags, args._abortController); closure.scope.parent = args._scope; return closure; } export function registerVariableCommands() { SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'listvar', callback: listVariablesCallback, aliases: ['listchatvar'], helpString: 'List registered chat variables.', })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'setvar', callback: (args, value) => String(setLocalVariable(args.key || args.name, value, args)), aliases: ['setchatvar'], returns: 'the set variable value', namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('local'), forceEnum: false, }), new SlashCommandNamedArgument( 'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false, ), ], unnamedArgumentList: [ new SlashCommandArgument( 'value', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY], true, ), ], helpString: ` <div> Set a local variable value and pass it down the pipe. The <code>index</code> argument is optional. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/setvar key=color green</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'getvar', callback: (args, value) => String(getLocalVariable(value, args)), aliases: ['getchatvar'], returns: 'the variable value', namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], enumProvider: commonEnumProviders.variables('local'), }), new SlashCommandNamedArgument( 'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false, ), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'key', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], isRequired: false, enumProvider: commonEnumProviders.variables('local'), }), ], helpString: ` <div> Get a local variable value and pass it down the pipe. The <code>index</code> argument is optional. </div> <div> <strong>Examples:</strong> <ul> <li> <pre><code class="language-stscript">/getvar height</code></pre> </li> <li> <pre><code class="language-stscript">/getvar key=height</code></pre> </li> <li> <pre><code class="language-stscript">/getvar index=3 costumes</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'addvar', callback: (args, value) => String(addLocalVariable(args.key || args.name, value)), aliases: ['addchatvar'], returns: 'the new variable value', namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('local'), forceEnum: false, }), ], unnamedArgumentList: [ new SlashCommandArgument( 'value to add to the variable', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, ), ], helpString: ` <div> Add a value to a local variable and pass the result down the pipe. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/addvar key=score 10</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'setglobalvar', callback: (args, value) => String(setGlobalVariable(args.key || args.name, value, args)), returns: 'the set global variable value', namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('global'), forceEnum: false, }), new SlashCommandNamedArgument( 'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false, ), ], unnamedArgumentList: [ new SlashCommandArgument( 'value', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY], true, ), ], helpString: ` <div> Set a global variable value and pass it down the pipe. The <code>index</code> argument is optional. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/setglobalvar key=color green</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'getglobalvar', callback: (args, value) => String(getGlobalVariable(value, args)), returns: 'global variable value', namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], enumProvider: commonEnumProviders.variables('global'), }), new SlashCommandNamedArgument( 'index', 'list index', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], false, ), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'key', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], enumProvider: commonEnumProviders.variables('global'), }), ], helpString: ` <div> Get a global variable value and pass it down the pipe. The <code>index</code> argument is optional. </div> <div> <strong>Examples:</strong> <ul> <li> <pre><code class="language-stscript">/getglobalvar height</code></pre> </li> <li> <pre><code class="language-stscript">/getglobalvar key=height</code></pre> </li> <li> <pre><code class="language-stscript">/getglobalvar index=3 costumes</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'addglobalvar', callback: (args, value) => String(addGlobalVariable(args.key || args.name, value)), returns: 'the new variable value', namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('global'), forceEnum: false, }), ], unnamedArgumentList: [ new SlashCommandArgument( 'value to add to the variable', [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING], true, ), ], helpString: ` <div> Add a value to a global variable and pass the result down the pipe. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/addglobalvar key=score 10</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'incvar', callback: (_, value) => String(incrementLocalVariable(value)), aliases: ['incchatvar'], returns: 'the new variable value', unnamedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('local'), forceEnum: false, }), ], helpString: ` <div> Increment a local variable by 1 and pass the result down the pipe. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/incvar score</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'decvar', callback: (_, value) => String(decrementLocalVariable(value)), aliases: ['decchatvar'], returns: 'the new variable value', unnamedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('local'), forceEnum: false, }), ], helpString: ` <div> Decrement a local variable by 1 and pass the result down the pipe. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/decvar score</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'incglobalvar', callback: (_, value) => String(incrementGlobalVariable(value)), returns: 'the new variable value', unnamedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('global'), forceEnum: false, }), ], helpString: ` <div> Increment a global variable by 1 and pass the result down the pipe. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/incglobalvar score</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'decglobalvar', callback: (_, value) => String(decrementGlobalVariable(value)), returns: 'the new variable value', unnamedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('global'), forceEnum: false, }), ], helpString: ` <div> Decrement a global variable by 1 and pass the result down the pipe. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/decglobalvar score</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'if', callback: ifCallback, returns: 'result of the executed command ("then" or "else")', namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'left', description: 'left operand', typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), SlashCommandNamedArgument.fromProps({ name: 'right', description: 'right operand', typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), new SlashCommandNamedArgument( 'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [ new SlashCommandEnumValue('gt', 'a > b'), new SlashCommandEnumValue('gte', 'a >= b'), new SlashCommandEnumValue('lt', 'a < b'), new SlashCommandEnumValue('lte', 'a <= b'), new SlashCommandEnumValue('eq', 'a == b'), new SlashCommandEnumValue('neq', 'a !== b'), new SlashCommandEnumValue('not', '!a'), new SlashCommandEnumValue('in', 'a includes b'), new SlashCommandEnumValue('nin', 'a not includes b'), ], ), new SlashCommandNamedArgument( 'else', 'command to execute if not true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], false, ), ], unnamedArgumentList: [ new SlashCommandArgument( 'command to execute if true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], true, ), ], splitUnnamedArgument: true, helpString: ` <div> Compares the value of the left operand <code>a</code> with the value of the right operand <code>b</code>, and if the condition yields true, then execute any valid slash command enclosed in quotes and pass the result of the command execution down the pipe. </div> <div> Numeric values and string literals for left and right operands supported. </div> <div> <strong>Available rules:</strong> <ul> <li>gt => a > b</li> <li>gte => a >= b</li> <li>lt => a < b</li> <li>lte => a <= b</li> <li>eq => a == b</li> <li>neq => a != b</li> <li>not => !a</li> <li>in (strings) => a includes b</li> <li>nin (strings) => a not includes b</li> </ul> </div> <div> <strong>Examples:</strong> <ul> <li> <pre><code class="language-stscript">/if left=score right=10 rule=gte "/speak You win"</code></pre> triggers a /speak command if the value of "score" is greater or equals 10. </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'while', callback: whileCallback, returns: 'result of the last executed command', namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'left', description: 'left operand', typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), SlashCommandNamedArgument.fromProps({ name: 'right', description: 'right operand', typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), new SlashCommandNamedArgument( 'rule', 'comparison rule', [ARGUMENT_TYPE.STRING], true, false, null, [ new SlashCommandEnumValue('gt', 'a > b'), new SlashCommandEnumValue('gte', 'a >= b'), new SlashCommandEnumValue('lt', 'a < b'), new SlashCommandEnumValue('lte', 'a <= b'), new SlashCommandEnumValue('eq', 'a == b'), new SlashCommandEnumValue('neq', 'a !== b'), new SlashCommandEnumValue('not', '!a'), new SlashCommandEnumValue('in', 'a includes b'), new SlashCommandEnumValue('nin', 'a not includes b'), ], ), new SlashCommandNamedArgument( 'guard', 'disable loop iteration limit', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOff')(), ), ], unnamedArgumentList: [ new SlashCommandArgument( 'command to execute while true', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], true, ), ], splitUnnamedArgument: true, helpString: ` <div> Compares the value of the left operand <code>a</code> with the value of the right operand <code>b</code>, and if the condition yields true, then execute any valid slash command enclosed in quotes. </div> <div> Numeric values and string literals for left and right operands supported. </div> <div> <strong>Available rules:</strong> <ul> <li>gt => a > b</li> <li>gte => a >= b</li> <li>lt => a < b</li> <li>lte => a <= b</li> <li>eq => a == b</li> <li>neq => a != b</li> <li>not => !a</li> <li>in (strings) => a includes b</li> <li>nin (strings) => a not includes b</li> </ul> </div> <div> <strong>Examples:</strong> <ul> <li> <pre><code class="language-stscript">/setvar key=i 0 | /while left=i right=10 rule=lte "/addvar key=i 1"</code></pre> adds 1 to the value of "i" until it reaches 10. </li> </ul> </div> <div> Loops are limited to 100 iterations by default, pass <code>guard=off</code> to disable. </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'times', callback: timesCallback, returns: 'result of the last executed command', namedArgumentList: [ new SlashCommandNamedArgument( 'guard', 'disable loop iteration limit', [ARGUMENT_TYPE.STRING], false, false, null, commonEnumProviders.boolean('onOff')(), ), ], unnamedArgumentList: [ new SlashCommandArgument( 'repeats', [ARGUMENT_TYPE.NUMBER], true, ), new SlashCommandArgument( 'command', [ARGUMENT_TYPE.CLOSURE, ARGUMENT_TYPE.SUBCOMMAND], true, ), ], splitUnnamedArgument: true, splitUnnamedArgumentCount: 1, helpString: ` <div> Execute any valid slash command enclosed in quotes <code>repeats</code> number of times. </div> <div> <strong>Examples:</strong> <ul> <li> <pre><code class="language-stscript">/setvar key=i 1 | /times 5 "/addvar key=i 1"</code></pre> adds 1 to the value of "i" 5 times. </li> <li> <pre><code class="language-stscript">/times 4 "/echo {{timesIndex}}"</code></pre> echos the numbers 0 through 4. <code>{{timesIndex}}</code> is replaced with the iteration number (zero-based). </li> </ul> </div> <div> Loops are limited to 100 iterations by default, pass <code>guard=off</code> to disable. </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'flushvar', callback: async (_, value) => deleteLocalVariable(value instanceof SlashCommandClosure ? (await value.execute())?.pipe : String(value)), aliases: ['flushchatvar'], unnamedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name or closure that returns a variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.CLOSURE], enumProvider: commonEnumProviders.variables('local'), }), ], helpString: ` <div> Delete a local variable. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/flushvar score</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'flushglobalvar', callback: async (_, value) => deleteGlobalVariable(value instanceof SlashCommandClosure ? (await value.execute())?.pipe : String(value)), namedArgumentList: [], unnamedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name or closure that returns a variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME, ARGUMENT_TYPE.CLOSURE], enumProvider: commonEnumProviders.variables('global'), }), ], helpString: ` <div> Deletes the specified global variable. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/flushglobalvar score</code></pre> Deletes the global variable <code>score</code>. </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'add', callback: (args, /**@type {string[]}*/value) => addValuesCallback(args, value.join(' ')), returns: 'sum of the provided values', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'values to sum', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, acceptsMultiple: true, enumProvider: (executor, scope)=>{ const vars = commonEnumProviders.variables('all')(executor, scope); vars.push( new SlashCommandEnumValue( 'any variable name', null, enumTypes.variable, enumIcons.variable, (input)=>/^\w*$/.test(input), (input)=>input, ), new SlashCommandEnumValue( 'any number', null, enumTypes.number, enumIcons.number, (input)=>input == '' || !Number.isNaN(Number(input)), (input)=>input, ), ); return vars; }, forceEnum: false, }), ], splitUnnamedArgument: true, helpString: ` <div> Performs an addition of the set of values and passes the result down the pipe. Can use variable names. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/add 10 i 30 j</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'mul', callback: (args, value) => mulValuesCallback(args, value), returns: 'product of the provided values', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'values to multiply', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, acceptsMultiple: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Performs a multiplication of the set of values and passes the result down the pipe. Can use variable names. </div> <div> <strong>Examples:</strong> <ul> <li> <pre><code class="language-stscript">/mul 10 i 30 j</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'max', callback: maxValuesCallback, returns: 'maximum value of the set of values', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'values to find the max', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, acceptsMultiple: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Returns the maximum value of the set of values and passes the result down the pipe. Can use variable names. </div> <div> <strong>Examples:</strong> <ul> <li> <pre><code class="language-stscript">/max 10 i 30 j</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'min', callback: minValuesCallback, returns: 'minimum value of the set of values', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'values to find the min', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, acceptsMultiple: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Returns the minimum value of the set of values and passes the result down the pipe. Can use variable names. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/min 10 i 30 j</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sub', callback: subValuesCallback, returns: 'difference of the provided values', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'values to find the difference', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, acceptsMultiple: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Performs a subtraction of the set of values and passes the result down the pipe. Can use variable names. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/sub i 5</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'div', callback: divValuesCallback, returns: 'result of division', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'dividend', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), SlashCommandArgument.fromProps({ description: 'divisor', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Performs a division of two values and passes the result down the pipe. Can use variable names. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/div 10 i</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'mod', callback: modValuesCallback, returns: 'result of modulo operation', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'dividend', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), SlashCommandArgument.fromProps({ description: 'divisor', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Performs a modulo operation of two values and passes the result down the pipe. Can use variable names. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/mod i 2</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'pow', callback: powValuesCallback, returns: 'result of power operation', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'base', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), SlashCommandArgument.fromProps({ description: 'exponent', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Performs a power operation of two values and passes the result down the pipe. Can use variable names. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/pow i 2</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sin', callback: sinValuesCallback, returns: 'sine of the provided value', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'value', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Performs a sine operation of a value and passes the result down the pipe. Can use variable names. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/sin i</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'cos', callback: cosValuesCallback, returns: 'cosine of the provided value', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'value', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Performs a cosine operation of a value and passes the result down the pipe. Can use variable names. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/cos i</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'log', callback: logValuesCallback, returns: 'log of the provided value', namedArgumentList: [], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'value', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Performs a logarithm operation of a value and passes the result down the pipe. Can use variable names. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/log i</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'abs', callback: absValuesCallback, returns: 'absolute value of the provided value', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'value', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Performs an absolute value operation of a value and passes the result down the pipe. Can use variable names. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/abs i</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'sqrt', callback: sqrtValuesCallback, returns: 'square root of the provided value', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'value', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Performs a square root operation of a value and passes the result down the pipe. Can use variable names. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/sqrt i</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'round', callback: roundValuesCallback, returns: 'rounded value', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'value', typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.VARIABLE_NAME], isRequired: true, enumProvider: commonEnumProviders.variables('all'), forceEnum: false, }), ], helpString: ` <div> Rounds a value and passes the result down the pipe. Can use variable names. </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/round i</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'len', callback: (_, value) => String(lenValuesCallback(value)), aliases: ['length'], returns: 'length of the provided value', unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'value', typeList: [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY], isRequired: true, forceEnum: false, }), ], helpString: ` <div> Gets the length of a value and passes the result down the pipe. <ul> <li> For strings, returns the number of characters. </li> <li> For lists and dictionaries, returns the number of elements. </li> <li> For numbers, returns the number of digits (including the sign and decimal point). </li> </ul> </div> <div> <strong>Example:</strong> <ul> <li> <pre><code class="language-stscript">/len Lorem ipsum | /echo</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'rand', callback: (args, value) => String(randValuesCallback(Number(args.from ?? 0), Number(args.to ?? (value ? value : 1)), args)), returns: 'random number', namedArgumentList: [ new SlashCommandNamedArgument( 'from', 'starting value for the range (inclusive)', [ARGUMENT_TYPE.NUMBER], false, false, '0', ), new SlashCommandNamedArgument( 'to', 'ending value for the range (inclusive)', [ARGUMENT_TYPE.NUMBER], false, false, '1', ), new SlashCommandNamedArgument( 'round', 'rounding method for the result', [ARGUMENT_TYPE.STRING], false, false, null, ['round', 'ceil', 'floor'], ), ], helpString: ` <div> Returns a random number between <code>from</code> and <code>to</code> (inclusive). </div> <div> <strong>Examples:</strong> <ul> <li> <pre><code class="language-stscript">/rand</code></pre> Returns a random number between 0 and 1. </li> <li> <pre><code class="language-stscript">/rand 10</code></pre> Returns a random number between 0 and 10. </li> <li> <pre><code class="language-stscript">/rand from=5 to=10</code></pre> Returns a random number between 5 and 10. </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'var', callback: (/** @type {NamedArguments} */ args, value) => varCallback(args, value), returns: 'the variable value', namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name; forces setting the variable, even if no value is provided', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], enumProvider: commonEnumProviders.variables('scope'), forceEnum: false, }), new SlashCommandNamedArgument( 'index', 'optional index for list or dictionary', [ARGUMENT_TYPE.NUMBER], false, // isRequired false, // acceptsMultiple ), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], enumProvider: commonEnumProviders.variables('scope'), forceEnum: false, }), new SlashCommandArgument( 'variable value', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY, ARGUMENT_TYPE.CLOSURE], false, // isRequired false, // acceptsMultiple ), ], splitUnnamedArgument: true, splitUnnamedArgumentCount: 1, helpString: ` <div> Get or set a variable. </div> <div> <strong>Examples:</strong> <ul> <li> <pre><code class="language-stscript">/let x foo | /var x foo bar | /var x | /echo</code></pre> </li> <li> <pre><code class="language-stscript">/let x foo | /var key=x foo bar | /var x | /echo</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'let', callback: (/** @type {NamedArguments} */ args, value) => letCallback(args, value), returns: 'the variable value', namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], enumProvider: commonEnumProviders.variables('scope'), forceEnum: false, }), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'variable name', typeList: [ARGUMENT_TYPE.VARIABLE_NAME], enumProvider: commonEnumProviders.variables('scope'), forceEnum: false, }), new SlashCommandArgument( 'variable value', [ARGUMENT_TYPE.STRING, ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.BOOLEAN, ARGUMENT_TYPE.LIST, ARGUMENT_TYPE.DICTIONARY, ARGUMENT_TYPE.CLOSURE], ), ], splitUnnamedArgument: true, splitUnnamedArgumentCount: 1, helpString: ` <div> Declares a new variable in the current scope. </div> <div> <strong>Examples:</strong> <ul> <li> <pre><code class="language-stscript">/let x foo bar | /echo {{var::x}}</code></pre> </li> <li> <pre><code class="language-stscript">/let key=x foo bar | /echo {{var::x}}</code></pre> </li> <li> <pre><code class="language-stscript">/let y</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'closure-serialize', /** * * @param {NamedArguments} args * @param {SlashCommandClosure} value * @returns {string} */ callback: (args, value) => closureSerializeCallback(args, value), unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'the closure to serialize', typeList: [ARGUMENT_TYPE.CLOSURE], isRequired: true, }), ], returns: 'serialized closure as string', helpString: ` <div> Serialize a closure as text that can be stored in global and chat variables. </div> <div> <strong>Examples:</strong> <ul> <li> <pre><code class="language-stscript">/closure-serialize {: x=1 /echo x is {{var::x}} and y is {{var::y}} :} |\n/setvar key=myClosure</code></pre> </li> </ul> </div> `, })); SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'closure-deserialize', /** * @param {NamedArguments} args * @param {UnnamedArguments} value * @returns {SlashCommandClosure} */ callback: (args, value) => closureDeserializeCallback(args, value), unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: 'serialized closure', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, }), ], returns: 'deserialized closure', helpString: ` <div> Deserialize a closure from text. </div> <div> <strong>Examples:</strong> <ul> <li> <pre><code class="language-stscript">/closure-deserialize {{getvar::myClosure}} |\n/let myClosure {{pipe}} |\n/let y bar |\n/:myClosure x=foo</code></pre> </li> </ul> </div> `, })); }