UNPKG

dexare

Version:

Modular and extendable Discord bot framework

268 lines (267 loc) 11.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const collection_1 = __importDefault(require("@discordjs/collection")); const eris_1 = __importDefault(require("eris")); const eventemitter3_1 = __importDefault(require("eventemitter3")); const constants_1 = require("../constants"); const commands_1 = __importDefault(require("../modules/commands")); const collector_1 = __importDefault(require("../modules/collector")); const logger_1 = __importDefault(require("../util/logger")); const events_1 = __importDefault(require("./events")); const permissions_1 = __importDefault(require("./permissions")); const memory_1 = __importDefault(require("../dataManagers/memory")); class DexareClient extends eventemitter3_1.default { constructor(config, bot) { // eslint-disable-next-line constructor-super super(); this.events = new events_1.default(this); this.logger = new logger_1.default(this, 'dexare/client'); this.modules = new collection_1.default(); this.commands = new commands_1.default(this); this.collector = new collector_1.default(this); this.data = new memory_1.default(this); // eslint-disable-next-line no-undef this._typingIntervals = new Map(); this._hookedEvents = []; this._erisEventsLogged = false; this.config = config; if (bot) this.bot = bot; else { let token = this.config.token; if (!this.config.token.startsWith('Bot ')) token = 'Bot ' + this.config.token; this.bot = new eris_1.default.Client(token, this.config.erisOptions); } this.permissions = new permissions_1.default(this); this.modules.set('commands', this.commands); this.commands._load(); this.modules.set('collector', this.collector); this.collector._load(); } /** * Load modules into the client. * @param moduleObjects The modules to load. * @returns The client for chaining purposes */ loadModules(...moduleObjects) { const modules = moduleObjects.map(this._resolveModule.bind(this)); const loadOrder = this._getLoadOrder(modules); for (const modName of loadOrder) { const mod = modules.find((mod) => mod.options.name === modName); if (this.modules.has(mod.options.name)) throw new Error(`A module in the client already has been named "${mod.options.name}".`); this._log('debug', `Loading module "${modName}"`); this.modules.set(modName, mod); mod._load(); } return this; } /** * Load modules into the client asynchronously. * @param moduleObjects The modules to load. * @returns The client for chaining purposes */ async loadModulesAsync(...moduleObjects) { const modules = moduleObjects.map(this._resolveModule.bind(this)); const loadOrder = this._getLoadOrder(modules); for (const modName of loadOrder) { const mod = modules.find((mod) => mod.options.name === modName); if (this.modules.has(mod.options.name)) throw new Error(`A module in the client already has been named "${mod.options.name}".`); this._log('debug', `Loading module "${modName}"`); this.modules.set(modName, mod); await mod._load(); } } /** * Loads a module asynchronously into the client. * @param moduleObject The module to load */ async loadModule(moduleObject) { const mod = this._resolveModule(moduleObject); if (this.modules.has(mod.options.name)) throw new Error(`A module in the client already has been named "${mod.options.name}".`); this._log('debug', `Loading module "${mod.options.name}"`); this.modules.set(mod.options.name, mod); await mod._load(); } /** * Unloads a module. * @param moduleName The module to unload */ async unloadModule(moduleName) { if (!this.modules.has(moduleName)) return; const mod = this.modules.get(moduleName); this._log('debug', `Unloading module "${moduleName}"`); await mod.unload(); this.modules.delete(moduleName); } /** * Loads a data manager asynchronously into the client. * @param moduleObject The manager to load * @param startOnLoad Whether to start the manager after loading */ async loadDataManager(mgrObject, startOnLoad = false) { if (typeof mgrObject === 'function') mgrObject = new mgrObject(this); else if (typeof mgrObject.default === 'function') mgrObject = new mgrObject.default(this); if (typeof mgrObject.start !== 'function') throw new Error(`Invalid data manager object to load: ${mgrObject}`); await this.data.stop(); this.data = mgrObject; if (startOnLoad) await this.data.start(); } /** * Log events to console. * @param logLevel The level to log at. * @param excludeModules The modules to exclude */ logToConsole(logLevel = 'info', excludeModules = []) { const levels = ['debug', 'info', 'warn', 'error']; const index = levels.indexOf(logLevel); this.on('logger', (level, moduleName, args) => { let importance = levels.indexOf(level); if (importance === -1) importance = 0; if (importance < index) return; if (excludeModules.includes(moduleName)) return; let logFunc = console.debug; if (level === 'info') logFunc = console.info; else if (level === 'error') logFunc = console.error; else if (level === 'warn') logFunc = console.warn; logFunc(level.toUpperCase(), `[${moduleName}]`, ...args); }); return this; } /** Logs informational Eris events to Dexare's logger event. */ logErisEvents() { if (this._erisEventsLogged) return this; this._erisEventsLogged = true; this.on('ready', () => this.emit('logger', 'info', 'eris', ['All shards ready.'])); this.on('disconnect', () => this.emit('logger', 'info', 'eris', ['All shards disconnected.'])); this.on('reconnecting', () => this.emit('logger', 'info', 'eris', ['Reconnecting...'])); this.on('debug', (message, id) => this.emit('logger', 'debug', 'eris', [message], { id })); this.on('warn', (message, id) => this.emit('logger', 'warn', 'eris', [message], { id })); this.on('error', (error, id) => this.emit('logger', 'error', 'eris', [error], { id })); this.on('connect', (id) => this.emit('logger', 'info', 'eris', ['Shard connected.'], { id })); this.on('hello', (trace, id) => this.emit('logger', 'debug', 'eris', ['Shard recieved hello.'], { id, trace })); this.on('shardReady', (id) => this.emit('logger', 'info', 'eris', ['Shard ready.'], { id })); this.on('shardResume', (id) => this.emit('logger', 'warn', 'eris', ['Shard resumed.'], { id })); this.on('shardDisconnect', (error, id) => this.emit('logger', 'warn', 'eris', ['Shard disconnected.', error], { id })); return this; } /** * Register an event. * @param event The event to register * @param listener The event listener */ on(event, listener) { if (typeof event === 'string' && !this._hookedEvents.includes(event) && constants_1.ErisEventNames.includes(event)) { // @ts-ignore this.bot.on(event, (...args) => this.emit(event, ...args)); this._hookedEvents.push(event); } return super.on(event, listener); } /** * Creates a promise that resolves on the next event * @param event The event to wait for */ waitTill(event) { return new Promise((resolve) => this.once(event, resolve)); } /** Connects and logs in to Discord. */ async connect() { await this.events.emitAsync('beforeConnect'); await this.data.start(); await this.bot.connect(); await this.events.emitAsync('afterConnect'); } /** Disconnects the bot. */ async disconnect(reconnect = false) { await this.events.emitAsync('beforeDisconnect', reconnect); await this.data.stop(); this.bot.disconnect({ reconnect }); await this.events.emitAsync('afterDisconnect', reconnect); } /** * Start typing in a channel * @param channelID The channel's ID to start typing in */ async startTyping(channelID) { if (this.isTyping(channelID)) return; await this.bot.sendChannelTyping(channelID); this._typingIntervals.set(channelID, setInterval(() => { this.bot.sendChannelTyping(channelID).catch(() => this.stopTyping(channelID)); }, 5000)); } /** * Whether the bot is currently typing in a channel. * @param channelID The channel ID to check for */ isTyping(channelID) { return this._typingIntervals.has(channelID); } /** * Stops typing in a channel. * @param channelID The channel's ID to stop typing in */ stopTyping(channelID) { if (!this.isTyping(channelID)) return; const interval = this._typingIntervals.get(channelID); clearInterval(interval); this._typingIntervals.delete(channelID); } /** @hidden */ _resolveModule(moduleObject) { if (typeof moduleObject === 'function') moduleObject = new moduleObject(this); else if (typeof moduleObject.default === 'function') moduleObject = new moduleObject.default(this); if (typeof moduleObject.load !== 'function') throw new Error(`Invalid module object to load: ${moduleObject}`); return moduleObject; } /** @hidden */ _getLoadOrder(modules) { const loadOrder = []; const insert = (mod) => { if (mod.options.requires && mod.options.requires.length) mod.options.requires.forEach((modName) => { const dep = modules.find((mod) => mod.options.name === modName) || this.modules.get(modName); if (!dep) throw new Error(`Module '${mod.options.name}' requires dependency '${modName}' which does not exist!`); if (!this.modules.has(modName)) insert(dep); }); if (!loadOrder.includes(mod.options.name)) loadOrder.push(mod.options.name); }; modules.forEach((mod) => insert(mod)); return loadOrder; } _log(level, ...args) { this.emit('logger', level, 'dexare', args); } } exports.default = DexareClient;