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",
"description": "Squeebot Plugin Management API and sockets",
"tags": ["api", "control", "management"],
"version": "0.0.0",
"version": "0.1.0",
"dependencies": [],
"npmDependencies": []
}

View File

@ -86,248 +86,354 @@ const sc = {
};
*/
const ControlCommands: { [key: string]: Function } = {
loadPlugin: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) {
throw new Error('This function takes 1 argument.');
}
p.stream.emitTo('core', 'pluginLoad', plugin);
},
unloadPlugin: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) {
throw new Error('This function takes 1 argument.');
}
p.stream.emitTo(plugin, 'pluginUnload', plugin);
},
listActivePlugins: async (p: ControlPlugin): Promise<IPluginManifest[]> => {
return p.core!.pluginManager.getLoaded().map((x: IPlugin) => x.manifest);
},
listInstalledPlugins: async (p: ControlPlugin): Promise<IPluginManifest[]> => {
return p.core!.pluginManager.availablePlugins;
},
installPlugin: async (p: ControlPlugin, plugin: string): Promise<IPluginManifest> => {
if (!plugin) {
throw new Error('This function takes 1 argument.');
}
return p.core!.repositoryManager.installPlugin(plugin);
},
uninstallPlugin: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) {
throw new Error('This function takes 1 argument.');
}
return p.core!.repositoryManager.uninstallPlugin(plugin);
},
enablePlugin: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) {
throw new Error('This function takes 1 argument.');
}
if (!p.core!.pluginManager.getAvailableByName(plugin)) {
throw new Error('No such plugin is available.');
}
interface ControlCommand {
execute: Function;
name: string;
plugin: string;
}
if (!p.core!.config.config.enabled) {
p.core!.config.config.enabled = [plugin];
} else if (p.core!.config.config.enabled.indexOf(plugin) === -1) {
p.core!.config.config.enabled.push(plugin);
}
return p.core!.config.save();
},
disablePlugin: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) {
throw new Error('This function takes 1 argument.');
}
if (!p.core!.pluginManager.getAvailableByName(plugin)) {
throw new Error('No such plugin is available.');
}
if (!p.core!.config.config.enabled) {
return;
}
const indx = p.core!.config.config.enabled.indexOf(plugin);
if (indx > -1) {
p.core!.config.config.enabled.splice(indx, 1);
}
return p.core!.config.save();
},
installRepository: async (p: ControlPlugin, url: string): Promise<IRepository> => {
if (!url) {
throw new Error('This function takes 1 argument.');
}
return p.core!.repositoryManager.installRepository(url);
},
uninstallRepository: async (p: ControlPlugin, repo: string): Promise<void> => {
if (!repo) {
throw new Error('This function takes 1 argument.');
}
return p.core!.repositoryManager.uninstallRepository(repo);
},
listRepositoryPlugins: async (p: ControlPlugin, repo: string): Promise<IRepoPluginDef[]> => {
if (!repo) {
throw new Error('This function takes 1 argument.');
}
const repoData = p.core!.repositoryManager.getRepoByName(repo);
if (!repoData) {
throw new Error('No such repository found.');
}
return repoData.plugins;
},
listRepositories: async (p: ControlPlugin): Promise<IRepository[]> => {
return p.core!.repositoryManager.getAll();
},
updateRepository: async (p: ControlPlugin, repo: string): Promise<IPluginManifest[]> => {
if (!repo) {
throw new Error('This function takes 1 argument.');
}
const repoData = p.core!.repositoryManager.getRepoByName(repo);
if (!repoData) {
throw new Error('No such repository found.');
}
return p.core!.repositoryManager.checkForUpdates(repoData);
},
newChannel: async (p: ControlPlugin, name: string, plugins?: string[]): Promise<void> => {
if (!name) {
throw new Error('This function takes 1 argument.');
}
if (p.core!.channelManager.getChannelByName(name)) {
throw new Error('A channel by that name already exists!');
}
p.core!.channelManager.addChannel({
name,
plugins: plugins || [],
enabled: true,
});
},
removeChannel: async (p: ControlPlugin, name: string): Promise<void> => {
if (!name) {
throw new Error('This function takes 1 argument.');
}
const chan = p.core!.channelManager.getChannelByName(name);
if (!chan) {
throw new Error('A channel by that name does not exists!');
}
p.core!.channelManager.removeChannel(chan);
},
enableChannel: async (p: ControlPlugin, name: string): Promise<void> => {
if (!name) {
throw new Error('This function takes 1 argument.');
}
const chan = p.core!.channelManager.getChannelByName(name);
if (!chan) {
throw new Error('A channel by that name does not exists!');
}
chan.enabled = true;
p.core!.config.config.channels = p.core!.channelManager.getAll();
await p.core!.config.save();
},
disableChannel: async (p: ControlPlugin, name: string): Promise<void> => {
if (!name) {
throw new Error('This function takes 1 argument.');
}
const chan = p.core!.channelManager.getChannelByName(name);
if (!chan) {
throw new Error('A channel by that name does not exists!');
}
chan.enabled = false;
p.core!.config.config.channels = p.core!.channelManager.getAll();
await p.core!.config.save();
},
addChannelPlugin: async (p: ControlPlugin, name: string, plugins: string | string[]): Promise<void> => {
if (!name || !plugins) {
throw new Error('This function takes 2 arguments.');
}
const chan = p.core!.channelManager.getChannelByName(name);
if (!chan) {
throw new Error('A channel by that name does not exists!');
}
if (!Array.isArray(plugins)) {
plugins = [plugins];
}
for (const plugin of plugins) {
if (chan.plugins.indexOf(plugin) === -1) {
chan.plugins.push(plugin);
let controlCommands: ControlCommand[] = [
{
name: 'execute',
plugin: 'control',
execute: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) {
throw new Error('This function takes 1 argument.');
}
}
p.stream.emitTo('core', 'pluginLoad', plugin);
},
},
removeChannelPlugin: async (p: ControlPlugin, name: string, plugins: string | string[]): Promise<void> => {
if (!name || !plugins) {
throw new Error('This function takes 2 arguments.');
}
const chan = p.core!.channelManager.getChannelByName(name);
if (!chan) {
throw new Error('A channel by that name does not exists!');
}
if (!Array.isArray(plugins)) {
plugins = [plugins];
}
for (const plugin of plugins) {
const idx = chan.plugins.indexOf(plugin);
if (idx !== -1) {
chan.plugins.splice(idx, 1);
{
name: 'unloadPlugin',
plugin: 'control',
execute: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) {
throw new Error('This function takes 1 argument.');
}
}
p.stream.emitTo(plugin, 'pluginUnload', plugin);
},
},
listChannels: async (p: ControlPlugin): Promise<IChannel[]> => {
return p.core!.channelManager.getAll();
{
name: 'listActivePlugins',
plugin: 'control',
execute: async (p: ControlPlugin): Promise<IPluginManifest[]> => {
return p.core!.pluginManager.getLoaded().map((x: IPlugin) => x.manifest);
},
},
getPluginConfig: async (p: ControlPlugin, name: string): Promise<any> => {
if (!name) {
throw new Error('This function takes 1 argument.');
}
const plugin = p.core!.pluginManager.getLoadedByName(name);
if (!plugin) {
throw new Error('No such plugin is currently running!');
}
return plugin.config.config;
{
name: 'listInstalledPlugins',
plugin: 'control',
execute: async (p: ControlPlugin): Promise<IPluginManifest[]> => {
return p.core!.pluginManager.availablePlugins;
},
},
getPluginConfigValue: async (p: ControlPlugin, name: string, key: string): Promise<any> => {
if (!name || !key) {
throw new Error('This function takes 2 arguments.');
}
const plugin = p.core!.pluginManager.getLoadedByName(name);
if (!plugin) {
throw new Error('No such plugin is currently running!');
}
return plugin.config.get(key);
{
name: 'installPlugin',
plugin: 'control',
execute: async (p: ControlPlugin, plugin: string): Promise<IPluginManifest> => {
if (!plugin) {
throw new Error('This function takes 1 argument.');
}
return p.core!.repositoryManager.installPlugin(plugin);
},
},
getPluginConfigSchema: async (p: ControlPlugin, name: string): Promise<any> => {
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;
{
name: 'uninstallPlugin',
plugin: 'control',
execute: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) {
throw new Error('This function takes 1 argument.');
}
return p.core!.repositoryManager.uninstallPlugin(plugin);
},
},
setPluginConfig: async (p: ControlPlugin, name: string, config: any): Promise<any> => {
if (!name || !config) {
throw new Error('This function takes 2 arguments.');
}
const plugin = p.core!.pluginManager.getLoadedByName(name);
if (!plugin) {
throw new Error('No such plugin is currently running!');
}
plugin.config.config = config;
return plugin.config.save();
{
name: 'enablePlugin',
plugin: 'control',
execute: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) {
throw new Error('This function takes 1 argument.');
}
if (!p.core!.pluginManager.getAvailableByName(plugin)) {
throw new Error('No such plugin is available.');
}
if (!p.core!.config.config.enabled) {
p.core!.config.config.enabled = [plugin];
} else if (p.core!.config.config.enabled.indexOf(plugin) === -1) {
p.core!.config.config.enabled.push(plugin);
}
return p.core!.config.save();
},
},
setPluginConfigValue: async (p: ControlPlugin, name: string, key: string, value: string): Promise<any> => {
if (!name || !key) {
throw new Error('This function takes 3 arguments.');
}
const plugin = p.core!.pluginManager.getLoadedByName(name);
if (!plugin) {
throw new Error('No such plugin is currently running!');
}
plugin.config.set(key, value);
return plugin.config.save();
{
name: 'disablePlugin',
plugin: 'control',
execute: async (p: ControlPlugin, plugin: string): Promise<void> => {
if (!plugin) {
throw new Error('This function takes 1 argument.');
}
if (!p.core!.pluginManager.getAvailableByName(plugin)) {
throw new Error('No such plugin is available.');
}
if (!p.core!.config.config.enabled) {
return;
}
const indx = p.core!.config.config.enabled.indexOf(plugin);
if (indx > -1) {
p.core!.config.config.enabled.splice(indx, 1);
}
return p.core!.config.save();
},
},
};
{
name: 'installRepository',
plugin: 'control',
execute: async (p: ControlPlugin, url: string): Promise<IRepository> => {
if (!url) {
throw new Error('This function takes 1 argument.');
}
return p.core!.repositoryManager.installRepository(url);
},
},
{
name: 'uninstallRepository',
plugin: 'control',
execute: async (p: ControlPlugin, repo: string): Promise<void> => {
if (!repo) {
throw new Error('This function takes 1 argument.');
}
return p.core!.repositoryManager.uninstallRepository(repo);
},
},
{
name: 'listRepositoryPlugins',
plugin: 'control',
execute: async (p: ControlPlugin, repo: string): Promise<IRepoPluginDef[]> => {
if (!repo) {
throw new Error('This function takes 1 argument.');
}
const repoData = p.core!.repositoryManager.getRepoByName(repo);
if (!repoData) {
throw new Error('No such repository found.');
}
return repoData.plugins;
},
},
{
name: 'listRepositories',
plugin: 'control',
execute: async (p: ControlPlugin): Promise<IRepository[]> => {
return p.core!.repositoryManager.getAll();
},
},
{
name: 'updateRepository',
plugin: 'control',
execute: async (p: ControlPlugin, repo: string): Promise<IPluginManifest[]> => {
if (!repo) {
throw new Error('This function takes 1 argument.');
}
const repoData = p.core!.repositoryManager.getRepoByName(repo);
if (!repoData) {
throw new Error('No such repository found.');
}
return p.core!.repositoryManager.checkForUpdates(repoData);
},
},
{
name: 'newChannel',
plugin: 'control',
execute: async (p: ControlPlugin, name: string, plugins?: string[]): Promise<void> => {
if (!name) {
throw new Error('This function takes 1 argument.');
}
if (p.core!.channelManager.getChannelByName(name)) {
throw new Error('A channel by that name already exists!');
}
p.core!.channelManager.addChannel({
name,
plugins: plugins || [],
enabled: true,
});
},
},
{
name: 'removeChannel',
plugin: 'control',
execute: async (p: ControlPlugin, name: string): Promise<void> => {
if (!name) {
throw new Error('This function takes 1 argument.');
}
const chan = p.core!.channelManager.getChannelByName(name);
if (!chan) {
throw new Error('A channel by that name does not exists!');
}
p.core!.channelManager.removeChannel(chan);
},
},
{
name: 'enableChannel',
plugin: 'control',
execute: async (p: ControlPlugin, name: string): Promise<void> => {
if (!name) {
throw new Error('This function takes 1 argument.');
}
const chan = p.core!.channelManager.getChannelByName(name);
if (!chan) {
throw new Error('A channel by that name does not exists!');
}
chan.enabled = true;
p.core!.config.config.channels = p.core!.channelManager.getAll();
await p.core!.config.save();
},
},
{
name: 'disableChannel',
plugin: 'control',
execute: async (p: ControlPlugin, name: string): Promise<void> => {
if (!name) {
throw new Error('This function takes 1 argument.');
}
const chan = p.core!.channelManager.getChannelByName(name);
if (!chan) {
throw new Error('A channel by that name does not exists!');
}
chan.enabled = false;
p.core!.config.config.channels = p.core!.channelManager.getAll();
await p.core!.config.save();
},
},
{
name: 'addChannelPlugin',
plugin: 'control',
execute: async (p: ControlPlugin, name: string, plugins: string | string[]): Promise<void> => {
if (!name || !plugins) {
throw new Error('This function takes 2 arguments.');
}
const chan = p.core!.channelManager.getChannelByName(name);
if (!chan) {
throw new Error('A channel by that name does not exists!');
}
if (!Array.isArray(plugins)) {
plugins = [plugins];
}
for (const plugin of plugins) {
if (chan.plugins.indexOf(plugin) === -1) {
chan.plugins.push(plugin);
}
}
},
},
{
name: 'removeChannelPlugin',
plugin: 'control',
execute: async (p: ControlPlugin, name: string, plugins: string | string[]): Promise<void> => {
if (!name || !plugins) {
throw new Error('This function takes 2 arguments.');
}
const chan = p.core!.channelManager.getChannelByName(name);
if (!chan) {
throw new Error('A channel by that name does not exists!');
}
if (!Array.isArray(plugins)) {
plugins = [plugins];
}
for (const plugin of plugins) {
const idx = chan.plugins.indexOf(plugin);
if (idx !== -1) {
chan.plugins.splice(idx, 1);
}
}
},
},
{
name: 'listChannels',
plugin: 'control',
execute: async (p: ControlPlugin): Promise<IChannel[]> => {
return p.core!.channelManager.getAll();
},
},
{
name: 'getPluginConfig',
plugin: 'control',
execute: async (p: ControlPlugin, name: string): Promise<any> => {
if (!name) {
throw new Error('This function takes 1 argument.');
}
const plugin = p.core!.pluginManager.getLoadedByName(name);
if (!plugin) {
throw new Error('No such plugin is currently running!');
}
return plugin.config.config;
},
},
{
name: 'getPluginConfigValue',
plugin: 'control',
execute: async (p: ControlPlugin, name: string, key: string): Promise<any> => {
if (!name || !key) {
throw new Error('This function takes 2 arguments.');
}
const plugin = p.core!.pluginManager.getLoadedByName(name);
if (!plugin) {
throw new Error('No such plugin is currently running!');
}
return plugin.config.get(key);
},
},
{
name: 'getPluginConfigSchema',
plugin: 'control',
execute: async (p: ControlPlugin, name: string): Promise<any> => {
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;
},
},
{
name: 'setPluginConfig',
plugin: 'control',
execute: async (p: ControlPlugin, name: string, config: any): Promise<any> => {
if (!name || !config) {
throw new Error('This function takes 2 arguments.');
}
const plugin = p.core!.pluginManager.getLoadedByName(name);
if (!plugin) {
throw new Error('No such plugin is currently running!');
}
plugin.config.config = config;
return plugin.config.save();
},
},
{
name: 'setPluginConfigValue',
plugin: 'control',
execute: async (p: ControlPlugin, name: string, key: string, value: string): Promise<any> => {
if (!name || !key) {
throw new Error('This function takes 3 arguments.');
}
const plugin = p.core!.pluginManager.getLoadedByName(name);
if (!plugin) {
throw new Error('No such plugin is currently running!');
}
plugin.config.set(key, value);
return plugin.config.save();
},
},
];
declare type StringAny = {[key: string]: any};
const match = ['key', 'cert', 'ca', 'dhparam', 'crl', 'pfx'];
@ -448,7 +554,7 @@ class ControlPlugin extends Plugin {
socket.setEncoding('utf8');
socket.write(JSON.stringify({
status: 'OK',
commands: Object.keys(ControlCommands),
commands: controlCommands.map(k => k.name),
}) + '\r\n');
socket.on('data', (data) => {
@ -488,6 +594,8 @@ class ControlPlugin extends Plugin {
parseTLSConfig(c.tls).then((options) => {
this.server = tls.createServer(options,
(socket) => this.handleIncoming(socket));
this.server.on('error',
(e) => logger.error('[%s] Secure socket error:', e.message));
this.server.listen(c.bind, () => {
logger.log('[%s] Secure socket listening on %s',
this.name, c.bind.toString());
@ -508,6 +616,7 @@ class ControlPlugin extends Plugin {
}
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, () => {
logger.log('[%s] Socket listening on %s',
this.name, c.bind.toString());
@ -522,10 +631,23 @@ class ControlPlugin extends Plugin {
if (!this.core) {
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');
}
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')
@ -550,6 +672,9 @@ class ControlPlugin extends Plugin {
plugin = plugin.manifest.name;
}
this.plugins.delete(plugin);
controlCommands = controlCommands.filter(k => {
return k.plugin !== plugin;
});
}
}
}

View File

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