diff --git a/.gitignore b/.gitignore index 7c479ac..1434376 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,7 @@ dist .svelte-kit # End of https://www.toptal.com/developers/gitignore/api/node + +# Plugins (These are to be handled in separate repos) +plugins/* +!plugins/README.md \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index dbde683..320cc68 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "discord-api-types": "^0.25.2", "discord.js": "^13.3.1", "dotenv": "^10.0.0", + "glob": "^8.0.3", "piston-client": "^1.0.2", "winston": "^3.3.3", "winston-daily-rotate-file": "^4.5.5" @@ -195,6 +196,19 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -368,6 +382,38 @@ "node": ">= 6" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "node_modules/glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -425,6 +471,17 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/moment": { "version": "2.29.3", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", @@ -465,6 +522,14 @@ "node": ">= 6" } }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -639,6 +704,11 @@ "node": ">= 6.4.0" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, "node_modules/ws": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", @@ -808,6 +878,19 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, "color": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", @@ -949,6 +1032,32 @@ "mime-types": "^2.1.12" } }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz", + "integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -994,6 +1103,14 @@ "mime-db": "1.52.0" } }, + "minimatch": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", + "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "requires": { + "brace-expansion": "^2.0.1" + } + }, "moment": { "version": "2.29.3", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz", @@ -1017,6 +1134,14 @@ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==" }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, "one-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", @@ -1156,6 +1281,11 @@ "triple-beam": "^1.3.0" } }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, "ws": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", diff --git a/package.json b/package.json index 27fed0c..6fd2aa2 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "discord-api-types": "^0.25.2", "discord.js": "^13.3.1", "dotenv": "^10.0.0", + "glob": "^8.0.3", "piston-client": "^1.0.2", "winston": "^3.3.3", "winston-daily-rotate-file": "^4.5.5" diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..e848a1a --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,23 @@ +# MoDiBo Plugins + +## File Structure + +Plugins should be placed here, each in their own directory. In that directory should be a `main.js` file with the required blueprint components. + +``` +plugins/ +- testPlugin/ +-- main.js +-- supportingFile.js +-- data.json +- testPlugin2/ +-- main.js +``` + +## To Enable a Plugin Installed Here... + +`//TODO` + +## Other `//TODO` + +- Finalize the wording of this document. Limited information about developing plugins should be placed here; the bulk of that should be in the actual documentation somewhere, either in the wiki or website. diff --git a/src/main.js b/src/main.js index 85b3213..2425db9 100644 --- a/src/main.js +++ b/src/main.js @@ -13,6 +13,7 @@ const winston = require("winston"); const winstonDiscord = require("./CustomDiscordWebhookTransport.js"); const winstonRotateFile = require("winston-daily-rotate-file"); const utils = require("./utils.js"); +const pluginManager = require("./plugin-manager.js"); const PREFIX = "$"; @@ -115,6 +116,9 @@ const bot = new Client({ bot.on("ready", async () => { // when loaded (ready event) bot.user.setActivity(`${PREFIX}help | ${PREFIX}info`, { type: "PLAYING" }); + + pluginManager.load(); + utils.logger.log("debug", `${bot.user.username} is ready...`); }); diff --git a/src/plugin-manager.js b/src/plugin-manager.js new file mode 100644 index 0000000..b4a9618 --- /dev/null +++ b/src/plugin-manager.js @@ -0,0 +1,67 @@ +const glob = require("glob"); +const utils = require("./utils.js"); +const PLUGIN_FILES = "../plugins/*/main.js"; + +/** + * Load plugin modules + * @param {Discord.Client} bot The instantiated Discord Bot object, for use in calling module onLoad + */ +function load(bot) { + //TODO Integrate with Config to only load enabled plugins + + utils.logger.log("debug", "Loading Plugins..."); + + glob.sync(PLUGIN_FILES).forEach((file) => { + let dash = file.split("/"); + if (dash.length !== 4) { + return; + } + + let dot = dash[3].split("."); + if (dot.length !== 2) { + return; + } + + let module = require(file); + let key = module.SLUG; + let loaded = false; + + if (module.processCommand) { + utils.plugins.command[key] = module; + loaded = true; + utils.logger.log("debug", `Loaded "${module.NAME}" as a Command Plugin`); + utils.plugins.command[key].processCommand(); + } + + if (module.processMessage) { + utils.plugins.message[key] = module; + loaded = true; + utils.logger.log("debug", `Loaded "${module.NAME}" as a Message Plugin`); + utils.plugins.message[key].processMessage(); + } + + if (module.startCron) { + utils.plugins.cron[key] = module; + loaded = true; + utils.logger.log("debug", `Loaded "${module.NAME}" as a Cron Plugin`); + } + + if (loaded && module.onLoad) { + utils.logger.log("debug", `Running "${module.NAME}" onLoad function...`); + module.onLoad(bot); + } + }); + + utils.logger.log("debug", "All Plugins Loaded"); + for (const [pluginType, list] of Object.entries(utils.plugins)) { + const pluginNames = Object.values(list).map((p) => `"${p.NAME}"`); + utils.logger.log( + "debug", + `${pluginType} plugins: ${pluginNames.join(", ")}` + ); + } +} + +module.exports = { + load, +}; diff --git a/src/utils.js b/src/utils.js index 26013d3..56f67f1 100644 --- a/src/utils.js +++ b/src/utils.js @@ -16,6 +16,13 @@ const SECOND = 1000; const MINUTE = 60 * SECOND; const HOUR = 60 * MINUTE; +// Loaded Plugin storage +let plugins = { + command: {}, + message: {}, + cron: {}, +}; + /** * Send a message to a channel of your choice. * @param {Discord.MessageEmbed|String} content The content to include in the message. @@ -141,4 +148,5 @@ module.exports = { reply, createEmbed, logger, + plugins, };