ramirez/src/plugins.js

159 lines
4.8 KiB
JavaScript

const attachments = require("./data/attachments");
const logs = require("./data/logs");
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 threads = require("./data/threads");
const displayRoles = require("./data/displayRoles");
const { PluginInstallationError } = require("./PluginInstallationError");
const pluginSources = {
npm: {
install(plugins) {
return new Promise((resolve, reject) => {
console.log(`Installing ${plugins.length} plugins from NPM...`);
// Rewrite GitHub npm package names to full GitHub tarball links to avoid
// needing to have Git installed to install these plugins.
// $1 package author, $2 package name, $3 branch (optional)
const npmGitHubPattern = /^([a-z0-9_.-]+)\/([a-z0-9_.-]+)(?:#([a-z0-9_.-]+))?$/i;
const rewrittenPluginNames = plugins.map(pluginName => {
const gitHubPackageParts = pluginName.match(npmGitHubPattern);
if (! gitHubPackageParts) {
return pluginName;
}
return `https://api.github.com/repos/${gitHubPackageParts[1]}/${gitHubPackageParts[2]}/tarball${gitHubPackageParts[3] ? "/" + gitHubPackageParts[3] : ""}`;
});
let stderr = "";
const npmProcessName = /^win/.test(process.platform) ? "npm.cmd" : "npm";
const npmProcess = childProcess.spawn(
npmProcessName,
["install", "--verbose", "--no-save", ...rewrittenPluginNames],
{ cwd: process.cwd() }
);
npmProcess.stderr.on("data", data => { stderr += String(data) });
npmProcess.on("close", code => {
if (code !== 0) {
return reject(new PluginInstallationError(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 PluginInstallationError(`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 PluginInstallationError(`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
* @param config
* @param commands
* @returns {PluginAPI}
*/
getPluginAPI({ bot, knex, config, commands }) {
return {
bot,
knex,
config,
commands: {
manager: commands.manager,
addGlobalCommand: commands.addGlobalCommand,
addInboxServerCommand: commands.addInboxServerCommand,
addInboxThreadCommand: commands.addInboxThreadCommand,
addAlias: commands.addAlias
},
attachments: {
addStorageType: attachments.addStorageType,
downloadAttachment: attachments.downloadAttachment,
saveAttachment: attachments.saveAttachment,
},
logs: {
addStorageType: logs.addStorageType,
saveLogToStorage: logs.saveLogToStorage,
getLogUrl: logs.getLogUrl,
getLogFile: logs.getLogFile,
getLogCustomResponse: logs.getLogCustomResponse,
},
hooks: {
beforeNewThread,
afterThreadClose,
},
formats,
webserver,
threads,
displayRoles,
};
},
};