Initialized Content
parent
12d913cf46
commit
68a56ee3a5
@ -0,0 +1,5 @@
|
|||||||
|
.log
|
||||||
|
.idea
|
||||||
|
data
|
||||||
|
package-lock.json
|
||||||
|
node_modules
|
@ -0,0 +1,124 @@
|
|||||||
|
const Discord = require("discord.js"),
|
||||||
|
logger = require('./lib/logging').getLogger(),
|
||||||
|
music = require('./lib/music');
|
||||||
|
cmd = require("./lib/cmd"),
|
||||||
|
client = new Discord.Client(),
|
||||||
|
args = require('args-parser')(process.argv),
|
||||||
|
authToken = args.token,
|
||||||
|
prefix = '~';
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
music.setLogger(logger);
|
||||||
|
cmd.setLogger(logger);
|
||||||
|
cmd.init();
|
||||||
|
registerCommands();
|
||||||
|
music.setClient(client);
|
||||||
|
client.login(authToken).then(()=> {
|
||||||
|
logger.debug("Logged in");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerCommands() {
|
||||||
|
cmd.createCommand('~', 'play', (msg, argv) => {
|
||||||
|
let vc = msg.member.voiceChannel;
|
||||||
|
let url = argv['url'];
|
||||||
|
if (!url) return 'No url given.';
|
||||||
|
try {
|
||||||
|
return music.play(vc, url);
|
||||||
|
} catch(err) {
|
||||||
|
logger.error(err);
|
||||||
|
msg.reply(`${JSON.stringify(err)}`);
|
||||||
|
}
|
||||||
|
}, ['url']);
|
||||||
|
|
||||||
|
cmd.createCommand('~', 'ping', () => {
|
||||||
|
return 'Pong!';
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.createCommand('~', 'join', (msg) => {
|
||||||
|
if (msg.member.voiceChannel) {
|
||||||
|
music.connect(msg.member.voiceChannel);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
msg.reply("You are not connected to a voicechannel.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.createCommand('~', 'stop', (msg) => {
|
||||||
|
let vc = msg.member.voiceChannel;
|
||||||
|
music.stop(vc);
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.createCommand('~', 'pause', (msg) => {
|
||||||
|
let vc = msg.member.voiceChannel;
|
||||||
|
music.pause(vc);
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.createCommand('~', 'resume', (msg) => {
|
||||||
|
let vc = msg.member.voiceChannel;
|
||||||
|
music.resume(vc);
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.createCommand('~', 'skip', (msg) => {
|
||||||
|
let vc = msg.member.voiceChannel;
|
||||||
|
music.skip(vc);
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.createCommand('~', 'plist', (msg) => {
|
||||||
|
let vc = msg.member.voiceChannel;
|
||||||
|
music.getQueue(vc, (songs) => {
|
||||||
|
let songlist = "**Songs**\n";
|
||||||
|
for (let i = 0; i < songs.length; i++) {
|
||||||
|
if (i > 10) break;
|
||||||
|
songlist += songs[i] + '\n';
|
||||||
|
}
|
||||||
|
msg.reply(songlist);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.createCommand('~', 'shuffle', (msg) => {
|
||||||
|
let vc = msg.member.voiceChannel;
|
||||||
|
music.shuffle(vc);
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.createCommand('~', 'current', (msg) => {
|
||||||
|
let vc = msg.member.voiceChannel;
|
||||||
|
music.nowPlaying(vc, (title, url) => {
|
||||||
|
msg.reply(`Playing: ${title}\n ${url}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.createCommand('_', 'repeat', (msg, argv) => {
|
||||||
|
return argv['repeattext'];
|
||||||
|
}, ['repeattext'])
|
||||||
|
}
|
||||||
|
|
||||||
|
// defining the client's handlers
|
||||||
|
|
||||||
|
client.on('ready', () => {
|
||||||
|
logger.info(`logged in as ${client.user.tag}!`);
|
||||||
|
client.user.setPresence({game: {name: "Trivernis' bot testing", type: "PLAYING"}, status: 'online'});
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('message', msg => {
|
||||||
|
try {
|
||||||
|
if (msg.author === client.user) {
|
||||||
|
logger.verbose(`ME: ${msg.content}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.verbose(`<${msg.author.username}>: ${msg.content}`);
|
||||||
|
let reply = cmd.parseMessage(msg);
|
||||||
|
if (reply) {
|
||||||
|
msg.reply(reply);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err.stack);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Executing the main function
|
||||||
|
if (typeof require !== 'undefined' && require.main === module) {
|
||||||
|
logger.info("Starting up... "); // log the current date so that the logfile is better to read.
|
||||||
|
main();
|
||||||
|
}
|
@ -0,0 +1,89 @@
|
|||||||
|
/* Module definition */
|
||||||
|
|
||||||
|
/* Variable Definition */
|
||||||
|
let logger = require('winston'),
|
||||||
|
commands = {};
|
||||||
|
|
||||||
|
/* Function Definition */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting the logger
|
||||||
|
* @param {Object} newLogger
|
||||||
|
*/
|
||||||
|
exports.setLogger = function(newLogger) {
|
||||||
|
logger = newLogger;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.createCommand = function(prefix, command, call, argv) {
|
||||||
|
try {
|
||||||
|
logger.debug(`Creating command ${command} with prefix ${prefix} and arguments ${argv}`);
|
||||||
|
if (!commands[prefix]) commands[prefix] = {}; // create Object commands prefix
|
||||||
|
commands[prefix][command] = { // assign the command
|
||||||
|
args: argv || [], // with arguments
|
||||||
|
callback: call // and function
|
||||||
|
};
|
||||||
|
logger.debug(`Created command ${prefix}${command}`);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(JSON.stringify(err));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the message by calling the assigned function for the command with arguments
|
||||||
|
* @param msg
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
exports.parseMessage = function(msg) {
|
||||||
|
logger.debug(`Recieved message ${msg.content} from ${msg.author.username}`);
|
||||||
|
let content = msg.content;
|
||||||
|
let matches = content.match(/^./g); // match with first symbol
|
||||||
|
logger.debug(matches);
|
||||||
|
if (matches) {
|
||||||
|
logger.debug(matches);
|
||||||
|
logger.debug(`Found prefix ${matches[0]} in message`);
|
||||||
|
let prefix = matches[0];
|
||||||
|
let prefixData = commands[prefix];
|
||||||
|
matches = content.replace(prefix, '').match(/^\w+/g); // match with the second word
|
||||||
|
if (matches && prefixData) {
|
||||||
|
logger.debug(`found command ${matches[0]} in message`);
|
||||||
|
let command = matches[0];
|
||||||
|
let commandFunction = prefixData[command];
|
||||||
|
let args = content
|
||||||
|
.replace(prefix, '')
|
||||||
|
.replace(command, '')
|
||||||
|
.replace(/^\s+/g, '')
|
||||||
|
.split(' ');
|
||||||
|
if (commandFunction) {
|
||||||
|
let argv = {};
|
||||||
|
if (commandFunction.args) {
|
||||||
|
for (let i = 0; i < commandFunction.args.length; i++) {
|
||||||
|
let arg = commandFunction.args[i];
|
||||||
|
argv[arg] = args[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (commandFunction.callback) {
|
||||||
|
logger.debug(`Found callback and args ${JSON.stringify(argv)} in message`);
|
||||||
|
return commandFunction.callback(msg, argv); // call the command function and return the result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the module by creating a help command
|
||||||
|
*/
|
||||||
|
exports.init = function() {
|
||||||
|
logger.verbose("Created help command");
|
||||||
|
this.createCommand("~", "help", () => {
|
||||||
|
let helpstr = "```markdown\n";
|
||||||
|
helpstr += "Commands\n---\n";
|
||||||
|
Object.keys(commands).forEach((key) => {
|
||||||
|
Object.keys(commands[key]).forEach((cmd) => {
|
||||||
|
helpstr += "\n" + key + cmd + " " + JSON.stringify(commands[key][cmd].args) + "\n";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
helpstr += "```";
|
||||||
|
return helpstr;
|
||||||
|
});
|
||||||
|
};
|
@ -0,0 +1,14 @@
|
|||||||
|
/* Module definition */
|
||||||
|
|
||||||
|
/* Variable Definition */
|
||||||
|
let logger = require('winston');
|
||||||
|
|
||||||
|
/* Function Definition */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting the logger
|
||||||
|
* @param {Object} newLogger
|
||||||
|
*/
|
||||||
|
exports.getLogger = function (newLogger) {
|
||||||
|
logger = newLogger;
|
||||||
|
};
|
@ -0,0 +1,60 @@
|
|||||||
|
const winston = require('winston'),
|
||||||
|
DailyRotateFile = require('winston-daily-rotate-file'),
|
||||||
|
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.level}] ${JSON.stringify(info.message)}`; //the logging format for the console
|
||||||
|
}),
|
||||||
|
loggingFullFormat = winston.format.combine(
|
||||||
|
winston.format.splat(),
|
||||||
|
winston.format.timestamp({
|
||||||
|
format: 'MM-DD HH:mm:ss.SSS' // don't include the year because the filename already tells
|
||||||
|
}),
|
||||||
|
fileLoggingFormat // the logging format for files that logs with a capitalized level
|
||||||
|
),
|
||||||
|
logger = winston.createLogger({
|
||||||
|
level: winston.config.npm.levels, // logs with npm levels
|
||||||
|
format: loggingFullFormat, // the full format for files
|
||||||
|
transports: [
|
||||||
|
new winston.transports.Console({
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.colorize(), // colorizes the console logging output
|
||||||
|
winston.format.splat(),
|
||||||
|
winston.format.timestamp({
|
||||||
|
format: 'YY-MM-DD HH:mm:ss.SSS' // logs with the year to the console
|
||||||
|
}),
|
||||||
|
consoleLoggingFormat // logs with the custom console format
|
||||||
|
),
|
||||||
|
level: args.loglevel || 'info' // logs to the console with the arg loglevel or info if it is not given
|
||||||
|
}),
|
||||||
|
new winston.transports.File({
|
||||||
|
level: 'debug', // logs with debug level to the active file
|
||||||
|
filename: './.log/latest.log', // the filename of the current file,
|
||||||
|
options: {flags: 'w'} // overwrites the file on restart
|
||||||
|
}),
|
||||||
|
new DailyRotateFile({
|
||||||
|
level: 'verbose', // log verbose in the rotating logvile
|
||||||
|
filename: './.log/%DATE%.log', // the pattern of the filename
|
||||||
|
datePattern: 'YYYY-MM-DD', // the pattern of %DATE%
|
||||||
|
zippedArchive: true, // indicates that old logfiles should get zipped
|
||||||
|
maxSize: '32m', // the maximum filesize
|
||||||
|
maxFiles: '30d' // the maximum files to keep
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A function to return the logger that has been created after appending an exception handler
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
exports.getLogger = function() {
|
||||||
|
logger.exceptions.handle(
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: './.log/exceptions.log'
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return logger;
|
||||||
|
};
|
@ -0,0 +1,246 @@
|
|||||||
|
const Discord = require("discord.js"),
|
||||||
|
ytdl = require("ytdl-core"),
|
||||||
|
ypi = require('youtube-playlist-info'),
|
||||||
|
yttl = require('get-youtube-title'),
|
||||||
|
ytapiKey = "AIzaSyBLF20r-c4mXoAT2qBFB5YlCgT0D-izOaU";
|
||||||
|
/* Variable Definition */
|
||||||
|
let logger = require('winston');
|
||||||
|
let client = null;
|
||||||
|
let connections = {};
|
||||||
|
let current = null;
|
||||||
|
let queue = [];
|
||||||
|
|
||||||
|
/* Function Definition */
|
||||||
|
// TODO: initCommands function that takes the cmd.js module as variable and uses it to create commands
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getting the logger;
|
||||||
|
* @param {Object} newLogger
|
||||||
|
*/
|
||||||
|
exports.setLogger = function (newLogger) {
|
||||||
|
logger = newLogger;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the discord Client for the module
|
||||||
|
* @param newClient
|
||||||
|
*/
|
||||||
|
exports.setClient = function(newClient) {
|
||||||
|
client = newClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to a voicechannel
|
||||||
|
* @param voiceChannel
|
||||||
|
*/
|
||||||
|
exports.connect = function(voiceChannel) {
|
||||||
|
logger.debug(JSON.stringify());
|
||||||
|
logger.verbose(`Connecting to voiceChannel ${voiceChannel.name}`);
|
||||||
|
if (client !== null) {
|
||||||
|
voiceChannel.join().then(connection => {
|
||||||
|
logger.info(`Connected to Voicechannel ${voiceChannel.name}`);
|
||||||
|
connections[voiceChannel.guild.id] = {
|
||||||
|
'conn': connection,
|
||||||
|
'disp': null,
|
||||||
|
'queue': [],
|
||||||
|
'playing': false,
|
||||||
|
current: null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.error("Client is null");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plays a file
|
||||||
|
* @param filename
|
||||||
|
*/
|
||||||
|
exports.playFile = function(voiceChannel, filename) {
|
||||||
|
let gid = voiceChannel.guild.id;
|
||||||
|
let conn = connections[gid].conn;
|
||||||
|
if (conn !== null) {
|
||||||
|
connections[gid].disp = conn.playFile(filename);
|
||||||
|
connections[gid].playing = true;
|
||||||
|
} else {
|
||||||
|
this.connect(voiceChannel);
|
||||||
|
logger.warn("Not connected to a voicechannel");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.play = function(voiceChannel, url) {
|
||||||
|
let gid = voiceChannel.guild.id;
|
||||||
|
if (!connections[gid]) this.connect(voiceChannel);
|
||||||
|
let conn = connections[gid].conn;
|
||||||
|
if (conn !== null) {
|
||||||
|
let plist = url.match(/(?<=\?list=)[\w\-]+/g);
|
||||||
|
if (plist) {
|
||||||
|
logger.debug(`Adding playlist ${plist} to queue`);
|
||||||
|
ypi(ytapiKey, plist).then(items => {
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
let vurl = `https://www.youtube.com/watch?v=${items[i].resourceId.videoId}`;
|
||||||
|
connections[gid].queue.push(vurl);
|
||||||
|
}
|
||||||
|
this.play(voiceChannel, connections[gid].queue.shift());
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!connections[gid].playing) {
|
||||||
|
logger.debug(`Playing ${url}`);
|
||||||
|
connections[gid].disp = conn.playStream(ytdl(url, {
|
||||||
|
filter: "audioonly"
|
||||||
|
}), {seek: 0, volume: 0.5});
|
||||||
|
connections[gid].disp.on('end', () => {
|
||||||
|
connections[gid].playing = false;
|
||||||
|
connections[gid].current = null;
|
||||||
|
if (connections[gid].queue.length > 0) {
|
||||||
|
this.play(voiceChannel, connections[gid].queue.shift());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connections[gid].playing = true;
|
||||||
|
connections[gid].current = url;
|
||||||
|
} else {
|
||||||
|
logger.debug(`Added ${url} to the queue`);
|
||||||
|
connections[gid].queue.push(url);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.warn("Not connected to a voicechannel");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the volume of the music
|
||||||
|
* @param percentage
|
||||||
|
* @param voiceChannel
|
||||||
|
*/
|
||||||
|
exports.setVolume = function(voiceChannel, percentage) {
|
||||||
|
let disp = connections[voiceChannel.guild.id].disp;
|
||||||
|
logger.verbose(`Setting volume to ${percentage}`);
|
||||||
|
if (disp !== null) {
|
||||||
|
disp.setVolume(percentage);
|
||||||
|
} else {
|
||||||
|
logger.warn("No dispatcher found.")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pauses the music
|
||||||
|
*/
|
||||||
|
exports.pause = function(voiceChannel) {
|
||||||
|
let disp = connections[voiceChannel.guild.id].disp;
|
||||||
|
logger.verbose("Pausing music...");
|
||||||
|
if (disp !== null) {
|
||||||
|
disp.pause();
|
||||||
|
} else {
|
||||||
|
logger.warn("No dispatcher found");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resumes the music
|
||||||
|
*/
|
||||||
|
exports.resume = function(voiceChannel) {
|
||||||
|
let disp = connections[voiceChannel.guild.id].disp;
|
||||||
|
logger.verbose("Resuming music...");
|
||||||
|
if (disp !== null) {
|
||||||
|
disp.resume();
|
||||||
|
} else {
|
||||||
|
logger.warn("No dispatcher found");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the music
|
||||||
|
*/
|
||||||
|
exports.stop = function(voiceChannel) {
|
||||||
|
let gid = voiceChannel.guild.id;
|
||||||
|
let disp = connections[gid].disp;
|
||||||
|
let conn = connections[gid].conn;
|
||||||
|
logger.verbose("Stopping music...");
|
||||||
|
if (disp !== null) {
|
||||||
|
disp.end();
|
||||||
|
logger.debug("Ended dispatcher");
|
||||||
|
}
|
||||||
|
if (conn !== null) {
|
||||||
|
conn.disconnect();
|
||||||
|
logger.debug("Ended connection");
|
||||||
|
}
|
||||||
|
connections[gid].playing = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips the song
|
||||||
|
*/
|
||||||
|
exports.skip = function(voiceChannel) {
|
||||||
|
let disp = connections[voiceChannel.guild.id].disp;
|
||||||
|
logger.debug("Skipping song");
|
||||||
|
if (disp !== null) {
|
||||||
|
disp.end();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* executes the callback when the titlelist is finished
|
||||||
|
*/
|
||||||
|
exports.getQueue = function(voiceChannel, callback) {
|
||||||
|
let titles = [];
|
||||||
|
connections[voiceChannel.guild.id].queue.forEach((url) => {
|
||||||
|
yttl(url.replace(/http(s)?:\/\/(www.)?youtube.com\/watch\?v=/g, ''), (err, title) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(err);
|
||||||
|
} else {
|
||||||
|
titles.push(title);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
setTimeout(() => callback(titles), 2000 );
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* evokes the callback function with the title of the current song
|
||||||
|
* @param callback
|
||||||
|
* @param voiceChannel
|
||||||
|
*/
|
||||||
|
exports.nowPlaying = function(voiceChannel, callback) {
|
||||||
|
let gid = voiceChannel.guild.id;
|
||||||
|
if (connections[gid].queue.length > 0) {
|
||||||
|
yttl(connections[gid].current.replace(/http(s)?:\/\/(www.)?youtube.com\/watch\?v=/g, ''), (err, title) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(err);
|
||||||
|
} else {
|
||||||
|
callback(title, connections[gid].current);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shuffles the queue
|
||||||
|
*/
|
||||||
|
exports.shuffle = function(voiceChannel) {
|
||||||
|
connections[voiceChannel.guild.id].queue = shuffle(queue);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shuffles an array with Fisher-Yates Shuffle
|
||||||
|
* @param array
|
||||||
|
* @returns {Array}
|
||||||
|
*/
|
||||||
|
function shuffle(array) {
|
||||||
|
let currentIndex = array.length, temporaryValue, randomIndex;
|
||||||
|
|
||||||
|
// While there remain elements to shuffle...
|
||||||
|
while (0 !== currentIndex) {
|
||||||
|
|
||||||
|
// Pick a remaining element...
|
||||||
|
randomIndex = Math.floor(Math.random() * currentIndex);
|
||||||
|
currentIndex -= 1;
|
||||||
|
|
||||||
|
// And swap it with the current element.
|
||||||
|
temporaryValue = array[currentIndex];
|
||||||
|
array[currentIndex] = array[randomIndex];
|
||||||
|
array[randomIndex] = temporaryValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "discordbot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"args-parser": "^1.1.0",
|
||||||
|
"discord.js": "^11.4.2",
|
||||||
|
"ffmpeg-binaries": "^4.0.0",
|
||||||
|
"get-youtube-title": "^1.0.0",
|
||||||
|
"opusscript": "0.0.6",
|
||||||
|
"winston": "^3.1.0",
|
||||||
|
"winston-daily-rotate-file": "^3.5.1",
|
||||||
|
"youtube-playlist-info": "^1.1.2",
|
||||||
|
"ytdl-core": "^0.26.3"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue