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
pull/51/head
Trivernis 6 years ago
parent 6f890dacee
commit e74fa83ed3

@ -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<void>}
*/
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,22 +68,21 @@ 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
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, {
await this.messageHandler.registerCommandModule(require('./lib/commands/ServerUtilityCommands').module, {
getGuildHandler: async (g) => await this.getGuildHandler(g),
logger: logger,
messageHandler: this.messageHandler,
config: config
});
@ -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<void>}
*/
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'});
});
}

@ -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 :("
}
}
}
}
}

@ -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."
}
}
}
}

@ -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<String|Discord.RichEmbed>}
*/
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);
}

@ -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);

@ -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();

@ -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;
}

@ -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;
}
})

@ -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;
}

@ -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;
}
/**

@ -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;
}

@ -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;
}

@ -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<void>}
*/
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');
}
}

@ -1,26 +1,31 @@
/* 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
}),
loggingFullFormat = winston.format.combine(
winston.format.splat(),
/**
* 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'
}),
winston.format.label({label: ''}),
winston.format.json()
);
let logger = winston.createLogger({
level: winston.config.npm.levels, // logs with npm levels
format: loggingFullFormat,
transports: [
/**
* Define all transports used.
* @type {any[]}
*/
let transports = [
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
@ -47,20 +52,71 @@ let logger = winston.createLogger({
maxFiles: '30d',
json: true
})
]
});
//class SpecialLogger extends winston.
];
/**
* 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 () {
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'
})
);
return logger;
};
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
});

@ -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
});

@ -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
}
});
assert(spy.called);
});
it('evaluates asynchronous', async function() {
let answer = new cmdLib.Answer(async () => {
return 'RESPONSE';
});
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;
}
});
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;
describe('Command', function() {
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;
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');
});
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('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);
})
});
});
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);
}
describe('lib/MessageLib', function() {
let msgLib = require('../lib/MessageLib');
assert(msgSpy.called);
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);
});
});
});

Loading…
Cancel
Save