266 lines
8.0 KiB
TypeScript
266 lines
8.0 KiB
TypeScript
import {
|
|
readConfigFileSnapshot,
|
|
validateConfigObject,
|
|
writeConfigFile,
|
|
} from "../../config/config.js";
|
|
import {
|
|
getConfigValueAtPath,
|
|
parseConfigPath,
|
|
setConfigValueAtPath,
|
|
unsetConfigValueAtPath,
|
|
} from "../../config/config-paths.js";
|
|
import {
|
|
getConfigOverrides,
|
|
resetConfigOverrides,
|
|
setConfigOverride,
|
|
unsetConfigOverride,
|
|
} from "../../config/runtime-overrides.js";
|
|
import { resolveChannelConfigWrites } from "../../channels/plugins/config-writes.js";
|
|
import { normalizeChannelId } from "../../channels/registry.js";
|
|
import { logVerbose } from "../../globals.js";
|
|
import type { CommandHandler } from "./commands-types.js";
|
|
import { parseConfigCommand } from "./config-commands.js";
|
|
import { parseDebugCommand } from "./debug-commands.js";
|
|
|
|
export const handleConfigCommand: CommandHandler = async (params, allowTextCommands) => {
|
|
if (!allowTextCommands) return null;
|
|
const configCommand = parseConfigCommand(params.command.commandBodyNormalized);
|
|
if (!configCommand) return null;
|
|
if (!params.command.isAuthorizedSender) {
|
|
logVerbose(
|
|
`Ignoring /config from unauthorized sender: ${params.command.senderId || "<unknown>"}`,
|
|
);
|
|
return { shouldContinue: false };
|
|
}
|
|
if (params.cfg.commands?.config !== true) {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: {
|
|
text: "⚠️ /config is disabled. Set commands.config=true to enable.",
|
|
},
|
|
};
|
|
}
|
|
if (configCommand.action === "error") {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: `⚠️ ${configCommand.message}` },
|
|
};
|
|
}
|
|
|
|
if (configCommand.action === "set" || configCommand.action === "unset") {
|
|
const channelId = params.command.channelId ?? normalizeChannelId(params.command.channel);
|
|
const allowWrites = resolveChannelConfigWrites({
|
|
cfg: params.cfg,
|
|
channelId,
|
|
accountId: params.ctx.AccountId,
|
|
});
|
|
if (!allowWrites) {
|
|
const channelLabel = channelId ?? "this channel";
|
|
const hint = channelId
|
|
? `channels.${channelId}.configWrites=true`
|
|
: "channels.<channel>.configWrites=true";
|
|
return {
|
|
shouldContinue: false,
|
|
reply: {
|
|
text: `⚠️ Config writes are disabled for ${channelLabel}. Set ${hint} to enable.`,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
const snapshot = await readConfigFileSnapshot();
|
|
if (!snapshot.valid || !snapshot.parsed || typeof snapshot.parsed !== "object") {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: {
|
|
text: "⚠️ Config file is invalid; fix it before using /config.",
|
|
},
|
|
};
|
|
}
|
|
const parsedBase = structuredClone(snapshot.parsed as Record<string, unknown>);
|
|
|
|
if (configCommand.action === "show") {
|
|
const pathRaw = configCommand.path?.trim();
|
|
if (pathRaw) {
|
|
const parsedPath = parseConfigPath(pathRaw);
|
|
if (!parsedPath.ok || !parsedPath.path) {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: `⚠️ ${parsedPath.error ?? "Invalid path."}` },
|
|
};
|
|
}
|
|
const value = getConfigValueAtPath(parsedBase, parsedPath.path);
|
|
const rendered = JSON.stringify(value ?? null, null, 2);
|
|
return {
|
|
shouldContinue: false,
|
|
reply: {
|
|
text: `⚙️ Config ${pathRaw}:\n\`\`\`json\n${rendered}\n\`\`\``,
|
|
},
|
|
};
|
|
}
|
|
const json = JSON.stringify(parsedBase, null, 2);
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: `⚙️ Config (raw):\n\`\`\`json\n${json}\n\`\`\`` },
|
|
};
|
|
}
|
|
|
|
if (configCommand.action === "unset") {
|
|
const parsedPath = parseConfigPath(configCommand.path);
|
|
if (!parsedPath.ok || !parsedPath.path) {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: `⚠️ ${parsedPath.error ?? "Invalid path."}` },
|
|
};
|
|
}
|
|
const removed = unsetConfigValueAtPath(parsedBase, parsedPath.path);
|
|
if (!removed) {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: `⚙️ No config value found for ${configCommand.path}.` },
|
|
};
|
|
}
|
|
const validated = validateConfigObject(parsedBase);
|
|
if (!validated.ok) {
|
|
const issue = validated.issues[0];
|
|
return {
|
|
shouldContinue: false,
|
|
reply: {
|
|
text: `⚠️ Config invalid after unset (${issue.path}: ${issue.message}).`,
|
|
},
|
|
};
|
|
}
|
|
await writeConfigFile(validated.config);
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: `⚙️ Config updated: ${configCommand.path} removed.` },
|
|
};
|
|
}
|
|
|
|
if (configCommand.action === "set") {
|
|
const parsedPath = parseConfigPath(configCommand.path);
|
|
if (!parsedPath.ok || !parsedPath.path) {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: `⚠️ ${parsedPath.error ?? "Invalid path."}` },
|
|
};
|
|
}
|
|
setConfigValueAtPath(parsedBase, parsedPath.path, configCommand.value);
|
|
const validated = validateConfigObject(parsedBase);
|
|
if (!validated.ok) {
|
|
const issue = validated.issues[0];
|
|
return {
|
|
shouldContinue: false,
|
|
reply: {
|
|
text: `⚠️ Config invalid after set (${issue.path}: ${issue.message}).`,
|
|
},
|
|
};
|
|
}
|
|
await writeConfigFile(validated.config);
|
|
const valueLabel =
|
|
typeof configCommand.value === "string"
|
|
? `"${configCommand.value}"`
|
|
: JSON.stringify(configCommand.value);
|
|
return {
|
|
shouldContinue: false,
|
|
reply: {
|
|
text: `⚙️ Config updated: ${configCommand.path}=${valueLabel ?? "null"}`,
|
|
},
|
|
};
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
export const handleDebugCommand: CommandHandler = async (params, allowTextCommands) => {
|
|
if (!allowTextCommands) return null;
|
|
const debugCommand = parseDebugCommand(params.command.commandBodyNormalized);
|
|
if (!debugCommand) return null;
|
|
if (!params.command.isAuthorizedSender) {
|
|
logVerbose(
|
|
`Ignoring /debug from unauthorized sender: ${params.command.senderId || "<unknown>"}`,
|
|
);
|
|
return { shouldContinue: false };
|
|
}
|
|
if (params.cfg.commands?.debug !== true) {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: {
|
|
text: "⚠️ /debug is disabled. Set commands.debug=true to enable.",
|
|
},
|
|
};
|
|
}
|
|
if (debugCommand.action === "error") {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: `⚠️ ${debugCommand.message}` },
|
|
};
|
|
}
|
|
if (debugCommand.action === "show") {
|
|
const overrides = getConfigOverrides();
|
|
const hasOverrides = Object.keys(overrides).length > 0;
|
|
if (!hasOverrides) {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: "⚙️ Debug overrides: (none)" },
|
|
};
|
|
}
|
|
const json = JSON.stringify(overrides, null, 2);
|
|
return {
|
|
shouldContinue: false,
|
|
reply: {
|
|
text: `⚙️ Debug overrides (memory-only):\n\`\`\`json\n${json}\n\`\`\``,
|
|
},
|
|
};
|
|
}
|
|
if (debugCommand.action === "reset") {
|
|
resetConfigOverrides();
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: "⚙️ Debug overrides cleared; using config on disk." },
|
|
};
|
|
}
|
|
if (debugCommand.action === "unset") {
|
|
const result = unsetConfigOverride(debugCommand.path);
|
|
if (!result.ok) {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: `⚠️ ${result.error ?? "Invalid path."}` },
|
|
};
|
|
}
|
|
if (!result.removed) {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: {
|
|
text: `⚙️ No debug override found for ${debugCommand.path}.`,
|
|
},
|
|
};
|
|
}
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: `⚙️ Debug override removed for ${debugCommand.path}.` },
|
|
};
|
|
}
|
|
if (debugCommand.action === "set") {
|
|
const result = setConfigOverride(debugCommand.path, debugCommand.value);
|
|
if (!result.ok) {
|
|
return {
|
|
shouldContinue: false,
|
|
reply: { text: `⚠️ ${result.error ?? "Invalid override."}` },
|
|
};
|
|
}
|
|
const valueLabel =
|
|
typeof debugCommand.value === "string"
|
|
? `"${debugCommand.value}"`
|
|
: JSON.stringify(debugCommand.value);
|
|
return {
|
|
shouldContinue: false,
|
|
reply: {
|
|
text: `⚙️ Debug override set: ${debugCommand.path}=${valueLabel ?? "null"}`,
|
|
},
|
|
};
|
|
}
|
|
|
|
return null;
|
|
};
|