Implement Subcommands; fix errors due to Settings <-> Plugins circular imports (#174)
This commit is contained in:
parent
95aa2d9d8d
commit
d72542405a
4 changed files with 68 additions and 24 deletions
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { makeCodeblock } from "../../utils/misc";
|
import { makeCodeblock } from "../../utils/misc";
|
||||||
import { generateId, sendBotMessage } from "./commandHelpers";
|
import { generateId, sendBotMessage } from "./commandHelpers";
|
||||||
import { ApplicationCommandInputType, ApplicationCommandType, Argument, Command, CommandContext, Option } from "./types";
|
import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, Argument, Command, CommandContext, Option } from "./types";
|
||||||
|
|
||||||
export * from "./commandHelpers";
|
export * from "./commandHelpers";
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
|
@ -79,7 +79,12 @@ export const _handleCommand = function (cmd: Command, args: Argument[], ctx: Com
|
||||||
}
|
}
|
||||||
} as never;
|
} as never;
|
||||||
|
|
||||||
function modifyOpt(opt: Option | Command) {
|
|
||||||
|
/**
|
||||||
|
* Prepare a Command Option for Discord by filling missing fields
|
||||||
|
* @param opt
|
||||||
|
*/
|
||||||
|
export function prepareOption<O extends Option | Command>(opt: O): O {
|
||||||
opt.displayName ||= opt.name;
|
opt.displayName ||= opt.name;
|
||||||
opt.displayDescription ||= opt.description;
|
opt.displayDescription ||= opt.description;
|
||||||
opt.options?.forEach((opt, i, opts) => {
|
opt.options?.forEach((opt, i, opts) => {
|
||||||
|
@ -88,11 +93,36 @@ function modifyOpt(opt: Option | Command) {
|
||||||
else if (opt === ReqPlaceholder) opts[i] = RequiredMessageOption;
|
else if (opt === ReqPlaceholder) opts[i] = RequiredMessageOption;
|
||||||
opt.choices?.forEach(x => x.displayName ||= x.name);
|
opt.choices?.forEach(x => x.displayName ||= x.name);
|
||||||
|
|
||||||
modifyOpt(opts[i]);
|
prepareOption(opts[i]);
|
||||||
|
});
|
||||||
|
return opt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yes, Discord registers individual commands for each subcommand
|
||||||
|
// TODO: This probably doesn't support nested subcommands. If that is ever needed,
|
||||||
|
// investigate
|
||||||
|
function registerSubCommands(cmd: Command, plugin: string) {
|
||||||
|
cmd.options?.forEach(o => {
|
||||||
|
if (o.type !== ApplicationCommandOptionType.SUB_COMMAND)
|
||||||
|
throw new Error("When specifying sub-command options, all options must be sub-commands.");
|
||||||
|
const subCmd = {
|
||||||
|
...cmd,
|
||||||
|
...o,
|
||||||
|
type: ApplicationCommandType.CHAT_INPUT,
|
||||||
|
name: `${cmd.name} ${o.name}`,
|
||||||
|
displayName: `${cmd.name} ${o.name}`,
|
||||||
|
subCommandPath: [{
|
||||||
|
name: o.name,
|
||||||
|
type: o.type,
|
||||||
|
displayName: o.name
|
||||||
|
}],
|
||||||
|
rootCommand: cmd
|
||||||
|
};
|
||||||
|
registerCommand(subCmd as any, plugin);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerCommand(command: Command, plugin: string) {
|
export function registerCommand<C extends Command>(command: C, plugin: string) {
|
||||||
if (!BUILT_IN) {
|
if (!BUILT_IN) {
|
||||||
console.warn(
|
console.warn(
|
||||||
"[CommandsAPI]",
|
"[CommandsAPI]",
|
||||||
|
@ -112,7 +142,13 @@ export function registerCommand(command: Command, plugin: string) {
|
||||||
command.inputType ??= ApplicationCommandInputType.BUILT_IN_TEXT;
|
command.inputType ??= ApplicationCommandInputType.BUILT_IN_TEXT;
|
||||||
command.plugin ||= plugin;
|
command.plugin ||= plugin;
|
||||||
|
|
||||||
modifyOpt(command);
|
prepareOption(command);
|
||||||
|
|
||||||
|
if (command.options?.[0]?.type === ApplicationCommandOptionType.SUB_COMMAND) {
|
||||||
|
registerSubCommands(command, plugin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
commands[command.name] = command;
|
commands[command.name] = command;
|
||||||
BUILT_IN.push(command);
|
BUILT_IN.push(command);
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,7 @@ export interface Argument {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
focused: undefined;
|
focused: undefined;
|
||||||
|
options: Argument[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Command {
|
export interface Command {
|
||||||
|
|
|
@ -42,12 +42,6 @@ const DefaultSettings: Settings = {
|
||||||
plugins: {}
|
plugins: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const plugin in plugins) {
|
|
||||||
DefaultSettings.plugins[plugin] = {
|
|
||||||
enabled: plugins[plugin].required ?? false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)) as Settings;
|
var settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)) as Settings;
|
||||||
mergeDefaults(settings, DefaultSettings);
|
mergeDefaults(settings, DefaultSettings);
|
||||||
|
@ -60,13 +54,19 @@ type SubscriptionCallback = ((newValue: any, path: string) => void) & { _path?:
|
||||||
const subscriptions = new Set<SubscriptionCallback>();
|
const subscriptions = new Set<SubscriptionCallback>();
|
||||||
|
|
||||||
// Wraps the passed settings object in a Proxy to nicely handle change listeners and default values
|
// Wraps the passed settings object in a Proxy to nicely handle change listeners and default values
|
||||||
function makeProxy(settings: Settings, root = settings, path = ""): Settings {
|
function makeProxy(settings: any, root = settings, path = ""): Settings {
|
||||||
return new Proxy(settings, {
|
return new Proxy(settings, {
|
||||||
get(target, p: string) {
|
get(target, p: string) {
|
||||||
const v = target[p];
|
const v = target[p];
|
||||||
|
|
||||||
// using "in" is important in the following cases to properly handle falsy or nullish values
|
// using "in" is important in the following cases to properly handle falsy or nullish values
|
||||||
if (!(p in target)) {
|
if (!(p in target)) {
|
||||||
|
// Return empty for plugins with no settings
|
||||||
|
if (path === "plugins" && p in plugins)
|
||||||
|
return target[p] = makeProxy({
|
||||||
|
enabled: plugins[p].required ?? false
|
||||||
|
}, root, `plugins/${p}`);
|
||||||
|
|
||||||
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
||||||
// the default value.
|
// the default value.
|
||||||
if (path.startsWith("plugins.")) {
|
if (path.startsWith("plugins.")) {
|
||||||
|
|
|
@ -28,16 +28,23 @@ const logger = new Logger("PluginManager", "#a6d189");
|
||||||
export const plugins = Plugins;
|
export const plugins = Plugins;
|
||||||
export const patches = [] as Patch[];
|
export const patches = [] as Patch[];
|
||||||
|
|
||||||
for (const plugin of Object.values(Plugins)) if (plugin.patches && Settings.plugins[plugin.name].enabled) {
|
export function isPluginEnabled(p: string) {
|
||||||
for (const patch of plugin.patches) {
|
return (Settings.plugins[p]?.enabled || Plugins[p]?.required) ?? false;
|
||||||
patch.plugin = plugin.name;
|
}
|
||||||
if (!Array.isArray(patch.replacement)) patch.replacement = [patch.replacement];
|
|
||||||
|
for (const p of Object.values(Plugins))
|
||||||
|
if (p.patches && isPluginEnabled(p.name)) {
|
||||||
|
for (const patch of p.patches) {
|
||||||
|
patch.plugin = p.name;
|
||||||
|
if (!Array.isArray(patch.replacement))
|
||||||
|
patch.replacement = [patch.replacement];
|
||||||
patches.push(patch);
|
patches.push(patch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function startAllPlugins() {
|
export function startAllPlugins() {
|
||||||
for (const name in Plugins) if (Settings.plugins[name].enabled) {
|
for (const name in Plugins)
|
||||||
|
if (isPluginEnabled(name)) {
|
||||||
startPlugin(Plugins[name]);
|
startPlugin(Plugins[name]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +52,7 @@ export function startAllPlugins() {
|
||||||
export function startDependenciesRecursive(p: Plugin) {
|
export function startDependenciesRecursive(p: Plugin) {
|
||||||
let restartNeeded = false;
|
let restartNeeded = false;
|
||||||
const failures: string[] = [];
|
const failures: string[] = [];
|
||||||
if (p.dependencies) for (const dep of p.dependencies) {
|
p.dependencies?.forEach(dep => {
|
||||||
if (!Settings.plugins[dep].enabled) {
|
if (!Settings.plugins[dep].enabled) {
|
||||||
startDependenciesRecursive(Plugins[dep]);
|
startDependenciesRecursive(Plugins[dep]);
|
||||||
// If the plugin has patches, don't start the plugin, just enable it.
|
// If the plugin has patches, don't start the plugin, just enable it.
|
||||||
|
@ -53,12 +60,12 @@ export function startDependenciesRecursive(p: Plugin) {
|
||||||
logger.warn(`Enabling dependency ${dep} requires restart.`);
|
logger.warn(`Enabling dependency ${dep} requires restart.`);
|
||||||
Settings.plugins[dep].enabled = true;
|
Settings.plugins[dep].enabled = true;
|
||||||
restartNeeded = true;
|
restartNeeded = true;
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
const result = startPlugin(Plugins[dep]);
|
const result = startPlugin(Plugins[dep]);
|
||||||
if (!result) failures.push(dep);
|
if (!result) failures.push(dep);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
return { restartNeeded, failures };
|
return { restartNeeded, failures };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue