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 { 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); | ||||
| } | ||||
|  |  | |||
|  | @ -81,6 +81,7 @@ export interface Argument { | |||
|     name: string; | ||||
|     value: string; | ||||
|     focused: undefined; | ||||
|     options: Argument[]; | ||||
| } | ||||
| 
 | ||||
| export interface Command { | ||||
|  |  | |||
|  | @ -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.")) { | ||||
|  |  | |||
|  | @ -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 }; | ||||
| } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue