From e74fa83ed33288b7458e3cf9420ecbd09b703eb9 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sat, 2 Mar 2019 20:10:13 +0100 Subject: [PATCH] Added Unit Tests & Logging - Added own logger class that includes the module name - added unit tests for MessageHandler, Command and Answer - removed unit tests for GuildHandler and lib/cmd - removed old command templates --- bot.js | 107 +++++----- commands/globalcommands.json | 109 ---------- commands/servercommands.json | 203 ------------------ lib/CommandLib.js | 11 + lib/MessageLib.js | 16 +- lib/MusicLib.js | 77 ++++--- lib/WebLib.js | 16 +- lib/commands/AnilistApiCommands/index.js | 6 + lib/commands/InfoCommands/index.js | 1 + lib/commands/MusicCommands/index.js | 2 - lib/commands/ServerUtilityCommands/index.js | 2 - lib/commands/UtilityCommands/index.js | 2 - lib/guilding.js | 19 +- lib/logging.js | 170 ++++++++++----- lib/utils.js | 39 ++-- test/test.js | 225 ++++++-------------- 16 files changed, 332 insertions(+), 673 deletions(-) delete mode 100644 commands/globalcommands.json delete mode 100644 commands/servercommands.json diff --git a/bot.js b/bot.js index 5d995fc..0edd8db 100644 --- a/bot.js +++ b/bot.js @@ -1,6 +1,6 @@ const Discord = require("discord.js"), fs = require('fs-extra'), - logger = require('./lib/logging').getLogger(), + logging = require('./lib/logging'), msgLib = require('./lib/MessageLib'), guilding = require('./lib/guilding'), utils = require('./lib/utils'), @@ -21,27 +21,27 @@ class Bot { constructor() { this.client = new Discord.Client({autoReconnect: true}); + this.logger = new logging.Logger(this); this.rotator = null; this.maindb = null; this.presences = []; - this.messageHandler = new msgLib.MessageHandler(this.client, logger); + this.messageHandler = new msgLib.MessageHandler(this.client); this.guildHandlers = {}; - logger.verbose('Verifying config'); + this.logger.verbose('Verifying config'); let configVerifyer = new utils.ConfigVerifyer(config, [ "api.botToken", "api.youTubeApiKey", "commandSettings.maxSequenceParallel", "commandSettings.maxSequenceSerial" ]); - if (!configVerifyer.verifyConfig(logger)) + if (!configVerifyer.verifyConfig(this.logger)) if (!args.i) { - logger.info('Invalid config. Exiting'); - logger.flush().then(() => { + this.logger.info('Invalid config. Exiting'); + this.logger.flush().then(() => { process.exit(1); }); } - guilding.setLogger(logger); } /** @@ -49,7 +49,7 @@ class Bot { * @returns {Promise} */ async initServices() { - logger.verbose('Registering cleanup function'); + this.logger.verbose('Registering cleanup function'); utils.Cleanup(() => { for (let gh in Object.values(this.guildHandlers)) @@ -57,10 +57,10 @@ class Bot { gh.destroy(); this.client.destroy().then(() => { - logger.debug('destroyed client'); + this.logger.debug('destroyed client'); }).catch((err) => { - logger.error(err.message); - logger.debug(err.stack); + this.logger.error(err.message); + this.logger.debug(err.stack); }); this.maindb.close(); }); @@ -68,25 +68,24 @@ class Bot { if (config.webinterface && config.webinterface.enabled) await this.initializeWebserver(); - logger.verbose('Registering commands'); - await this.messageHandler - .registerCommandModule(require('./lib/commands/AnilistApiCommands').module, {}); - await this.messageHandler - .registerCommandModule(require('./lib/commands/UtilityCommands').module, {bot: this, logger: logger, config: config}); - await this.messageHandler - .registerCommandModule(require('./lib/commands/InfoCommands').module, {client: this.client, messageHandler: this.messageHandler}); - await this.messageHandler - .registerCommandModule(require('./lib/commands/MusicCommands').module, { - getGuildHandler: async (g) => await this.getGuildHandler(g), - logger: logger - }); - await this.messageHandler - .registerCommandModule(require('./lib/commands/ServerUtilityCommands').module, { - getGuildHandler: async (g) => await this.getGuildHandler(g), - logger: logger, - messageHandler: this.messageHandler, - config: config - }); + this.logger.verbose('Registering commands'); + await this.messageHandler.registerCommandModule(require('./lib/commands/AnilistApiCommands').module, {}); + await this.messageHandler.registerCommandModule(require('./lib/commands/UtilityCommands').module, { + bot: this, + config: config + }); + await this.messageHandler.registerCommandModule(require('./lib/commands/InfoCommands').module, { + client: this.client, + messageHandler: this.messageHandler + }); + await this.messageHandler.registerCommandModule(require('./lib/commands/MusicCommands').module, { + getGuildHandler: async (g) => await this.getGuildHandler(g) + }); + await this.messageHandler.registerCommandModule(require('./lib/commands/ServerUtilityCommands').module, { + getGuildHandler: async (g) => await this.getGuildHandler(g), + messageHandler: this.messageHandler, + config: config + }); this.registerEvents(); } @@ -96,11 +95,11 @@ class Bot { */ async start() { await this.client.login(authToken); - logger.debug("Logged in"); + this.logger.debug("Logged in"); if (this.webServer) { this.webServer.start(); - logger.info(`WebServer runing on port ${this.webServer.port}`); + this.logger.info(`WebServer runing on port ${this.webServer.port}`); } } @@ -109,9 +108,9 @@ class Bot { * @returns {Promise} */ async initializeDatabase() { - logger.debug('Checking for ./data/ existence'); + this.logger.debug('Checking for ./data/ existence'); await fs.ensureDir('./data'); - logger.verbose('Connecting to main database'); + this.logger.verbose('Connecting to main database'); this.maindb = new sqliteAsync.Database('./data/main.db'); await this.maindb.init(); @@ -119,7 +118,7 @@ class Bot { ${utils.sql.pkIdSerial}, text VARCHAR(255) UNIQUE NOT NULL )`); - logger.debug('Loading Presences...'); + this.logger.debug('Loading Presences...'); await this.loadPresences(); } @@ -127,12 +126,11 @@ class Bot { * initializes the api webserver */ async initializeWebserver() { - logger.verbose('Importing weblib'); + this.logger.verbose('Importing weblib'); weblib = require('./lib/WebLib'); - weblib.setLogger(logger); - logger.verbose('Creating WebServer'); + this.logger.verbose('Creating WebServer'); this.webServer = new weblib.WebServer(config.webinterface.port || 8080); - logger.debug('Setting Reference Objects to webserver'); + this.logger.debug('Setting Reference Objects to webserver'); await this.webServer.setReferenceObjects({ client: this.client, @@ -159,7 +157,7 @@ class Bot { lineReader.on('line', (line) => { this.maindb.run('INSERT INTO presences (text) VALUES (?)', [line], (err) => { if (err) - logger.warn(err.message); + this.logger.warn(err.message); }); this.presences.push(line); @@ -190,8 +188,8 @@ class Bot { this.client.user.setPresence({ game: {name: `${gamepresence} | ${pr}`, type: "PLAYING"}, status: 'online' - }).then(() => logger.debug(`Presence rotation to ${pr}`)) - .catch((err) => logger.warn(err.message)); + }).then(() => this.logger.debug(`Presence rotation to ${pr}`)) + .catch((err) => this.logger.warn(err.message)); } @@ -200,12 +198,12 @@ class Bot { */ registerEvents() { this.client.on('error', (err) => { - logger.error(err.message); - logger.debug(err.stack); + this.logger.error(err.message); + this.logger.debug(err.stack); }); this.client.on('ready', () => { - logger.info(`logged in as ${this.client.user.tag}!`); + this.logger.info(`logged in as ${this.client.user.tag}!`); this.client.user.setPresence({ game: { @@ -213,7 +211,7 @@ class Bot { }, status: 'online' }).catch((err) => { if (err) - logger.warn(err.message); + this.logger.warn(err.message); }); }); @@ -248,18 +246,19 @@ class Bot { // Executing the main function if (typeof require !== 'undefined' && require.main === module) { - logger.info("Starting up... "); - logger.debug('Calling constructor...'); + let logger = logging.logger; + logger.info("Starting up... ", {m: 'bot.init'}); + logger.debug('Calling constructor...', {m: 'bot.init'}); let discordBot = new Bot(); - logger.debug('Initializing services...'); + logger.debug('Initializing services...', {m: 'bot.init'}); discordBot.initServices().then(() => { - logger.debug('Starting Bot...'); + logger.debug('Starting Bot...', {m: 'bot.init'}); discordBot.start().catch((err) => { //eslint-disable-line promise/no-nesting - logger.error(err.message); - logger.debug(err.stack); + logger.error(err.message, {m: 'bot.init'}); + logger.debug(err.stack, {m: 'bot.init'}); }); }).catch((err) => { - logger.error(err.message); - logger.debug(err.stack); + logger.error(err.message, {m: 'bot.init'}); + logger.debug(err.stack, {m: 'bot.init'}); }); } diff --git a/commands/globalcommands.json b/commands/globalcommands.json deleted file mode 100644 index ca49c38..0000000 --- a/commands/globalcommands.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "utils": { - "help": { - "name": "help", - "permission": "all", - "description": "Shows this help command.", - "category": "Utility", - "args": [ - "command" - ] - }, - "say": { - "name": "say", - "permission": "all", - "description": "Says something. ~say [String].", - "category": "Utility" - }, - "addpresence": { - "name": "addpresence", - "permission": "owner", - "description": "Adds a presence to presences.", - "category": "Utility" - }, - "shutdown": { - "name": "shutdown", - "description": "Shuts the bot down.", - "permission": "owner", - "category": "Utility" - }, - "rotate": { - "name": "rotate", - "description": "Forces a presence rotation", - "permission": "owner", - "category": "Utility" - }, - "createUser": { - "name": "createUser", - "permission": "owner", - "description": "Creates a new user for the webinterface.", - "category": "Utility", - "args": [ - "username", - "password", - "scope" - ] - }, - "bugreport": { - "name": "bug", - "permission": "all", - "description": "Get info about how to report a bug", - "category": "Utility", - "response": { - "bug_report": "Please report your bugs [here](https://github.com/Trivernis/discordbot.js/issues)" - } - } - }, - "info": { - "about": { - "name": "about", - "permission": "all", - "description": "Shows information about this bot.", - "category": "Info", - "response": { - "about_icon": "This icon war created by [blackrose14344](https://www.deviantart.com/blackrose14344). \n [Original](https://www.deviantart.com/blackrose14344/art/2B-Chi-B-685771489)", - "about_creator": "This bot was created by Trivernis. More about this bot [here](https://github.com/Trivernis/discordbot.js)." - } - }, - "ping": { - "name": "ping", - "permission": "owner", - "description": "Answers with the current average ping of the bot.", - "category": "Info" - }, - "uptime": { - "name": "uptime", - "permission": "owner", - "description": "Answers with the current uptime of the bot.", - "category": "Info" - }, - "guilds": { - "name": "guilds", - "permission": "owner", - "description": "Answers with the number of guilds the bot has joined.", - "category": "Info" - } - }, - "api": { - "AniList": { - "animeSearch": { - "name": "anime", - "permission": "all", - "description": "Answers the anime found for that name on AniList.", - "category": "AniList", - "response": { - "not_found": "The Anime was not found :(" - } - }, - "mangaSearch": { - "name": "manga", - "permission": "all", - "description": "Answers the manga found for that name on AniList.", - "category": "AniList", - "response": { - "not_found": "The Manga was not found :(" - } - } - } - } -} diff --git a/commands/servercommands.json b/commands/servercommands.json deleted file mode 100644 index e83b393..0000000 --- a/commands/servercommands.json +++ /dev/null @@ -1,203 +0,0 @@ -{ - "utils": { - "roles": { - "name": "roles", - "permission": "all", - "description": "Shows the roles used for commands on the server.", - "category": "Utility" - }, - "savecmd": { - "name": "savecmd", - "permission": "moderator", - "description": "Saves a sequence of commands under a new name. ~save [cmdsequence] [cmdname]. Semicoli must be escaped with \\ (Backslash)", - "category": "Utility", - "response": { - "no_recursion": "You are **not** allowed to execute another saved command in this sequence. This is a safety measure to avoid endlessly recursive calls.", - "sequence_too_long": "This command sequence is too long!" - } - }, - "savedcmd": { - "name": "savedcmd", - "permission": "all", - "description": "Displays the saved commands.", - "category": "Utility", - "response": { - "no_commands": "There are no saved commands." - } - }, - "deletecmd": { - "name": "deletecmd", - "permission": "moderator", - "description": "Delete a saved command.", - "args": [ - "cmdname" - ], - "category": "Utility" - }, - "execute": { - "name": "execute", - "permission": "all", - "args": [ - "cmdname" - ], - "description": "Execute saved commands.", - "category": "Utility", - "response": { - "not_found": "This command could not be found." - } - } - }, - "music": { - "play": { - "name": "play", - "permission": "all", - "args": [ - "url" - ], - "description": "Adds the url to the YouTube video/playlist into the queue.", - "category": "Music", - "response": { - "success": "Added Song/Playlist to the queue.", - "failure": "Failed adding Song/Playlist to the queue.", - "url_invalid": "This is not a valid url!", - "no_url": "I need an url to a video to play!", - "no_voicechannel": "You need to join a voicechannel to do that!" - } - }, - "playnext": { - "name": "playnext", - "permission": "all", - "args": [ - "url" - ], - "description": "Adds the url to the YouTube video as next song to the queue.", - "category": "Music", - "response": { - "success": "Added Song as next Song to the queue.", - "failure": "Failed adding Song as next Song to the queue.", - "url_invalid": "This is not a valid url!", - "no_url": "I need an url to a video to play", - "no_voicechannel": "You need to join a voicechannel to do that!" - } - }, - "join": { - "name": "join", - "permission": "all", - "description": "Joins the VC you are in.", - "category": "Music", - "response": { - "not_connected": "You are not connected to a Voice Channel." - } - }, - "stop": { - "name": "stop", - "permission": "dj", - "description": "Stops playing music and leaves.", - "category": "Music", - "response": { - "success": "Stopping now...", - "not_playing": "I'm not playing music at the moment." - } - }, - "pause": { - "name": "pause", - "permission": "all", - "description": "Pauses playing.", - "category": "Music", - "response": { - "success": "Pausing playback.", - "not_playing": "I'm not playing music at the moment." - } - }, - "resume": { - "name": "resume", - "permission": "all", - "description": "Resumes playing.", - "category": "Music", - "response": { - "success": "Resuming playback.", - "not_playing": "I'm not playing music at the moment." - } - }, - "skip": { - "name": "skip", - "permission": "dj", - "description": "Skips the current song.", - "category": "Music", - "response": { - "success": "Skipping to the next song.", - "not_playing": "I'm not playing music at the moment." - } - }, - "clear": { - "name": "clear", - "permission": "dj", - "description": "Clears the queue.", - "category": "Music", - "response": { - "success": "The Queue has been cleared." - } - }, - "playlist": { - "name": "queue", - "permission": "all", - "description": "Shows the next ten songs.", - "category": "Music" - }, - "current": { - "name": "np", - "permission": "all", - "description": "Shows the currently playing song.", - "category": "Music", - "response": { - "not_playing": "I'm not playing music at the moment." - } - }, - "shuffle": { - "name": "shuffle", - "permission": "all", - "description": "Shuffles the playlist.", - "category": "Music", - "response": { - "success": "The Queue has been shuffled." - } - }, - "repeat": { - "name": "repeat", - "permission": "all", - "description": "Toggle listening on repeat.", - "category": "Music", - "response": { - "repeat_true": "Listening on repeat now!", - "repeat_false": "Not listening on repeat anymore." - } - }, - "savemedia": { - "name": "savemedia", - "permission": "dj", - "args": [ - "url" - ], - "description": "Saves the YouTube song/playlist with a specific name", - "category": "Music" - }, - "savedmedia": { - "name": "savedmedia", - "permission": "all", - "description": "Prints out all saved playlists and songs.", - "category": "Music", - "response": { - "no_saved": "There are no saved songs/playlists :(" - } - }, - "deletemedia": { - "name": "deletemedia", - "permission": "dj", - "description": "Deletes a saved media entry. ~deletemedia [name]", - "category": "Music", - "response": { - "no_name": "You must provide a name for the media that shall be deleted." - } - } - } -} diff --git a/lib/CommandLib.js b/lib/CommandLib.js index 5e8526b..01674e2 100644 --- a/lib/CommandLib.js +++ b/lib/CommandLib.js @@ -1,6 +1,7 @@ const Discord = require('discord.js'), yaml = require('js-yaml'), fsx = require('fs-extra'), + logging = require('./logging'), config = require('../config.json'), utils = require('./utils'); @@ -96,6 +97,7 @@ class CommandHandler { this.prefix = prefix; this.scope = scope; this.commands = {}; + this._logger = new logging.Logger(`${this.constructor.name}@${Object.keys(scopes)[this.scope]}`); } /** @@ -105,9 +107,11 @@ class CommandHandler { * @returns {Boolean | String | Promise} */ handleCommand(commandMessage, message) { + this._logger.debug(`Handling command ${commandMessage}`); let commandName = commandMessage.match(/^\S+/); if (commandName.length > 0) commandName = commandName[0]; + this._logger.silly(`Command name is ${commandName}`); if (commandName.indexOf(this.prefix) >= 0) { commandName = commandName.replace(this.prefix, ''); let argsString = commandMessage.replace(/^\S+/, ''); @@ -117,17 +121,21 @@ class CommandHandler { let args = argsString.match(/\S+/g); let command = this.commands[commandName]; if (command && this._checkPermission(message, command.permission)) { + this._logger.silly(`Permission ${command.permission} granted for command ${commandName}`); let kwargs = {}; if (args) for (let i = 0; i < Math.min(command.args.length, args.length); i++) kwargs[command.args[i]] = args[i]; return command.answer(message, kwargs, argsString); } else if (command) { + this._logger.silly(`Permission ${command.permission} denied for command ${commandName}`); return "You don't have permission for this command"; } else { + this._logger.silly(`Command ${commandName} not found.`); return false; } } else { + this._logger.silly(`No prefix found in command ${commandName}`); return false; } } @@ -139,6 +147,7 @@ class CommandHandler { registerCommand(command) { command.prefix = this.prefix; this.commands[command.name] = command; + this._logger.debug(`Registered ${command.name} on handler`); return this; } @@ -174,6 +183,7 @@ class CommandModule { */ constructor(scope) { this.scope = scope; + this._logger = new logging.Logger(this); } /** @@ -183,6 +193,7 @@ class CommandModule { */ async _loadTemplate(file) { let templateString = await fsx.readFile(this.templateFile || file, {encoding: 'utf-8'}); + this._logger.silly(`Loaded Template file ${this.templateFile || file}`); this.template = yaml.safeLoad(templateString); } diff --git a/lib/MessageLib.js b/lib/MessageLib.js index dd69d54..3642b43 100644 --- a/lib/MessageLib.js +++ b/lib/MessageLib.js @@ -1,6 +1,7 @@ const cmdLib = require('./CommandLib'), config = require('../config.json'), Discord = require('discord.js'), + logging = require('./logging'), promiseWaterfall = require('promise-waterfall'); /* eslint no-useless-escape: 0 */ @@ -11,11 +12,10 @@ class MessageHandler { * Message Handler to handle messages. Listens on the * _client message event. * @param client {Discord.Client} - * @param logger {winston._logger} */ - constructor (client, logger) { - this.logger = logger; + constructor (client) { this.discordClient = client; + this.logger = new logging.Logger(this); this.globalCmdHandler = new cmdLib.CommandHandler(config.prefix, cmdLib.CommandScopes.Global); this.userCmdHandler = new cmdLib.CommandHandler(config.prefix, @@ -70,7 +70,7 @@ class MessageHandler { _registerEvents() { this.logger.debug('Registering message event...'); this.discordClient.on('message', async (msg) => { - this.logger.debug(`<${msg.guild? msg.channel.name+'@'+msg.guild.name : 'PRIVATE'}> ${msg.author.tag}: ${msg.content}`); + this.logger.verbose(`<${msg.guild? msg.channel.name+'@'+msg.guild.name : 'PRIVATE'}> ${msg.author.tag}: ${msg.content}`); if (msg.author !== this.discordClient.user && this._checkPrefixStart(msg.content) && !this._checkRateReached(msg.author)) { @@ -90,7 +90,7 @@ class MessageHandler { * @private */ _parseSyntax(message) { - this.logger.debug('Parsing command sequence...'); + this.logger.silly('Parsing command sequence...'); let commandSequence = []; let content = message.content; let strings = content.match(/".+?"/g) || []; @@ -114,14 +114,14 @@ class MessageHandler { * Executes a sequence of commands */ async executeCommandSequence(cmdSequence, message) { - this.logger.debug(`Executing command sequence: ${JSON.stringify(cmdSequence)} ...`); + this.logger.silly(`Executing command sequence: ${JSON.stringify(cmdSequence)} ...`); let scopeCmdHandler = this.getScopeHandler(message); await Promise.all(cmdSequence.map((sq) => promiseWaterfall(sq.map((cmd) => async () => { try { - this.logger.debug(`Executing command ${cmd}`); + this.logger.silly(`Executing command ${cmd}`); let globalResult = await this.globalCmdHandler.handleCommand(cmd, message); let scopeResult = await scopeCmdHandler.handleCommand(cmd, message); - this.logger.debug(`globalResult: ${globalResult}, scopeResult: ${scopeResult}`); + this.logger.silly(`globalResult: ${globalResult}, scopeResult: ${scopeResult}`); if (scopeResult) this._answerMessage(message, scopeResult); diff --git a/lib/MusicLib.js b/lib/MusicLib.js index 7467e9c..d6558da 100644 --- a/lib/MusicLib.js +++ b/lib/MusicLib.js @@ -3,15 +3,9 @@ const ytdl = require("ytdl-core"), yttl = require('get-youtube-title'), config = require('../config.json'), utils = require('./utils.js'), + logging = require('./logging'), ytapiKey = config.api.youTubeApiKey; - -let logger = require('winston'); - -exports.setLogger = function (newLogger) { - logger = newLogger; -}; - /** * The Music Player class is used to handle music playing tasks on Discord Servers (Guilds). * @type {MusicPlayer} @@ -28,6 +22,8 @@ class MusicPlayer { this.voiceChannel = voiceChannel; this.quality = 'lowest'; this.exitTimeout = null; + this._logger = new logging.Logger(this); + this._logger.silly('Initialized Music Player'); } /** @@ -45,9 +41,9 @@ class MusicPlayer { } else if (voiceChannel) this.voiceChannel = voiceChannel; - logger.verbose(`Connecting to voiceChannel ${this.voiceChannel.name}`); + this._logger.verbose(`Connecting to voiceChannel ${this.voiceChannel.name}`); let connection = await this.voiceChannel.join(); - logger.info(`Connected to Voicechannel ${this.voiceChannel.name}`); + this._logger.info(`Connected to Voicechannel ${this.voiceChannel.name}`); this.conn = connection; } @@ -59,7 +55,6 @@ class MusicPlayer { this.repeat = value; if (this.current) this.queue.push(this.current); - } /** @@ -81,7 +76,7 @@ class MusicPlayer { updateChannel(voiceChannel) { if (voiceChannel) { this.voiceChannel = voiceChannel; - logger.debug(`Updated voiceChannel to ${this.voiceChannel.name}`); + this._logger.debug(`Updated voiceChannel to ${this.voiceChannel.name}`); } } @@ -96,7 +91,7 @@ class MusicPlayer { this.disp = this.conn.playFile(filename); this.playing = true; } else { - logger.warn("Not connected to a voicechannel. Connection now."); + this._logger.warn("Not connected to a voicechannel. Connection now."); this.connect(this.voiceChannel).then(() => { this.playFile(filename); }); @@ -111,13 +106,13 @@ class MusicPlayer { if (this.exitTimeout) { clearTimeout(this.exitTimeout); this.exitTimeout = null; - logger.debug(`Cleared exit timout for ${this.voiceChannel.name}`); + this._logger.debug(`Cleared exit timout for ${this.voiceChannel.name}`); } if (this.connected && this.voiceChannel.members.size === 1) { - logger.debug(`Set exit timout for ${this.voiceChannel.name}`); + this._logger.debug(`Set exit timout for ${this.voiceChannel.name}`); this.exitTimeout = setTimeout(() => { if (this.connected && this.voiceChannel.members.size === 1) - logger.verbose(`Exiting ${this.voiceChannel.name}`); + this._logger.verbose(`Exiting ${this.voiceChannel.name}`); this.stop(); }, config.music.timeout || 300000); } @@ -133,7 +128,7 @@ class MusicPlayer { async playYouTube(url, playnext) { let plist = utils.YouTube.getPlaylistIdFromUrl(url); if (plist) { - logger.debug(`Adding playlist ${plist} to queue`); + this._logger.debug(`Adding playlist ${plist} to queue`); let playlistItems = await ypi(ytapiKey, plist); let firstSong = utils.YouTube.getVideoUrlFromId(playlistItems.shift().resourceId.videoId); let firstSongTitle = null; @@ -141,14 +136,14 @@ class MusicPlayer { firstSongTitle = await this.getVideoName(firstSong); } catch(err) { if (err.message !== 'Not found') { - logger.warn(err.message); - logger.debug(err.stack); + this._logger.warn(err.message); + this._logger.debug(err.stack); } } if (this.repeat) this.queue.push({'url': firstSong, 'title': firstSongTitle}); - this.playYouTube(firstSong).catch((err) => logger.warn(err.message)); + this.playYouTube(firstSong).catch((err) => this._logger.warn(err.message)); for (let item of playlistItems) { let vurl = utils.YouTube.getVideoUrlFromId(item.resourceId.videoId); @@ -156,14 +151,14 @@ class MusicPlayer { this.queue.push({'url': vurl, 'title': await this.getVideoName(vurl)}); //eslint-disable-line no-await-in-loop } catch (err) { if (err.message !== 'Not found') { - logger.warn(err.message); - logger.debug(err.stack); + this._logger.warn(err.message); + this._logger.debug(err.stack); } } } - logger.debug(`Added ${playlistItems.length} songs to the queue`); + this._logger.debug(`Added ${playlistItems.length} songs to the queue`); } else if (!this.playing || !this.disp) { - logger.debug(`Playing ${url}`); + this._logger.debug(`Playing ${url}`); this.current = ({'url': url, 'title': await this.getVideoName(url)}); this.disp = this.conn.playStream(ytdl(url, @@ -178,7 +173,7 @@ class MusicPlayer { this.current = this.queue.shift(); if (this.repeat) // listen on repeat this.queue.push(this.current); - this.playYouTube(this.current.url).catch((err) => logger.warn(err.message)); + this.playYouTube(this.current.url).catch((err) => this._logger.warn(err.message)); } else { this.stop(); } @@ -186,7 +181,7 @@ class MusicPlayer { }); this.playing = true; } else { - logger.debug(`Added ${url} to the queue`); + this._logger.debug(`Added ${url} to the queue`); if (playnext) this.queue.unshift({'url': url, 'title': await this.getVideoName(url)}); else @@ -204,7 +199,7 @@ class MusicPlayer { return new Promise((resolve, reject) => { yttl(utils.YouTube.getVideoIdFromUrl(url), (err, title) => { if (err) { - logger.debug(JSON.stringify(err)); + this._logger.debug(JSON.stringify(err)); reject(err); } else { resolve(title); @@ -218,12 +213,12 @@ class MusicPlayer { * @param percentage {Number} */ setVolume(percentage) { - logger.verbose(`Setting volume to ${percentage}`); + this._logger.verbose(`Setting volume to ${percentage}`); if (this.disp !== null) { this.volume = percentage; this.disp.setVolume(percentage); } else { - logger.warn("No dispatcher found."); + this._logger.warn("No dispatcher found."); } } @@ -231,11 +226,11 @@ class MusicPlayer { * Pauses if a dispatcher exists */ pause() { - logger.verbose("Pausing music..."); + this._logger.verbose("Pausing music..."); if (this.disp !== null) this.disp.pause(); else - logger.warn("No dispatcher found"); + this._logger.warn("No dispatcher found"); } @@ -243,11 +238,11 @@ class MusicPlayer { * Resumes if a dispatcher exists */ resume() { - logger.verbose("Resuming music..."); + this._logger.verbose("Resuming music..."); if (this.disp !== null) this.disp.resume(); else - logger.warn("No dispatcher found"); + this._logger.warn("No dispatcher found"); } @@ -259,25 +254,25 @@ class MusicPlayer { this.playing = false; this.queue = []; this.current = null; - logger.verbose("Stopping music..."); + this._logger.verbose("Stopping music..."); try { if (this.disp) { this.disp.end('stop'); this.disp = null; - logger.debug("Ended dispatcher"); + this._logger.debug("Ended dispatcher"); } if (this.conn) { this.conn.disconnect(); this.conn = null; - logger.debug("Ended connection"); + this._logger.debug("Ended connection"); } if (this.voiceChannel) { this.voiceChannel.leave(); - logger.debug("Left VoiceChannel"); - logger.info(`Disconnected from Voicechannel ${this.voiceChannel.name}`); + this._logger.debug("Left VoiceChannel"); + this._logger.info(`Disconnected from Voicechannel ${this.voiceChannel.name}`); } } catch (error) { - logger.verbose(JSON.stringify(error)); + this._logger.verbose(JSON.stringify(error)); } } @@ -287,7 +282,7 @@ class MusicPlayer { * It tries to play the next song with playYouTube */ skip() { - logger.debug("Skipping song"); + this._logger.debug("Skipping song"); if (this.disp !== null) { this.disp.end(); } else { @@ -295,8 +290,8 @@ class MusicPlayer { if (this.queue.length > 0) { this.current = this.queue.shift(); this.playYouTube(this.current.url).catch((err) => { - logger.error(err.message); - logger.debug(err.stack); + this._logger.error(err.message); + this._logger.debug(err.stack); }); } else { this.stop(); diff --git a/lib/WebLib.js b/lib/WebLib.js index 2949816..3134530 100644 --- a/lib/WebLib.js +++ b/lib/WebLib.js @@ -4,6 +4,7 @@ const express = require('express'), compression = require('compression'), md5 = require('js-md5'), sha512 = require('js-sha512'), + logging = require('./logging'), fs = require('fs'), session = require('express-session'), SQLiteStore = require('connect-sqlite3')(session), @@ -12,12 +13,6 @@ const express = require('express'), config = require('../config.json'), utils = require('../lib/utils'); -let logger = require('winston'); - -exports.setLogger = function (newLogger) { - logger = newLogger; -}; - exports.WebServer = class { constructor(port) { this.app = express(); @@ -25,6 +20,7 @@ exports.WebServer = class { this.port = port; this.schema = buildSchema(fs.readFileSync('./lib/api/graphql/schema.gql', 'utf-8')); this.root = {}; + this._logger = new logging.Logger(this); } /** @@ -68,7 +64,7 @@ exports.WebServer = class { } else { let user = await this.maindb.get('SELECT * FROM users WHERE username = ? AND password = ?', [req.body.username, req.body.password]); if (!user) { - logger.debug(`User ${req.body.username} failed to authenticate`); + this._logger.debug(`User ${req.body.username} failed to authenticate`); res.render('login', {msg: 'Login failed!'}); } else { req.session.user = user; @@ -107,10 +103,10 @@ exports.WebServer = class { if (config.webinterface.https.certFile) sslCert = fs.readFileSync(config.webinterface.https.certFile, 'utf-8'); if (sslKey && sslCert) { - logger.verbose('Creating https server.'); + this._logger.verbose('Creating https server.'); this.server = require('https').createServer({key: sslKey, cert: sslCert}, this.app); } else { - logger.warn('Key or certificate file not found. Fallback to http server.'); + this._logger.warn('Key or certificate file not found. Fallback to http server.'); this.server = require('http').createServer(this.app); } } else { @@ -175,7 +171,7 @@ exports.WebServer = class { .slice(args.offset, args.offset + args.first) .map(async (x) => new Guild(x, await objects.getGuildHandler(x)))); } catch (err) { - logger.error(err.stack); + this._logger.error(err.stack); return null; } diff --git a/lib/commands/AnilistApiCommands/index.js b/lib/commands/AnilistApiCommands/index.js index b6dc426..6ee5628 100644 --- a/lib/commands/AnilistApiCommands/index.js +++ b/lib/commands/AnilistApiCommands/index.js @@ -59,8 +59,11 @@ class AniListCommandModule extends cmdLib.CommandModule { new cmdLib.Answer(async (m, k, s) => { try { let animeData = await anilistApi.searchAnimeByName(s); + this._logger.silly(`Anime Query returned ${JSON.stringify(animeData)}`); return new RichMediaInfo(animeData); } catch (err) { + if (err.message) + this._logger.verbose(err.message); return this.template.anime_search.not_found; } })); @@ -70,8 +73,11 @@ class AniListCommandModule extends cmdLib.CommandModule { new cmdLib.Answer(async (m, k, s) => { try { let mangaData = await anilistApi.searchMangaByName(s); + this._logger.silly(`Manga Query returned ${JSON.stringify(mangaData)}`); return new RichMediaInfo(mangaData); } catch (err) { + if (err.message) + this._logger.verbose(err.message); return this.template.manga_search.not_found; } }) diff --git a/lib/commands/InfoCommands/index.js b/lib/commands/InfoCommands/index.js index d243897..e2cc39b 100644 --- a/lib/commands/InfoCommands/index.js +++ b/lib/commands/InfoCommands/index.js @@ -39,6 +39,7 @@ class InfoCommandModule extends cmdLib.CommandModule { helpEmbed.addField(cat, catCommands[cat]); helpEmbed.setFooter(prefix + 'help [command] for more info to each command'); + this._logger.silly('Created help embed'); return helpEmbed; } diff --git a/lib/commands/MusicCommands/index.js b/lib/commands/MusicCommands/index.js index c667706..d368d87 100644 --- a/lib/commands/MusicCommands/index.js +++ b/lib/commands/MusicCommands/index.js @@ -11,13 +11,11 @@ class MusicCommandModule extends cmdLib.CommandModule { /** * @param opts {Object} properties: * getGuildHandler - a function to get the guild handler for a guild. - * logger - the logger instance */ constructor(opts) { super(cmdLib.CommandScopes.Guild); this.templateFile = location + '/MusicCommandsTemplate.yaml'; this._getGuildHandler = opts.getGuildHandler; - this._logger = opts.logger; } /** diff --git a/lib/commands/ServerUtilityCommands/index.js b/lib/commands/ServerUtilityCommands/index.js index 2198c5c..92dcb8c 100644 --- a/lib/commands/ServerUtilityCommands/index.js +++ b/lib/commands/ServerUtilityCommands/index.js @@ -10,7 +10,6 @@ class ServerUtilityCommandModule extends cmdLib.CommandModule { * @param opts {Object} properties: * getGuildHandler - a function to get the guild handler for the guild * messagehandler - the MessageHandler instance - * logger - the instance of the logger. * config - the config object */ constructor(opts) { @@ -18,7 +17,6 @@ class ServerUtilityCommandModule extends cmdLib.CommandModule { this.templateFile = location + '/ServerUtilityCommandsTemplate.yaml'; this._messageHandler = opts.messageHandler; this._getGuildHandler = opts.getGuildHandler; - this._logger = opts.logger; this._config = opts.config; } diff --git a/lib/commands/UtilityCommands/index.js b/lib/commands/UtilityCommands/index.js index d7e1a6a..49167e4 100644 --- a/lib/commands/UtilityCommands/index.js +++ b/lib/commands/UtilityCommands/index.js @@ -14,14 +14,12 @@ class UtilityCommandModule extends cmdLib.CommandModule { /** * @param opts {Object} properties: * bot - the instance of the bot. - * logger - the instance of the logger. * config - the config object */ constructor(opts) { super(cmdLib.CommandScopes.User); this.templateFile = location + '/UtilityCommandsTemplate.yaml'; this._bot = opts.bot; - this._logger = opts.logger; this._config = opts.config; } diff --git a/lib/guilding.js b/lib/guilding.js index ed5bb75..e2276fc 100644 --- a/lib/guilding.js +++ b/lib/guilding.js @@ -2,16 +2,10 @@ const music = require('./MusicLib'), utils = require('./utils'), config = require('../config.json'), sqliteAsync = require('./sqliteAsync'), + logging = require('./logging'), fs = require('fs-extra'), dataDir = config.dataPath || './data'; -let logger = require('winston'); - -exports.setLogger = function (newLogger) { - logger = newLogger; - music.setLogger(logger); -}; - /** * The Guild Handler handles guild settings and data. * @type {GuildHandler} @@ -20,7 +14,9 @@ class GuildHandler { constructor(guild) { this.guild = guild; + this._logger = new logging.Logger(`${this.constructor.name}@${this.guild}`); this.musicPlayer = new music.MusicPlayer(null); + this._logger.silly('Initialized Guild Handler'); } /** @@ -28,10 +24,12 @@ class GuildHandler { * @returns {Promise} */ async initDatabase() { + this._logger.silly('Initializing Database'); await fs.ensureDir(dataDir + '/gdb'); this.db = new sqliteAsync.Database(`${dataDir}/gdb/${this.guild}.db`); await this.db.init(); - logger.debug(`Connected to the database for ${this.guild}`); + this._logger.debug(`Connected to the database for ${this.guild}`); + this._logger.debug('Creating Databases'); await this.createTables(); } @@ -39,7 +37,9 @@ class GuildHandler { * Destroys the guild handler */ destroy() { + this._logger.debug('Ending musicPlayer'); this.musicPlayer.stop(); + this._logger.debug('Ending Database'); this.db.close(); } @@ -57,16 +57,19 @@ class GuildHandler { author_name VARCHAR(128), content TEXT NOT NULL )`); + this._logger.silly('Created Table messages'); await this.db.run(`${utils.sql.tableExistCreate} playlists ( ${utils.sql.pkIdSerial}, name VARCHAR(32) UNIQUE NOT NULL, url VARCHAR(255) NOT NULL )`); + this._logger.silly('Created Table playlists'); await this.db.run(`${utils.sql.tableExistCreate} commands ( ${utils.sql.pkIdSerial}, name VARCHAR(32) UNIQUE NOT NULL, command VARCHAR(255) NOT NULL )`); + this._logger.silly('Created Table commands'); } } diff --git a/lib/logging.js b/lib/logging.js index 8d8caa4..98166ef 100644 --- a/lib/logging.js +++ b/lib/logging.js @@ -1,66 +1,122 @@ /* eslint-disable no-unused-vars */ const winston = require('winston'), DailyRotateFile = require('winston-daily-rotate-file'), - args = require('args-parser')(process.argv), + args = require('args-parser')(process.argv); - fileLoggingFormat = winston.format.printf(info => { - return `${info.timestamp} ${info.level.toUpperCase()}: ${JSON.stringify(info.message)}`; // the logging format for files - }), - consoleLoggingFormat = winston.format.printf(info => { - return `${info.timestamp} {${info.label}} [${info.level}] ${JSON.stringify(info.message)}`; //the logging format for the console +/** + * Set console format to simple string format + * @type {Format} + */ +const consoleLoggingFormat = winston.format.printf(info => { + return `${info.timestamp} {${info.module || info.m || 'DEFAULT'}} [${info.level}] ${JSON.stringify(info.message)}`; //the logging format for the console +}); + +/** + * Set full format to combination of formats + * @type {Format} + */ +const loggingFullFormat = winston.format.combine( + winston.format.timestamp({ + format: 'YY-MM-DD HH:mm:ss.SSS' }), - loggingFullFormat = winston.format.combine( - winston.format.splat(), - winston.format.timestamp({ - format: 'YY-MM-DD HH:mm:ss.SSS' - }), - winston.format.label({label: ''}), - winston.format.json() - ); -let logger = winston.createLogger({ - level: winston.config.npm.levels, // logs with npm levels - format: loggingFullFormat, - transports: [ - new winston.transports.Console({ - format: winston.format.combine( - winston.format.colorize(), - winston.format.splat(), - winston.format.timestamp({ - format: 'YY-MM-DD HH:mm:ss.SSS' - }), - winston.format.label({label: ''}), - consoleLoggingFormat - ), - level: args.loglevel || 'info' - }), - new winston.transports.File({ - level: 'debug', - filename: './.log/latest.log', - options: {flags: 'w'} // overwrites the file on restart + winston.format.json() +); +/** + * Define all transports used. + * @type {any[]} + */ +let transports = [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.splat(), + winston.format.timestamp({ + format: 'YY-MM-DD HH:mm:ss.SSS' }), - new DailyRotateFile({ - level: 'verbose', - filename: './.log/%DATE%.log', - datePattern: 'YYYY-MM-DD', - zippedArchive: true, - maxSize: '32m', - maxFiles: '30d', - json: true - }) - ] - }); - -//class SpecialLogger extends winston. + winston.format.label({label: ''}), + consoleLoggingFormat + ), + level: args.loglevel || 'info' + }), + new winston.transports.File({ + level: 'debug', + filename: './.log/latest.log', + options: {flags: 'w'} // overwrites the file on restart + }), + new DailyRotateFile({ + level: 'verbose', + filename: './.log/%DATE%.log', + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '32m', + maxFiles: '30d', + json: true + }) +]; /** - * A function to return the logger that has been created after appending an exception handler - * @returns {Object} + * Define the logger + * @type {winston.Logger} */ -exports.getLogger = function () { - logger.exceptions.handle( - new winston.transports.File({ - filename: './.log/exceptions.log' - }) - ); - return logger; -}; +let logger = winston.createLogger({ + level: winston.config.npm.levels, + format: loggingFullFormat, + transports: transports +}); + + +// Define exception handling +logger.exceptions.handle( + new winston.transports.File({ + filename: './.log/exceptions.log' + }) +); + +class ModuleLogger { + + constructor(moduleInstance) { + this.logger = logger; + if (moduleInstance.constructor) { + switch (moduleInstance.constructor.name) { + case 'String': + this.logName = moduleInstance; + break; + case 'Number': + this.logName = moduleInstance.toString(); + break; + default: + this.logName = moduleInstance.constructor.name; + } + } else { + this.logName = moduleInstance.toString(); + } + } + + silly(msg, meta) { + logger.silly(msg, {m: this.logName, ...meta}); + } + + debug(msg, meta) { + logger.debug(msg, {m: this.logName, ...meta}); + } + + verbose(msg, meta) { + logger.verbose(msg, {m: this.logName, ...meta}); + } + + info(msg, meta) { + logger.info(msg, {m: this.logName, ...meta}); + } + warn(msg, meta) { + logger.warn(msg, {m: this.logName, ...meta}); + } + + error(msg, meta) { + logger.error(msg, {m: this.logName, ...meta}); + } +} + +Object.assign(exports, { + logger: logger, + Logger: ModuleLogger +}); diff --git a/lib/utils.js b/lib/utils.js index 747af42..620689c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -11,7 +11,7 @@ function noOp() { * @param {String} filename The name of the file. * @return {String} A string that represents the file-extension. */ -exports.getExtension = function (filename) { +function getFileExtension (filename) { if (!filename) return null; try { @@ -24,7 +24,7 @@ exports.getExtension = function (filename) { console.error(error); return null; } -}; +} /** * Walks the path to the objects attribute and returns the value. @@ -32,7 +32,7 @@ exports.getExtension = function (filename) { * @param attributePath * @returns {undefined/Object} */ -exports.objectDeepFind = function (object, attributePath) { +function objectDeepFind (object, attributePath) { let current = object, paths = attributePath.split('.'); for (let path of paths) @@ -42,7 +42,7 @@ exports.objectDeepFind = function (object, attributePath) { return undefined; return current; -}; +} /** * Shuffles an array with Fisher-Yates Shuffle @@ -74,7 +74,7 @@ exports.shuffleArray = function(array) { * @constructor * @author CanyonCasa & Pier-Luc Gendreau on StackOverflow */ -exports.Cleanup = function Cleanup(callback) { +function Cleanup(callback) { // attach user callback to the process event emitter // if no callback, it will still exit gracefully on Ctrl-C @@ -98,9 +98,9 @@ exports.Cleanup = function Cleanup(callback) { console.log(e.stack); process.exit(99); }); -}; +} -exports.getSplitDuration = function (duration) { +function getSplitDuration (duration) { let dur = duration; let retObj = {}; retObj.milliseconds = dur % 1000; @@ -113,23 +113,23 @@ exports.getSplitDuration = function (duration) { dur = Math.floor(dur / 24); retObj.days = dur; return retObj; -}; +} /** * Resolves a nested promise by resolving it iterative. * @param promise * @returns {Promise<*>} */ -exports.resolveNestedPromise = async function(promise) { +async function resolveNestedPromise (promise) { let result = await promise; while (result instanceof Promise) result = await result; // eslint-disable-line no-await-in-loop return result; -}; +} /* Classes */ -exports.YouTube = class { +class YouTube { /** * returns if an url is a valid youtube url (without checking for an entity id) * @param url @@ -220,9 +220,9 @@ exports.YouTube = class { let id = exports.YouTube.getVideoIdFromUrl(url); return id? `https://i3.ytimg.com/vi/${id}/maxresdefault.jpg` : null; } -}; +} -exports.ConfigVerifyer = class { +class ConfigVerifyer { /** * @param confObj * @param required {Array} the attributes that are required for the bot to work @@ -255,7 +255,7 @@ exports.ConfigVerifyer = class { logger.error(`Missing required Attributes ${this.missingAttributes.join(', ')}`); } -}; +} exports.sql = { tableExistCreate: 'CREATE TABLE IF NOT EXISTS', @@ -270,3 +270,14 @@ exports.logLevels = { 'warn': 3, 'error:': 4 }; + +Object.assign(exports, { + resolveNestedPromise: resolveNestedPromise, + YouTube: YouTube, + ConfigVerifyer: ConfigVerifyer, + getSplitDuration: getSplitDuration, + getExtension: getFileExtension, + getFileExtension: getFileExtension, + objectDeepFind: objectDeepFind, + Cleanup: Cleanup +}); diff --git a/test/test.js b/test/test.js index 0b18266..5eb5b33 100644 --- a/test/test.js +++ b/test/test.js @@ -3,7 +3,6 @@ const mockobjects = require('./mockobjects.js'), sinon = require('sinon'), assert = require('assert'), rewire = require('rewire'); -let Discord = require("discord.js"); mockobjects.mockLogger = { error: () => {}, @@ -180,7 +179,7 @@ describe('lib/utils', function() { describe('lib/music', function() { - const music = rewire('../lib/music'); + const music = rewire('../lib/MusicLib'); const Readable = require('stream').Readable; music.__set__("logger", mockobjects.mockLogger); @@ -204,7 +203,7 @@ describe('lib/music', function() { describe('#MusicPlayer', function () { it('connects to a VoiceChannel', function (done) { - let dj = new music.DJ(mockobjects.mockVoicechannel); + let dj = new music.MusicPlayer(mockobjects.mockVoicechannel); dj.connect().then(()=> { assert(dj.connected); done(); @@ -212,7 +211,7 @@ describe('lib/music', function() { }); it('listens on Repeat', function () { - let dj = new music.DJ(mockobjects.mockVoicechannel); + let dj = new music.MusicPlayer(mockobjects.mockVoicechannel); dj.current = {'url': '', 'title': ''}; dj.listenOnRepeat = true; @@ -222,7 +221,7 @@ describe('lib/music', function() { it('plays Files', function (done) { - let dj = new music.DJ(mockobjects.mockVoicechannel); + let dj = new music.MusicPlayer(mockobjects.mockVoicechannel); dj.connect().then(() => { dj.playFile(); assert(dj.playing); @@ -231,7 +230,7 @@ describe('lib/music', function() { }); it('plays YouTube urls', function (done) { - let dj = new music.DJ(mockobjects.mockVoicechannel); + let dj = new music.MusicPlayer(mockobjects.mockVoicechannel); dj.connect().then(() => { dj.playYouTube('http://www.youtube.com/watch?v=ABCDEFGHIJK'); setTimeout(() => { @@ -242,7 +241,7 @@ describe('lib/music', function() { }); it('gets the video name', function (done) { - let dj = new music.DJ(mockobjects.mockVoicechannel); + let dj = new music.MusicPlayer(mockobjects.mockVoicechannel); dj.getVideoName('http://www.youtube.com/watch?v=ABCDEFGHIJK').then((name) => { assert(name === 'test'); done(); @@ -250,7 +249,7 @@ describe('lib/music', function() { }); it('sets the volume', function(done) { - let dj = new music.DJ(mockobjects.mockVoicechannel); + let dj = new music.MusicPlayer(mockobjects.mockVoicechannel); dj.connect().then(() => { dj.playFile(); dj.setVolume(100); @@ -260,7 +259,7 @@ describe('lib/music', function() { }); it('pauses playback', function(done) { - let dj = new music.DJ(mockobjects.mockVoicechannel); + let dj = new music.MusicPlayer(mockobjects.mockVoicechannel); dj.connect().then(() => { dj.playFile(); dj.pause(); @@ -269,7 +268,7 @@ describe('lib/music', function() { }); it('resumes playback', function(done) { - let dj = new music.DJ(mockobjects.mockVoicechannel); + let dj = new music.MusicPlayer(mockobjects.mockVoicechannel); dj.connect().then(() => { dj.playFile(); dj.resume(); @@ -278,7 +277,7 @@ describe('lib/music', function() { }); it('stops playback', function(done) { - let dj = new music.DJ(mockobjects.mockVoicechannel); + let dj = new music.MusicPlayer(mockobjects.mockVoicechannel); dj.connect().then(() => { dj.playFile(); assert(dj.playing); @@ -289,7 +288,7 @@ describe('lib/music', function() { }); it('skips songs', function(done) { - let dj = new music.DJ(mockobjects.mockVoicechannel); + let dj = new music.MusicPlayer(mockobjects.mockVoicechannel); dj.connect().then(() => { dj.playYouTube('http://www.youtube.com/watch?v=ABCDEFGHIJK'); dj.playYouTube('http://www.youtube.com/watch?v=ABCDEFGHIJK'); @@ -302,7 +301,7 @@ describe('lib/music', function() { }); it('returns a playlist', function(done) { - let dj = new music.DJ(mockobjects.mockVoicechannel); + let dj = new music.MusicPlayer(mockobjects.mockVoicechannel); dj.connect().then(() => { dj.queue = [{ 'title': 'title', @@ -315,7 +314,7 @@ describe('lib/music', function() { }); it('clears the queue', function(done) { - let dj = new music.DJ(mockobjects.mockVoicechannel); + let dj = new music.MusicPlayer(mockobjects.mockVoicechannel); dj.connect().then(() => { dj.queue = [{ 'title': 'title', @@ -330,163 +329,63 @@ describe('lib/music', function() { }); }); -describe('lib/cmd', function() { - const cmd = rewire('../lib/cmd'); - cmd.__set__("logger", mockobjects.mockLogger); +describe('lib/CommandLib', function() { + let cmdLib = require('../lib/CommandLib'); - describe('#Servant', function() { + describe('Answer', function() { - it('creates commands', function() { - let servant = new cmd.Servant(''); - servant.createCommand(mockobjects.mockCommand, mockobjects.mockCommand.textReply); - assert(servant.commands['test']); - servant.createCommand(mockobjects.mockCommand, mockobjects.mockCommand.promiseReply); - assert(servant.commands['test']); - servant.createCommand(mockobjects.mockCommand, mockobjects.mockCommand.richEmbedReply); - assert(servant.commands['test']); + it('evaluates synchronous', async function() { + let answer = new cmdLib.Answer(() => 'RESPONSE'); + assert((await answer.evaluate({}, {}, {})) === 'RESPONSE'); }); - it('removes commands', function() { - let servant = new cmd.Servant(''); - servant.createCommand(mockobjects.mockCommand, mockobjects.mockCommand.textReply); - assert(servant.commands['test']); - servant.removeCommand('test'); - assert(!servant.commands['test']); - }); - - it('parses commands', function() { - let spy = sinon.spy(); - let servant = new cmd.Servant(''); - servant.createCommand(mockobjects.mockCommand, spy); - assert(servant.commands['test']); - assert(!spy.called); - servant.parseCommand({ - content: 'test', - author: { - tag: undefined - } + it('evaluates asynchronous', async function() { + let answer = new cmdLib.Answer(async () => { + return 'RESPONSE'; }); - assert(spy.called); - }); + assert((await answer.evaluate({}, {}, {})) === 'RESPONSE'); + }) }); -}); -describe('lib/guilding', function*() { // deactivated because of problems with sqlite3 and rewire - const guilding = rewire('../lib/guilding'); - const servercommands = require('../commands/servercommands'); - guilding.__set__("sqliteAsync", null); - guilding.__set__("fs-extra", { - ensureDir: async() => { - return true; - } + describe('Command', function() { + + it('answers with Answer objects', async function() { + let cmd = new cmdLib.Command({ + name: 'TEST', + prefix: '', + description: 'TESTDESCRIPTION', + permission: 'TESTPERM', + usage: 'TESTUSAGE' + },new cmdLib.Answer(() => 'RESPONSE')); + assert((await cmd.answer({}, {}, {})) === 'RESPONSE'); + }); + + it('generates help for itself', function() { + let cmd = new cmdLib.Command({ + name: 'TEST', + prefix: '', + description: 'TESTDESCRIPTION', + permission: 'TESTPERM', + usage: 'TESTUSAGE' + },new cmdLib.Answer(() => 'RESPONSE')); + assert(cmd.help); + }) }); - guilding.setLogger(mockobjects.mockLogger); - - describe('#GuildHandler', function() { - - it('initializes', function() { - let gh = new guilding.GuildHandler('test', ''); - gh.db = new mockobjects.MockDatabase('', ()=>{}); - gh.createTables(); - gh.registerMusicCommands(); - gh.ready = true; - assert(gh.ready); - }); - - it('destroyes itself', function() { - let gh = new guilding.GuildHandler('test', ''); - gh.db = new mockobjects.MockDatabase('', ()=>{}); - gh.createTables(); - gh.registerMusicCommands(); - gh.ready = true; - gh.destroy(); - assert(!gh.dj.conn); - }); - - it('answers messages', function() { - let gh = new guilding.GuildHandler('test', ''); - gh.db = new mockobjects.MockDatabase('', ()=>{}); - gh.createTables(); - gh.registerMusicCommands(); - gh.ready = true; - let msgSpy = sinon.spy(); - gh.answerMessage({ - content: 'test', - author: { - tag: undefined - }, - reply: msgSpy, - channel: { - send: msgSpy - } - }, 'Answer'); - assert(msgSpy.called); - }); - - it('handles messages', function() { - let gh = new guilding.GuildHandler('test', '~'); - gh.db = new mockobjects.MockDatabase('', ()=>{}); - gh.ready = true; - let cbSpy = sinon.spy(); - gh.servant.createCommand(mockobjects.mockCommand, cbSpy); - assert(gh.servant.commands['~test']); - gh.handleMessage({ - content: '~test', - author: { - tag: undefined - }}); - assert(cbSpy.called); - }); - - it('connects and plays', function(done) { - const music = rewire('../lib/music'); - const Readable = require('stream').Readable; - - music.__set__("logger", mockobjects.mockLogger); - music.__set__("yttl", (id, cb) => { - cb(null, 'test'); - }); - music.__set__('ytdl', () => { - let s = new Readable(); - s._read = () => {}; - s.push('chunkofdataabc'); - s.push(null); - return s; - }); - let gh = new guilding.GuildHandler('test', '~'); - gh.db = new mockobjects.MockDatabase('', ()=>{}); - gh.ready = true; - gh.musicPlayer = new music.DJ(mockobjects.mockVoicechannel); - gh.connectAndPlay(mockobjects.mockVoicechannel, 'test', false).then(() => { - done(); - }); - }); - - it('handles all servercommands', function() { - let gh = new guilding.GuildHandler('test', '~'); - gh.db = new mockobjects.MockDatabase('', ()=>{}); - gh.registerMusicCommands(); - gh.ready = true; - let msgSpy = sinon.spy(); - let msg = { - content: 'test', - author: { - tag: undefined - }, - reply: msgSpy, - channel: { - send: msgSpy - } - }; - - for (let category of Object.keys(servercommands)) - for (let command of Object.keys(servercommands[category])) { - msg.content = '~' + command; - gh.handleMessage(msg); - } - +}); - assert(msgSpy.called); - }); +describe('lib/MessageLib', function() { + let msgLib = require('../lib/MessageLib'); + + describe('MessageHandler', function() { + it ('parses a command syntax', function() { + let msgHandler = new msgLib.MessageHandler({ + on: () => {} + }); + let parsedSyntax = msgHandler.parseSyntaxString('_help cmd&& _ping; _uptime'); + assert(parsedSyntax.length === 2); + assert(parsedSyntax[0].length === 2); + assert(parsedSyntax[1].length === 1); + }); }); + });