From 49b0660f95457b913f6785478dbc5a79d560874b Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Sat, 9 Oct 2021 13:14:12 +0300 Subject: [PATCH] control plugin loads schemas --- control/plugin.json | 2 +- control/plugin.ts | 184 +++++++++++++++++++++++++++++--------------- squeebot.repo.json | 2 +- 3 files changed, 124 insertions(+), 64 deletions(-) diff --git a/control/plugin.json b/control/plugin.json index 06c83f2..ffcbeea 100644 --- a/control/plugin.json +++ b/control/plugin.json @@ -3,7 +3,7 @@ "name": "control", "description": "Squeebot Plugin Management API and sockets", "tags": ["api", "control", "management"], - "version": "0.1.2", + "version": "0.1.3", "dependencies": [], "npmDependencies": [] } diff --git a/control/plugin.ts b/control/plugin.ts index 3722149..abb836a 100644 --- a/control/plugin.ts +++ b/control/plugin.ts @@ -23,7 +23,7 @@ interface ControlCommand { let controlCommands: ControlCommand[] = [ { - name: 'execute', + name: 'loadPlugin', plugin: 'control', execute: async (p: ControlPlugin, plugin: string): Promise => { if (!plugin) { @@ -325,11 +325,7 @@ let controlCommands: ControlCommand[] = [ if (!name) { throw new Error('This function takes 1 argument.'); } - const plugin = p.plugins.get(name); - if (!plugin) { - throw new Error('This plugin has not registered a schema in control.'); - } - return plugin; + return p.getPluginSchema(name); }, }, { @@ -405,6 +401,126 @@ class ControlPlugin extends Plugin { this.addEventListener('core', (core: ISqueebotCore) => this.core = core); this.emitTo('core', 'request-core', this.name); this.createSocket(); + this.getPluginSchema(this.name).catch( + () => logger.error('[control] How embarrasing! control could not load it\'s own schema!') + ); + } + + /** + * Load a plugin schema from it's schema file, if it exists. + * @param name Plugin name + * @returns Plugin schema + */ + public async loadPluginSchema(name: string): Promise { + if (!this.core) { + throw new Error('control could not access the core.'); + } + + const { pluginsPath } = this.core.environment; + const schemaPath = path.join(pluginsPath, name, 'schema.json'); + let schema: any; + + try { + const fileRead = await fs.readFile(schemaPath, { encoding: 'utf8' }); + schema = JSON.parse(fileRead); + } catch (e: any) { + throw new Error('No schema file found, it is not accessible or is not valid JSON.'); + } + + if (!schema.type) { + throw new Error('Schema does not specify what type of object it is referencing.'); + } + + this.plugins.set(name, schema); + return schema; + } + + /** + * Register a plugin's configuration schema directly instead of reading from file. + * @param name Plugin name + * @param confspec Static schema + */ + public registerPluginConfigSchema(name: string, confspec?: any): void { + this.plugins.set(name, confspec); + } + + /** + * Load a plugin's configuration schema from memory or from file. + * @param name Plugin name + * @returns Schema + * @throws Error if schema is not found or is invalid + */ + public async getPluginSchema(name: string): Promise { + if (this.plugins.has(name)) { + return this.plugins.get(name); + } + + return this.loadPluginSchema(name); + } + + /** + * Execute a registered control command. + * @param command Control command + * @param args Control command arguments + * @returns Control command response + */ + public async executeControlCommand(command: string, args: string[]): Promise { + if (!this.core) { + throw new Error('The control plugin cannot control the bot right now.'); + } + const cmdobj = controlCommands.find(k => k.name === command); + if (!cmdobj || !cmdobj.execute) { + throw new Error('No such command'); + } + return cmdobj.execute.call(this, this, ...args); + } + + /** + * Register a new custom control command. + * @param obj ControlCommand object + */ + public registerControlCommand(obj: ControlCommand): void { + if (!obj.execute || !obj.name || !obj.plugin) { + throw new Error('Invalid command object.'); + } + const exists = controlCommands.find(k => k.name === obj.name); + if (exists) { + throw new Error('Control commands should not be overwritten.'); + } + controlCommands.push(obj); + logger.log('[%s] registered control command', this.name, obj.name); + } + + public listControlCommands(): string[] { + return controlCommands.map((command) => command.name); + } + + @EventListener('pluginUnload') + public unloadEventHandler(plugin: string | Plugin): void { + if (plugin === this.name || plugin === this) { + logger.debug('[%s]', this.name, 'shutting down..'); + + if (this.server) { + logger.log('[%s] Stopping socket server..', this.name); + this.server.close(); + for (const sock of this.sockets) { + sock.destroy(); + } + this.sockets.clear(); + } + + this.plugins.clear(); + this.emit('pluginUnloaded', this); + } + } + + @EventListener('pluginUnloaded') + public unloadedEventHandler(plugin: string | Plugin): void { + if (typeof plugin !== 'string') { + plugin = plugin.manifest.name; + } + this.plugins.delete(plugin); + controlCommands = controlCommands.filter(k => k.plugin !== plugin); } private errorToClient(socket: TLSSocket | Socket, error: Error): void { @@ -551,62 +667,6 @@ class ControlPlugin extends Plugin { this.name, c.bind.toString()); }); } - - public registerPluginConfigSchema(name: string, confspec?: any): void { - this.plugins.set(name, confspec); - } - - public async executeControlCommand(command: string, args: string[]): Promise { - if (!this.core) { - throw new Error('The control plugin cannot control the bot right now.'); - } - const cmdobj = controlCommands.find(k => k.name === command); - if (!cmdobj || !cmdobj.execute) { - throw new Error('No such command'); - } - return cmdobj.execute.call(this, this, ...args); - } - - public registerControlCommand(obj: ControlCommand): void { - if (!obj.execute || !obj.name || !obj.plugin) { - throw new Error('Invalid command object.'); - } - const exists = controlCommands.find(k => k.name === obj.name); - if (exists) { - throw new Error('Control commands should not be overwritten.'); - } - controlCommands.push(obj); - logger.log('[%s] registered control command', this.name, obj.name); - } - - @EventListener('pluginUnload') - public unloadEventHandler(plugin: string | Plugin): void { - if (plugin === this.name || plugin === this) { - logger.debug('[%s]', this.name, 'shutting down..'); - - if (this.server) { - logger.log('[%s] Stopping socket server..', this.name); - this.server.close(); - for (const sock of this.sockets) { - sock.destroy(); - } - this.sockets.clear(); - } - - this.plugins.clear(); - this.config.save().then(() => - this.emit('pluginUnloaded', this)); - } - } - - @EventListener('pluginUnloaded') - public unloadedEventHandler(plugin: string | Plugin): void { - if (typeof plugin !== 'string') { - plugin = plugin.manifest.name; - } - this.plugins.delete(plugin); - controlCommands = controlCommands.filter(k => k.plugin !== plugin); - } } module.exports = ControlPlugin; diff --git a/squeebot.repo.json b/squeebot.repo.json index 2a6b3a8..e1d1333 100644 --- a/squeebot.repo.json +++ b/squeebot.repo.json @@ -3,7 +3,7 @@ "plugins": [ { "name": "control", - "version": "0.1.2" + "version": "0.1.3" }, { "name": "cron",