const mkdirp = require('mkdirp');
const path = require('path');
const botutil = require('./botutil.js');
const CommandContainer = require('./commandcontainer.js')
/**
* The base class to be extended by all bot plugins.
* It provides methods to facilitate the implementation of a plugin.
*/
class Plugin {
/**
* The identifier is used to identify plugin and to
* provide a location to store permanent data.
* @param {String} identifier the plugins unique identifier
*/
constructor(identifier) {
if(!identifier) throw Error("plugin identifier cannot be empty!")
this._handleCommandImpl = this._handleCommandImpl.bind(this)
/**
* The plugin's unique identifier.
* @type {String}
*/
this.identifier = identifier
/**
* True if the plugin is running.
* @readonly
* @type {Boolean}
*/
this.running = false
/**
* A map that stores the commands for this plugin.
* @type {CommandContainer}
*/
this.commandContainer = new CommandContainer()
}
/**
* Called when the plugin should be started.
* The client property is available when this method is called.
*/
startPlugin() {}
/**
* Called when the plugin should be stopped.
* The plugin must release the client instance.
*/
stopPlugin() {}
/**
* Get the configuration file path for this plugin.
* @returns {String} a relative path to the configuration file
*/
configFileLocation() {
const configLocation = `./config/${this.identifier}_config.json`
mkdirp.sync(path.dirname(configLocation))
return configLocation
}
/**
* Get the data directory for this plugin.
* @returns {String} a relative path without a trailing slash
*/
dataDirLocation() {
const dataLocation = `./data/${this.identifier}`
return dataLocation
}
/**
* Called to specify the default configuration parameters.
* The config object will not be loaded from the file if the default config returns null.
* @returns {Object} a object containing the default configuration parameters or
* null if a config file is not needed
*/
defaultConfig() {
return null
}
_handleCommandImpl(message) {
if(message.content.substring(0, 1) !== '!') {
return
}
const args = message.content.substring(1).split(' ')
const cmd = args.splice(0, 1)[0]
const command = this.commandContainer.get(cmd)
if(!command) return
if(!command.options.handleBotMsgs) {
if(message.author.bot) return
}
if(command.options.requiredChannelType) {
switch(command.options.requiredChannelType){
case "direct":
if(["dm", "group"].indexOf(message.channel.type)===-1) {
message.channel.send("You must send this command in a direct message!")
return
}
break;
case "guild":
if(["text", "voice", "category"].indexOf(message.channel.type)===-1) {
message.channel.send("You must send this command in a channel!")
return
}
break;
default:
throw Error(`unknown required channel type: ${command.options.requiredChannelType}!`)
}
}
const guild = this.client.guilds.cache.first()
if(command.options.elevated) {
if(!guild.member(message.author).hasPermission("ADMINISTRATOR")) {
message.channel.send("Insufficient permissions! You need to be an administrator to do that.")
return
}
}
command.handler(args, message)
}
_startPluginImpl(client) {
this.client = client
mkdirp.sync(this.dataDirLocation())
const config = this.defaultConfig()
if(config) {
botutil.writeJSONObjectSyncIfNotExists(this.configFileLocation(), config)
this.config = botutil.loadJSONSyncIfExists(this.configFileLocation())
}
this.running = true
this.client.on('message', this._handleCommandImpl)
this.startPlugin()
}
_stopPluginImpl() {
this.stopPlugin()
this.client.removeListener('message', this._handleCommandImpl)
this.client = null
this.running = false
}
}
module.exports = Plugin