plugin-registerable control commands

This commit is contained in:
Evert Prants 2021-01-31 15:22:49 +02:00
parent 51561a1d42
commit 293f949afc
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
3 changed files with 363 additions and 238 deletions

View File

@ -3,7 +3,7 @@
"name": "control", "name": "control",
"description": "Squeebot Plugin Management API and sockets", "description": "Squeebot Plugin Management API and sockets",
"tags": ["api", "control", "management"], "tags": ["api", "control", "management"],
"version": "0.0.0", "version": "0.1.0",
"dependencies": [], "dependencies": [],
"npmDependencies": [] "npmDependencies": []
} }

View File

@ -86,38 +86,71 @@ const sc = {
}; };
*/ */
const ControlCommands: { [key: string]: Function } = { interface ControlCommand {
loadPlugin: async (p: ControlPlugin, plugin: string): Promise<void> => { execute: Function;
name: string;
plugin: string;
}
let controlCommands: ControlCommand[] = [
{
name: 'execute',
plugin: 'control',
execute: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) { if (!plugin) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
p.stream.emitTo('core', 'pluginLoad', plugin); p.stream.emitTo('core', 'pluginLoad', plugin);
}, },
unloadPlugin: async (p: ControlPlugin, plugin: string): Promise<void> => { },
{
name: 'unloadPlugin',
plugin: 'control',
execute: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) { if (!plugin) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
p.stream.emitTo(plugin, 'pluginUnload', plugin); p.stream.emitTo(plugin, 'pluginUnload', plugin);
}, },
listActivePlugins: async (p: ControlPlugin): Promise<IPluginManifest[]> => { },
{
name: 'listActivePlugins',
plugin: 'control',
execute: async (p: ControlPlugin): Promise<IPluginManifest[]> => {
return p.core!.pluginManager.getLoaded().map((x: IPlugin) => x.manifest); return p.core!.pluginManager.getLoaded().map((x: IPlugin) => x.manifest);
}, },
listInstalledPlugins: async (p: ControlPlugin): Promise<IPluginManifest[]> => { },
{
name: 'listInstalledPlugins',
plugin: 'control',
execute: async (p: ControlPlugin): Promise<IPluginManifest[]> => {
return p.core!.pluginManager.availablePlugins; return p.core!.pluginManager.availablePlugins;
}, },
installPlugin: async (p: ControlPlugin, plugin: string): Promise<IPluginManifest> => { },
{
name: 'installPlugin',
plugin: 'control',
execute: async (p: ControlPlugin, plugin: string): Promise<IPluginManifest> => {
if (!plugin) { if (!plugin) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
return p.core!.repositoryManager.installPlugin(plugin); return p.core!.repositoryManager.installPlugin(plugin);
}, },
uninstallPlugin: async (p: ControlPlugin, plugin: string): Promise<void> => { },
{
name: 'uninstallPlugin',
plugin: 'control',
execute: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) { if (!plugin) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
return p.core!.repositoryManager.uninstallPlugin(plugin); return p.core!.repositoryManager.uninstallPlugin(plugin);
}, },
enablePlugin: async (p: ControlPlugin, plugin: string): Promise<void> => { },
{
name: 'enablePlugin',
plugin: 'control',
execute: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) { if (!plugin) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
@ -133,7 +166,11 @@ const ControlCommands: { [key: string]: Function } = {
return p.core!.config.save(); return p.core!.config.save();
}, },
disablePlugin: async (p: ControlPlugin, plugin: string): Promise<void> => { },
{
name: 'disablePlugin',
plugin: 'control',
execute: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) { if (!plugin) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
@ -151,19 +188,31 @@ const ControlCommands: { [key: string]: Function } = {
return p.core!.config.save(); return p.core!.config.save();
}, },
installRepository: async (p: ControlPlugin, url: string): Promise<IRepository> => { },
{
name: 'installRepository',
plugin: 'control',
execute: async (p: ControlPlugin, url: string): Promise<IRepository> => {
if (!url) { if (!url) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
return p.core!.repositoryManager.installRepository(url); return p.core!.repositoryManager.installRepository(url);
}, },
uninstallRepository: async (p: ControlPlugin, repo: string): Promise<void> => { },
{
name: 'uninstallRepository',
plugin: 'control',
execute: async (p: ControlPlugin, repo: string): Promise<void> => {
if (!repo) { if (!repo) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
return p.core!.repositoryManager.uninstallRepository(repo); return p.core!.repositoryManager.uninstallRepository(repo);
}, },
listRepositoryPlugins: async (p: ControlPlugin, repo: string): Promise<IRepoPluginDef[]> => { },
{
name: 'listRepositoryPlugins',
plugin: 'control',
execute: async (p: ControlPlugin, repo: string): Promise<IRepoPluginDef[]> => {
if (!repo) { if (!repo) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
@ -173,10 +222,18 @@ const ControlCommands: { [key: string]: Function } = {
} }
return repoData.plugins; return repoData.plugins;
}, },
listRepositories: async (p: ControlPlugin): Promise<IRepository[]> => { },
{
name: 'listRepositories',
plugin: 'control',
execute: async (p: ControlPlugin): Promise<IRepository[]> => {
return p.core!.repositoryManager.getAll(); return p.core!.repositoryManager.getAll();
}, },
updateRepository: async (p: ControlPlugin, repo: string): Promise<IPluginManifest[]> => { },
{
name: 'updateRepository',
plugin: 'control',
execute: async (p: ControlPlugin, repo: string): Promise<IPluginManifest[]> => {
if (!repo) { if (!repo) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
@ -186,7 +243,11 @@ const ControlCommands: { [key: string]: Function } = {
} }
return p.core!.repositoryManager.checkForUpdates(repoData); return p.core!.repositoryManager.checkForUpdates(repoData);
}, },
newChannel: async (p: ControlPlugin, name: string, plugins?: string[]): Promise<void> => { },
{
name: 'newChannel',
plugin: 'control',
execute: async (p: ControlPlugin, name: string, plugins?: string[]): Promise<void> => {
if (!name) { if (!name) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
@ -200,7 +261,11 @@ const ControlCommands: { [key: string]: Function } = {
enabled: true, enabled: true,
}); });
}, },
removeChannel: async (p: ControlPlugin, name: string): Promise<void> => { },
{
name: 'removeChannel',
plugin: 'control',
execute: async (p: ControlPlugin, name: string): Promise<void> => {
if (!name) { if (!name) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
@ -211,7 +276,11 @@ const ControlCommands: { [key: string]: Function } = {
p.core!.channelManager.removeChannel(chan); p.core!.channelManager.removeChannel(chan);
}, },
enableChannel: async (p: ControlPlugin, name: string): Promise<void> => { },
{
name: 'enableChannel',
plugin: 'control',
execute: async (p: ControlPlugin, name: string): Promise<void> => {
if (!name) { if (!name) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
@ -224,7 +293,11 @@ const ControlCommands: { [key: string]: Function } = {
p.core!.config.config.channels = p.core!.channelManager.getAll(); p.core!.config.config.channels = p.core!.channelManager.getAll();
await p.core!.config.save(); await p.core!.config.save();
}, },
disableChannel: async (p: ControlPlugin, name: string): Promise<void> => { },
{
name: 'disableChannel',
plugin: 'control',
execute: async (p: ControlPlugin, name: string): Promise<void> => {
if (!name) { if (!name) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
@ -237,7 +310,11 @@ const ControlCommands: { [key: string]: Function } = {
p.core!.config.config.channels = p.core!.channelManager.getAll(); p.core!.config.config.channels = p.core!.channelManager.getAll();
await p.core!.config.save(); await p.core!.config.save();
}, },
addChannelPlugin: async (p: ControlPlugin, name: string, plugins: string | string[]): Promise<void> => { },
{
name: 'addChannelPlugin',
plugin: 'control',
execute: async (p: ControlPlugin, name: string, plugins: string | string[]): Promise<void> => {
if (!name || !plugins) { if (!name || !plugins) {
throw new Error('This function takes 2 arguments.'); throw new Error('This function takes 2 arguments.');
} }
@ -254,7 +331,11 @@ const ControlCommands: { [key: string]: Function } = {
} }
} }
}, },
removeChannelPlugin: async (p: ControlPlugin, name: string, plugins: string | string[]): Promise<void> => { },
{
name: 'removeChannelPlugin',
plugin: 'control',
execute: async (p: ControlPlugin, name: string, plugins: string | string[]): Promise<void> => {
if (!name || !plugins) { if (!name || !plugins) {
throw new Error('This function takes 2 arguments.'); throw new Error('This function takes 2 arguments.');
} }
@ -272,10 +353,18 @@ const ControlCommands: { [key: string]: Function } = {
} }
} }
}, },
listChannels: async (p: ControlPlugin): Promise<IChannel[]> => { },
{
name: 'listChannels',
plugin: 'control',
execute: async (p: ControlPlugin): Promise<IChannel[]> => {
return p.core!.channelManager.getAll(); return p.core!.channelManager.getAll();
}, },
getPluginConfig: async (p: ControlPlugin, name: string): Promise<any> => { },
{
name: 'getPluginConfig',
plugin: 'control',
execute: async (p: ControlPlugin, name: string): Promise<any> => {
if (!name) { if (!name) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
@ -285,7 +374,11 @@ const ControlCommands: { [key: string]: Function } = {
} }
return plugin.config.config; return plugin.config.config;
}, },
getPluginConfigValue: async (p: ControlPlugin, name: string, key: string): Promise<any> => { },
{
name: 'getPluginConfigValue',
plugin: 'control',
execute: async (p: ControlPlugin, name: string, key: string): Promise<any> => {
if (!name || !key) { if (!name || !key) {
throw new Error('This function takes 2 arguments.'); throw new Error('This function takes 2 arguments.');
} }
@ -295,7 +388,11 @@ const ControlCommands: { [key: string]: Function } = {
} }
return plugin.config.get(key); return plugin.config.get(key);
}, },
getPluginConfigSchema: async (p: ControlPlugin, name: string): Promise<any> => { },
{
name: 'getPluginConfigSchema',
plugin: 'control',
execute: async (p: ControlPlugin, name: string): Promise<any> => {
if (!name) { if (!name) {
throw new Error('This function takes 1 argument.'); throw new Error('This function takes 1 argument.');
} }
@ -305,7 +402,11 @@ const ControlCommands: { [key: string]: Function } = {
} }
return plugin; return plugin;
}, },
setPluginConfig: async (p: ControlPlugin, name: string, config: any): Promise<any> => { },
{
name: 'setPluginConfig',
plugin: 'control',
execute: async (p: ControlPlugin, name: string, config: any): Promise<any> => {
if (!name || !config) { if (!name || !config) {
throw new Error('This function takes 2 arguments.'); throw new Error('This function takes 2 arguments.');
} }
@ -316,7 +417,11 @@ const ControlCommands: { [key: string]: Function } = {
plugin.config.config = config; plugin.config.config = config;
return plugin.config.save(); return plugin.config.save();
}, },
setPluginConfigValue: async (p: ControlPlugin, name: string, key: string, value: string): Promise<any> => { },
{
name: 'setPluginConfigValue',
plugin: 'control',
execute: async (p: ControlPlugin, name: string, key: string, value: string): Promise<any> => {
if (!name || !key) { if (!name || !key) {
throw new Error('This function takes 3 arguments.'); throw new Error('This function takes 3 arguments.');
} }
@ -327,7 +432,8 @@ const ControlCommands: { [key: string]: Function } = {
plugin.config.set(key, value); plugin.config.set(key, value);
return plugin.config.save(); return plugin.config.save();
}, },
}; },
];
declare type StringAny = {[key: string]: any}; declare type StringAny = {[key: string]: any};
const match = ['key', 'cert', 'ca', 'dhparam', 'crl', 'pfx']; const match = ['key', 'cert', 'ca', 'dhparam', 'crl', 'pfx'];
@ -448,7 +554,7 @@ class ControlPlugin extends Plugin {
socket.setEncoding('utf8'); socket.setEncoding('utf8');
socket.write(JSON.stringify({ socket.write(JSON.stringify({
status: 'OK', status: 'OK',
commands: Object.keys(ControlCommands), commands: controlCommands.map(k => k.name),
}) + '\r\n'); }) + '\r\n');
socket.on('data', (data) => { socket.on('data', (data) => {
@ -488,6 +594,8 @@ class ControlPlugin extends Plugin {
parseTLSConfig(c.tls).then((options) => { parseTLSConfig(c.tls).then((options) => {
this.server = tls.createServer(options, this.server = tls.createServer(options,
(socket) => this.handleIncoming(socket)); (socket) => this.handleIncoming(socket));
this.server.on('error',
(e) => logger.error('[%s] Secure socket error:', e.message));
this.server.listen(c.bind, () => { this.server.listen(c.bind, () => {
logger.log('[%s] Secure socket listening on %s', logger.log('[%s] Secure socket listening on %s',
this.name, c.bind.toString()); this.name, c.bind.toString());
@ -508,6 +616,7 @@ class ControlPlugin extends Plugin {
} }
this.server = net.createServer((socket) => this.handleIncoming(socket)); this.server = net.createServer((socket) => this.handleIncoming(socket));
this.server.on('error', (e) => logger.error('[%s] Socket error:', e.message));
this.server.listen(c.bind, () => { this.server.listen(c.bind, () => {
logger.log('[%s] Socket listening on %s', logger.log('[%s] Socket listening on %s',
this.name, c.bind.toString()); this.name, c.bind.toString());
@ -522,10 +631,23 @@ class ControlPlugin extends Plugin {
if (!this.core) { if (!this.core) {
throw new Error('The control plugin cannot control the bot right now.'); throw new Error('The control plugin cannot control the bot right now.');
} }
if (!(command in ControlCommands)) { const cmdobj = controlCommands.find(k => k.name === command);
if (!cmdobj || !cmdobj.execute) {
throw new Error('No such command'); throw new Error('No such command');
} }
return ControlCommands[command].call(this, this, ...args); 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') @EventListener('pluginUnload')
@ -550,6 +672,9 @@ class ControlPlugin extends Plugin {
plugin = plugin.manifest.name; plugin = plugin.manifest.name;
} }
this.plugins.delete(plugin); this.plugins.delete(plugin);
controlCommands = controlCommands.filter(k => {
return k.plugin !== plugin;
});
} }
} }
} }

View File

@ -3,15 +3,15 @@
"plugins": [ "plugins": [
{ {
"name": "control", "name": "control",
"version": "0.0.0" "version": "0.1.0"
}, },
{ {
"name": "permissions", "name": "permissions",
"version": "0.0.0" "version": "0.1.0"
}, },
{ {
"name": "simplecommands", "name": "simplecommands",
"version": "1.1.0" "version": "1.1.1"
} }
], ],
"typescript": true "typescript": true