UNPKG

eris-boiler

Version:
390 lines (350 loc) 10.6 kB
const { join } = require('path') const { promises: { readdir, stat } } = require('fs') const { Client } = require('@alex-taxiera/eris') const Orator = require('../orator') const RAMManager = require('../ram-manager') const StatusManager = require('../status-manager') const { logger, ExtendedMap } = require('../../util') const defaults = require('../../config/settings.json') class DataClient extends Client { /** * Class representing a DataClient. * @extends {Client} * @param {String} token The Discord bot token. * @param {DataClientOptions} [options] The DataClient options. */ constructor (token, { databaseManager = new RAMManager(), oratorOptions, statusManagerOptions, erisOptions, disabledEvents = [] } = {}) { super(token, erisOptions) /** * @type {DatabaseManager} */ this.dbm = databaseManager /** * @type {Orator} */ this.ora = new Orator( defaults.oratorOptions.defaultPrefix, { ...defaults.oratorOptions, ...oratorOptions } ) /** * @type {StatusManager} */ this.sm = new StatusManager( this, databaseManager, { ...defaults.statusManagerOptions, ...statusManagerOptions } ) /** * @type {ExtendedMap<string, Command<this>>} */ this.commands = new ExtendedMap() /** * @type {ExtendedMap<string, Permission>} */ this.permissions = new ExtendedMap() const fromRoot = (path) => join(__dirname, '../..', path) this._commandsToLoad = [ fromRoot('commands') ] this._settingCommandsToLoad = [] this._eventsToLoad = [ fromRoot('events') ] this._permissionsToLoad = [ fromRoot('permissions') ] // load built in events and commands this .on('ready', this._onReady.bind(this)) .on('guildCreate', this._onGuildCreate.bind(this)) .on('messageCreate', this._onMessageCreate.bind(this)) } /** * Connect to discord. * @returns {Promise<void>} */ async connect () { await Promise.all([ this._loadLoadables('commands', this._commandsToLoad), this._loadLoadables('events', this._eventsToLoad), this._loadLoadables('permissions', this._permissionsToLoad) ]) await this._loadLoadables('settings', this._settingCommandsToLoad) this.ora.permissions = this.permissions logger.info('Connecting to Discord...') return super.connect() } /** * Find a command from commands. * @param {string} name Name of command to search. * @param {ExtendedMap<string, Command<this>>} commands A collection of commands to search instead of the build in commands. * @returns {Command<this>|void} */ findCommand (name, commands = this.commands) { if (name) { return commands.find( (command) => command.name.toLowerCase() === name.toLowerCase() || command.aliases.includes(name.toLowerCase()) ) } } /** * Ready event handler. * @private * @returns {void} */ _onReady () { logger.success('Connected') this._addNewGuilds() this._setOwner() this.sm.initialize() } /** * guildCreate event handler. * @private * @param {Guild} guild The guild to add. * @returns {Promise<DatabaseObject|void>} The new guild database object. */ _onGuildCreate (guild) { return this._addGuild(guild) } /** * messageCreate event handler. * @private * @param {Message} msg The message that triggered the event {@link https://abal.moe/Eris/docs/Message|(link)}. * @returns {Promise<void>} */ _onMessageCreate (msg) { return this.ora.processMessage(this, msg) } /** * Check guilds and add new ones. * @private * @returns {Array<Promise<DatabaseObject|void>>} The list of newly added guild database objects. */ async _addNewGuilds () { const dbGuilds = await this.dbm.newQuery('guild').find() const toAdd = this.guilds.filter( (guild) => !dbGuilds.find((dbGuild) => dbGuild.id === guild.id) ) return Promise.all(toAdd.map((guild) => this._addGuild(guild))) } /** * Add a guild to the database. * @private * @param {Guild} guild The guild to add. * @returns {Promise<DatabaseObject|void>} The new guild database object. */ _addGuild (guild) { return this.dbm.newObject('guild') .save({ id: guild.id }) .catch((error) => { logger.error('Could not add guild:', error) }) } /** * Set bot owner. * @private * @returns {Promise<void>} */ async _setOwner () { const { owner, team } = await this.getOAuthApplication() this.owners = (team && team.members) || [ owner ] } /** * Load data files. * @private * @param {string} path The path to the loadable file/directory. * @returns {Array<LoadableObject>} The loadable objects loaded from file. */ async _loadFiles (path) { const file = await stat(path) const files = file.isDirectory() ? await readdir(path) : [ '' ] const res = [] for (const fd of files) { const filePath = join(path, fd) if ( (filePath.match(/(?<!\.(?:test|spec|d))\.[jt]sx?$/)) || (await stat(filePath)).isDirectory() ) { try { let data = require(filePath) if (data.__esModule) { data = data.default } if (!data.isIndex) { res.push(data) } } catch (e) { logger.error( `Unable to read ${path}/${fd}:\n\t\t\u0020${e}` ) } } } return res } /** * Resolve loadable, be it path or array. * @private * @param {Loadable} loadable Parse a loadable to clean up any arrays or paths. * @returns {Array<LoadableObject>} The cleaned loadable(s). */ async _resolveLoadables (loadable) { if (!Array.isArray(loadable)) { loadable = [ loadable ] } const ax = [] for (const dx of loadable) { if (typeof dx === 'string') { ax.push(...(await this._loadFiles(dx))) } else { ax.push(dx) } } return ax } /** * Add commands to store. * @param {...string|Command<this>|Array<string|Command<this>>} commands Commands to add to store. * @returns {DataClient} Current state of DataClient. */ addCommands (...commands) { return this._addLoadables(commands, this._commandsToLoad) } /** * Add settings commands to store. * @param {...string|Command<this>|Array<string|Command<this>>} commands Commands to add to store. * @returns {DataClient} Current state of DataClient. */ addSettingCommands (...commands) { return this._addLoadables(commands, this._settingCommandsToLoad) } /** * Add events to store. * @param {...string|DiscordEvent|Array<string|DiscordEvent>} events Events to add to store. * @returns {DataClient} Current state of DataClient. */ addEvents (...events) { return this._addLoadables(events, this._eventsToLoad) } /** * Add permissions to store. * @param {...string|Permission|Array<string|Permission>} permissions Permissions to add to store. * @returns {DataClient} Current state of DataClient. */ addPermissions (...permissions) { return this._addLoadables(permissions, this._permissionsToLoad) } /** * Add loadables to store. * @private * @param {Array<Loadable>} loadables Array of things to load. * @param {Array<Loadable>} store Store to save loadables too. * @returns {DataClient} Current state of DataClient. */ _addLoadables (loadables, store) { let cx = loadables.length while (cx--) { if (Array.isArray(loadables[cx])) { let i = loadables[cx].length while (i--) { store.push(loadables[cx][i]) } } else { store.push(loadables[cx]) } } return this } /** * Loads some loadable files, calls correct loader function based on type. * @private * @param {string} type Type of loadable. * @param {Array<Loadable>} store Array of things to load. * @returns {Promise<void>} */ async _loadLoadables (type, store) { if (!store.length) { return } logger.info(`Loading ${type}...`) let loader switch (type) { case 'commands': loader = this._loadCommand break case 'settings': loader = this._loadSettingCommand break case 'events': loader = this._loadEvent break case 'permissions': loader = this._loadPermission break default: throw Error(`Unknown type: ${type}`) } for (const loadables of store) { for (const loadable of await this._resolveLoadables(loadables)) { loader.call(this, loadable) } } store = [] } /** * Command loader. * @private * @param {Command<this>} command The command to load. * @returns {void} */ _loadCommand (command) { this.commands.set(command.name, command) } /** * SettingCommand loader. * @private * @param {SettingCommand<this>} command The command to load. * @returns {void} */ _loadSettingCommand (command) { this.commands.get('settings').subCommands.set(command.name, command) } /** * Permission loader. * @private * @param {Permission} permission The permission to load. * @returns {void} */ _loadPermission (permission) { this.permissions.set(permission.level, permission) } /** * Event loader. * @private * @param {DiscordEvent} event The event to load. * @returns {void} */ _loadEvent (event) { this.on(event.name, event.run.bind(null, this)) } } module.exports = DataClient /** * @typedef DataClientOptions * @property {DatabaseManager} [databaseManager] The DatabaseManager. * @property {OratorOptions} [oratorOptions] Params to pass to the Orator class. * @property {StatusManagerOptions} [statusManagerOptions] StatusManagerOptions object. * @property {Object} [options.erisOptions] Options to pass to Eris Client. */ /** * @typedef {string|LoadableObject|Array<string|LoadableObject>} Loadable */ /** * @typedef {Command<any>|DiscordEvent<any>|Permission} LoadableObject */