diff --git a/package-lock.json b/package-lock.json index 83a9ec5a..b39d82a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,6 +51,17 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", @@ -1042,6 +1053,16 @@ } } }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, "figlet": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.2.1.tgz", @@ -2154,6 +2175,11 @@ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", @@ -2161,9 +2187,9 @@ "dev": true }, "knub": { - "version": "16.1.1", - "resolved": "https://registry.npmjs.org/knub/-/knub-16.1.1.tgz", - "integrity": "sha512-UvAPGuyjFYDAMBVNQCxPbJXmDbaPYMmNaOhwRjFKvKdiOBsXrIxSBYtxNafx6tWG9HAO1qEg0+gyOIR0KFuCEg==", + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/knub/-/knub-16.3.0.tgz", + "integrity": "sha512-sT0sntmffQjhQaA3OjmjDBuplgnPtbmaFSGpYsi8icYtqFOmlDwXHpOOVRxBGomWH7SbHf3TP7EFQRh/WXAvNg==", "requires": { "escape-string-regexp": "^1.0.5", "lodash.at": "^4.6.0", @@ -3134,6 +3160,11 @@ "ps-tree": "^1.1.0" } }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -4035,6 +4066,14 @@ "xdg-basedir": "^3.0.0" } }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", diff --git a/package.json b/package.json index 9ce30564..51f156d5 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dependencies": { "@types/lodash.at": "^4.6.3", "@types/moment-timezone": "^0.5.6", + "ajv": "^6.7.0", "cross-env": "^5.2.0", "dotenv": "^4.0.0", "emoji-regex": "^7.0.1", @@ -33,7 +34,7 @@ "escape-string-regexp": "^1.0.5", "humanize-duration": "^3.15.0", "js-yaml": "^3.12.0", - "knub": "^16.1.1", + "knub": "^16.3.0", "lodash.at": "^4.6.0", "lodash.chunk": "^4.2.0", "lodash.difference": "^4.5.0", diff --git a/src/plugins/ZeppelinPlugin.ts b/src/plugins/ZeppelinPlugin.ts index 21bbff2f..40ebfc7c 100644 --- a/src/plugins/ZeppelinPlugin.ts +++ b/src/plugins/ZeppelinPlugin.ts @@ -1,9 +1,11 @@ -import { Plugin } from "knub"; +import { IPluginOptions, Plugin } from "knub"; import { PluginRuntimeError } from "../PluginRuntimeError"; -import { TextableChannel } from "eris"; -import { errorMessage, successMessage } from "../utils"; +import Ajv, { ErrorObject } from "ajv"; export class ZeppelinPlugin extends Plugin { + protected configSchema: any; + protected permissionsSchema: any; + protected throwPluginRuntimeError(message: string) { throw new PluginRuntimeError(message, this.runtimePluginName, this.guildId); } @@ -17,4 +19,59 @@ export class ZeppelinPlugin extends Plugin { const memberLevel = this.getMemberLevel(member2); return ourLevel > memberLevel; } + + public validateOptions(options: IPluginOptions): ErrorObject[] | null { + // Validate config values + if (this.configSchema) { + const ajv = new Ajv(); + const validate = ajv.compile(this.configSchema); + + if (options.config) { + const isValid = validate(options.config); + if (!isValid) return validate.errors; + } + + if (options.overrides) { + for (const override of options.overrides) { + if (override.config) { + const isValid = validate(override.config); + if (!isValid) return validate.errors; + } + } + } + } + + // Validate permission values + if (this.permissionsSchema) { + const ajv = new Ajv(); + const validate = ajv.compile(this.permissionsSchema); + + if (options.permissions) { + const isValid = validate(options.permissions); + if (!isValid) return validate.errors; + } + + if (options.overrides) { + for (const override of options.overrides) { + if (override.permissions) { + const isValid = validate(override.permissions); + if (!isValid) return validate.errors; + } + } + } + } + + // No errors, return null + return null; + } + + public async runLoad(): Promise { + const mergedOptions = this.getMergedOptions(); + const validationErrors = this.validateOptions(mergedOptions); + if (validationErrors) { + throw new Error(`Invalid options:\n${validationErrors.join("\n")}`); + } + + return this.onLoad(); + } }