aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVen <vendicated@riseup.net>2022-10-29 20:45:31 +0200
committerGitHub <noreply@github.com>2022-10-29 20:45:31 +0200
commitd72542405af8873a542b55128d8b0c6311183235 (patch)
treecaa295bcec18eb56ca2360558130fc0c9c7aa047 /src
parent95aa2d9d8d850c27e88fce76f065d5e71c8022c2 (diff)
downloadVencord-d72542405af8873a542b55128d8b0c6311183235.tar.gz
Vencord-d72542405af8873a542b55128d8b0c6311183235.tar.bz2
Vencord-d72542405af8873a542b55128d8b0c6311183235.zip
Implement Subcommands; fix errors due to Settings <-> Plugins circular imports (#174)
Diffstat (limited to 'src')
-rw-r--r--src/api/Commands/index.ts46
-rw-r--r--src/api/Commands/types.ts1
-rw-r--r--src/api/settings.ts14
-rw-r--r--src/plugins/index.ts31
4 files changed, 68 insertions, 24 deletions
diff --git a/src/api/Commands/index.ts b/src/api/Commands/index.ts
index 3b42379..a20ac50 100644
--- a/src/api/Commands/index.ts
+++ b/src/api/Commands/index.ts
@@ -18,7 +18,7 @@
import { makeCodeblock } from "../../utils/misc";
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 "./types";
@@ -79,7 +79,12 @@ export const _handleCommand = function (cmd: Command, args: Argument[], ctx: Com
}
} 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.displayDescription ||= opt.description;
opt.options?.forEach((opt, i, opts) => {
@@ -88,11 +93,36 @@ function modifyOpt(opt: Option | Command) {
else if (opt === ReqPlaceholder) opts[i] = RequiredMessageOption;
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) {
console.warn(
"[CommandsAPI]",
@@ -112,7 +142,13 @@ export function registerCommand(command: Command, plugin: string) {
command.inputType ??= ApplicationCommandInputType.BUILT_IN_TEXT;
command.plugin ||= plugin;
- modifyOpt(command);
+ prepareOption(command);
+
+ if (command.options?.[0]?.type === ApplicationCommandOptionType.SUB_COMMAND) {
+ registerSubCommands(command, plugin);
+ return;
+ }
+
commands[command.name] = command;
BUILT_IN.push(command);
}
diff --git a/src/api/Commands/types.ts b/src/api/Commands/types.ts
index a40353f..9acab66 100644
--- a/src/api/Commands/types.ts
+++ b/src/api/Commands/types.ts
@@ -81,6 +81,7 @@ export interface Argument {
name: string;
value: string;
focused: undefined;
+ options: Argument[];
}
export interface Command {
diff --git a/src/api/settings.ts b/src/api/settings.ts
index e25572f..9e518c6 100644
--- a/src/api/settings.ts
+++ b/src/api/settings.ts
@@ -42,12 +42,6 @@ const DefaultSettings: Settings = {
plugins: {}
};
-for (const plugin in plugins) {
- DefaultSettings.plugins[plugin] = {
- enabled: plugins[plugin].required ?? false
- };
-}
-
try {
var settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)) as Settings;
mergeDefaults(settings, DefaultSettings);
@@ -60,13 +54,19 @@ type SubscriptionCallback = ((newValue: any, path: string) => void) & { _path?:
const subscriptions = new Set<SubscriptionCallback>();
// 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, {
get(target, p: string) {
const v = target[p];
// using "in" is important in the following cases to properly handle falsy or nullish values
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
// the default value.
if (path.startsWith("plugins.")) {
diff --git a/src/plugins/index.ts b/src/plugins/index.ts
index fdb256c..be6fae3 100644
--- a/src/plugins/index.ts
+++ b/src/plugins/index.ts
@@ -28,24 +28,31 @@ const logger = new Logger("PluginManager", "#a6d189");
export const plugins = Plugins;
export const patches = [] as Patch[];
-for (const plugin of Object.values(Plugins)) if (plugin.patches && Settings.plugins[plugin.name].enabled) {
- for (const patch of plugin.patches) {
- patch.plugin = plugin.name;
- if (!Array.isArray(patch.replacement)) patch.replacement = [patch.replacement];
- patches.push(patch);
- }
+export function isPluginEnabled(p: string) {
+ return (Settings.plugins[p]?.enabled || Plugins[p]?.required) ?? false;
}
-export function startAllPlugins() {
- for (const name in Plugins) if (Settings.plugins[name].enabled) {
- startPlugin(Plugins[name]);
+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);
+ }
}
+
+export function startAllPlugins() {
+ for (const name in Plugins)
+ if (isPluginEnabled(name)) {
+ startPlugin(Plugins[name]);
+ }
}
export function startDependenciesRecursive(p: Plugin) {
let restartNeeded = false;
const failures: string[] = [];
- if (p.dependencies) for (const dep of p.dependencies) {
+ p.dependencies?.forEach(dep => {
if (!Settings.plugins[dep].enabled) {
startDependenciesRecursive(Plugins[dep]);
// 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.`);
Settings.plugins[dep].enabled = true;
restartNeeded = true;
- continue;
+ return;
}
const result = startPlugin(Plugins[dep]);
if (!result) failures.push(dep);
}
- }
+ });
return { restartNeeded, failures };
}