const Discord = require('discord.js')
/**
* Handles Guild Specific Settings
*/
class SettingsManager {
constructor () {
/**
* Settings Schema.
* DO NOT MODIFY THIS DIRECTLY. Use the .register() and .unregister() methods instead.
* @type {object}
*/
this.schema = { }
/**
* Default values for settings. Do not touch either.
* @type {object}
*/
this.defaults = { }
/**
* Global defaults.
* Useful to override defaults without having to change the schema.
* @type {object}
*/
this.globals = { }
/**
* Like globals, but forced.
* @type {object}
*/
this.overrides = { }
// Built-in settings
this.register('restrict', { type: Boolean, def: false })
this.register('prefix', { type: String, def: Core.properties.prefix, min: 1 })
this.register('commandChannel', { type: Discord.TextChannel })
this.register('voiceChannel', { type: Discord.VoiceChannel })
this.register('allowNSFW', { type: Boolean, def: false })
this.register('locale', { type: Core.Locale, def: Core.properties.locale })
}
/**
* Checks if a type is valid.
* @param {*} type
*/
validateType (type) {
if (!type) throw new Error('No type specified.')
if ([
Boolean,
String,
Number,
Discord.VoiceChannel,
Discord.TextChannel,
Discord.User,
Core.Locale
].indexOf(type) < 0) throw new Error('Invalid type specified.')
}
/**
* Registers a setting parameter to the schema.
* @param {string} key - Key of the parameter
* @param {object} params - Parameters
* @param {*} params.type - Type of the parameter (Boolean, String, Number, etc)
* @param {*} params.def - Default value of the parameter.
* @param {number} params.min - For numbers, it specifies the minimum value, for strings, the minimum length.
* @param {number} params.max - Same as above, but it specifies the maximum value or length.
* @param {boolean} params.integer - For the "number" type, only accept integers.
*/
register (key, params) {
this.validateType(params.type)
this.schema[key] = params
if (params.def != null) this.defaults[key] = params.def
else this.defaults[key] = null
}
/**
* Unregisters a setting parameter from the schema.
* @param {string} key - Key to remove
*/
unregister (key) {
if (this.defaults[key]) delete this.defaults[key]
if (this.schema[key]) delete this.schema[key]
}
/**
* Gets all the parameters from a guild
* @param {Discord.Guild} guild
*/
async getForGuild (guild) {
if (guild) {
const g = await Core.guilds.getGuild(guild)
const gSett = g.data.settings || {}
// TODO: Exclude parameters from disabled modules
return Object.freeze(Object.assign({}, this.defaults, this.globals, gSett, this.overrides))
} else {
return Object.freeze(Object.assign({}, this.defaults, this.globals, this.overrides))
}
}
/**
* Gets a specific parameter from a guild
* @param {Discord.Guild} guild
* @param {string} key
*/
async getGuildParam (guild, key) {
if (!this.schema[key]) throw new Error(`The parameter "${key}" does not exist.`)
const sett = await this.getForGuild(guild)
return sett[key]
}
async setGuildParam (guild, key, value) {
if (!this.schema[key]) throw new Error(`The parameter "${key}" does not exist.`)
// Fail if an override is set
if (this.overrides[key] != null) throw new Error(`This setting was overriden by the bot owner.`)
// Get the parameters from the schema
const params = this.schema[key]
this.validateType(params.type)
// Load current parameter
let newVal = await this.getGuildParam(guild, key)
switch (params.type) {
case Boolean:
// Positive Answers
if (['yes', 'y', 'true', 'on', '1'].indexOf(value.toLowerCase()) >= 0) newVal = true
if (['no', 'n', 'false', 'off', '0'].indexOf(value.toLowerCase()) >= 0) newVal = false
break
case String:
newVal = value.toString()
if (params.min != null && newVal.length < params.min) throw new Error('Value is too short.')
if (params.max != null && newVal.length > params.max) throw new Error('Value is too long.')
break
case Number:
newVal = parseFloat(value)
if (params.integer) newVal = parseInt(value)
if (params.min != null && newVal < params.min) throw new Error('Value is too low.')
if (params.max != null && newVal > params.max) throw new Error('Value is too high.')
break
case Core.Locale:
const loc = value.split('.')[0]
const lang = loc.split('_')[0]
const country = loc.split('_')[1]
const match = Object.keys(Core.locales.loaded).find(l => {
if (l.split('_')[0] === lang && l.split('_')[1] === country) return true
if (l.split('_')[0] === lang && !country) return true
})
if (match) {
newVal = match
} else {
throw new Error('Invalid language.')
}
break
// TODO: Beter handling of these types.
case Discord.VoiceChannel:
case Discord.TextChannel:
case Discord.User:
if (value === '*') newVal = undefined
else newVal = value.id || value.match(/\d+/)[0]
break
}
const g = await Core.guilds.getGuild(guild)
if (!g.data.settings) g.data.settings = {}
g.data.settings[key] = newVal
await g.saveData()
}
}
module.exports = SettingsManager