/** * @file modules/variables/var-commands.js * @description 变量斜杠命令与宏替换,常驻模块 */ import { getContext } from "../../../../../extensions.js"; import { getLocalVariable, setLocalVariable } from "../../../../../variables.js"; import { createModuleEvents, event_types } from "../../core/event-manager.js"; import jsYaml from "../../libs/js-yaml.mjs"; import { lwbSplitPathWithBrackets, lwbSplitPathAndValue, normalizePath, ensureDeepContainer, safeJSONStringify, maybeParseObject, valueToString, deepClone, } from "../../core/variable-path.js"; const MODULE_ID = 'varCommands'; const TAG_RE_XBGETVAR = /\{\{xbgetvar::([^}]+)\}\}/gi; const TAG_RE_XBGETVAR_YAML = /\{\{xbgetvar_yaml::([^}]+)\}\}/gi; let events = null; let initialized = false; function getMsgKey(msg) { return (typeof msg?.mes === 'string') ? 'mes' : (typeof msg?.content === 'string' ? 'content' : null); } export function parseValueForSet(value) { try { const t = String(value ?? '').trim(); if (t.startsWith('{') || t.startsWith('[')) { try { return JSON.parse(t); } catch {} } const looksLikeJson = (t[0] === '{' || t[0] === '[') && /[:\],}]/.test(t); if (looksLikeJson && !t.includes('"') && t.includes("'")) { try { return JSON.parse(t.replace(/'/g, '"')); } catch {} } if (t === 'true' || t === 'false' || t === 'null') { return JSON.parse(t); } if (/^-?\d+(\.\d+)?$/.test(t)) { return JSON.parse(t); } return value; } catch { return value; } } function extractPathFromArgs(namedArgs, unnamedArgs) { try { if (namedArgs && typeof namedArgs.key === 'string' && namedArgs.key.trim()) { return String(namedArgs.key).trim(); } const arr = Array.isArray(unnamedArgs) ? unnamedArgs : [unnamedArgs]; const first = String(arr[0] ?? '').trim(); const m = /^key\s*=\s*(.+)$/i.exec(first); return m ? m[1].trim() : first; } catch { return ''; } } function ensureAbsTargetPath(basePath, token) { const t = String(token || '').trim(); if (!t) return String(basePath || ''); const base = String(basePath || ''); if (t === base || t.startsWith(base + '.')) return t; return base ? (base + '.' + t) : t; } function segmentsRelativeToBase(absPath, basePath) { const segs = lwbSplitPathWithBrackets(absPath); const baseSegs = lwbSplitPathWithBrackets(basePath); if (!segs.length || !baseSegs.length) return segs || []; const matches = baseSegs.every((b, i) => String(segs[i]) === String(b)); return matches ? segs.slice(baseSegs.length) : segs; } function setDeepBySegments(target, segs, value) { let cur = target; for (let i = 0; i < segs.length; i++) { const isLast = i === segs.length - 1; const key = segs[i]; if (isLast) { cur[key] = value; } else { const nxt = cur[key]; const nextSeg = segs[i + 1]; const wantArray = (typeof nextSeg === 'number'); // 已存在且类型正确:继续深入 if (wantArray && Array.isArray(nxt)) { cur = nxt; continue; } if (!wantArray && (nxt && typeof nxt === 'object') && !Array.isArray(nxt)) { cur = nxt; continue; } // 不存在或类型不匹配:创建正确的容器 cur[key] = wantArray ? [] : {}; cur = cur[key]; } } } export function lwbResolveVarPath(path) { try { const segs = lwbSplitPathWithBrackets(path); if (!segs.length) return ''; const rootName = String(segs[0]); const rootRaw = getLocalVariable(rootName); if (segs.length === 1) { return valueToString(rootRaw); } const obj = maybeParseObject(rootRaw); if (!obj) return ''; let cur = obj; for (let i = 1; i < segs.length; i++) { cur = cur?.[segs[i]]; if (cur === undefined) return ''; } return valueToString(cur); } catch { return ''; } } export function replaceXbGetVarInString(s) { s = String(s ?? ''); if (!s || s.indexOf('{{xbgetvar::') === -1) return s; TAG_RE_XBGETVAR.lastIndex = 0; return s.replace(TAG_RE_XBGETVAR, (_, p) => lwbResolveVarPath(p)); } /** * 将 {{xbgetvar_yaml::路径}} 替换为 YAML 格式的值 * @param {string} s * @returns {string} */ export function replaceXbGetVarYamlInString(s) { s = String(s ?? ''); if (!s || s.indexOf('{{xbgetvar_yaml::') === -1) return s; TAG_RE_XBGETVAR_YAML.lastIndex = 0; return s.replace(TAG_RE_XBGETVAR_YAML, (_, p) => { const value = lwbResolveVarPath(p); if (!value) return ''; // 尝试解析为对象/数组,然后转 YAML try { const parsed = JSON.parse(value); if (typeof parsed === 'object' && parsed !== null) { return jsYaml.dump(parsed, { indent: 2, lineWidth: -1, noRefs: true, quotingType: '"', }).trim(); } return value; } catch { return value; } }); } export function replaceXbGetVarInChat(chat) { if (!Array.isArray(chat)) return; for (const msg of chat) { try { const key = getMsgKey(msg); if (!key) continue; const old = String(msg[key] ?? ''); const hasJson = old.indexOf('{{xbgetvar::') !== -1; const hasYaml = old.indexOf('{{xbgetvar_yaml::') !== -1; if (!hasJson && !hasYaml) continue; let result = hasJson ? replaceXbGetVarInString(old) : old; result = hasYaml ? replaceXbGetVarYamlInString(result) : result; msg[key] = result; } catch {} } } export function applyXbGetVarForMessage(messageId, writeback = true) { try { const ctx = getContext(); const msg = ctx?.chat?.[messageId]; if (!msg) return; const key = getMsgKey(msg); if (!key) return; const old = String(msg[key] ?? ''); const hasJson = old.indexOf('{{xbgetvar::') !== -1; const hasYaml = old.indexOf('{{xbgetvar_yaml::') !== -1; if (!hasJson && !hasYaml) return; let out = hasJson ? replaceXbGetVarInString(old) : old; out = hasYaml ? replaceXbGetVarYamlInString(out) : out; if (writeback && out !== old) { msg[key] = out; } } catch {} } export function parseDirectivesTokenList(tokens) { const out = { ro: false, objectPolicy: null, arrayPolicy: null, constraints: {}, clear: false }; for (const tok of tokens) { const t = String(tok || '').trim(); if (!t) continue; if (t === '$ro') { out.ro = true; continue; } if (t === '$ext') { out.objectPolicy = 'ext'; continue; } if (t === '$prune') { out.objectPolicy = 'prune'; continue; } if (t === '$free') { out.objectPolicy = 'free'; continue; } if (t === '$grow') { out.arrayPolicy = 'grow'; continue; } if (t === '$shrink') { out.arrayPolicy = 'shrink'; continue; } if (t === '$list') { out.arrayPolicy = 'list'; continue; } if (t.startsWith('$min=')) { const num = Number(t.slice(5)); if (Number.isFinite(num)) out.constraints.min = num; continue; } if (t.startsWith('$max=')) { const num = Number(t.slice(5)); if (Number.isFinite(num)) out.constraints.max = num; continue; } if (t.startsWith('$range=')) { const m = t.match(/^\$range=\[\s*(-?\d+(?:\.\d+)?)\s*,\s*(-?\d+(?:\.\d+)?)\s*\]$/); if (m) { const a = Number(m[1]), b = Number(m[2]); if (Number.isFinite(a) && Number.isFinite(b)) { out.constraints.min = Math.min(a, b); out.constraints.max = Math.max(a, b); } } continue; } if (t.startsWith('$step=')) { const num = Number(t.slice(6)); if (Number.isFinite(num)) { out.constraints.step = Math.max(0, Math.abs(num)); } continue; } if (t.startsWith('$enum=')) { const m = t.match(/^\$enum=\{\s*([^}]+)\s*\}$/); if (m) { const vals = m[1].split(/[;;]/).map(s => s.trim()).filter(Boolean); if (vals.length) out.constraints.enum = vals; } continue; } if (t.startsWith('$match=')) { const raw = t.slice(7); if (raw.startsWith('/') && raw.lastIndexOf('/') > 0) { const last = raw.lastIndexOf('/'); const pattern = raw.slice(1, last).replace(/\\\//g, '/'); const flags = raw.slice(last + 1) || ''; out.constraints.regex = { source: pattern, flags }; } continue; } if (t === '$clear') { out.clear = true; continue; } } return out; } export function expandShorthandRuleObject(basePath, valueObj) { try { const base = String(basePath || ''); const isObj = v => v && typeof v === 'object' && !Array.isArray(v); if (!isObj(valueObj)) return null; function stripDollarKeysDeep(val) { if (Array.isArray(val)) return val.map(stripDollarKeysDeep); if (isObj(val)) { const out = {}; for (const k in val) { if (!Object.prototype.hasOwnProperty.call(val, k)) continue; if (String(k).trim().startsWith('$')) continue; out[k] = stripDollarKeysDeep(val[k]); } return out; } return val; } function formatPathWithBrackets(pathStr) { const segs = lwbSplitPathWithBrackets(String(pathStr || '')); let out = ''; for (const s of segs) { if (typeof s === 'number') out += `[${s}]`; else out += out ? `.${s}` : `${s}`; } return out; } function assignDeep(dst, src) { for (const k in src) { if (!Object.prototype.hasOwnProperty.call(src, k)) continue; const v = src[k]; if (v && typeof v === 'object' && !Array.isArray(v)) { if (!dst[k] || typeof dst[k] !== 'object' || Array.isArray(dst[k])) { dst[k] = {}; } assignDeep(dst[k], v); } else { dst[k] = v; } } } const rulesTop = {}; const dataTree = {}; function writeDataAt(relPathStr, val) { const abs = ensureAbsTargetPath(base, relPathStr); const relSegs = segmentsRelativeToBase(abs, base); if (relSegs.length) { setDeepBySegments(dataTree, relSegs, val); } else { if (val && typeof val === 'object' && !Array.isArray(val)) { assignDeep(dataTree, val); } else { dataTree['$root'] = val; } } } function walk(node, currentRelPathStr) { if (Array.isArray(node)) { const cleanedArr = node.map(stripDollarKeysDeep); if (currentRelPathStr) writeDataAt(currentRelPathStr, cleanedArr); for (let i = 0; i < node.length; i++) { const el = node[i]; if (el && typeof el === 'object') { const childRel = currentRelPathStr ? `${currentRelPathStr}.${i}` : String(i); walk(el, childRel); } } return; } if (!isObj(node)) { if (currentRelPathStr) writeDataAt(currentRelPathStr, node); return; } const cleaned = stripDollarKeysDeep(node); if (currentRelPathStr) writeDataAt(currentRelPathStr, cleaned); else assignDeep(dataTree, cleaned); for (const key in node) { if (!Object.prototype.hasOwnProperty.call(node, key)) continue; const v = node[key]; const keyStr = String(key).trim(); const isRule = keyStr.startsWith('$'); if (!isRule) { const childRel = currentRelPathStr ? `${currentRelPathStr}.${keyStr}` : keyStr; if (v && typeof v === 'object') walk(v, childRel); continue; } const rest = keyStr.slice(1).trim(); if (!rest) continue; const parts = rest.split(/\s+/).filter(Boolean); if (!parts.length) continue; const targetToken = parts.pop(); const dirs = parts.map(t => String(t).trim().startsWith('$') ? String(t).trim() : ('$' + String(t).trim()) ); const fullRelTarget = currentRelPathStr ? `${currentRelPathStr}.${targetToken}` : targetToken; const absTarget = ensureAbsTargetPath(base, fullRelTarget); const absDisplay = formatPathWithBrackets(absTarget); const ruleKey = `$ ${dirs.join(' ')} ${absDisplay}`.trim(); rulesTop[ruleKey] = {}; if (v !== undefined) { const cleanedVal = stripDollarKeysDeep(v); writeDataAt(fullRelTarget, cleanedVal); if (v && typeof v === 'object') { walk(v, fullRelTarget); } } } } walk(valueObj, ''); const out = {}; assignDeep(out, rulesTop); assignDeep(out, dataTree); return out; } catch { return null; } } export function lwbAssignVarPath(path, value) { try { const segs = lwbSplitPathWithBrackets(path); if (!segs.length) return ''; const rootName = String(segs[0]); let vParsed = parseValueForSet(value); if (vParsed && typeof vParsed === 'object') { try { if (globalThis.LWB_Guard?.loadRules) { const res = globalThis.LWB_Guard.loadRules(vParsed, rootName); if (res?.cleanValue !== undefined) vParsed = res.cleanValue; if (res?.rulesDelta && typeof res.rulesDelta === 'object') { if (globalThis.LWB_Guard?.applyDeltaTable) { globalThis.LWB_Guard.applyDeltaTable(res.rulesDelta); } else if (globalThis.LWB_Guard?.applyDelta) { for (const [p, d] of Object.entries(res.rulesDelta)) { globalThis.LWB_Guard.applyDelta(p, d); } } globalThis.LWB_Guard.save?.(); } } } catch {} } const absPath = normalizePath(path); let guardOk = true; let guardVal = vParsed; try { if (globalThis.LWB_Guard?.validate) { const g = globalThis.LWB_Guard.validate('set', absPath, vParsed); guardOk = !!g?.allow; if ('value' in g) guardVal = g.value; } } catch {} if (!guardOk) return ''; if (segs.length === 1) { if (guardVal && typeof guardVal === 'object') { setLocalVariable(rootName, safeJSONStringify(guardVal)); } else { setLocalVariable(rootName, String(guardVal ?? '')); } return ''; } const rootRaw = getLocalVariable(rootName); let obj; const parsed = maybeParseObject(rootRaw); if (parsed) { obj = deepClone(parsed); } else { // 若根变量不存在:A[0].x 这类路径期望根为数组 obj = (typeof segs[1] === 'number') ? [] : {}; } const { parent, lastKey } = ensureDeepContainer(obj, segs.slice(1)); parent[lastKey] = guardVal; setLocalVariable(rootName, safeJSONStringify(obj)); return ''; } catch { return ''; } } export function lwbAddVarPath(path, increment) { try { const segs = lwbSplitPathWithBrackets(path); if (!segs.length) return ''; const currentStr = lwbResolveVarPath(path); const incStr = String(increment ?? ''); const currentNum = Number(currentStr); const incNum = Number(incStr); const bothNumeric = currentStr !== '' && incStr !== '' && Number.isFinite(currentNum) && Number.isFinite(incNum); const newValue = bothNumeric ? (currentNum + incNum) : (currentStr + incStr); lwbAssignVarPath(path, newValue); return valueToString(newValue); } catch { return ''; } } /** * 删除变量或深层属性(支持点路径/中括号路径) * @param {string} path * @returns {string} 空字符串 */ export function lwbDeleteVarPath(path) { try { const segs = lwbSplitPathWithBrackets(path); if (!segs.length) return ''; const rootName = String(segs[0]); const absPath = normalizePath(path); // 只有根变量:对齐 /flushvar 的“清空”语义 if (segs.length === 1) { try { if (globalThis.LWB_Guard?.validate) { const g = globalThis.LWB_Guard.validate('delNode', absPath); if (!g?.allow) return ''; } } catch {} setLocalVariable(rootName, ''); return ''; } const rootRaw = getLocalVariable(rootName); const parsed = maybeParseObject(rootRaw); if (!parsed) return ''; const obj = deepClone(parsed); const subSegs = segs.slice(1); let cur = obj; for (let i = 0; i < subSegs.length - 1; i++) { cur = cur?.[subSegs[i]]; if (cur == null || typeof cur !== 'object') return ''; } try { if (globalThis.LWB_Guard?.validate) { const g = globalThis.LWB_Guard.validate('delNode', absPath); if (!g?.allow) return ''; } } catch {} const lastKey = subSegs[subSegs.length - 1]; if (Array.isArray(cur)) { if (typeof lastKey === 'number' && lastKey >= 0 && lastKey < cur.length) { cur.splice(lastKey, 1); } else { const equal = (a, b) => a === b || a == b || String(a) === String(b); for (let i = cur.length - 1; i >= 0; i--) { if (equal(cur[i], lastKey)) cur.splice(i, 1); } } } else { try { delete cur[lastKey]; } catch {} } setLocalVariable(rootName, safeJSONStringify(obj)); return ''; } catch { return ''; } } /** * 向数组推入值(支持点路径/中括号路径) * @param {string} path * @param {*} value * @returns {string} 新数组长度(字符串) */ export function lwbPushVarPath(path, value) { try { const segs = lwbSplitPathWithBrackets(path); if (!segs.length) return ''; const rootName = String(segs[0]); const absPath = normalizePath(path); const vParsed = parseValueForSet(value); // 仅根变量:将 root 视为数组 if (segs.length === 1) { try { if (globalThis.LWB_Guard?.validate) { const g = globalThis.LWB_Guard.validate('push', absPath, vParsed); if (!g?.allow) return ''; } } catch {} const rootRaw = getLocalVariable(rootName); let arr; try { arr = JSON.parse(rootRaw); } catch { arr = undefined; } if (!Array.isArray(arr)) arr = rootRaw != null && rootRaw !== '' ? [rootRaw] : []; arr.push(vParsed); setLocalVariable(rootName, safeJSONStringify(arr)); return String(arr.length); } const rootRaw = getLocalVariable(rootName); let obj; const parsed = maybeParseObject(rootRaw); if (parsed) { obj = deepClone(parsed); } else { const firstSubSeg = segs[1]; obj = typeof firstSubSeg === 'number' ? [] : {}; } const { parent, lastKey } = ensureDeepContainer(obj, segs.slice(1)); let arr = parent[lastKey]; if (!Array.isArray(arr)) { arr = arr != null ? [arr] : []; } try { if (globalThis.LWB_Guard?.validate) { const g = globalThis.LWB_Guard.validate('push', absPath, vParsed); if (!g?.allow) return ''; } } catch {} arr.push(vParsed); parent[lastKey] = arr; setLocalVariable(rootName, safeJSONStringify(obj)); return String(arr.length); } catch { return ''; } } function registerXbGetVarSlashCommand() { try { const ctx = getContext(); const { SlashCommandParser, SlashCommand, SlashCommandArgument, ARGUMENT_TYPE } = ctx || {}; if (!SlashCommandParser?.addCommandObject || !SlashCommand?.fromProps || !SlashCommandArgument?.fromProps) { return; } SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'xbgetvar', returns: 'string', helpString: `
通过点/中括号路径获取嵌套的本地变量值
支持 ["0"] 强制字符串键、[0] 数组索引
示例:
/xbgetvar 人物状态.姓名
/xbgetvar A[0].name | /echo {{pipe}}
`, unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: '变量路径', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, acceptsMultiple: false, }), ], callback: (namedArgs, unnamedArgs) => { try { const path = extractPathFromArgs(namedArgs, unnamedArgs); return lwbResolveVarPath(String(path || '')); } catch { return ''; } }, })); } catch {} } function registerXbSetVarSlashCommand() { try { const ctx = getContext(); const { SlashCommandParser, SlashCommand, SlashCommandArgument, ARGUMENT_TYPE } = ctx || {}; if (!SlashCommandParser?.addCommandObject || !SlashCommand?.fromProps || !SlashCommandArgument?.fromProps) { return; } function joinUnnamed(args) { if (Array.isArray(args)) { return args.filter(v => v != null).map(v => String(v)).join(' ').trim(); } return String(args ?? '').trim(); } function splitTokensBySpace(s) { return String(s || '').split(/\s+/).filter(Boolean); } function isDirectiveToken(tok) { const t = String(tok || '').trim(); if (!t) return false; if (['$ro', '$ext', '$prune', '$free', '$grow', '$shrink', '$list', '$clear'].includes(t)) { return true; } if (/^\$(min|max|range|enum|match|step)=/.test(t)) { return true; } return false; } function parseKeyAndValue(namedArgs, unnamedArgs) { const unnamedJoined = joinUnnamed(unnamedArgs); const hasNamedKey = typeof namedArgs?.key === 'string' && namedArgs.key.trim().length > 0; if (hasNamedKey) { const keyRaw = namedArgs.key.trim(); const keyParts = splitTokensBySpace(keyRaw); if (keyParts.length > 1 && keyParts.every((p, i) => isDirectiveToken(p) || i === keyParts.length - 1 )) { const directives = keyParts.slice(0, -1); const realPath = keyParts[keyParts.length - 1]; return { directives, realPath, valueText: unnamedJoined }; } if (isDirectiveToken(keyRaw)) { const m = unnamedJoined.match(/^\S+/); const realPath = m ? m[0] : ''; const valueText = realPath ? unnamedJoined.slice(realPath.length).trim() : ''; return { directives: [keyRaw], realPath, valueText }; } return { directives: [], realPath: keyRaw, valueText: unnamedJoined }; } const firstRaw = joinUnnamed(unnamedArgs); if (!firstRaw) return { directives: [], realPath: '', valueText: '' }; const sp = lwbSplitPathAndValue(firstRaw); let head = String(sp.path || '').trim(); let rest = String(sp.value || '').trim(); const parts = splitTokensBySpace(head); if (parts.length > 1 && parts.every((p, i) => isDirectiveToken(p) || i === parts.length - 1 )) { const directives = parts.slice(0, -1); const realPath = parts[parts.length - 1]; return { directives, realPath, valueText: rest }; } if (isDirectiveToken(head)) { const m = rest.match(/^\S+/); const realPath = m ? m[0] : ''; const valueText = realPath ? rest.slice(realPath.length).trim() : ''; return { directives: [head], realPath, valueText }; } return { directives: [], realPath: head, valueText: rest }; } SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'xbsetvar', returns: 'string', helpString: `
设置嵌套本地变量
支持指令前缀:$ro, $min=, $max=, $list 等
示例:
/xbsetvar A.B.C 123
/xbsetvar key="$list 情节小结" ["item1"]
`, unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: '变量路径或(指令 + 路径)', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, acceptsMultiple: false, }), SlashCommandArgument.fromProps({ description: '要设置的值', typeList: [ARGUMENT_TYPE.STRING], isRequired: false, acceptsMultiple: false, }), ], callback: (namedArgs, unnamedArgs) => { try { const parsed = parseKeyAndValue(namedArgs, unnamedArgs); const directives = parsed.directives || []; const realPath = String(parsed.realPath || '').trim(); let rest = String(parsed.valueText || '').trim(); if (!realPath) return ''; if (directives.length > 0 && globalThis.LWB_Guard?.applyDelta) { const delta = parseDirectivesTokenList(directives); const absPath = normalizePath(realPath); globalThis.LWB_Guard.applyDelta(absPath, delta); globalThis.LWB_Guard.save?.(); } let toSet = rest; try { const parsedVal = parseValueForSet(rest); if (parsedVal && typeof parsedVal === 'object' && !Array.isArray(parsedVal)) { const expanded = expandShorthandRuleObject(realPath, parsedVal); if (expanded && typeof expanded === 'object') { toSet = safeJSONStringify(expanded) || rest; } } } catch {} lwbAssignVarPath(realPath, toSet); return ''; } catch { return ''; } }, })); } catch {} } function registerXbAddVarSlashCommand() { try { const ctx = getContext(); const { SlashCommandParser, SlashCommand, SlashCommandArgument, SlashCommandNamedArgument, ARGUMENT_TYPE } = ctx || {}; if (!SlashCommandParser?.addCommandObject || !SlashCommand?.fromProps || !SlashCommandArgument?.fromProps) { return; } SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'xbaddvar', returns: 'string', helpString: `
通过点路径增加变量值
两者都为数字时执行加法,否则执行字符串拼接
示例:
/xbaddvar key=人物状态.金币 100
/xbaddvar A.B.count 1
/xbaddvar 名字 _后缀
`, namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: '变量路径', typeList: [ARGUMENT_TYPE.STRING], isRequired: false, }), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: '路径+增量 或 仅增量', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, }), ], callback: (namedArgs, unnamedArgs) => { try { let path, increment; const unnamedJoined = Array.isArray(unnamedArgs) ? unnamedArgs.filter(v => v != null).map(String).join(' ').trim() : String(unnamedArgs ?? '').trim(); if (namedArgs?.key && String(namedArgs.key).trim()) { path = String(namedArgs.key).trim(); increment = unnamedJoined; } else { const sp = lwbSplitPathAndValue(unnamedJoined); path = sp.path; increment = sp.value; } if (!path) return ''; return lwbAddVarPath(path, increment); } catch { return ''; } }, })); } catch {} } function registerXbDelVarSlashCommand() { try { const ctx = getContext(); const { SlashCommandParser, SlashCommand, SlashCommandArgument, ARGUMENT_TYPE } = ctx || {}; if (!SlashCommandParser?.addCommandObject || !SlashCommand?.fromProps || !SlashCommandArgument?.fromProps) { return; } SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'xbdelvar', returns: 'string', helpString: `
删除变量或深层属性
示例:
/xbdelvar 临时变量
/xbdelvar 角色状态.临时buff
/xbdelvar 背包[0]
`, unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: '变量路径', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, }), ], callback: (namedArgs, unnamedArgs) => { try { const path = extractPathFromArgs(namedArgs, unnamedArgs); if (!path) return ''; return lwbDeleteVarPath(path); } catch { return ''; } }, })); } catch {} } function registerXbPushVarSlashCommand() { try { const ctx = getContext(); const { SlashCommandParser, SlashCommand, SlashCommandArgument, SlashCommandNamedArgument, ARGUMENT_TYPE } = ctx || {}; if (!SlashCommandParser?.addCommandObject || !SlashCommand?.fromProps || !SlashCommandArgument?.fromProps) { return; } SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'xbpushvar', returns: 'string', helpString: `
向数组推入值
返回新数组长度
示例:
/xbpushvar key=背包 苹果
/xbpushvar 角色.技能列表 火球术
`, namedArgumentList: [ SlashCommandNamedArgument.fromProps({ name: 'key', description: '数组路径', typeList: [ARGUMENT_TYPE.STRING], isRequired: false, }), ], unnamedArgumentList: [ SlashCommandArgument.fromProps({ description: '路径+值 或 仅值', typeList: [ARGUMENT_TYPE.STRING], isRequired: true, }), ], callback: (namedArgs, unnamedArgs) => { try { let path, value; const unnamedJoined = Array.isArray(unnamedArgs) ? unnamedArgs.filter(v => v != null).map(String).join(' ').trim() : String(unnamedArgs ?? '').trim(); if (namedArgs?.key && String(namedArgs.key).trim()) { path = String(namedArgs.key).trim(); value = unnamedJoined; } else { const sp = lwbSplitPathAndValue(unnamedJoined); path = sp.path; value = sp.value; } if (!path) return ''; return lwbPushVarPath(path, value); } catch { return ''; } }, })); } catch {} } function onMessageRendered(data) { try { if (globalThis.LWB_Guard?.validate) return; const id = typeof data === 'object' && data !== null ? (data.messageId ?? data.id ?? data) : data; if (typeof id === 'number') { applyXbGetVarForMessage(id, true); } } catch {} } export function initVarCommands() { if (initialized) return; initialized = true; events = createModuleEvents(MODULE_ID); registerXbGetVarSlashCommand(); registerXbSetVarSlashCommand(); registerXbAddVarSlashCommand(); registerXbDelVarSlashCommand(); registerXbPushVarSlashCommand(); events.on(event_types.USER_MESSAGE_RENDERED, onMessageRendered); events.on(event_types.CHARACTER_MESSAGE_RENDERED, onMessageRendered); events.on(event_types.MESSAGE_UPDATED, onMessageRendered); events.on(event_types.MESSAGE_EDITED, onMessageRendered); events.on(event_types.MESSAGE_SWIPED, onMessageRendered); } export function cleanupVarCommands() { if (!initialized) return; events?.cleanup(); events = null; initialized = false; } export { MODULE_ID, };