diff --git a/CHANGELOG.md b/CHANGELOG.md index 51b78a2e..d36578c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- New Beta Configuration (Needs heavy testing) + +### Removed +- Old Configuration System ## [0.12.4] - 2017-01-13 ### Added diff --git a/README.md b/README.md index 2853524a..1fde8200 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # Komada -[![Discord](https://discordapp.com/api/guilds/234357395646578688/embed.png)](http://discord.gg/QnWkbXV) +[![Discord](https://discordapp.com/api/guilds/260202843686830080/embed.png)](https://discord.gg/dgs8263) [![npm](https://img.shields.io/npm/v/komada.svg?maxAge=3600)](https://www.npmjs.com/package/komada) [![npm](https://img.shields.io/npm/dt/komada.svg?maxAge=3600)](https://www.npmjs.com/package/komada) -[![Build Status](https://travis-ci.org/eslachance/komada.svg?branch=indev)](https://travis-ci.org/eslachance/komada) -[![David](https://img.shields.io/david/eslachance/komada.svg?maxAge=3600)](https://david-dm.org/eslachance/komada) +[![Build Status](https://travis-ci.org/dirigeants/komada.svg?branch=indev)](https://travis-ci.org/dirigeants/komada) +[![David](https://img.shields.io/david/dirigeants/komada.svg?maxAge=3600)](https://david-dm.org/dirigeants/komada) > "Stay a while, and listen!" @@ -44,7 +44,7 @@ npm install node app.js ``` -> Requires Node 6 or higher (because Discord.js requires that), also requires Discord.js v10, installed automatically with `npm install`. +> Requires Node 6 or higher (because Discord.js requires that), also requires Discord.js v11, installed automatically with `npm install`. ## Quick & Dirty Reference Guide > For creating your own pieces @@ -220,6 +220,9 @@ exports.run = (client, msg) => { }; ``` +> Note: Technically, this means that monitors are message events. You can use this trick +to get around the normal amount of message events in Komada.. *cough* + ### Using Methods Methods are just Discord.js native functions added to Komada, so that we may @@ -231,7 +234,6 @@ Current Methods are: Collections => `client.methods.Collection` Rich Embed Builder => `client.methods.Embed` Message Collector => `client.methods.MessageCollector` -ShardingManager => `client.methods.Shard` WebhookClient => `client.methods.Webhook` To use any of the methods, you follow this same structure: diff --git a/app.js b/app.js index d72bcc0c..9127a2fd 100644 --- a/app.js +++ b/app.js @@ -1,6 +1,7 @@ const Discord = require("discord.js"); const chalk = require("chalk"); const loadFunctions = require("./functions/loadFunctions.js"); +const Config = require("./classes/Config.js"); const clk = new chalk.constructor({ enabled: true }); @@ -28,6 +29,8 @@ exports.start = (config) => { client.coreBaseDir = `${__dirname}/`; client.clientBaseDir = `${process.cwd()}/`; + client.guildConfs = Config.guildConfs; + client.configuration = Config; // Load core functions, then everything else loadFunctions(client).then(() => { @@ -42,6 +45,7 @@ exports.start = (config) => { client.once("ready", () => { client.config.prefixMention = new RegExp(`^<@!?${client.user.id}>`); + Config.initialize(client); for (const func in client.funcs) { if (client.funcs[func].init) client.funcs[func].init(client); } @@ -53,7 +57,7 @@ exports.start = (config) => { client.on("message", (msg) => { if (msg.author.bot) return; - const conf = client.funcs.confs.get(msg.guild); + const conf = Config.get(msg.guild); msg.guildConf = conf; client.i18n.use(conf.lang); client.funcs.runMessageMonitors(client, msg).catch(reason => msg.channel.sendMessage(reason).catch(console.error)); diff --git a/classes/Config.js b/classes/Config.js new file mode 100644 index 00000000..7b4a2261 --- /dev/null +++ b/classes/Config.js @@ -0,0 +1,598 @@ +/* eslint-disable no-restricted-syntax, no-underscore-dangle, no-unused-vars */ +const fs = require("fs-extra-promise"); +const path = require("path"); + +const guildConfs = new Map(); +let dataDir = ""; +const defaultFile = "default.json"; +let defaultConf = {}; + +/** The starting point for creating a String configuration key. */ +class StringConfig { + /** + * @param {Config} conf The guild configuration obtained from the guildConfs map. + * @param {Object} data The data you want to append to this String configuration key. + * @returns {StringConfig} + */ + constructor(conf, data) { + if (typeof data !== "string") this.data = ""; + else this.data = data; + this.type = "String"; + if (data.possibles) this.possibles = data.possibles; + else this.possibles = []; + Object.defineProperty(this, "_id", { value: conf._id }); + return this; + } + + /** + * Sets the value of a string configurations possibles. This takes into account the list of acceptable answers from the possibles array. + * @param {string} value The value you want to set this key to. + * @returns {StringConfig} + */ + set(value) { + if (!value) return "Please supply a value to set."; + if (this.possibles.length !== 0 && !this.possibles.includes(value)) return `That is not a valid option. Valid options: ${this.possibles.join(", ")}`; + this.data = value.toString(); + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } + + /** + * Adds a value(s) to list of acceptable answers for this key. Accepts one item or an array of items. + * @param {string|array} value The value(s) you want to add to this key. + * @returns {StringConfig} + */ + add(value) { + if (!value) return "Please supply a value to add to the possibles array."; + if (value instanceof Array) { + value.forEach((val) => { + if (this.possibles.includes(val)) return `The value ${val} is already in ${this.possibles}.`; + return this.possibles.push(val); + }); + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } + if (this.possibles.includes(value)) return `The value ${value} is already in ${this.possibles}.`; + this.possibles.push(value); + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } + + /** + * Deletes a value(s) from the string configurations possibles. Accepts one item or an array of items. + * @param {string|array} value The value(s) you want to delete from this key. + * @returns {StringConfig} + */ + del(value) { + if (!value) return "Please supply a value to add to the possibles array"; + if (value instanceof Array) { + value.forEach((val) => { + if (!this.possibles.includes(val)) return `The value ${value} is not in ${this.possibles}.`; + return this.possibles.splice(this.possibles.indexOf(val), 1); + }); + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } + if (!this.possibles.includes(value)) return `The value ${value} is not in ${this.possibles}.`; + this.possibles.splice(this.possibles.indexOf(value), 1); + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } +} + +/** The starting point for creating an Array Configuration key. */ +class ArrayConfig { + /** + * @param {Config} conf The guild configuration obtained from the guildConfs map. + * @param {object} data The data you want to append to this Array configuration key. + * @returns {ArrayConfig} + */ + constructor(conf, data) { + if (!(data instanceof Array)) this.data = []; + else this.data = data; + this.type = "Array"; + Object.defineProperty(this, "_id", { value: conf._id }); + return this; + } + + /** + * Adds a value(s) to the array. Accepts a single value or an array of values. + * @param {string|array} value The value(s) to add to the array. + * @returns {ArrayConfig} + */ + add(value) { + if (!value) return "Please supply a value to add to the array."; + if (value instanceof Array) { + value.forEach((val) => { + if (!this.data.includes(val)) return "That value is not in the array."; + return this.data.splice(this.data.indexOf(value), 1); + }); + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } + if (this.data.includes(value)) return "That value is already in the array."; + this.data.push(value); + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } + + /** + * Deletes a value(s) from the array. Accepts a single value or an array of values. + * @param {string|array} value The value(s) to delete from the array. + * @returns {ArrayConfig} + */ + del(value) { + if (!value) return "Please supply a value to delete from the array"; + if (value instanceof Array) { + value.forEach((val) => { + if (!this.data.includes(val)) return "That value is not in the array."; + return this.data.splice(this.data.indexOf(value), 1); + }); + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } + if (!this.data.includes(value)) return "That value is not in the array."; + this.data.splice(this.data.indexOf(value), 1); + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } +} + +/** The starting point for creating a Boolean configuration key. */ +class BooleanConfig { + /** + * @param {Config} conf A guilds configuration obtained from the guildConfs map. + * @param {object} data The data you want to append to this boolean key. + * @returns {BooleanConfig} + */ + constructor(conf, data) { + if (typeof data !== "boolean") this.data = false; + else this.data = data; + this.type = "Boolean"; + Object.defineProperty(this, "_id", { value: conf._id }); + return this; + } + + /** + * Toggles a boolean statement for the boolean key. + * @returns {BooleanConfig} + */ + toggle() { + if (this.data === true) this.data = false; + else this.data = true; + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } +} + +/** The starting point for creating a Number configuration key. */ +class NumberConfig { + /** + * @param {Config} conf A guilds configuration obtained from the guildConfs map. + * @param {object} data The data you want to append to this number key. + * @returns {NumberConfig} + */ + constructor(conf, data) { + if (typeof data !== "number") this.data = 0; + else this.data = data; + if (data.min) this.min = data.min; + if (data.max) this.max = data.max; + this.type = "Number"; + Object.defineProperty(this, "_id", { value: conf._id }); + return this; + } + + /** + * Sets the value for a number key, according to the minimum and maximum values if they apply. + * @param {number} value The value you want to set the key as. + * @returns {NumberConfig} + */ + set(value) { + if (!value) return `Error, value is ${value}. Please supply a value to set.`; + if (this.min && parseInt(value) < this.min) return `Error while setting the value. ${value} is less than ${this.min}`; + if (this.max && parseInt(value) > this.max) return `Error while setting the value. ${value} is more than ${this.max}`; + this.data = value; + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } + + /** + * Sets the minimum value a number key can be. + * @param {number} value The value you want to set the minimum as. + * @returns {NumberConfig} + */ + setMin(value) { + this.min = value; + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } + + /** + * Sets the maximum value a number key can bey. + * @param {number} value The value you want to set the maximum as. + * @returns {NumberConfig} + */ + setMax(value) { + this.max = value; + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } + +} + +/** + * The starting point for creating a Guild configuration + */ +class Config { + /** + * @param {Client} client The Discord.js client. + * @param {GuildID} guildID The guild for which the configuration is being made. + * @param {Config} [config] The local config to add to the configuration. + */ + constructor(client, guildID, config = {}) { + /** + * The guild to create the configuration for. + * @type {GuildID} + * @readonly + */ + Object.defineProperty(this, "_id", { value: guildID }); + /** + * The default prefix to use for the bot if one is not in the configuration. + * @type {String} + */ + this.prefix = new StringConfig(this, client.config.prefix); + /** + * The array of disabled commands that are unable to be used in a guild. + * @type {Array} + */ + this.disabledCommands = new ArrayConfig(this, []); + /** + * The default role name to look for when assigning mod level permissions. + * @type {String} + */ + this.modRole = new StringConfig(this, "Mods"); + /** + * The default role name to look for when assigning admin level permissions. + * @type {String} + */ + this.adminRole = new StringConfig(this, "Devs"); + /** + * The default language to use for the bot. + * @type {String} + */ + this.lang = new StringConfig(this, "en"); + if (typeof config === "object") { + for (const prop in config) { + if (config[prop].type === "String") { + this[prop] = new StringConfig(this, config[prop].data); + } else if (config[prop].type === "Boolean") { + this[prop] = new BooleanConfig(this, config[prop].data); + } else if (config[prop].type === "Number") { + this[prop] = new NumberConfig(this, config[prop].data); + } else if (config[prop].type === "Array") { + this[prop] = new ArrayConfig(this, config[prop].data); + } else { + client.funcs.log("Invalid Key type inside of your configuration. Komada will ignore this key until it is fixed.", "warn"); + } + } + } + return this; + } + + + /** + * Allows you to add a key to a guild configuration. Note: This should never be called + * directly as it could cause unwanted side effects. + * @param {string} key The key to add to the configuration. + * @param {string|array|number|boolean} defaultValue The value for the key. + * @param {string} type The type of key you want to add. + * @returns {Config} + */ + addKey(key, defaultValue, type) { + if (type === "String") { + this[key] = new StringConfig(this, defaultValue); + } else if (type === "Boolean") { + this[key] = new BooleanConfig(this, defaultValue); + } else if (type === "Number") { + this[key] = new NumberConfig(this, defaultValue); + } else if (type === "Array") { + this[key] = new ArrayConfig(this, defaultValue); + } else { + console.log(`Invalid Key Type: Type: ${type}`); + } + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this; + } + + /** + * Deletes a key from the respected guild configuration. + * This should never be called directly. + * @param {string} key The key to delete from the configuration + * @returns {null} + */ + delKey(key) { + delete this[key]; + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return null; + } + + /** + * Resets a key for the respected guild configuration. + * @param {string} key The key to reset in the configuration. + * @returns {Config} + */ + reset(key) { + if (this[key].type === "String") { + this[key] = new StringConfig(this, defaultConf[key].data); + } else if (this[key].type === "Boolean") { + this[key] = new BooleanConfig(this, defaultConf[key].data); + } else if (this[key].type === "Number") { + this[key] = new NumberConfig(this, defaultConf[key].data); + } else if (this[key].type === "Array") { + this[key] = new ArrayConfig(this, defaultConf[key].data); + } + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${this._id}.json`), guildConfs.get(this._id)); + return this[key]; + } + + /** + * Checks the guild configuration for a key + * @param {string} key The key to check the guild configuration for. + * @returns {boolean} + */ + has(key) { + if (!key) return "Please supply a key."; + return (key in this); + } + + /** + * Simplifies the guild configuration for use in commands and modules. + * @param {Guild} guild The guild to get a configuration for. + * @returns {object} + * @static + * @example + * //Example of what this returns + * { prefix: '--', disabledCommands: [], modRole: 'Mods', adminRole: 'Devs', lang: 'en' } + */ + static get(guild) { + const conf = {}; + if (guild && guildConfs.has(guild.id)) { + const guildConf = guildConfs.get(guild.id); + for (const key in guildConf) { + if (guildConf[key]) conf[key] = guildConf[key].data; + else conf[key] = defaultConf[key].data; + } + } + return conf; + } + + /** + * Set the default value for a key in the default configuration. + * @param {string} key The key for which you want to change. + * @param {array|boolean|number|string} defaultValue The value you want to set as the default. + * @returns {object} Returns the new default configuration for the key. + * @static + */ + static set(key, defaultValue) { + if (!key || !defaultValue) return `You supplied ${key}, ${defaultValue}. Please supply both a key, and a default value.`; + if (!defaultConf[key]) return `The key ${key} does not seem to be present in the default configuration.`; + if (defaultConf[key].type === "Array") this.add(key, defaultValue); + if (defaultConf[key].type === "Boolean") this.toggle(key); + if (defaultConf[key].type === "Number" || defaultConf[key].type === "String") defaultConf[key].data = defaultValue; + else return "Unsupported Configuration Type! Cannot set the value."; + fs.outputJSONAsync(`${dataDir}${path.sep}${defaultFile}`, defaultConf); + return defaultConf[key]; + } + + /** + * Sets the default minimum value for a Number key + * @param {string} key The Number key for which you want to set the minimum value for. + * @param {number} defaultMinValue The value you want to set as the "minimum" value. + * @returns {object} Returns the new default configuration for the key. + * @static + */ + static setMin(key, defaultMinValue) { + if (!key || !defaultMinValue) return `You supplied ${key}, ${defaultMinValue}. Please supply both a key, and a default min value.`; + if (!defaultConf[key].type !== "Number") return "You cannot use this method on non-Numeral configurations."; + defaultConf[key].min = defaultMinValue; + fs.outputJSONAsync(`${dataDir}${path.sep}${defaultFile}`, defaultConf); + return defaultConf[key]; + } + + /** + * Sets the default maximum value for a Number key + * @param {string} key The Number key for which you want to set the maximum value for. + * @param {number} defaultMaxValue The value you want to set as the "maximum" value. + * @returns {object} Returns the new default configuration for the key. + * @static + */ + static setMax(key, defaultMaxValue) { + if (!key || !defaultMaxValue) return `You supplied ${key}, ${defaultMaxValue}. Please supply both a key, and a default max value.`; + if (!defaultConf[key].type !== "Number") return "You cannot use this method on non-Numeral configurations."; + defaultConf[key].min = defaultMaxValue; + fs.outputJSONAsync(`${dataDir}${path.sep}${defaultFile}`, defaultConf); + return defaultConf[key]; + } + + /** + * Adds a value to the data array for an Array key. + * @param {string} key The Array key for which you want to add value(s) for. + * @param {string} defaultValue The value for which you want to add to the array. + * @returns {object} Returns the new default configuration for the key. + * @static + */ + static add(key, defaultValue) { + if (!key || !defaultValue) return `You supplied ${key}, ${defaultValue}. Please supply both a key, and a default value.`; + if (!defaultConf[key].type !== "Array") return "You cannot use this method on non-Array configuration options."; + if (defaultConf[key].data.includes(defaultValue)) return `The default value ${defaultValue} is already in ${defaultConf[key].data}.`; + defaultConf[key].data.push(defaultValue); + fs.outputJSONAsync(`${dataDir}${path.sep}${defaultFile}`, defaultConf); + return defaultConf[key]; + } + + /** + * Deletes a value from the data array for an Array key. + * @param {string} key The array key for which you want to delete value(s) from. + * @param {string} defaultValue The value for which you want to remove from the array. + * @returns {object} Returns the new default configuration for the key. + * @static + */ + static del(key, defaultValue) { + if (!key || !defaultValue) return `You supplied ${key}, ${defaultValue}. Please supply both a key, and a default value.`; + if (!defaultConf[key].type !== "Array") return "You cannot use this method on non-Array configuration options."; + if (!defaultConf[key].data.includes(defaultValue)) return `The default value ${defaultValue} is not in ${defaultConf[key].data}.`; + defaultConf[key].data.splice(defaultConf[key].indexOf(defaultValue), 1); + fs.outputJSONAsync(`${dataDir}${path.sep}${defaultFile}`, defaultConf); + return defaultConf[key]; + } + + /** + * Toggles the true/false statement for a Boolean key + * @param {string} key The boolean key for which you want to toggle the statement for. + * @returns {objct} Returns the new default configuration for the key. + * @static + */ + static toggle(key) { + if (!key) return "Please supply a key to toggle the value for."; + if (!defaultConf[key].type !== "Boolean") return "You cannot use this method on non-Boolean configuration options."; + if (defaultConf[key].data === true) defaultConf[key].data = false; + else defaultConf[key].data = false; + fs.outputJSONAsync(`${dataDir}${path.sep}${defaultFile}`, defaultConf); + return defaultConf[key]; + } + + /** + * Checks if the guildConfs Map has the specified guild. + * @param {Guild} guild The guild to check the Map for. + * @returns {boolean} + * @static + */ + static has(guild) { + if (!guild) return "Please supply a guild."; + return (guildConfs.has(guild.id)); + } + + /** + * Checks if the default configuration has a specified key. + * @param {string} key The key for which to check the default configuration for. + * @returns {boolean} + * @static + */ + static hasKey(key) { + if (!key) return "Please supply a key to check for."; + return (key in defaultConf); + } + + /** + * Adds a key to the default configuration, and every guilds configuration. + * @param {string} key The key for which to add to the default and all guild configurations. + * @param {string|number|boolean|array} defaultValue The value for which you want to set as the default value. + * @param {string} [type] The type of key this will be. This can currently be Strings, Numbers, Arrays, or Booleans. + * @returns {object} Returns the entire default configuration + * @static + */ + static addKey(key, defaultValue, type = defaultValue.constructor.name) { + if (!key || !defaultValue) return `You supplied ${key}, ${defaultValue}. Please provide both.`; + if (defaultConf[key]) return "There's no reason to add this key, it already exists."; + if (["TextChannel", "GuildChannel", "Message", "User", "GuildMember", "Guild", "Role", "VoiceChannel", "Emoji", "Invite"].includes(type)) { + defaultValue = defaultValue.id; + } + if (defaultValue.constructor.name !== type && defaultValue.constructor.name !== null) { + return "Invalid Value was provided"; + } + defaultConf[key] = { type: defaultValue.constructor.name, data: defaultValue }; + guildConfs.forEach((config) => { + config.addKey(key, defaultValue, type); + }); + fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${defaultFile}`), defaultConf); + return defaultConf; + } + + /** + * Deletes a key from the default configuration, and every guilds configuration. + * @param {string} key The key for which to add to the default and all guild configurations. + * @returns The new default configuration + * @static + */ + static delKey(key) { + if (!key) return "Please supply a key to delete from the default configuration."; + if (!defaultConf[key]) return `The key ${key} does not seem to be present in the default configuration.`; + if (["modRole", "adminRole", "disabledCommands", "prefix", "lang"].includes(key)) { + return `The key ${key} is core and cannot be deleted.`; + } + delete defaultConf[key]; + fs.outputJSONAsync(`${dataDir}${path.sep}${defaultFile}`, defaultConf); + this.guildConfs.forEach((config) => { + config.delKey(key); + }); + return defaultConf; + } + + /** + * Inserts a guild into the guildConfs map and deletes the configuration JSON. + * This should never be called by anyone, this is purely for the guildCreate event. + * @param {Client} client The Discord.js Client + * @param {Guild} guild The Guild being inserted into the map. + * @returns {String} + * @static + */ + static insert(client, guild) { + if (!guild) return "Please specify a guild to remove."; + guildConfs.set(guild.id, new Config(client, guild.id, defaultConf)); + fs.outputJSONAsync(`${dataDir}${path.sep}${guild.id}.json`, guildConfs.get(guild.id)); + return `Inserted ${guild.name} succesfully.`; + } + + /** + * Removes a guild from the guildConfs map and deletes the configuration JSON. + * This should never be called by anyone, this is purely for the guildDelete event. + * @param {Guild} guild The guild being removed from the map. + * @returns {String} + * @static + */ + static remove(guild) { + if (!guild) return "Please specify a guild to remove."; + guildConfs.delete(guild.id); + fs.removeAsync(path.resolve(`${dataDir}${path.sep}${guild.id}.json`)); + return `Removed ${guild.name} succesfully.`; + } + + /** + * The motherboard of our Configuration system. + * There's no reason to ever call this as it's called internally upon startup. + * @param {Client} client The Discord.js Client + * @returns {null} + * @static + */ + static initialize(client) { + defaultConf = { + prefix: { type: "String", data: client.config.prefix }, + disabledCommands: { type: "Array", data: [] }, + modRole: { type: "String", data: "Mods" }, + adminRole: { type: "String", data: "Devs" }, + lang: { type: "String", data: "en" }, + }; + dataDir = path.resolve(`${client.clientBaseDir}${path.sep}bwd${path.sep}conf`); + fs.ensureFileAsync(`${dataDir}${path.sep}${defaultFile}`) + .then(() => { + fs.readJSONAsync(path.resolve(`${dataDir}${path.sep}${defaultFile}`)) + .then((conf) => { + defaultConf = conf; + }).catch(() => { + fs.outputJSONAsync(`${dataDir}${path.sep}${defaultFile}`, defaultConf); + }); + client.guilds.forEach((guild) => { + fs.readJSONAsync(path.resolve(`${dataDir}${path.sep}${guild.id}.json`)) + .then((thisConf) => { + guildConfs.set(guild.id, new Config(client, guild.id, thisConf)); + }).catch(() => { + guildConfs.set(guild.id, new Config(client, guild.id)); + }); + }); + return null; + }); + } +} + +module.exports = Config; +module.exports.guildConfs = guildConfs; diff --git a/functions/confs.js b/functions/confs.js deleted file mode 100644 index 8a15a040..00000000 --- a/functions/confs.js +++ /dev/null @@ -1,254 +0,0 @@ -const fs = require("fs-extra-promise"); -const path = require("path"); - -const guildConfs = new Map(); -let dataDir = ""; -const defaultFile = "default.json"; -let defaultConf = {}; - -exports.init = (client) => { - dataDir = path.resolve(`${client.clientBaseDir}${path.sep}bwd${path.sep}conf`); - - defaultConf = { - prefix: { type: "String", data: client.config.prefix }, - disabledCommands: { type: "Array", data: [] }, - modRole: { type: "String", data: "Mods" }, - adminRole: { type: "String", data: "Devs" }, - lang: { type: "String", data: "en" }, - }; - - fs.ensureFileAsync(`${dataDir}${path.sep}${defaultFile}`) - .then(() => { - fs.readJSONAsync(path.resolve(`${dataDir}${path.sep}${defaultFile}`)) - .then((currentDefaultConf) => { - Object.keys(defaultConf).forEach((key) => { - if (!currentDefaultConf.hasOwnProperty(key)) currentDefaultConf[key] = defaultConf[key]; - }); - fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${defaultFile}`), currentDefaultConf) - .then(() => { - defaultConf = currentDefaultConf; - }); - }).catch(() => { - fs.outputJSONAsync(`${dataDir}${path.sep}${defaultFile}`, defaultConf); - }); - fs.walk(dataDir) - .on("data", (item) => { - const fileinfo = path.parse(item.path); - if (!fileinfo.ext) return; - if (fileinfo.name === "default") return; - const guildID = fileinfo.name; - fs.readJSONAsync(path.resolve(`${dataDir}${path.sep}${fileinfo.base}`)) - .then((thisConf) => { - guildConfs.set(guildID, thisConf); - }).catch(err => client.funcs.log(err, "error")); - }) - .on("end", () => { - client.funcs.log("Guild Confs have finished loading", "log"); - }); - }); -}; - -exports.remove = (guild) => { - if (!guildConfs.has(guild.id)) { - return false; - } - - fs.removeAsync(path.resolve(`${dataDir}${path.sep}${guild.id}.json`)); - return `${guild.name} has been successfully removed!`; -}; - -exports.has = guild => guildConfs.has(guild.id); - -exports.get = (guild) => { - const conf = {}; - if (!!guild && guildConfs.has(guild.id)) { - const guildConf = guildConfs.get(guild.id); - for (const key in guildConf) { - if (guildConf[key]) conf[key] = guildConf[key].data; - else conf[key] = defaultConf[key].data; - } - } - for (const key in defaultConf) { - if (!conf[key]) conf[key] = defaultConf[key].data; - } - return conf; -}; - -exports.getRaw = (guild) => { - const conf = {}; - if (guild && guildConfs.has(guild.id)) { - const guildConf = guildConfs.get(guild.id); - for (const key in guildConf) { - if (guildConf[key]) conf[key] = guildConf[key]; - else conf[key] = defaultConf[key]; - } - } - for (const key in defaultConf) { - if (!conf[key]) conf[key] = defaultConf[key]; - } - return conf; -}; - -exports.addKey = (key, defaultValue, min = null, max = null) => { - const type = defaultValue.constructor.name; - if (["TextChannel", "GuildChannel", "Message", "User", "GuildMember", "Guild", "Role", "VoiceChannel", "Emoji", "Invite"].includes(type)) { - defaultValue = defaultValue.id; - } - if (defaultValue.constructor.name !== type && defaultValue.constructor.name !== null) { - return "Invalid Value was provided"; - } - defaultConf[key] = { type: defaultValue.constructor.name, data: defaultValue }; - if (type === "Number") { - defaultConf[key].min = min; - defaultConf[key].max = max; - } - fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${defaultFile}`), defaultConf); - return "Key has been successfully added."; -}; - -exports.setKey = (key, defaultValue) => { - if (!(key in defaultConf)) { - throw new Error(`The key ${key} does not seem to be present in the default configuration.`); - } - switch (defaultConf[key].type) { - case "Array": { - const dataArray = []; - if (defaultConf[key]) { - dataArray.splice(dataArray.indexOf(defaultValue), 1); - defaultValue = dataArray; - } else { - dataArray.push(defaultValue); - defaultValue = dataArray; - } - break; - } - case "Boolean": - if (defaultValue === "true") { - defaultValue = true; - } else if (defaultValue === "false") { - defaultValue = false; - } else { - throw new Error(`The value ${defaultValue} does not correspond to the type boolean.`); - } - break; - case "Number": - defaultValue = parseInt(defaultValue); - if (isNaN(defaultValue)) { - throw new Error(`The value ${defaultValue} does not correspond to the type integer.`); - } else if (defaultConf[key].max !== null && defaultValue > defaultConf[key].max) { - throw new Error(`The value ${defaultValue} is bigger than the max value ${defaultConf[key].max}`); - } else if (defaultConf[key].min !== null && defaultValue < defaultConf[key].min) { - throw new Error(`The value ${defaultValue} is smaller than the min value ${defaultConf[key].min}`); - } - break; - default: - defaultValue = defaultValue.toString(); - } - defaultConf[key].data = defaultValue; - fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${defaultFile}`), defaultConf); - return defaultConf; -}; - -exports.resetKey = (guild, ...keys) => { - keys.forEach((key, element) => { // eslint-disable-line no-unused-vars - if (!(key in defaultConf)) { - throw new Error(`The key ${key} does not seem to be present in the default configuration.`); - } - }); - if (!guildConfs.has(guild.id)) { - throw new Error(`The guild ${guild.name}(${guild.id}) not found while trying to reset ${keys.join(", ")}`); - } - fs.readJSONAsync(path.resolve(`${dataDir}${path.sep}${guild.id}.json`)) - .then((thisConf) => { - keys.forEach((key, element) => { // eslint-disable-line no-unused-vars - if (!(key in thisConf)) { - throw new Error(`The key ${key} does not seem to be present in the server configuration.`); - } - delete thisConf[key]; - }); - if (Object.keys(thisConf).length > 0) { - guildConfs.set(guild.id, thisConf); - fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${guild.id}.json`), thisConf); - return; - } - guildConfs.set(guild.id, defaultConf); - fs.removeAsync(path.resolve(`${dataDir}${path.sep}${guild.id}.json`)); - }); -}; - -exports.delKey = (key) => { - if (!(key in defaultConf)) { - throw new Error(`The key ${key} does not seem to be present in the default configuration.`); - } - if (["modRole", "adminRole", "disabledCommands", "prefix", "lang"].includes(key)) { - throw new Error(`The key ${key} is core and cannot be deleted.`); - } - delete defaultConf[key]; - fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${defaultFile}`), defaultConf) - .then(() => { - const MapIter = guildConfs.keys(); - guildConfs.forEach((conf) => { - delete conf[key]; - if (Object.keys(conf).length > 0) { - fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${MapIter.next().value}.json`), conf); - return; - } - fs.removeAsync(path.resolve(`${dataDir}${path.sep}${MapIter.next().value}.json`)); - }); - }); -}; - -exports.hasKey = key => (key in defaultConf); - -exports.set = (guild, key, value) => { - let thisConf = {}; - if (guildConfs.has(guild.id)) { - thisConf = guildConfs.get(guild.id); - } - - if (!(key in defaultConf)) { - throw new Error(`The key ${key} is not valid according to the Default Configuration.`); - } - - switch (defaultConf[key].type) { - case "Array": { - const dataArray = []; - if (thisConf[key]) { - dataArray.splice(dataArray.indexOf(value), 1); - value = dataArray; - } else { - dataArray.push(value); - value = dataArray; - } - break; - } - case "Boolean": - if (value === "true") { - value = true; - } else if (value === "false") { - value = false; - } else { - throw new Error(`The value ${value} does not correspond to the Boolean type.`); - } - break; - case "Number": - value = parseInt(value); - if (isNaN(value)) { - throw new Error(`The value ${value} does not correspond to the Number type.`); - } else if (defaultConf[key].max !== null && value > defaultConf[key].max) { - throw new Error(`The value ${value} is bigger than the max value ${defaultConf[key].max}`); - } else if (defaultConf[key].min !== null && value < defaultConf[key].min) { - throw new Error(`The value ${value} is smaller than the min value ${defaultConf[key].min}`); - } - break; - default: - value = value.toString(); - } - - thisConf[key] = { data: value, type: defaultConf[key].type }; - - guildConfs.set(guild.id, thisConf); - fs.outputJSONAsync(path.resolve(`${dataDir}${path.sep}${guild.id}.json`), thisConf); - - return thisConf; -}; diff --git a/functions/permissionLevel.js b/functions/permissionLevel.js index f32b8107..19ca4a45 100644 --- a/functions/permissionLevel.js +++ b/functions/permissionLevel.js @@ -1,7 +1,7 @@ module.exports = (client, user, guild) => new Promise((resolve, reject) => { let permlvl = 0; if (guild) { - const guildConf = client.funcs.confs.get(guild); + const guildConf = client.configuration.get(guild); try { const modRole = guild.roles.find("name", guildConf.modRole); guild.fetchMember(user).then((member) => { diff --git a/package.json b/package.json index ee4a17ec..1709dd98 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "komada", - "version": "0.12.5", + "version": "0.13.0", "author": "Evelyne Lachance", "description": "Komada: Croatian for 'pieces', is a modular bot system including reloading modules and easy to use custom commands.", "main": "app.js",