promisify, multi-argument interactive mode commands, readme, license

This commit is contained in:
Evert Prants 2020-11-28 22:07:20 +02:00
parent 7633dda7a5
commit 037ccb62c4
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
4 changed files with 224 additions and 112 deletions

19
LICENSE.txt Normal file
View File

@ -0,0 +1,19 @@
The MIT License (MIT)
Copyright © 2020 Evert "Diamond" Prants <evert@lunasqu.ee>
Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the “Software”), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

63
README.md Normal file
View File

@ -0,0 +1,63 @@
# Squeebot CLI
This package provides the runtime and tooling for Squeebot 3.x.x!
This package provides two binaries via npm: `squeebot` and `squeebotd`.
## Running Squeebot
1. Create an environment: `$ squeebot new <name> [<path>]`
2. Execute the environment in interactive mode using `squeebotd`: `$ squeebotd -i <path>/<name>.json`
3. Install plugins (documented below)
`squeebotd` just takes the path to the generated json file and creates all other necessary files and directories by itself.
The primary configuration will be located in `<path>/configs/squeebot.json`.
## Installing plugins
In order to install plugins, you have to add a repository. Repositories are JSON files served over HTTP.
For example, installing core plugins (Interactive mode `-i` on `squeebotd`):
1. `repository install https://(TODO)/repository.json`
2. `plugin install control mqtt`
3. `plugin list`
## Interactive mode commands
The following commands are available when starting in interactive mode:
* `repository help`
* `plugin help`
* `channel help`
* `quit`
## Creating a repository
In order to create a repository, you need to do the following:
1. Create a new repository: `$ squeebot repository new <name> [<path>]`
2. Build your new repository: `$ squeebot repository build <path>`
3. Your built plugins, both archived and unarchived, are in `<path>/.out`.
Repositories are created with a TypeScript build environment by default. If you do not wish to use TypeScript (not recommended) for your plugins,
add the `-t` flag to `squeebot repository new` command.
The build command supports a `-w` argument which will execute the entire build command again when you make changes (watch mode).
**Including deployments, if `-d` is present!**
### Creating plugins
Within your new repository, each directory you make will be a plugin. The plugin **must** contain the following:
1. `plugin.json` - Manifest for your plugin.
2. `plugin.js` - Has to be a JavaScript file which exports a class inherited from `Plugin` at `@squeebot/core/lib/plugin`.
**Note:** `plugin.js` only has to exist in the distribution, so `.ts` is fine, but you have to use `$ squeebot repository build <path>`
to build them into JavaScript files.
Plugin manifest (`plugin.json`) example:
```
{
"name": "plugin-name", // The name of your plugin, must match the name of the directory
"version": "0.0.0", // The version of your plugin, must be semantic versioning!
"description": "", // Optional description for this plugin
"dependencies": [], // List of plugins this plugin depends on
"npmDependencies": [], // List of npm modules this plugin depends on. Supports versions, example 'thing@1.0.0'
}
```
### Deploying the repository
You can configure deployment options using a file called `deployment.json` in your repository root. Currently supported deployment methods:
1. TODO!
In order to activate your configured deployment, use the `-d` flag when building your repository. `-o` flag skips build and deploys immediately.

View File

@ -6,18 +6,17 @@ import { Squeebot } from './core';
export class SqueebotCLI {
constructor(private bot: Squeebot) {}
private checkUpdate(repo: IRepository): void {
this.bot.repositoryManager.checkForUpdates(repo).then((updatable) => {
if (updatable.length) {
logger.log('[%s] The following plugins can be updated:', repo.name,
updatable.map((u) => u.name).join(', '));
} else {
logger.log('[%s] All plugins are up-to-date!', repo.name);
}
}, (e) => logger.error(e.message));
private async checkUpdate(repo: IRepository): Promise<void> {
const updatable = await this.bot.repositoryManager.checkForUpdates(repo);
if (updatable.length) {
logger.log('[%s] The following plugins can be updated:', repo.name,
updatable.map((u) => u.name).join(', '));
} else {
logger.log('[%s] All plugins are up-to-date!', repo.name);
}
}
private repositoryCommand(...args: any[]): void {
private async repositoryCommand(...args: any[]): Promise<void> {
const help = 'repository add <url> | update <name> | remove <name>';
if (!args[0] || args[0] === 'help') {
logger.log(help);
@ -34,9 +33,10 @@ export class SqueebotCLI {
return;
}
this.bot.repositoryManager.installRepository(args[1]).then((repo) => {
for (const urlp of args.slice(1)) {
const repo = await this.bot.repositoryManager.installRepository(urlp);
logger.log('Installed repository %s!', repo.name);
}, (e) => logger.error(e.message));
}
break;
case 'r':
case 'rem':
@ -47,9 +47,10 @@ export class SqueebotCLI {
return;
}
this.bot.repositoryManager.uninstallRepository(args[1]).then(() => {
logger.log('Installed repository %s.', args[1]);
}, (e) => logger.error(e.message));
for (const namep of args.slice(1)) {
await this.bot.repositoryManager.uninstallRepository(namep);
logger.log('Installed repository %s.', namep);
};
break;
case 'u':
case 'upd':
@ -57,25 +58,27 @@ export class SqueebotCLI {
if (!args[1]) {
const repos = this.bot.repositoryManager.getAll();
for (const repo of repos) {
this.checkUpdate(repo);
await this.checkUpdate(repo);
}
return;
}
const repo = this.bot.repositoryManager.getRepoByName(args[1]);
if (!repo) {
logger.error('No such repository found.');
return;
}
for (const namep of args.slice(1)) {
const repo = this.bot.repositoryManager.getRepoByName(namep);
if (!repo) {
logger.error('No such repository "%s" found.', namep);
return;
}
this.checkUpdate(repo);
await this.checkUpdate(repo);
};
break;
default:
logger.log(help);
}
}
private pluginCommand(...args: any[]): void {
private async pluginCommand(...args: any[]): Promise<void> {
const help = 'plugin install | update | uninstall | enable | disable | start | stop | list | running [<name>]';
if (!args[0] || args[0] === 'help' || (!args[1] && args[0] !== 'list' && args[0] !== 'running')) {
logger.log(help);
@ -87,17 +90,18 @@ export class SqueebotCLI {
case 'u':
case 'install':
case 'update':
this.bot.repositoryManager.installPlugin(args[1]).then((mf) => {
for (const name of args.slice(1)) {
const mf = await this.bot.repositoryManager.installPlugin(name);
logger.log('Installed plugin %s version %s!', mf.name, mf.version);
}, (e) => console.error(e.message));
}
break;
case 'uninst':
case 'remove':
case 'uninstall':
this.bot.repositoryManager.uninstallPlugin(args[1]).then(() => {
logger.log('Uninstalled plugin %s.', args[1]);
}, (e) => console.error(e.message));
for (const name of args.slice(1)) {
await this.bot.repositoryManager.uninstallPlugin(name)
logger.log('Uninstalled plugin %s.', name);
}
break;
case 'list':
logger.log('Installed plugins:',
@ -111,73 +115,79 @@ export class SqueebotCLI {
case 'run':
case 'load':
case 'start':
const plugin = this.bot.pluginManager.getAvailableByName(args[1]);
if (!plugin) {
logger.error('No such plugin is available. Maybe try installing it? plugin install', args[1]);
return;
for (const name of args.slice(1)) {
const plugin = this.bot.pluginManager.getAvailableByName(name);
if (!plugin) {
logger.error('"%s" is not available. Maybe try installing it? plugin install', name, name);
return;
}
await this.bot.pluginManager.load(plugin);
logger.log('Started plugin "%s" successfully.', name);
}
this.bot.pluginManager.load(plugin).then((p) => {
logger.log('Started plugin "%s" successfully.', args[1]);
}, (e) => logger.error(e.stack));
break;
case 'stop':
case 'kill':
if (!this.bot.pluginManager.getAvailableByName(args[1])) {
logger.error('No such plugin is available.');
return;
}
for (const name of args.slice(1)) {
if (!this.bot.pluginManager.getAvailableByName(name)) {
logger.error('No such plugin is available.');
return;
}
logger.log('Stopping plugin', args[1]);
this.bot.stream.emitTo(args[1], 'pluginUnload', args[1]);
logger.log('Stopping plugin', name);
this.bot.stream.emitTo(name, 'pluginUnload', name);
}
break;
case 'enable':
if (!this.bot.pluginManager.getAvailableByName(args[1])) {
logger.error('No such plugin is available.');
return;
for (const name of args.slice(1)) {
if (!this.bot.pluginManager.getAvailableByName(name)) {
logger.error('No such plugin "%s" is available.', name);
return;
}
logger.log('Enabling plugin', name);
if (!this.bot.config.config.enabled) {
this.bot.config.config.enabled = [name];
return;
}
if (this.bot.config.config.enabled.indexOf(name) === -1) {
this.bot.config.config.enabled.push(name);
}
}
logger.log('Enabling plugin', args[1]);
if (!this.bot.config.config.enabled) {
this.bot.config.config.enabled = [args[1]];
return;
}
if (this.bot.config.config.enabled.indexOf(args[1]) === -1) {
this.bot.config.config.enabled.push(args[1]);
}
this.bot.config.save();
await this.bot.config.save();
break;
case 'disable':
if (!this.bot.pluginManager.getAvailableByName(args[1])) {
logger.error('No such plugin is available.');
return;
for (const name of args.slice(1)) {
if (!this.bot.pluginManager.getAvailableByName(name)) {
logger.error('No such plugin "%s" is available.', name);
return;
}
logger.log('Disabling plugin', name);
if (!this.bot.config.config.enabled) {
return;
}
const indx = this.bot.config.config.enabled.indexOf(name);
if (indx > -1) {
this.bot.config.config.enabled.splice(indx, 1);
}
}
logger.log('Disabling plugin', args[1]);
if (!this.bot.config.config.enabled) {
return;
}
const indx = this.bot.config.config.enabled.indexOf(args[1]);
if (indx > -1) {
this.bot.config.config.enabled.splice(indx, 1);
}
this.bot.config.save();
await this.bot.config.save();
break;
default:
logger.log(help);
}
}
private channelCommand(...args: any[]): void {
const help = 'channel new | del | addplugin | delplugin [<name>] [<plugin>]';
private async channelCommand(...args: any[]): Promise<void> {
const help = 'channel new | del | list | addplugin | delplugin [<name>] [<plugin>]';
if (!args[0] || args[0] === 'help' || (!args[1] && args[0] !== 'list')) {
logger.log(help);
return;
@ -201,15 +211,17 @@ export class SqueebotCLI {
case 'del':
case 'delete':
case 'remove':
const chan = this.bot.channelManager.getChannelByName(args[1]);
if (!chan) {
logger.error('No such channel exists!');
return;
for (const name of args.slice(1)) {
const chan = this.bot.channelManager.getChannelByName(name);
if (!chan) {
logger.error('No such channel "%s" exists!', name);
return;
}
this.bot.channelManager.removeChannel(chan);
logger.log('Channel "%s" removed!', name);
}
this.bot.channelManager.removeChannel(chan);
logger.log('Channel removed!');
break;
case 'addp':
case 'addplugin':
@ -224,11 +236,18 @@ export class SqueebotCLI {
return;
}
if (chan1.plugins.indexOf(args[2]) !== -1) {
chan1.plugins.push(args[2]);
}
for (const name of args.slice(2)) {
if (chan1.plugins.indexOf(name) === -1) {
chan1.plugins.push(name);
}
logger.log('Plugin added to channel!');
logger.log('Plugin "%s" added to channel!', name);
}
break;
case 'list':
logger.log('Channels:\n', this.bot.channelManager.getAll().map((chan) => {
return ` => ${chan.name}: ${chan.plugins.join(', ')} (${chan.enabled ? 'enabled' : 'disabled'})`;
}).join('\n'));
break;
case 'remp':
case 'delp':
@ -245,37 +264,43 @@ export class SqueebotCLI {
return;
}
const idx = chan2.plugins.indexOf(args[2]);
if (idx !== -1) {
chan2.plugins.splice(idx, 1);
for (const name of args.slice(2)) {
const idx = chan2.plugins.indexOf(args[2]);
if (idx !== -1) {
chan2.plugins.splice(idx, 1);
}
logger.log('Plugin "%s" added to channel!', name);
}
logger.log('Plugin added to channel!');
break;
case 'enable':
const chan3 = this.bot.channelManager.getChannelByName(args[1]);
if (!chan3) {
logger.error('No such channel exists!');
return;
}
for (const name of args.slice(1)) {
const chan = this.bot.channelManager.getChannelByName(name);
if (!chan) {
logger.error('No such channel "%s" exists!', name);
return;
}
chan3.enabled = true;
logger.log('Channel enabled!');
chan.enabled = true;
logger.log('Channel "%s" enabled!', name);
}
break;
case 'disable':
const chan4 = this.bot.channelManager.getChannelByName(args[1]);
if (!chan4) {
logger.error('No such channel exists!');
return;
}
for (const name of args.slice(1)) {
const chan = this.bot.channelManager.getChannelByName(name);
if (!chan) {
logger.error('No such channel "%s" exists!', name);
return;
}
chan4.enabled = false;
logger.log('Channel disabled!');
chan.enabled = false;
logger.log('Channel "%s" disabled!', name);
}
break;
}
this.bot.config.config.channels = this.bot.channelManager.getAll();
this.bot.config.save();
await this.bot.config.save();
}
public attach(rl: ReadLine) {
@ -290,17 +315,20 @@ export class SqueebotCLI {
case 'r':
case 'repo':
case 'repository':
this.repositoryCommand(...split.slice(1));
this.repositoryCommand(...split.slice(1)).catch(
e => logger.error(e.message));
break;
case 'p':
case 'pl':
case 'plugin':
this.pluginCommand(...split.slice(1));
this.pluginCommand(...split.slice(1)).catch(
e => logger.error(e.message));
break;
case 'c':
case 'chan':
case 'channel':
this.channelCommand(...split.slice(1));
this.channelCommand(...split.slice(1)).catch(
e => logger.error(e.message));
break;
case 'stop':
case 'exit':
@ -309,6 +337,6 @@ export class SqueebotCLI {
this.bot.shutdown();
break;
}
})
});
}
}

View File

@ -26,6 +26,7 @@ const tsConfig = {
};
const gitignore = `/node_modules/
/.out/
deployment.json`;
function dummyEnvironment(location: string): IEnvironment {
@ -108,6 +109,7 @@ async function newRepository(
await fs.writeJson(path.join(location, 'tsconfig.json'), tsConfig);
gitIgnore += '\n*.js';
gitIgnore += '\n*.d.ts';
gitIgnore += '\n*.tsbuildinfo';
console.log('Adding TypeScript scripts to package.json');
const pkgjson = path.join(location, 'package.json');