Add support for different plugin sources; add support for installing plugins from npm

cshd
Dragory 2020-10-04 16:46:43 +03:00
parent e6bdc4cd8c
commit d8c531cb4d
No known key found for this signature in database
GPG Key ID: 5F387BA66DF8AAC1
6 changed files with 1104 additions and 111 deletions

1051
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -34,6 +34,7 @@
"moment": "^2.27.0",
"mv": "^2.1.1",
"mysql2": "^2.1.0",
"pacote": "^11.1.11",
"public-ip": "^4.0.2",
"sqlite3": "^5.0.0",
"tmp": "^0.1.0",

View File

@ -7,29 +7,13 @@ const knex = require("./knex");
const {messageQueue} = require("./queue");
const utils = require("./utils");
const { createCommandManager } = require("./commands");
const { getPluginAPI, loadPlugin } = require("./plugins");
const { getPluginAPI, installPlugins, loadPlugins } = require("./plugins");
const { callBeforeNewThreadHooks } = require("./hooks/beforeNewThread");
const blocked = require("./data/blocked");
const threads = require("./data/threads");
const updates = require("./data/updates");
const reply = require("./modules/reply");
const close = require("./modules/close");
const snippets = require("./modules/snippets");
const logs = require("./modules/logs");
const move = require("./modules/move");
const block = require("./modules/block");
const suspend = require("./modules/suspend");
const { plugin: webserver } = require("./modules/webserver");
const greeting = require("./modules/greeting");
const typingProxy = require("./modules/typingProxy");
const version = require("./modules/version");
const newthread = require("./modules/newthread");
const idModule = require("./modules/id");
const alert = require("./modules/alert");
const joinLeaveNotification = require("./modules/joinLeaveNotification");
const {ACCIDENTAL_THREAD_MESSAGES} = require("./data/constants");
module.exports = {
@ -301,36 +285,29 @@ async function initPlugins() {
// Load plugins
const builtInPlugins = [
reply,
close,
logs,
block,
move,
snippets,
suspend,
greeting,
webserver,
typingProxy,
version,
newthread,
idModule,
alert,
joinLeaveNotification
"file:./src/modules/reply",
"file:./src/modules/close",
"file:./src/modules/logs",
"file:./src/modules/block",
"file:./src/modules/move",
"file:./src/modules/snippets",
"file:./src/modules/suspend",
"file:./src/modules/greeting",
"file:./src/modules/webserverPlugin",
"file:./src/modules/typingProxy",
"file:./src/modules/version",
"file:./src/modules/newthread",
"file:./src/modules/id",
"file:./src/modules/alert",
"file:./src/modules/joinLeaveNotification",
];
const plugins = [...builtInPlugins];
const plugins = [...builtInPlugins, ...config.plugins];
if (config.plugins && config.plugins.length) {
for (const plugin of config.plugins) {
const pluginFn = require(`../${plugin}`);
plugins.push(pluginFn);
}
}
await installPlugins(plugins);
const pluginApi = getPluginAPI({ bot, knex, config, commands });
for (const plugin of plugins) {
await loadPlugin(plugin, pluginApi);
}
await loadPlugins(plugins, pluginApi);
if (config.updateNotifications) {
updates.startVersionRefreshLoop();

View File

@ -64,9 +64,4 @@ server.on("error", err => {
console.log("[WARN] Web server error:", err.message);
});
module.exports = {
server,
plugin() {
server.listen(config.port);
},
};
module.exports = server;

View File

@ -0,0 +1,5 @@
const server = require("./webserver");
module.exports = ({ config }) => {
server.listen(config.port);
};

View File

@ -4,8 +4,94 @@ const { beforeNewThread } = require("./hooks/beforeNewThread");
const { afterThreadClose } = require("./hooks/afterThreadClose");
const formats = require("./formatters");
const { server: webserver } = require("./modules/webserver");
const childProcess = require("child_process");
const pacote = require("pacote");
const path = require("path");
const pluginSources = {
npm: {
install(plugins) {
return new Promise((resolve, reject) => {
console.log(`Installing ${plugins.length} plugins from NPM...`);
let stderr = "";
const npmProcess = childProcess.spawn("npm", ["install", "--no-save", ...plugins], { cwd: process.cwd() });
npmProcess.stderr.on("data", data => { stderr += String(data) });
npmProcess.on("close", code => {
if (code !== 0) {
return reject(new Error(stderr));
}
return resolve();
});
});
},
async load(plugin, pluginApi) {
const manifest = await pacote.manifest(plugin);
const packageName = manifest.name;
const pluginFn = require(packageName);
if (typeof pluginFn !== "function") {
throw new Error(`Plugin '${plugin}' is not a valid plugin`);
}
return pluginFn(pluginApi);
},
},
file: {
install(plugins) {},
load(plugin, pluginApi) {
const requirePath = path.join(__dirname, "..", plugin);
const pluginFn = require(requirePath);
if (typeof pluginFn !== "function") {
throw new Error(`Plugin '${plugin}' is not a valid plugin`);
}
return pluginFn(pluginApi);
},
}
};
const defaultPluginSource = "file";
function splitPluginSource(pluginName) {
for (const pluginSource of Object.keys(pluginSources)) {
if (pluginName.startsWith(`${pluginSource}:`)) {
return {
source: pluginSource,
plugin: pluginName.slice(pluginSource.length + 1),
};
}
}
return {
source: defaultPluginSource,
plugin: pluginName,
};
}
module.exports = {
async installPlugins(plugins) {
const pluginsBySource = {};
for (const pluginName of plugins) {
const { source, plugin } = splitPluginSource(pluginName);
pluginsBySource[source] = pluginsBySource[source] || [];
pluginsBySource[source].push(plugin);
}
for (const [source, sourcePlugins] of Object.entries(pluginsBySource)) {
await pluginSources[source].install(sourcePlugins);
}
},
async loadPlugins(plugins, pluginApi) {
for (const pluginName of plugins) {
const { source, plugin } = splitPluginSource(pluginName);
await pluginSources[source].load(plugin, pluginApi);
}
},
/**
* @param bot
* @param knex
@ -44,8 +130,4 @@ module.exports = {
webserver,
};
},
async loadPlugin(plugin, api) {
await plugin(api);
}
};