Started restructuring command handling

pull/51/head
Trivernis 6 years ago
parent 42ee8cc4c5
commit 0223180053

@ -94,6 +94,15 @@
"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 :("
}
}
}
}

@ -0,0 +1,181 @@
const Discord = require('discord.js');
const scopes = {
'Global': 0,
'User': 1,
'Guild': 2
};
class Answer {
/**
* Creates an new Answer object with func as answer logic.
* @param func
*/
constructor(func) {
this.func = func;
}
/**
* Evaluates the answer string for the answer object.
* @param message
* @param kwargs
* @param argsString
* @returns {Promise<*>}
*/
async evaluate(message, kwargs, argsString) {
let result = this.func(message, kwargs, argsString);
switch (result.constructor.name) {
case 'Promise':
return await this.evaluate(await result);
default:
return result;
}
}
}
class Command {
/**
* Creates a new command object where the answer function needs
* to be implemented for it to work.
* @param template {JSON:{}}
* @param answer {Answer}
*/
constructor(template, answer) {
this.name = template.name;
this.description = template.description;
this.args = template.args || [];
this.permission = template.permission;
this.category = template.category || 'Other';
this.usage = template.usage ||
`\`${this.name} [${this.args.join('][')}\``.replace('[]', '');
this.answObj = answer;
if (!template.name)
throw new Error("Template doesn't define a name.");
}
/**
* This method is meant to be replaced by logic.
* @abstract
* @param message {Discord.Message}
* @param kwargs {JSON}
* @param argsString {String} The raw argument string.
* @returns {String}
*/
async answer(message, kwargs, argsString) {
return await this.answObj.evaluate(message, kwargs, argsString);
}
/**
* Returns rich help embed for this command.
* @returns {Discord.RichEmbed}
*/
get help() {
return new ExtendedRichEmbed(`Help for ${this.name}`)
.addFields({
'Usage': this.usage,
'Description': this.description,
'Permission Role': this.permission
});
}
}
class CommandHandler {
/**
* Initializes the CommandHandler
* @param prefix {String} The prefix of all commands.
* @param scope {Number} A scope from the CommandScopes (scopes)
*/
constructor(prefix, scope) {
this.prefix = prefix;
this.scope = scope;
this.commands = {};
}
/**
* Handles the command and responds to the message.
* @param commandMessage {String}
* @param message {Discord.Message}
* @returns {Boolean | Promise<String|Discord.RichEmbed>}
*/
handleCommand(commandMessage, message) {
let commandName = commandMessage.match(/^\S+/);
if (commandName.indexOf(this.prefix) > 0) {
commandName = commandName.replace(this.prefix);
let argsString = commandMessage.replace(/^\S+/, '');
let args = argsString(/\S+/g);
let command = this.commands[commandName];
let kwargs = {};
for (let i = 0; i < Math.min(command.kwargs, args.length); i++)
kwargs[command.kwargs[i]] = args[i];
return command.answer(message, kwargs, argsString);
} else {
return false;
}
}
/**
* Registers the command so that the handler can use it.
* @param name {String}
* @param command {Command}
*/
registerCommand(name, command) {
this.commands[name] = command;
}
/**
* Registers a map of commands containing of the name and the command.
* @param commandMap {Map}
*/
registerCommands(commandMap) {
for (let [name, cmd] in commandMap)
this.commands[name] = cmd;
}
}
class ExtendedRichEmbed extends Discord.RichEmbed {
/**
* Constructor that automatically set's the Title and Timestamp.
* @param title {String}
*/
constructor(title) {
super();
this.setTitle(title);
this.setTimestamp();
}
/**
* Adds a Field when a name is given or adds a blank Field otherwise
* @param name {String}
* @param content {String}
*/
addNonemptyField(name, content) {
if (name && name.length > 0 && content)
this.addField(name, content);
}
/**
* Adds the fields defined in the fields JSON
* @param fields {JSON}
*/
addFields(fields) {
for (let [name, value] in Object.entries(fields))
this.addNonemptyField(name, value);
}
}
// -- exports -- //
Object.assign(exports, {
Answer: Answer,
Command: Command,
CommandHandler: CommandHandler,
ExtendedRichEmbed: ExtendedRichEmbed,
CommandScopes: scopes
});

@ -0,0 +1,122 @@
const cmdLib = require('CommandLib'),
config = require('../config.json'),
Discord = require('discord.js'),
promiseWaterfall = require('promise-waterfall');
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;
this.discordClient = client;
this.globalCmdHandler = new cmdLib.CommandHandler(config.prefix,
cmdLib.CommandScopes.Global);
this.userCmdHandler = new cmdLib.CommandHandler(config.prefix,
cmdLib.CommandScopes.User);
this.guildCmdHandler = new cmdLib.CommandHandler(config.prefix,
cmdLib.CommandScopes.Guild);
this._registerEvents();
}
/**
* Returns the handler fitting the scope
* @param scope {Number}
* @returns {cmdLib.CommandHandler}
*/
getHandler(scope) {
switch (scope) {
case cmdLib.CommandScopes.Global:
return this.globalCmdHandler;
case cmdLib.CommandScopes.Guild:
return this.guildCmdHandler;
case cmdLib.CommandScopes.User:
return this.userCmdHandler;
}
}
/**
* Registering event handlers.
* @private
*/
_registerEvents() {
this.discordClient.on('message', async (msg) => {
let sequence = this._parseSyntax(msg);
await this._executeCommandSequence(sequence);
});
}
/**
* Parses the syntax of a message into a command array.
* @param message
* @returns {Array<Array<String>>}
* @private
*/
_parseSyntax(message) {
let commandSequence = [];
let content = message.content;
let strings = content.match(/".+?"/g);
for (let string in strings)
content.replace(string, string // escape all special chars
.replace(';', '\\;'))
.replace('&', '\\&');
let independentCommands = content // independent command sequende with ;
.split(/(?<!\\);/g)
.map(x => x.replace(/^ +/, ''));
for (let indepCommand in independentCommands)
commandSequence.push(indepCommand
.split(/(?<!\\)&&/g) // dependend sequence with && (like unix)
.map(x => x.replace(/^ +/, ''))
);
return commandSequence;
}
/**
* Executes a sequence of commands
*/
async _executeCommandSequence(cmdSequence, message) {
let scopeCmdHandler = this._getScopeHandlers(message);
await Promise.all(cmdSequence.map(async (sq) => {
return await promiseWaterfall(sq.map(async (cmd) => {
let globalResult = await this.globalCmdHandler.handleCommand(cmd, message);
let scopeResult = await scopeCmdHandler.handleCommand(cmd, message);
if (scopeResult)
this._answerMessage(message, scopeResult);
else if (globalResult)
this._answerMessage(message, globalResult);
}));
}));
}
/**
* Returns two commandHandlers for the messages scope.
* @param message
* @private
*/
_getScopeHandler(message) {
if (message.guild)
return this.guildCmdHandler;
else
return this.userCmdHandler;
}
/**
* Answers
* @param message {Discord.Message}
* @param answer {String | Discord.RichEmbed}
* @private
*/
_answerMessage(message, answer) {
if (answer)
if (answer instanceof Discord.RichEmbed)
message.channel.send('', answer);
else
message.channel.send(answer);
}
}

@ -6,7 +6,7 @@ const fetch = require('node-fetch'),
/**
* Return a graphql query read from a file from a configured path.
* @param name
* @returns {Promise<*>}
* @returns {Promise<String>}
*/
async function getGraphqlQuery(name) {
return await fsx.readFile(`${queryPath}/${name}.gql`, {encoding: 'utf-8'});
@ -16,7 +16,7 @@ async function getGraphqlQuery(name) {
* Post a query read from a file to the configured graphql endpoint and return the data.
* @param queryName
* @param queryVariables
* @returns {Promise<any>}
* @returns {Promise<JSON>}
*/
function postGraphqlQuery(queryName, queryVariables) {
return new Promise(async (resolve, reject) => {
@ -40,7 +40,7 @@ function postGraphqlQuery(queryName, queryVariables) {
/**
* Get an anime by id.
* @param id
* @returns {Promise<any>}
* @returns {Promise<JSON>}
*/
exports.getAnimeById = async function(id) {
let data = await postGraphqlQuery('AnimeById', {id: id});
@ -53,7 +53,7 @@ exports.getAnimeById = async function(id) {
/**
* Get a manga by id.
* @param id
* @returns {Promise<any>}
* @returns {Promise<JSON>}
*/
exports.getMangaById = async function(id) {
let data = await postGraphqlQuery('MangaById', {id: id});
@ -66,7 +66,7 @@ exports.getMangaById = async function(id) {
/**
* Search for a media entry by name and return it.
* @param name
* @returns {Promise<any>}
* @returns {Promise<JSON>}
*/
exports.searchMediaByName = async function(name) {
let data = await postGraphqlQuery('MediaSearchByName', {name: name});
@ -95,9 +95,9 @@ exports.searchAnimeByName = async function(name) {
* @returns {Promise<*>}
*/
exports.searchMangaByName = async function(name) {
let data = await postGraphqlQuery('MediaSearchByName', {name: name, type: 'MANGA'}).data;
let data = await postGraphqlQuery('MediaSearchByName', {name: name, type: 'MANGA'});
if (data && data.Media && data.Media.id)
return await postGraphqlQuery('MangaById', {id: data.Media.id});
return await exports.getMangaById(data.Media.id);
else
return null;
};

@ -14,7 +14,7 @@ let logger = require('winston'),
/**
* @type {Servant}
*/
exports.Servant = class {
class Servant {
constructor(prefix) {
this.commands = {};
this.prefix = prefix;
@ -110,7 +110,7 @@ exports.Servant = class {
let argv = argvars.slice(nLength);
logger.debug(`Executing callback for command: ${command}, kwargs: ${kwargs}, argv: ${argv}`);
try {
let locResult = returnFunction? () => cmd.callback(msg, kwargs, argv) : cmd.callback(msg, kwargs, argv);
let locResult = returnFunction ? () => cmd.callback(msg, kwargs, argv) : cmd.callback(msg, kwargs, argv);
return locResult || globResult;
} catch (err) {
logger.error(err.message);
@ -137,7 +137,7 @@ exports.Servant = class {
answers.push(this.processCommand(msg, globResult[i], commands[i],
true, previousCommand)); // return function to avoid "race conditions"
let commandMatch = (commands[i].match(/^.\w+/) || [])[0];
previousCommand = this.commands[commandMatch]? commandMatch : previousCommand;
previousCommand = this.commands[commandMatch] ? commandMatch : previousCommand;
}
return answers;
@ -146,15 +146,15 @@ exports.Servant = class {
}
}
};
}
/**
* Getting the logger
* @param {Object} newLogger
*/
exports.setLogger = function (newLogger) {
function setModuleLogger(newLogger) {
logger = newLogger;
};
}
/**
* Creates a global command that can be executed in every channel.
@ -162,7 +162,7 @@ exports.setLogger = function (newLogger) {
* @param template
* @param call
*/
exports.createGlobalCommand = function (prefix, template, call) {
function createGlobalCommand(prefix, template, call) {
if (!template.name) {
logger.debug(`Name of command template is null or undef. Failed to create command.`);
return;
@ -176,7 +176,7 @@ exports.createGlobalCommand = function (prefix, template, call) {
'category': template.category || 'Other'
};
logger.debug(`Created global command: ${prefix + template.name}, args: ${template.args}`);
};
}
/**
@ -191,9 +191,9 @@ exports.parseMessage = function (msg) {
/**
* Initializes the module by creating a help command
*/
exports.init = function (prefix) {
function initModule(prefix) {
logger.verbose("Creating help command...");
this.createGlobalCommand((prefix || config.prefix), gcmdTempl.utils.help, (msg, kwargs) => {
createGlobalCommand((prefix || config.prefix), gcmdTempl.utils.help, (msg, kwargs) => {
if (kwargs.command) {
let cmd = kwargs.command;
if (cmd.charAt(0) !== prefix)
@ -209,7 +209,7 @@ exports.init = function (prefix) {
return createHelpEmbed(globCommands, msg, prefix);
}
});
};
}
/**
* Processes commands for command series.
@ -227,7 +227,7 @@ function processCommand(cmd, msg, content, returnFunction) {
kwargs[cmd.args[i]] = argvars[i];
let argv = argvars.slice(nLength);
logger.debug(`Executing callback for command: ${cmd.name}, kwargs: ${JSON.stringify(kwargs)}, argv: ${argv}`);
return returnFunction? () => cmd.callback(msg, kwargs, argv) : cmd.callback(msg, kwargs, argv);
return returnFunction ? () => cmd.callback(msg, kwargs, argv) : cmd.callback(msg, kwargs, argv);
}
/**
@ -251,7 +251,7 @@ function parseGlobalCommand(msg) {
let previousCommand = '';
for (let commandPart of commands) {
let command = (commandPart.match(/^.\w+/) || [])[0] || previousCommand;
previousCommand = globCommands[command]? command : previousCommand;
previousCommand = globCommands[command] ? command : previousCommand;
if (!commandPart || !globCommands[command]) {
commandPart = `${previousCommand} ${commandPart}`;
command = previousCommand;
@ -316,8 +316,7 @@ function checkPermission(msg, rolePerm) {
return true;
if (msg.author.tag === args.owner || config.owners.includes(msg.author.tag))
return true;
else
if (msg.member && rolePerm && rolePerm !== 'owner' && msg.member.roles
else if (msg.member && rolePerm && rolePerm !== 'owner' && msg.member.roles
.some(role => (role.name.toLowerCase() === rolePerm.toLowerCase() || role.name.toLowerCase() === 'botcommander')))
return true;
@ -329,14 +328,14 @@ function checkPermission(msg, rolePerm) {
* @param prefix
* @param bot - the instance of the bot that called
*/
exports.registerUtilityCommands = function(prefix, bot) {
function registerUtilityCommands(prefix, bot) {
// responde with the commands args
exports.createGlobalCommand(prefix, gcmdTempl.utils.say, (msg, argv, args) => {
createGlobalCommand(prefix, gcmdTempl.utils.say, (msg, argv, args) => {
return args.join(' ');
});
// adds a presence that will be saved in the presence file and added to the rotation
exports.createGlobalCommand(prefix, gcmdTempl.utils.addpresence, async (msg, argv, args) => {
createGlobalCommand(prefix, gcmdTempl.utils.addpresence, async (msg, argv, args) => {
let p = args.join(' ');
this.presences.push(p);
await bot.maindb.run('INSERT INTO presences (text) VALUES (?)', [p]);
@ -344,7 +343,7 @@ exports.registerUtilityCommands = function(prefix, bot) {
});
// shuts down the bot after destroying the client
exports.createGlobalCommand(prefix, gcmdTempl.utils.shutdown, async (msg) => {
createGlobalCommand(prefix, gcmdTempl.utils.shutdown, async (msg) => {
try {
await msg.reply('Shutting down...');
logger.debug('Destroying client...');
@ -370,7 +369,7 @@ exports.registerUtilityCommands = function(prefix, bot) {
});
// forces a presence rotation
exports.createGlobalCommand(prefix, gcmdTempl.utils.rotate, () => {
createGlobalCommand(prefix, gcmdTempl.utils.rotate, () => {
try {
bot.client.clearInterval(this.rotator);
bot.rotatePresence();
@ -380,7 +379,7 @@ exports.registerUtilityCommands = function(prefix, bot) {
}
});
exports.createGlobalCommand(prefix, gcmdTempl.utils.createUser, (msg, argv) => {
createGlobalCommand(prefix, gcmdTempl.utils.createUser, (msg, argv) => {
return new Promise((resolve, reject) => {
if (msg.guild) {
resolve("It's not save here! Try again via PM.");
@ -398,26 +397,26 @@ exports.registerUtilityCommands = function(prefix, bot) {
});
});
exports.createGlobalCommand(prefix, gcmdTempl.utils.bugreport, () => {
createGlobalCommand(prefix, gcmdTempl.utils.bugreport, () => {
return new Discord.RichEmbed()
.setTitle('Where to report a bug?')
.setDescription(gcmdTempl.utils.bugreport.response.bug_report);
});
};
}
/**
* Registers the bot's info commands
* @param prefix {String}
* @param bot {Object}
*/
exports.registerInfoCommands = function(prefix, bot) {
function registerInfoCommands(prefix, bot) {
// ping command that returns the ping attribute of the client
exports.createGlobalCommand(prefix, gcmdTempl.info.ping, () => {
createGlobalCommand(prefix, gcmdTempl.info.ping, () => {
return `Current average ping: \`${bot.client.ping} ms\``;
});
// returns the time the bot is running
exports.createGlobalCommand(prefix, gcmdTempl.info.uptime, () => {
createGlobalCommand(prefix, gcmdTempl.info.uptime, () => {
let uptime = utils.getSplitDuration(bot.client.uptime);
return new Discord.RichEmbed().setDescription(`
**${uptime.days}** days
@ -429,30 +428,30 @@ exports.registerInfoCommands = function(prefix, bot) {
});
// returns the number of guilds, the bot has joined
exports.createGlobalCommand(prefix, gcmdTempl.info.guilds, () => {
createGlobalCommand(prefix, gcmdTempl.info.guilds, () => {
return `Number of guilds: \`${bot.client.guilds.size}\``;
});
// returns information about the bot
exports.createGlobalCommand(prefix, gcmdTempl.info.about, () => {
createGlobalCommand(prefix, gcmdTempl.info.about, () => {
return new Discord.RichEmbed()
.setTitle('About')
.setDescription(gcmdTempl.info.about.response.about_creator)
.addField('Icon', gcmdTempl.info.about.response.about_icon);
});
};
}
/**
* Registers all commands that use the anilist api.
* @param prefix {String}
*/
exports.registerAnilistApiCommands = function(prefix) {
const anilistApi = require('./anilistApiLib');
function registerAnilistApiCommands(prefix) {
const anilistApi = require('./api/AnilistApi');
// returns the anime found for the name
exports.createGlobalCommand(prefix, gcmdTempl.api.AniList.animeSearch, async (msg, kwargs, argv) => {
createGlobalCommand(prefix, gcmdTempl.api.AniList.animeSearch, async (msg, kwargs, args) => {
try {
let animeData = await anilistApi.searchAnimeByName(argv.join(' '));
let animeData = await anilistApi.searchAnimeByName(args.join(' '));
if (animeData) {
let response = new Discord.RichEmbed()
.setTitle(animeData.title.romaji)
@ -461,9 +460,10 @@ exports.registerAnilistApiCommands = function(prefix) {
.setURL(animeData.siteUrl)
.setColor(animeData.coverImage.color)
.addField('Genres', animeData.genres.join(', '))
.setFooter('Provided by anilist.co')
.setTimestamp();
if (animeData.studios.studioList.length > 0)
response.addField(animeData.studios.studioList.length === 1? 'Studio' : 'Studios', animeData.studios.studioList.map(x => `[${x.name}](${x.siteUrl})`));
response.addField(animeData.studios.studioList.length === 1 ? 'Studio' : 'Studios', animeData.studios.studioList.map(x => `[${x.name}](${x.siteUrl})`));
response.addField('Scoring', `**Average Score:** ${animeData.averageScore}
**Favourites:** ${animeData.favourites}`);
@ -477,7 +477,7 @@ exports.registerAnilistApiCommands = function(prefix) {
if (animeData.nextAiringEpisode)
response.addField('Next Episode', `**Episode** ${animeData.nextAiringEpisode.episode}
**Airing at:** ${new Date(animeData.nextAiringEpisode.airingAt*1000).toUTCString()}`);
**Airing at:** ${new Date(animeData.nextAiringEpisode.airingAt * 1000).toUTCString()}`);
if (animeData.endDate.day)
response.addField('End Date', `
@ -496,4 +496,45 @@ exports.registerAnilistApiCommands = function(prefix) {
return gcmdTempl.api.AniList.animeSearch.response.not_found;
}
});
};
createGlobalCommand(prefix, gcmdTempl.api.AniList.mangaSearch, async (msg, kwargs, args) => {
try {
let mangaData = await anilistApi.searchMangaByName(args.join(' '));
if (mangaData) {
let response = new Discord.RichEmbed()
.setTitle(mangaData.title.romaji)
.setThumbnail(mangaData.coverImage.large)
.setDescription(mangaData.description.replace(/<\/?.*?>/g, ''))
.setURL(mangaData.siteUrl)
.setFooter('Provided by anilist.co')
.setTimestamp();
if (mangaData.endDate.day)
response.addField('End Date', `
${mangaData.endDate.day}.${mangaData.endDate.month}.${mangaData.endDate.year}`);
return response;
} else {
return gcmdTempl.api.AniList.mangaSearch.response.not_found;
}
} catch (err) {
if (err.message) {
logger.warn(err.message);
logger.debug(err.stack);
} else {
logger.debug(JSON.stringify(err));
}
return gcmdTempl.api.AniList.mangaSearch.response.not_found;
}
});
}
// -- exports -- //
Object.assign(exports, {
init: initModule,
Servant: Servant,
registerAnilistApiCommands: registerAnilistApiCommands,
registerInfoCommands: registerInfoCommands,
registerUtilityCommands: registerUtilityCommands,
setLogger: setModuleLogger,
createGlobalCommand: createGlobalCommand
});

@ -0,0 +1,56 @@
const cmdLib = require('../../../CommandLib'),
yaml = require('js-yaml'),
fsx = require('fs-extra'),
templateFile = 'AniListCommandsTemplate.yaml';
class RichMediaInfo extends cmdLib.ExtendedRichEmbed {
/**
* Creates a rich embed with info for AniListApi Media.
* @param mediaInfo
*/
constructor(mediaInfo) {
super(mediaInfo.title.romaji);
this.setDescription(mediaInfo.description.replace(/<\/?.*?>/g, ''))
.setThumbnail(mediaInfo.coverImage.large)
.setURL(mediaInfo.siteUrl)
.setColor(mediaInfo.coverImage.color)
.setFooter('Provided by AniList.co');
let fields = {
'Genres': mediaInfo.genres.join(' '),
'Studios': mediaInfo.studios.studioList.map(x => `[${x.name}](${x.siteUrl})`),
'Scoring': `**AverageScore**: ${mediaInfo.averageScore}\n**Favourites**${mediaInfo.favourites}`,
'Episodes': mediaInfo.episodes,
'Duration': null,
'Season': mediaInfo.season,
'Status': mediaInfo.status,
'Format': mediaInfo.format
};
if (mediaInfo.duration)
fields['Episode Duration'] = `${mediaInfo.duration} min`;
if (mediaInfo.startDate.day)
fields['Start Date'] = `${mediaInfo.startDate.day}.${mediaInfo.startDate.month}.${mediaInfo.startDate.year}`;
if (mediaInfo.nextAiringEpisode) {
let epInfo = mediaInfo.nextAiringEpisode;
fields['Next Episode'] = `**Episode** ${epInfo.episode}\n**Airing at:** ${new Date(epInfo.airingAt * 1000).toUTCString()}`;
}
if (mediaInfo.endDate.day)
fields['End Date'] = `${mediaInfo.endDate.day}.${mediaInfo.endDate.month}.${mediaInfo.endDate.year}`;
this.addFields(fields);
}
}
// -- initialize -- //
let template = null;
async function init() {
let templateString = fsx.readFile(templateFile, {encoding: 'utf-8'});
template = yaml.safeLoad(templateString);
}
// -- exports -- //
Object.assign(exports, {
init: init
});

@ -7,16 +7,17 @@ const winston = require('winston'),
return `${info.timestamp} ${info.level.toUpperCase()}: ${JSON.stringify(info.message)}`; // the logging format for files
}),
consoleLoggingFormat = winston.format.printf(info => {
return `${info.timestamp} [${info.level}] ${JSON.stringify(info.message)}`; //the logging format for the console
return `${info.timestamp} {${info.label}} [${info.level}] ${JSON.stringify(info.message)}`; //the logging format for the console
}),
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()
),
logger = winston.createLogger({
);
let logger = winston.createLogger({
level: winston.config.npm.levels, // logs with npm levels
format: loggingFullFormat,
transports: [
@ -27,6 +28,7 @@ const winston = require('winston'),
winston.format.timestamp({
format: 'YY-MM-DD HH:mm:ss.SSS'
}),
winston.format.label({label: ''}),
consoleLoggingFormat
),
level: args.loglevel || 'info'
@ -48,6 +50,8 @@ const winston = require('winston'),
]
});
//class SpecialLogger extends winston.
/**
* A function to return the logger that has been created after appending an exception handler
* @returns {Object}

@ -32,7 +32,8 @@
"winston": "3.2.1",
"winston-daily-rotate-file": "3.7.0",
"youtube-playlist-info": "1.1.2",
"ytdl-core": "0.29.1"
"ytdl-core": "0.29.1",
"js-yaml": "latest"
},
"devDependencies": {
"assert": "1.4.1",
@ -47,7 +48,8 @@
},
"eslintConfig": {
"parserOptions": {
"ecmaVersion": 2018
"ecmaVersion": 2018,
"sourceType": "module"
},
"env": {
"node": true,

Loading…
Cancel
Save