Restructuring and new Commands

- Added Server Utility Commands
- Removed lib/cmd
- Renamed lib/music
- renamed lib/weblib
- removed command parsing function from GuildHandler
- renamed DJ to MusicPlayer
- updated graphql schema and interface to new names
pull/51/head
Trivernis 6 years ago
parent 556c3a5e7f
commit 4d7018d510

134
bot.js

@ -2,12 +2,10 @@ const Discord = require("discord.js"),
fs = require('fs-extra'), fs = require('fs-extra'),
logger = require('./lib/logging').getLogger(), logger = require('./lib/logging').getLogger(),
msgLib = require('./lib/MessageLib'), msgLib = require('./lib/MessageLib'),
cmd = require("./lib/cmd"),
guilding = require('./lib/guilding'), guilding = require('./lib/guilding'),
utils = require('./lib/utils'), utils = require('./lib/utils'),
config = require('./config.json'), config = require('./config.json'),
args = require('args-parser')(process.argv), args = require('args-parser')(process.argv),
waterfall = require('promise-waterfall'),
sqliteAsync = require('./lib/sqliteAsync'), sqliteAsync = require('./lib/sqliteAsync'),
authToken = args.token || config.api.botToken, authToken = args.token || config.api.botToken,
prefix = args.prefix || config.prefix || '~', prefix = args.prefix || config.prefix || '~',
@ -15,21 +13,26 @@ const Discord = require("discord.js"),
let weblib = null; let weblib = null;
/**
* The Bot class handles the initialization and Mangagement of the Discord bot and
* is the main class.
*/
class Bot { class Bot {
constructor() { constructor() {
this.client = new Discord.Client({autoReconnect: true}); this.client = new Discord.Client({autoReconnect: true});
this.mention = false;
this.rotator = null; this.rotator = null;
this.maindb = null; this.maindb = null;
this.presences = []; this.presences = [];
this.messageHandler = new msgLib.MessageHandler(this.client, logger); this.messageHandler = new msgLib.MessageHandler(this.client, logger);
this.guildHandlers = []; this.guildHandlers = {};
this.userRates = {};
logger.verbose('Verifying config'); logger.verbose('Verifying config');
let configVerifyer = new utils.ConfigVerifyer(config, [ let configVerifyer = new utils.ConfigVerifyer(config, [
"api.botToken", "api.youTubeApiKey" "api.botToken", "api.youTubeApiKey",
"commandSettings.maxSequenceParallel",
"commandSettings.maxSequenceSerial"
]); ]);
if (!configVerifyer.verifyConfig(logger)) if (!configVerifyer.verifyConfig(logger))
if (!args.i) { if (!args.i) {
@ -38,7 +41,6 @@ class Bot {
process.exit(1); process.exit(1);
}); });
} }
cmd.setLogger(logger);
guilding.setLogger(logger); guilding.setLogger(logger);
} }
@ -64,7 +66,7 @@ class Bot {
}); });
await this.initializeDatabase(); await this.initializeDatabase();
if (config.webservice && config.webservice.enabled) if (config.webinterface && config.webinterface.enabled)
await this.initializeWebserver(); await this.initializeWebserver();
logger.verbose('Registering commands'); logger.verbose('Registering commands');
await this.messageHandler await this.messageHandler
@ -74,12 +76,18 @@ class Bot {
await this.messageHandler await this.messageHandler
.registerCommandModule(require('./lib/commands/InfoCommands').module, {client: this.client, messageHandler: this.messageHandler}); .registerCommandModule(require('./lib/commands/InfoCommands').module, {client: this.client, messageHandler: this.messageHandler});
await this.messageHandler await this.messageHandler
.registerCommandModule(require('./lib/commands/MusicCommands').module, {getGuildHandler: (g) => { .registerCommandModule(require('./lib/commands/MusicCommands').module, {
return this.getGuildHandler(g, prefix); getGuildHandler: async (g) => await this.getGuildHandler(g),
}, logger: logger}) logger: logger
//this.registerCommands(); });
this.registerCallbacks(); await this.messageHandler
cmd.init(prefix); .registerCommandModule(require('./lib/commands/ServerUtilityCommands').module, {
getGuildHandler: async (g) => await this.getGuildHandler(g),
logger: logger,
messageHandler: this.messageHandler,
config: config
});
this.registerEvents();
} }
/** /**
@ -120,10 +128,10 @@ class Bot {
*/ */
async initializeWebserver() { async initializeWebserver() {
logger.verbose('Importing weblib'); logger.verbose('Importing weblib');
weblib = require('./lib/weblib'); weblib = require('./lib/WebLib');
weblib.setLogger(logger); weblib.setLogger(logger);
logger.verbose('Creating WebServer'); logger.verbose('Creating WebServer');
this.webServer = new weblib.WebServer(config.webservice.port || 8080); this.webServer = new weblib.WebServer(config.webinterface.port || 8080);
logger.debug('Setting Reference Objects to webserver'); logger.debug('Setting Reference Objects to webserver');
await this.webServer.setReferenceObjects({ await this.webServer.setReferenceObjects({
@ -131,7 +139,7 @@ class Bot {
presences: this.presences, presences: this.presences,
maindb: this.maindb, maindb: this.maindb,
prefix: prefix, prefix: prefix,
getGuildHandler: (guild) => this.getGuildHandler(guild, prefix), getGuildHandler: async (g) => await this.getGuildHandler(g),
guildHandlers: this.guildHandlers guildHandlers: this.guildHandlers
}); });
} }
@ -172,14 +180,6 @@ class Bot {
} }
} }
/**
* registeres global commands
*/
registerCommands() {
cmd.registerUtilityCommands(prefix, this);
cmd.registerInfoCommands(prefix, this);
}
/** /**
* changes the presence of the bot by using one stored in the presences array * changes the presence of the bot by using one stored in the presences array
*/ */
@ -198,7 +198,7 @@ class Bot {
/** /**
* Registeres callbacks for client events message and ready * Registeres callbacks for client events message and ready
*/ */
registerCallbacks() { registerEvents() {
this.client.on('error', (err) => { this.client.on('error', (err) => {
logger.error(err.message); logger.error(err.message);
logger.debug(err.stack); logger.debug(err.stack);
@ -217,93 +217,27 @@ class Bot {
}); });
}); });
/*
this.client.on('message', async (msg) => {
try {
if (msg.author === this.client.user) {
logger.verbose(`ME: ${msg.content}`);
return;
}
if (this.checkRate(msg.author.tag)) {
logger.verbose(`<${msg.author.tag}>: ${msg.content}`);
if (!msg.guild) {
let reply = cmd.parseMessage(msg);
await this.answerMessage(msg, reply);
} else {
let gh = await this.getGuildHandler(msg.guild, prefix);
await gh.handleMessage(msg);
}
if (((Date.now() - this.userRates[msg.author.tag].last)/1000) > (config.rateLimitTime || 10))
this.userRates[msg.author.tag].count = 0;
else
this.userRates[msg.author.tag].count++;
this.userRates[msg.author.tag].last = Date.now();
this.userRates[msg.author.tag].reached = false;
} else if (!this.userRates[msg.author.tag].reached) {
logger.verbose(`${msg.author.tag} reached it's rate limit.`);
this.userRates[msg.author.tag].reached = true;
}
} catch (err) {
logger.error(err.message);
logger.debug(err.stack);
}
});*/
this.client.on('voiceStateUpdate', async (oldMember, newMember) => { this.client.on('voiceStateUpdate', async (oldMember, newMember) => {
let gh = await this.getGuildHandler(newMember.guild, prefix); let gh = await this.getGuildHandler(newMember.guild);
if (newMember.user === this.client.user) { if (newMember.user === this.client.user) {
if (newMember.voiceChannel) if (newMember.voiceChannel)
gh.dj.updateChannel(newMember.voiceChannel); gh.musicPlayer.updateChannel(newMember.voiceChannel);
} else { } else {
if (oldMember.voiceChannel === gh.dj.voiceChannel || newMember.voiceChannel === gh.dj.voiceChannel) if (oldMember.voiceChannel === gh.musicPlayer.voiceChannel || newMember.voiceChannel === gh.musicPlayer.voiceChannel)
gh.dj.checkListeners(); gh.musicPlayer.checkListeners();
} }
}); });
} }
/**
* Returns true if the user has not reached it's rate limit.
* @param usertag
* @returns {boolean}
*/
checkRate(usertag) {
if (!this.userRates[usertag])
this.userRates[usertag] = {last: Date.now(), count: 0};
return ((Date.now() - this.userRates[usertag].last)/1000) > (config.rateLimitTime || 10) ||
this.userRates[usertag].count < (config.rateLimitCount || 5);
}
/**
* Sends the answer recieved from the commands callback.
* Handles the sending differently depending on the type of the callback return
* @param msg
* @param answer
*/
async answerMessage(msg, answer) {
if (answer instanceof Discord.RichEmbed) {
(this.mention) ? msg.reply('', answer) : msg.channel.send('', answer);
} else if (answer instanceof Promise) {
let resolvedAnswer = await answer;
await this.answerMessage(msg, resolvedAnswer);
} else if (answer instanceof Array) {
await waterfall(answer.map((x) => async () => await this.answerMessage(msg, x))); // execute each after another
} else if ({}.toString.call(answer) === '[object Function]') {
await this.answerMessage(msg, answer());
} else if (answer) {
(this.mention) ? msg.reply(answer) : msg.channel.send(answer);
}
}
/** /**
* Returns the guild handler by id, creates one if it doesn't exist and returns it then * Returns the guild handler by id, creates one if it doesn't exist and returns it then
* @param guild * @param guild {Guild}
* @param prefix
* @returns {*} * @returns {*}
*/ */
async getGuildHandler(guild, prefix) { async getGuildHandler(guild) {
if (!this.guildHandlers[guild.id]) { if (!this.guildHandlers[guild.id]) {
let newGuildHandler = new guilding.GuildHandler(guild, prefix); let newGuildHandler = new guilding.GuildHandler(guild);
await newGuildHandler.initDatabase(); await newGuildHandler.initDatabase();
this.guildHandlers[guild.id] = newGuildHandler; this.guildHandlers[guild.id] = newGuildHandler;
} }
@ -314,7 +248,7 @@ class Bot {
// Executing the main function // Executing the main function
if (typeof require !== 'undefined' && require.main === module) { if (typeof require !== 'undefined' && require.main === module) {
logger.info("Starting up... "); // log the current date so that the logfile is better to read. logger.info("Starting up... ");
logger.debug('Calling constructor...'); logger.debug('Calling constructor...');
let discordBot = new Bot(); let discordBot = new Bot();
logger.debug('Initializing services...'); logger.debug('Initializing services...');

@ -1,6 +1,7 @@
const Discord = require('discord.js'), const Discord = require('discord.js'),
yaml = require('js-yaml'), yaml = require('js-yaml'),
fsx = require('fs-extra'), fsx = require('fs-extra'),
config = require('../config.json'),
utils = require('./utils'); utils = require('./utils');
const scopes = { const scopes = {
@ -101,7 +102,7 @@ class CommandHandler {
* Handles the command and responds to the message. * Handles the command and responds to the message.
* @param commandMessage {String} * @param commandMessage {String}
* @param message {Discord.Message} * @param message {Discord.Message}
* @returns {Boolean | Promise<String|Discord.RichEmbed>} * @returns {Boolean | String | Promise<String|Discord.RichEmbed>}
*/ */
handleCommand(commandMessage, message) { handleCommand(commandMessage, message) {
let commandName = commandMessage.match(/^\S+/); let commandName = commandMessage.match(/^\S+/);
@ -115,12 +116,14 @@ class CommandHandler {
.replace(/\s+$/, ''); // trailing whitespace .replace(/\s+$/, ''); // trailing whitespace
let args = argsString.match(/\S+/g); let args = argsString.match(/\S+/g);
let command = this.commands[commandName]; let command = this.commands[commandName];
if (command) { if (command && this._checkPermission(message, command.permission)) {
let kwargs = {}; let kwargs = {};
if (args) if (args)
for (let i = 0; i < Math.min(command.args.length, args.length); i++) for (let i = 0; i < Math.min(command.args.length, args.length); i++)
kwargs[command.args[i]] = args[i]; kwargs[command.args[i]] = args[i];
return command.answer(message, kwargs, argsString); return command.answer(message, kwargs, argsString);
} else if (command) {
return "You don't have permission for this command";
} else { } else {
return false; return false;
} }
@ -131,7 +134,6 @@ class CommandHandler {
/** /**
* Registers the command so that the handler can use it. * Registers the command so that the handler can use it.
* @param name {String}
* @param command {Command} * @param command {Command}
*/ */
registerCommand(command) { registerCommand(command) {
@ -139,6 +141,26 @@ class CommandHandler {
this.commands[command.name] = command; this.commands[command.name] = command;
return this; return this;
} }
/**
* Checks if the author of the message has the given permission
* @param msg {Discord.Message}
* @param rolePerm {String} Permission String
* @returns {boolean}
* @private
*/
_checkPermission(msg, rolePerm) {
if (!rolePerm || ['all', 'any', 'everyone'].includes(rolePerm))
return true;
if (config.owners.includes(msg.author.tag))
return true;
else if (msg.member && rolePerm && rolePerm !== 'owner' && msg.member.roles
.some(role => (role.name.toLowerCase() === rolePerm.toLowerCase() ||
role.name.toLowerCase() === 'botcommander')))
return true;
return false;
}
} }
/** /**

@ -3,6 +3,8 @@ const cmdLib = require('./CommandLib'),
Discord = require('discord.js'), Discord = require('discord.js'),
promiseWaterfall = require('promise-waterfall'); promiseWaterfall = require('promise-waterfall');
/* eslint no-useless-escape: 0 */
class MessageHandler { class MessageHandler {
/** /**
@ -20,6 +22,7 @@ class MessageHandler {
cmdLib.CommandScopes.User); cmdLib.CommandScopes.User);
this.guildCmdHandler = new cmdLib.CommandHandler(config.prefix, this.guildCmdHandler = new cmdLib.CommandHandler(config.prefix,
cmdLib.CommandScopes.Guild); cmdLib.CommandScopes.Guild);
this.userRates = {};
this._registerEvents(); this._registerEvents();
} }
@ -51,6 +54,15 @@ class MessageHandler {
await cmdModule.register(this.getHandler(cmdModule.scope)); await cmdModule.register(this.getHandler(cmdModule.scope));
} }
/**
* Parses a string to a command sequence Array.
* Workaround to not reveal the private parseSyntax function.
* @param synStr {String}
*/
parseSyntaxString(synStr) {
return this._parseSyntax({content: synStr});
}
/** /**
* Registering event handlers. * Registering event handlers.
* @private * @private
@ -59,10 +71,13 @@ class MessageHandler {
this.logger.debug('Registering message event...'); this.logger.debug('Registering message event...');
this.discordClient.on('message', async (msg) => { 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.debug(`<${msg.guild? msg.channel.name+'@'+msg.guild.name : 'PRIVATE'}> ${msg.author.tag}: ${msg.content}`);
if (msg.author !== this.discordClient.user) { if (msg.author !== this.discordClient.user
&& this._checkPrefixStart(msg.content)
&& !this._checkRateReached(msg.author)) {
let sequence = this._parseSyntax(msg); let sequence = this._parseSyntax(msg);
this.logger.debug(`Syntax parsing returned: ${JSON.stringify(sequence)}`); this.logger.debug(`Syntax parsing returned: ${JSON.stringify(sequence)}`);
await this._executeCommandSequence(sequence, msg); await this.executeCommandSequence(sequence, msg);
this.logger.debug('Executed command sequence'); this.logger.debug('Executed command sequence');
} }
}); });
@ -82,8 +97,8 @@ class MessageHandler {
for (let string of strings) for (let string of strings)
content = content.replace(string, string // escape all special chars content = content.replace(string, string // escape all special chars
.replace(';', '\\;') .replace(/;/g, '\\;')
.replace('&', '\\&')); .replace(/&/g, '\\&'));
let independentCommands = content // independent command sequende with ; let independentCommands = content // independent command sequende with ;
.split(/(?<!\\);/g) .split(/(?<!\\);/g)
.map(x => x.replace(/^ +/, '')); .map(x => x.replace(/^ +/, ''));
@ -98,7 +113,7 @@ class MessageHandler {
/** /**
* Executes a sequence of commands * Executes a sequence of commands
*/ */
async _executeCommandSequence(cmdSequence, message) { async executeCommandSequence(cmdSequence, message) {
this.logger.debug(`Executing command sequence: ${JSON.stringify(cmdSequence)} ...`); this.logger.debug(`Executing command sequence: ${JSON.stringify(cmdSequence)} ...`);
let scopeCmdHandler = this.getScopeHandler(message); let scopeCmdHandler = this.getScopeHandler(message);
await Promise.all(cmdSequence.map((sq) => promiseWaterfall(sq.map((cmd) => async () => { await Promise.all(cmdSequence.map((sq) => promiseWaterfall(sq.map((cmd) => async () => {
@ -145,6 +160,40 @@ class MessageHandler {
else else
message.channel.send(answer); message.channel.send(answer);
} }
/**
* Checks if the messageString beginns with a command prefix.
* @param msgString {String}
* @private
*/
_checkPrefixStart(msgString) {
let p1 = this.globalCmdHandler.prefix;
let p2 = this.guildCmdHandler.prefix;
let p3 = this.userCmdHandler.prefix;
return (
new RegExp(`^\\s*?${p1}`).test(msgString) ||
new RegExp(`^\\s*?${p2}`).test(msgString) ||
new RegExp(`^\\s*?${p3}`).test(msgString));
}
/**
* Checks if the user has reached the command rate limit and updates it.
* @param user {Discord.User}
* @returns {boolean}
* @private
*/
_checkRateReached(user) {
if (!this.userRates[user.tag])
this.userRates[user.tag] = {last: 0, count: 0};
let userEntry = this.userRates[user.tag];
let reached = ((Date.now() - userEntry.last)/1000) < (config.rateLimitTime || 10)
&& userEntry.count > (config.rateLimitCount || 5);
if (((Date.now() - userEntry.last)/1000) > (config.rateLimitTime || 10))
this.userRates[user.tag].count = 0;
this.userRates[user.tag].last = Date.now();
this.userRates[user.tag].count++;
return reached;
}
} }
Object.assign(exports, { Object.assign(exports, {

@ -1,20 +1,22 @@
const ytdl = require("ytdl-core"), const ytdl = require("ytdl-core"),
ypi = require('youtube-playlist-info'), ypi = require('youtube-playlist-info'),
yttl = require('get-youtube-title'), yttl = require('get-youtube-title'),
args = require('args-parser')(process.argv),
config = require('../config.json'), config = require('../config.json'),
utils = require('./utils.js'), utils = require('./utils.js'),
ytapiKey = args.ytapi || config.api.youTubeApiKey; ytapiKey = config.api.youTubeApiKey;
/* Variable Definition */
let logger = require('winston');
/* Function Definition */ let logger = require('winston');
exports.setLogger = function (newLogger) { exports.setLogger = function (newLogger) {
logger = newLogger; logger = newLogger;
}; };
exports.DJ = class { /**
* The Music Player class is used to handle music playing tasks on Discord Servers (Guilds).
* @type {MusicPlayer}
*/
class MusicPlayer {
constructor(voiceChannel) { constructor(voiceChannel) {
this.conn = null; this.conn = null;
this.disp = null; this.disp = null;
@ -51,7 +53,7 @@ exports.DJ = class {
/** /**
* Defining setter for listenOnRepeat to include the current song into the repeating loop. * Defining setter for listenOnRepeat to include the current song into the repeating loop.
* @param value * @param value {Boolean}
*/ */
set listenOnRepeat(value) { set listenOnRepeat(value) {
this.repeat = value; this.repeat = value;
@ -74,7 +76,7 @@ exports.DJ = class {
/** /**
* Updates the channel e.g. when the bot is moved to another channel. * Updates the channel e.g. when the bot is moved to another channel.
* @param voiceChannel * @param voiceChannel {Discord.VoiceChannel}
*/ */
updateChannel(voiceChannel) { updateChannel(voiceChannel) {
if (voiceChannel) { if (voiceChannel) {
@ -86,7 +88,7 @@ exports.DJ = class {
/** /**
* Plays a file for the given filename. * Plays a file for the given filename.
* TODO: Implement queue * TODO: Implement queue
* @param filename * @param filename {String}
* @todo * @todo
*/ */
playFile(filename) { playFile(filename) {
@ -125,8 +127,8 @@ exports.DJ = class {
* Plays the url of the current song if there is no song playing or puts it in the queue. * Plays the url of the current song if there is no song playing or puts it in the queue.
* If the url is a playlist, the videos of the playlist are fetched and put * If the url is a playlist, the videos of the playlist are fetched and put
* in the queue. For each song the title is saved in the queue too. * in the queue. For each song the title is saved in the queue too.
* @param url * @param url {String}
* @param playnext * @param playnext {Boolean}
*/ */
async playYouTube(url, playnext) { async playYouTube(url, playnext) {
let plist = utils.YouTube.getPlaylistIdFromUrl(url); let plist = utils.YouTube.getPlaylistIdFromUrl(url);
@ -195,7 +197,7 @@ exports.DJ = class {
/** /**
* Gets the name of the YouTube Video at url * Gets the name of the YouTube Video at url
* @param url * @param url {String}
* @returns {Promise<>} * @returns {Promise<>}
*/ */
getVideoName(url) { getVideoName(url) {
@ -213,7 +215,7 @@ exports.DJ = class {
/** /**
* Sets the volume of the dispatcher to the given value * Sets the volume of the dispatcher to the given value
* @param percentage * @param percentage {Number}
*/ */
setVolume(percentage) { setVolume(percentage) {
logger.verbose(`Setting volume to ${percentage}`); logger.verbose(`Setting volume to ${percentage}`);
@ -323,4 +325,8 @@ exports.DJ = class {
clear() { clear() {
this.queue = []; this.queue = [];
} }
}; }
Object.assign(exports, {
MusicPlayer: MusicPlayer
});

@ -41,7 +41,7 @@ exports.WebServer = class {
this.app.use(require('cors')()); this.app.use(require('cors')());
this.app.use(session({ this.app.use(session({
store: new SQLiteStore({dir: './data', db: 'sessions.db'}), store: new SQLiteStore({dir: './data', db: 'sessions.db'}),
secret: config.webservice.sessionSecret, secret: config.webinterface.sessionSecret,
resave: false, resave: false,
saveUninitialized: true, saveUninitialized: true,
cookie: {secure: 'auto'}, cookie: {secure: 'auto'},
@ -89,7 +89,7 @@ exports.WebServer = class {
this.app.use('/graphql', graphqlHTTP({ this.app.use('/graphql', graphqlHTTP({
schema: this.schema, schema: this.schema,
rootValue: this.root, rootValue: this.root,
graphiql: config.webservice.graphiql || false graphiql: config.webinterface.graphiql || false
})); }));
} }
@ -98,14 +98,14 @@ exports.WebServer = class {
*/ */
start() { start() {
this.configureExpress(); this.configureExpress();
if (config.webservice.https && config.webservice.https.enabled) { if (config.webinterface.https && config.webinterface.https.enabled) {
let sslKey = null; let sslKey = null;
let sslCert = null; let sslCert = null;
if (config.webservice.https.keyFile) if (config.webinterface.https.keyFile)
sslKey = fs.readFileSync(config.webservice.https.keyFile, 'utf-8'); sslKey = fs.readFileSync(config.webinterface.https.keyFile, 'utf-8');
if (config.webservice.https.certFile) if (config.webinterface.https.certFile)
sslCert = fs.readFileSync(config.webservice.https.certFile, 'utf-8'); sslCert = fs.readFileSync(config.webinterface.https.certFile, 'utf-8');
if (sslKey && sslCert) { if (sslKey && sslCert) {
logger.verbose('Creating https server.'); logger.verbose('Creating https server.');
this.server = require('https').createServer({key: sslKey, cert: sslCert}, this.app); this.server = require('https').createServer({key: sslKey, cert: sslCert}, this.app);
@ -200,8 +200,8 @@ exports.WebServer = class {
return dcGuilds.filter((x) => { return dcGuilds.filter((x) => {
let gh = objects.guildHandlers[x.id]; let gh = objects.guildHandlers[x.id];
if (gh) if (gh)
if (gh.dj) if (gh.musicPlayer)
return gh.dj.playing; return gh.musicPlayer.playing;
else else
return false; return false;
else else
@ -272,12 +272,12 @@ function generateUUID(input) {
} }
/** /**
* Used for graphql attribute access to the lib/music/DJ * Used for graphql attribute access to the lib/music/MusicPlayer
*/ */
class DJ { class MusicPlayer {
constructor(musicDj) { constructor(musicPlayer) {
this.dj = musicDj; this.musicPlayer = musicPlayer;
this.quality = musicDj.quality; this.quality = musicPlayer.quality;
} }
queue(args) { queue(args) {
@ -297,35 +297,35 @@ class DJ {
} }
get playing() { get playing() {
return this.dj.playing; return this.musicPlayer.playing;
} }
get connected() { get connected() {
return this.dj.connected; return this.musicPlayer.connected;
} }
get paused() { get paused() {
return this.dj.disp? this.dj.disp.paused : false; return this.musicPlayer.disp? this.dj.disp.paused : false;
} }
get queueCount() { get queueCount() {
return this.dj.queue.length; return this.musicPlayer.queue.length;
} }
get songStartTime() { get songStartTime() {
return this.dj.disp.player.streamingData.startTime; return this.musicPlayer.disp.player.streamingData.startTime;
} }
get volume() { get volume() {
return this.dj.volume; return this.musicPlayer.volume;
} }
get repeat() { get repeat() {
return this.dj.repeat; return this.musicPlayer.repeat;
} }
get currentSong() { get currentSong() {
let x = this.dj.current; let x = this.musicPlayer.current;
return { return {
id: generateID(['Media', x.url]), id: generateID(['Media', x.url]),
name: x.title, name: x.title,
@ -335,7 +335,7 @@ class DJ {
} }
get voiceChannel() { get voiceChannel() {
return this.dj.voiceChannel.name; return this.musicPlayer.voiceChannel.name;
} }
} }
@ -358,7 +358,7 @@ class Guild {
this.ready = guildHandler.ready; this.ready = guildHandler.ready;
this.prSaved = null; this.prSaved = null;
this.guildHandler = guildHandler; this.guildHandler = guildHandler;
this.dj = this.guildHandler.dj ? new DJ(this.guildHandler.dj) : null; this.musicPlayer = this.guildHandler.musicPlayer ? new MusicPlayer(this.guildHandler.musicPlayer) : null;
} }
async querySaved() { async querySaved() {

@ -26,7 +26,7 @@ type GuildMember {
roles(first: Int = 10, offset: Int = 0, id: String): [Role] roles(first: Int = 10, offset: Int = 0, id: String): [Role]
highestRole: Role highestRole: Role
} }
type DJ { type MusicPlayer {
queue(first: Int = 10, offset: Int = 0, id: String): [MediaEntry] queue(first: Int = 10, offset: Int = 0, id: String): [MediaEntry]
queueCount: Int! queueCount: Int!
songStartTime: String songStartTime: String
@ -44,7 +44,7 @@ type Guild {
discordId: String discordId: String
name: String name: String
owner: GuildMember owner: GuildMember
dj: DJ musicPlayer: MusicPlayer
members(first: Int = 10, offset: Int = 0, id: String): [GuildMember] members(first: Int = 10, offset: Int = 0, id: String): [GuildMember]
memberCount: Int! memberCount: Int!
roles(first: Int = 10, offset: Int = 0, id: String): [Role] roles(first: Int = 10, offset: Int = 0, id: String): [Role]

@ -1,540 +0,0 @@
/* Module definition */
/* Variable Definition */
const Discord = require('discord.js'),
args = require('args-parser')(process.argv),
config = require('../config.json'),
gcmdTempl = require('../commands/globalcommands'),
scmdTempl = require('../commands/servercommands'),
utils = require('./utils');
let logger = require('winston'),
globCommands = {};
/**
* @type {Servant}
*/
class Servant {
constructor(prefix) {
this.commands = {};
this.prefix = prefix;
// show all commands (except the owner commands if the user is not an owner)
this.createCommand(gcmdTempl.utils.help, (msg, kwargs) => {
if (kwargs.command) {
let cmd = kwargs.command;
let allCommands = {...globCommands, ...this.commands};
if (cmd.charAt(0) !== prefix)
cmd = this.prefix + cmd;
if (allCommands[cmd])
return new Discord.RichEmbed()
.setTitle(`Help for ${cmd}`)
.addField('Usage', `\`${cmd} [${allCommands[cmd].args.join('] [')}]\``.replace('[]', ''))
.addField('Description', allCommands[cmd].description)
.addField('Permission Role', allCommands[cmd].role || 'all');
else
return 'Command not found :(';
} else {
let allCommands = {...globCommands, ...this.commands};
return createHelpEmbed(allCommands, msg, prefix);
}
});
// show all roles that are used by commands
this.createCommand(scmdTempl.utils.roles, () => {
let roles = [];
Object.values(globCommands).concat(Object.values(this.commands)).sort().forEach((value) => {
roles.push(value.role || 'all');
});
return `**Roles**\n${[...new Set(roles)].join('\n')}`;
});
}
/**
* Creates a command entry in the private commands dict
* @param template
* @param call
*/
createCommand(template, call) {
if (!template.name) {
logger.debug(`Name of command template is null or undef. Failed creating command.`);
return;
}
this.commands[this.prefix + template.name] = {
'args': template.args || [],
'description': template.description,
'callback': call,
'role': template.permission,
'category': template.category || 'Other'
};
logger.debug(`Created server command: ${this.prefix + template.name}, args: ${template.args}`);
}
/**
* Removes a command
* @param command
* @deprecated Why would you want to remove a command?
*/
removeCommand(command) {
delete this.commands[command];
}
/**
* Processes the command
* @param msg
* @param globResult
* @param content
* @param returnFunction Boolean if the return value should be a function.
* @param fallback
* @returns {*}
*/
processCommand(msg, globResult, content, returnFunction, fallback) {
let command = (content.match(/^.\w+/) || [])[0];
if (!command || !this.commands[command])
if (fallback && !globResult) {
command = fallback;
content = `${fallback} ${content}`;
} else {
return globResult;
}
let cmd = this.commands[command];
if (!checkPermission(msg, cmd.role))
return 'No Permission';
logger.debug(`Permission <${cmd.role || 'all'}> granted for command ${command} for user <${msg.author.tag}>`);
let argvars = content.match(/(?<= )\S+/g) || [];
let kwargs = {};
let nLength = Math.min(cmd.args.length, argvars.length);
for (let i = 0; i < nLength; i++)
kwargs[cmd.args[i]] = argvars[i];
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);
return locResult || globResult;
} catch (err) {
logger.error(err.message);
return `The command \`${command}\` has thrown an error.`;
}
}
/**
* Parses the message and executes the command callback for the found command entry in the commands dict
* @param msg
* @returns {*}
*/
parseCommand(msg) {
let globResult = parseGlobalCommand(msg);
logger.debug(`Global command result is ${globResult}`);
let content = msg.content;
let commands = content.split(/(?<!\\);/).map(x => x.replace(/^ +/, ''));
if (commands.length === 1) {
return this.processCommand(msg, globResult, content);
} else if (commands.length < (config.maxCmdSequenceLength || 5)) {
let answers = [];
let previousCommand = (commands[0].match(/^.\w+/) || [])[0];
for (let i = 0; i < commands.length; i++) {
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;
}
return answers;
} else {
return 'This command sequence is too long!';
}
}
}
/**
* Getting the logger
* @param {Object} newLogger
*/
function setModuleLogger(newLogger) {
logger = newLogger;
}
/**
* Creates a global command that can be executed in every channel.
* @param prefix
* @param template
* @param call
*/
function createGlobalCommand(prefix, template, call) {
if (!template.name) {
logger.debug(`Name of command template is null or undef. Failed to create command.`);
return;
}
globCommands[prefix + template.name] = {
'args': template.args || [],
'description': template.description,
'callback': call,
'role': template.permission,
'name': template.name,
'category': template.category || 'Other'
};
logger.debug(`Created global command: ${prefix + template.name}, args: ${template.args}`);
}
/**
* Parses a message for a global command
* @param msg
* @returns {boolean|*}
*/
exports.parseMessage = function (msg) {
return parseGlobalCommand(msg);
};
/**
* Initializes the module by creating a help command
*/
function initModule(prefix) {
logger.verbose("Creating help command...");
createGlobalCommand((prefix || config.prefix), gcmdTempl.utils.help, (msg, kwargs) => {
if (kwargs.command) {
let cmd = kwargs.command;
if (cmd.charAt(0) !== prefix)
cmd = prefix + cmd;
if (globCommands[cmd])
return new Discord.RichEmbed()
.setTitle(`Help for ${cmd}`)
.addField('Usage', `\`${cmd} [${globCommands[cmd].args.join('] [')}]\``.replace('[]', ''))
.addField('Description', globCommands[cmd].description)
.addField('Permission Role', globCommands[cmd].role || 'all');
} else {
return createHelpEmbed(globCommands, msg, prefix);
}
});
}
/**
* Processes commands for command series.
* @param cmd
* @param msg
* @param content
* @param returnFunction
* @returns {function(): *}
*/
function processCommand(cmd, msg, content, returnFunction) {
let argvars = content.match(/(?<= )\S+/g) || [];
let kwargs = {};
let nLength = Math.min(cmd.args.length, argvars.length);
for (let i = 0; i < nLength; i++)
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);
}
/**
* Parses the message by calling the assigned function for the command with arguments
* @param msg
*/
function parseGlobalCommand(msg) {
let content = msg.content;
let commands = content.split(/(?<!\\);/).map(x => x.replace(/^ +/, ''));
if (commands.length === 1) {
let command = (content.match(/^.\w+/) || [])[0];
if (!command || !globCommands[command])
return false;
let cmd = globCommands[command];
if (!checkPermission(msg, cmd.role))
return false;
logger.debug(`Permission <${cmd.role}> granted for command ${command} for user <${msg.author.tag}>`);
return processCommand(cmd, msg, content);
} else if (commands.length < (config.maxCmdSequenceLength || 5)) {
let answers = [];
let previousCommand = '';
for (let commandPart of commands) {
let command = (commandPart.match(/^.\w+/) || [])[0] || previousCommand;
previousCommand = globCommands[command] ? command : previousCommand;
if (!commandPart || !globCommands[command]) {
commandPart = `${previousCommand} ${commandPart}`;
command = previousCommand;
}
if (command && globCommands[command]) {
let cmd = globCommands[command];
if (checkPermission(msg, cmd.role)) {
logger.debug(`Permission <${cmd.role}> granted for command ${command} for user <${msg.author.tag}>`);
answers.push(processCommand(cmd, msg, commandPart,
true)); // return an function to avoid "race conditions"
} else {
answers.push(false);
}
} else {
answers.push(false);
}
}
return answers;
} else {
return 'This command sequence is too long!';
}
}
/**
* Creates a rich embed that contains help for all commands in the commands object
* @param commands {Object}
* @param msg {module:discord.js.Message}
* @param prefix {String}
* @returns {module:discord.js.RichEmbed}
*/
function createHelpEmbed(commands, msg, prefix) {
let helpEmbed = new Discord.RichEmbed()
.setTitle('Commands')
.setDescription('Create a sequence of commands with `;` (semicolon).')
.setTimestamp();
let categories = [];
let catCommands = {};
Object.entries(commands).sort().forEach(([key, value]) => {
if (value.role !== 'owner' || checkPermission(msg, 'owner'))
if (!categories.includes(value.category)) {
categories.push(value.category);
catCommands[value.category] = `\`${key}\` \t`;
} else {
catCommands[value.category] += `\`${key}\` \t`;
}
});
for (let cat of categories)
helpEmbed.addField(cat, catCommands[cat]);
helpEmbed.setFooter(prefix + 'help [command] for more info to each command');
return helpEmbed;
}
/**
* @param msg
* @param rolePerm {String}
* @returns {boolean}
*/
function checkPermission(msg, rolePerm) {
if (!rolePerm || ['all', 'any', 'everyone'].includes(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
.some(role => (role.name.toLowerCase() === rolePerm.toLowerCase() || role.name.toLowerCase() === 'botcommander')))
return true;
return false;
}
/**
* Registers the bot's utility commands
* @param prefix
* @param bot - the instance of the bot that called
*/
function registerUtilityCommands(prefix, bot) {
// responde with the commands 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
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]);
return `Added Presence \`${p}\``;
});
// shuts down the bot after destroying the client
createGlobalCommand(prefix, gcmdTempl.utils.shutdown, async (msg) => {
try {
await msg.reply('Shutting down...');
logger.debug('Destroying client...');
} catch (err) {
logger.error(err.message);
logger.debug(err.stack);
}
try {
await bot.client.destroy();
logger.debug('Exiting server...');
} catch (err) {
logger.error(err.message);
logger.debug(err.stack);
}
try {
await bot.webServer.stop();
logger.debug(`Exiting Process...`);
process.exit(0);
} catch (err) {
logger.error(err.message);
logger.debug(err.stack);
}
});
// forces a presence rotation
createGlobalCommand(prefix, gcmdTempl.utils.rotate, () => {
try {
bot.client.clearInterval(this.rotator);
bot.rotatePresence();
bot.rotator = this.client.setInterval(() => this.rotatePresence(), config.presence_duration);
} catch (error) {
logger.warn(error.message);
}
});
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.");
} else if (argv.username && argv.scope) {
logger.debug(`Creating user entry ${argv.username}, scope: ${argv.scope}`);
bot.webServer.createUser(argv.username, argv.password, argv.scope, false).then((token) => {
resolve(`Created entry
username: ${argv.username},
scope: ${argv.scope},
token: ${token}
`);
}).catch((err) => reject(err.message));
}
});
});
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}
*/
function registerInfoCommands(prefix, bot) {
// ping command that returns the ping attribute of the client
createGlobalCommand(prefix, gcmdTempl.info.ping, () => {
return `Current average ping: \`${bot.client.ping} ms\``;
});
// returns the time the bot is running
createGlobalCommand(prefix, gcmdTempl.info.uptime, () => {
let uptime = utils.getSplitDuration(bot.client.uptime);
return new Discord.RichEmbed().setDescription(`
**${uptime.days}** days
**${uptime.hours}** hours
**${uptime.minutes}** minutes
**${uptime.seconds}** seconds
**${uptime.milliseconds}** milliseconds
`).setTitle('Uptime');
});
// returns the number of guilds, the bot has joined
createGlobalCommand(prefix, gcmdTempl.info.guilds, () => {
return `Number of guilds: \`${bot.client.guilds.size}\``;
});
// returns information about the bot
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}
*/
function registerAnilistApiCommands(prefix) {
const anilistApi = require('./api/AnilistApi');
// returns the anime found for the name
createGlobalCommand(prefix, gcmdTempl.api.AniList.animeSearch, async (msg, kwargs, args) => {
try {
let animeData = await anilistApi.searchAnimeByName(args.join(' '));
if (animeData) {
let response = new Discord.RichEmbed()
.setTitle(animeData.title.romaji)
.setDescription(animeData.description.replace(/<\/?.*?>/g, ''))
.setThumbnail(animeData.coverImage.large)
.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('Scoring', `**Average Score:** ${animeData.averageScore}
**Favourites:** ${animeData.favourites}`);
if (animeData.episodes)
response.addField('Episodes', animeData.episodes);
response.addField('Season', animeData.season);
if (animeData.startDate.day)
response.addField('Start Date', `
${animeData.startDate.day}.${animeData.startDate.month}.${animeData.startDate.year}`);
if (animeData.nextAiringEpisode)
response.addField('Next Episode', `**Episode** ${animeData.nextAiringEpisode.episode}
**Airing at:** ${new Date(animeData.nextAiringEpisode.airingAt * 1000).toUTCString()}`);
if (animeData.endDate.day)
response.addField('End Date', `
${animeData.endDate.day}.${animeData.endDate.month}.${animeData.endDate.year}`);
return response;
} else {
return gcmdTempl.api.AniList.animeSearch.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.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
});

@ -78,8 +78,8 @@ class AniListCommandModule extends cmdLib.CommandModule {
); );
// registering commands // registering commands
commandHandler.registerCommand(animeSearch); commandHandler.registerCommand(animeSearch)
commandHandler.registerCommand(mangaSearch); .registerCommand(mangaSearch);
} }
} }

@ -39,5 +39,6 @@ help:
Shows help for bot ocmmands. Shows help for bot ocmmands.
permission: all permission: all
category: Info category: Info
embed_color: 0xffffff
args: args:
- command - command

@ -21,9 +21,10 @@ class InfoCommandModule extends cmdLib.CommandModule {
this._messageHandler = opts.messageHandler; this._messageHandler = opts.messageHandler;
} }
_createHelpEmbed(commands, msg, prefix) { _createHelpEmbed(commands, msg, prefix, embedColor = 0xfff) {
let helpEmbed = new cmdLib.ExtendedRichEmbed('Commands') let helpEmbed = new cmdLib.ExtendedRichEmbed('Commands')
.setDescription('Create a sequence of commands with `;` and `&&`.'); .setDescription('Create a sequence of commands with `;` and `&&`.')
.setColor(embedColor);
let categories = []; let categories = [];
let catCommands = {}; let catCommands = {};
Object.entries(commands).sort().forEach(([key, value]) => { Object.entries(commands).sort().forEach(([key, value]) => {
@ -89,10 +90,10 @@ class InfoCommandModule extends cmdLib.CommandModule {
if (k.command) { if (k.command) {
k.command = k.command.replace(globH.prefix, ''); k.command = k.command.replace(globH.prefix, '');
let commandInstance = globH.commands[k.command] || scopeH.commands[k.command]; let commandInstance = globH.commands[k.command] || scopeH.commands[k.command];
return commandInstance.help; return commandInstance.help.setColor(this.template.help.embed_color);
} else { } else {
let commandObj = {...globH.commands, ...scopeH.commands}; let commandObj = {...globH.commands, ...scopeH.commands};
return this._createHelpEmbed(commandObj, m, globH.prefix); return this._createHelpEmbed(commandObj, m, globH.prefix, this.template.help.embed_color);
} }
}) })
); );

@ -53,7 +53,7 @@ stop:
name: stop name: stop
description: > description: >
Stops the media playback and leaves the VoiceChannel. Stops the media playback and leaves the VoiceChannel.
permission: dj permission: musicPlayer
category: Music category: Music
response: response:
success: > success: >
@ -89,7 +89,7 @@ skip:
name: skip name: skip
description: > description: >
Skips the currently playing song. Skips the currently playing song.
permission: dj permission: musicPlayer
category: Music category: Music
response: response:
success: > success: >
@ -101,7 +101,7 @@ clear:
name: clear name: clear
description: > description: >
Clears the media queue. Clears the media queue.
permission: dj permission: musicPlayer
category: Music category: Music
response: response:
success: > success: >
@ -150,7 +150,7 @@ save_media:
name: savemedia name: savemedia
description: > description: >
Saves the YouTube URL with a specific name. Saves the YouTube URL with a specific name.
permission: dj permission: musicPlayer
category: Music category: Music
args: args:
- url - url
@ -160,7 +160,7 @@ delete_media:
name: deletemedia name: deletemedia
description: > description: >
Deletes a saved YouTube URL from saved media. Deletes a saved YouTube URL from saved media.
permission: dj permission: musicPlayer
category: Music category: Music
usage: deletemedia [name] usage: deletemedia [name]
response: response:

@ -30,11 +30,11 @@ class MusicCommandModule extends cmdLib.CommandModule {
* @private * @private
*/ */
async _connectAndPlay(gh, vc, url, next) { async _connectAndPlay(gh, vc, url, next) {
if (!gh.dj.connected) { if (!gh.musicPlayer.connected) {
await gh.dj.connect(vc); await gh.musicPlayer.connect(vc);
await gh.dj.playYouTube(url, next); await gh.musicPlayer.playYouTube(url, next);
} else { } else {
await gh.dj.playYouTube(url, next); await gh.musicPlayer.playYouTube(url, next);
} }
} }
@ -50,7 +50,7 @@ class MusicCommandModule extends cmdLib.CommandModule {
*/ */
async _playFunction(m, k, s, t, n) { async _playFunction(m, k, s, t, n) {
let gh = await this._getGuildHandler(m.guild); let gh = await this._getGuildHandler(m.guild);
let vc = gh.dj.voiceChannel || m.member.voiceChannel; let vc = gh.musicPlayer.voiceChannel || m.member.voiceChannel;
let url = k['url']; let url = k['url'];
if (!vc) if (!vc)
return t.response.no_voicechannel; return t.response.no_voicechannel;
@ -94,7 +94,7 @@ class MusicCommandModule extends cmdLib.CommandModule {
new cmdLib.Answer(async (m) => { new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild); let gh = await this._getGuildHandler(m.guild);
if (m.member.voiceChannel) if (m.member.voiceChannel)
await gh.dj.connect(m.member.voiceChannel); await gh.musicPlayer.connect(m.member.voiceChannel);
else else
return this.template.join.response.no_voicechannel; return this.template.join.response.no_voicechannel;
}) })
@ -104,8 +104,8 @@ class MusicCommandModule extends cmdLib.CommandModule {
this.template.stop, this.template.stop,
new cmdLib.Answer(async (m) => { new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild); let gh = await this._getGuildHandler(m.guild);
if (gh.dj.connected) { if (gh.musicPlayer.connected) {
gh.dj.stop(); gh.musicPlayer.stop();
return this.template.stop.success; return this.template.stop.success;
} else { } else {
return this.template.stop.not_playing; return this.template.stop.not_playing;
@ -117,8 +117,8 @@ class MusicCommandModule extends cmdLib.CommandModule {
this.template.pause, this.template.pause,
new cmdLib.Answer(async (m) => { new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild); let gh = await this._getGuildHandler(m.guild);
if (gh.dj.playing) { if (gh.musicPlayer.playing) {
gh.dj.pause(); gh.musicPlayer.pause();
return this.template.pause.response.success; return this.template.pause.response.success;
} else { } else {
return this.template.pause.response.not_playing; return this.template.pause.response.not_playing;
@ -130,8 +130,8 @@ class MusicCommandModule extends cmdLib.CommandModule {
this.template.resume, this.template.resume,
new cmdLib.Answer(async (m) => { new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild); let gh = await this._getGuildHandler(m.guild);
if (gh.dj.playing) { if (gh.musicPlayer.playing) {
gh.dj.resume(); gh.musicPlayer.resume();
return this.template.resume.response.success; return this.template.resume.response.success;
} else { } else {
return this.template.resume.response.not_playing; return this.template.resume.response.not_playing;
@ -143,8 +143,8 @@ class MusicCommandModule extends cmdLib.CommandModule {
this.template.skip, this.template.skip,
new cmdLib.Answer(async (m) => { new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild); let gh = await this._getGuildHandler(m.guild);
if (gh.dj.playing) { if (gh.musicPlayer.playing) {
gh.dj.skip(); gh.musicPlayer.skip();
return this.template.skip.response.success; return this.template.skip.response.success;
} else { } else {
return this.template.skip.response.not_playing; return this.template.skip.response.not_playing;
@ -156,7 +156,7 @@ class MusicCommandModule extends cmdLib.CommandModule {
this.template.clear, this.template.clear,
new cmdLib.Answer(async (m) => { new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild); let gh = await this._getGuildHandler(m.guild);
gh.dj.clear(); gh.musicPlayer.clear();
return this.template.clear.response.success; return this.template.clear.response.success;
}) })
); );
@ -165,14 +165,14 @@ class MusicCommandModule extends cmdLib.CommandModule {
this.template.media_queue, this.template.media_queue,
new cmdLib.Answer(async (m) => { new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild); let gh = await this._getGuildHandler(m.guild);
this._logger.debug(`Found ${gh.dj.queue.length} songs.`); this._logger.debug(`Found ${gh.musicPlayer.queue.length} songs.`);
let description = ''; let description = '';
for (let i = 0; i < Math.min(gh.dj.queue.length, 9); i++) { for (let i = 0; i < Math.min(gh.musicPlayer.queue.length, 9); i++) {
let entry = gh.dj.queue[i]; let entry = gh.musicPlayer.queue[i];
description += `[${entry.title}](${entry.url})\n`; description += `[${entry.title}](${entry.url})\n`;
} }
return new cmdLib.ExtendedRichEmbed(`${gh.dj.queue.length} songs in queue`) return new cmdLib.ExtendedRichEmbed(`${gh.musicPlayer.queue.length} songs in queue`)
.setDescription(description); .setDescription(description);
}) })
); );
@ -181,7 +181,7 @@ class MusicCommandModule extends cmdLib.CommandModule {
this.template.media_current, this.template.media_current,
new cmdLib.Answer(async (m) => { new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild); let gh = await this._getGuildHandler(m.guild);
let song = gh.dj.song; let song = gh.musicPlayer.song;
if (song) if (song)
return new cmdLib.ExtendedRichEmbed('Now playing:') return new cmdLib.ExtendedRichEmbed('Now playing:')
.setDescription(`[${song.title}](${song.url})`) .setDescription(`[${song.title}](${song.url})`)
@ -196,7 +196,7 @@ class MusicCommandModule extends cmdLib.CommandModule {
this.template.shuffle, this.template.shuffle,
new cmdLib.Answer(async (m) => { new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild); let gh = await this._getGuildHandler(m.guild);
gh.dj.shuffle(); gh.musicPlayer.shuffle();
return this.template.shuffle.response.success; return this.template.shuffle.response.success;
}) })
); );
@ -205,8 +205,8 @@ class MusicCommandModule extends cmdLib.CommandModule {
this.template.toggle_repeat, this.template.toggle_repeat,
new cmdLib.Answer(async (m) => { new cmdLib.Answer(async (m) => {
let gh = await this._getGuildHandler(m.guild); let gh = await this._getGuildHandler(m.guild);
gh.dj.repeat = !gh.dj.repeat; gh.musicPlayer.repeat = !gh.musicPlayer.repeat;
return gh.dj.repeat? return gh.musicPlayer.repeat?
this.template.toggle_repeat.response.repeat_true : this.template.toggle_repeat.response.repeat_true :
this.template.toggle_repeat.response.repeat_false; this.template.toggle_repeat.response.repeat_false;
}) })

@ -20,9 +20,9 @@ class UtilityCommandModule extends cmdLib.CommandModule {
constructor(opts) { constructor(opts) {
super(cmdLib.CommandScopes.User); super(cmdLib.CommandScopes.User);
this.templateFile = location + '/UtilityCommandsTemplate.yaml'; this.templateFile = location + '/UtilityCommandsTemplate.yaml';
this.bot = opts.bot; this._bot = opts.bot;
this.logger = opts.logger; this._logger = opts.logger;
this.config = opts.config; this._config = opts.config;
} }
async register(commandHandler) { async register(commandHandler) {
@ -31,8 +31,8 @@ class UtilityCommandModule extends cmdLib.CommandModule {
let addPresence = new cmdLib.Command( let addPresence = new cmdLib.Command(
this.template.add_presence, this.template.add_presence,
new cmdLib.Answer(async (m, k, s) => { new cmdLib.Answer(async (m, k, s) => {
this.bot.presences.push(s); this._bot.presences.push(s);
await this.bot.maindb.run('INSERT INTO presences (text) VALUES (?)', [s]); await this._bot.maindb.run('INSERT INTO presences (text) VALUES (?)', [s]);
return `Added Presence \`${s}\``; return `Added Presence \`${s}\``;
}) })
); );
@ -41,12 +41,12 @@ class UtilityCommandModule extends cmdLib.CommandModule {
this.template.rotate_presence, this.template.rotate_presence,
new cmdLib.Answer(() => { new cmdLib.Answer(() => {
try { try {
this.bot.client.clearInterval(this.rotator); this._bot.client.clearInterval(this._bot.rotator);
this.bot.rotatePresence(); this._bot.rotatePresence();
this.bot.rotator = this.bot.client.setInterval(() => this.bot.rotatePresence(), this._bot.rotator = this._bot.client.setInterval(() => this._bot.rotatePresence(),
this.config.presence_duration); this._config.presence_duration);
} catch (error) { } catch (error) {
this.logger.warn(error.message); this._logger.warn(error.message);
} }
}) })
); );
@ -56,25 +56,25 @@ class UtilityCommandModule extends cmdLib.CommandModule {
new cmdLib.Answer(async (m) => { new cmdLib.Answer(async (m) => {
try { try {
await m.reply('Shutting down...'); await m.reply('Shutting down...');
this.logger.debug('Destroying client...'); this._logger.debug('Destroying client...');
await this._bot.client.destroy();
} catch (err) { } catch (err) {
this.logger.error(err.message); this._logger.error(err.message);
this.logger.debug(err.stack); this._logger.debug(err.stack);
} }
try { try {
await this.bot.client.destroy(); this._logger.debug('Exiting server...');
this.logger.debug('Exiting server...'); await this._bot.webServer.stop();
} catch (err) { } catch (err) {
this.logger.error(err.message); this._logger.error(err.message);
this.logger.debug(err.stack); this._logger.debug(err.stack);
} }
try { try {
await this.bot.webServer.stop(); this._logger.debug(`Exiting Process...`);
this.logger.debug(`Exiting Process...`);
process.exit(0); process.exit(0);
} catch (err) { } catch (err) {
this.logger.error(err.message); this._logger.error(err.message);
this.logger.debug(err.stack); this._logger.debug(err.stack);
} }
}) })
); );
@ -83,8 +83,8 @@ class UtilityCommandModule extends cmdLib.CommandModule {
this.template.create_user, this.template.create_user,
new cmdLib.Answer(async (m, k) => { new cmdLib.Answer(async (m, k) => {
if (k.username &&k.password && k.scope) { if (k.username &&k.password && k.scope) {
this.logger.debug(`Creating user entry for ${k.username}`); this._logger.debug(`Creating user entry for ${k.username}`);
let token = await this.bot.webServer.createUser( let token = await this._bot.webServer.createUser(
k.username, k.password, k.scope, false); k.username, k.password, k.scope, false);
return `${k.username}'s token is ${token}`; return `${k.username}'s token is ${token}`;
} }
@ -100,11 +100,11 @@ class UtilityCommandModule extends cmdLib.CommandModule {
); );
// register commands // register commands
commandHandler.registerCommand(addPresence); commandHandler.registerCommand(addPresence)
commandHandler.registerCommand(rotatePresence); .registerCommand(rotatePresence)
commandHandler.registerCommand(shutdown); .registerCommand(shutdown)
commandHandler.registerCommand(createUser); .registerCommand(createUser)
commandHandler.registerCommand(bugReport); .registerCommand(bugReport);
} }
} }

@ -1,49 +1,45 @@
const cmd = require('./cmd'), const music = require('./MusicLib'),
music = require('./music'),
utils = require('./utils'), utils = require('./utils'),
config = require('../config.json'), config = require('../config.json'),
sqliteAsync = require('./sqliteAsync'), sqliteAsync = require('./sqliteAsync'),
fs = require('fs-extra'), fs = require('fs-extra'),
servercmd = require('../commands/servercommands'),
Discord = require('discord.js'),
waterfall = require('promise-waterfall'),
dataDir = config.dataPath || './data'; dataDir = config.dataPath || './data';
let logger = require('winston'); let logger = require('winston');
exports.setLogger = function (newLogger) { exports.setLogger = function (newLogger) {
logger = newLogger; logger = newLogger;
music.setLogger(logger); music.setLogger(logger);
cmd.setLogger(logger);
}; };
/** /**
* Server-Specific commands, music and more * The Guild Handler handles guild settings and data.
* @type {GuildHandler} * @type {GuildHandler}
*/ */
exports.GuildHandler = class { class GuildHandler {
constructor(guild, prefix) {
constructor(guild) {
this.guild = guild; this.guild = guild;
this.dj = null; this.musicPlayer = new music.MusicPlayer(null);
this.mention = false;
this.prefix = prefix || config.prefix;
this.servant = new cmd.Servant(this.prefix);
} }
/**
* Initializes the database
* @returns {Promise<void>}
*/
async initDatabase() { async initDatabase() {
await fs.ensureDir(dataDir + '/gdb'); await fs.ensureDir(dataDir + '/gdb');
this.db = new sqliteAsync.Database(`${dataDir}/gdb/${this.guild}.db`); this.db = new sqliteAsync.Database(`${dataDir}/gdb/${this.guild}.db`);
await this.db.init(); await this.db.init();
logger.debug(`Connected to the database for ${this.guild}`); logger.debug(`Connected to the database for ${this.guild}`);
await this.createTables(); await this.createTables();
// register commands
this.registerCommands();
} }
/** /**
* Destroys the guild handler * Destroys the guild handler
*/ */
destroy() { destroy() {
this.dj.stop(); this.musicPlayer.stop();
this.db.close(); this.db.close();
} }
@ -72,283 +68,8 @@ exports.GuildHandler = class {
command VARCHAR(255) NOT NULL command VARCHAR(255) NOT NULL
)`); )`);
} }
}
/** Object.assign(exports, {
* Answers a message via mention if mentioning is active or with just sending it to the same channel. GuildHandler: GuildHandler
* @param msg });
* @param answer
*/
async answerMessage(msg, answer) {
if (answer instanceof Promise || answer)
if (answer instanceof Discord.RichEmbed) {
(this.mention) ? msg.reply('', answer) : msg.channel.send('', answer);
} else if (answer instanceof Promise) {
let resolvedAnswer = await answer;
await this.answerMessage(msg, resolvedAnswer);
} else if (answer instanceof Array) {
await waterfall(answer.map((x) => async () => await this.answerMessage(msg, x)));
} else if ({}.toString.call(answer) === '[object Function]') { // check if the answer is of type function
await this.answerMessage(msg, answer());
} else {
(this.mention) ? msg.reply(answer) : msg.channel.send(answer);
}
}
/**
* handles the message by letting the servant parse the command. Depending on the message setting it
* replies or just sends the answer.
* @param msg
*/
async handleMessage(msg) {
if (this.db)
await this.db.run(
'INSERT INTO messages (author, creation_timestamp, author_name, content) values (?, ?, ?, ?)',
[msg.author.id, msg.createdTimestamp, msg.author.username, msg.content]
);
await this.answerMessage(msg, this.servant.parseCommand(msg));
}
/**
* Connect to a voice-channel if not connected and play the url
* @param vc
* @param url
* @param next
*/
async connectAndPlay(vc, url, next) {
if (!this.dj.connected) {
await this.dj.connect(vc);
await this.dj.playYouTube(url, next);
} else {
await this.dj.playYouTube(url, next);
}
}
/**
* registers all music commands and initializes a dj
*/
registerCommands() {
this.dj = new music.DJ();
let playCb = async (msg, kwargs, argv, template, next) => {
let vc = this.dj.voiceChannel || msg.member.voiceChannel;
let url = kwargs['url'];
if (!vc)
return template.response.no_voicechannel;
if (!url)
return template.response.no_url;
if (!utils.YouTube.isValidEntityUrl(url)) {
if (argv && argv.length > 0)
url += ' ' + argv.join(' '); // join to get the whole expression behind the command
let row = await this.db.get('SELECT url FROM playlists WHERE name = ?', [url]);
if (!row) {
logger.debug('Got invalid url for play command.');
return template.response.url_invalid;
} else {
await this.connectAndPlay(vc, row.url, next);
return template.response.success;
}
} else {
await this.connectAndPlay(vc, url, next);
return template.response.success;
}
};
// play command
this.servant.createCommand(servercmd.music.play, async (msg, kwargs, argv) => {
return await playCb(msg, kwargs, argv, servercmd.music.play, false);
});
// playnext command
this.servant.createCommand(servercmd.music.playnext, async (msg, kwargs, argv) => {
return await playCb(msg, kwargs, argv, servercmd.music.playnext, true);
});
// join command
this.servant.createCommand(servercmd.music.join, (msg) => {
if (msg.member.voiceChannel)
this.dj.connect(msg.member.voiceChannel);
else
return servercmd.music.join.response.not_connected;
});
// stop command
this.servant.createCommand(servercmd.music.stop, () => {
if (this.dj.connected) {
this.dj.stop();
return servercmd.music.stop.response.success;
} else {
return servercmd.music.stop.response.not_playing;
}
});
// pause command
this.servant.createCommand(servercmd.music.pause, () => {
if (this.dj.playing) {
this.dj.pause();
return servercmd.music.pause.response.success;
} else {
return servercmd.music.pause.response.not_playing;
}
});
// resume command
this.servant.createCommand(servercmd.music.resume, () => {
if (this.dj.playing) {
this.dj.resume();
return servercmd.music.resume.response.success;
} else {
return servercmd.music.resume.response.not_playing;
}
});
// skip command
this.servant.createCommand(servercmd.music.skip, () => {
if (this.dj.playing) {
this.dj.skip();
return servercmd.music.skip.response.success;
} else {
return servercmd.music.skip.response.not_playing;
}
});
// clear command
this.servant.createCommand(servercmd.music.clear, () => {
this.dj.clear();
return servercmd.music.clear.response.success;
});
// playlist command
this.servant.createCommand(servercmd.music.playlist, () => {
logger.debug(`found ${this.dj.queue.length} songs`);
let describtion = '';
for (let i = 0; i < Math.min(this.dj.queue.length, 9); i++) {
let entry = this.dj.queue[i];
describtion += `[${entry.title}](${entry.url})\n`;
}
return new Discord.RichEmbed()
.setTitle(`${this.dj.queue.length} songs in queue`)
.setDescription(describtion);
});
// np command
this.servant.createCommand(servercmd.music.current, () => {
let song = this.dj.song;
if (song)
return new Discord.RichEmbed()
.setTitle('Now playing:')
.setDescription(`[${song.title}](${song.url})`)
.setImage(utils.YouTube.getVideoThumbnailUrlFromUrl(song.url))
.setColor(0x00aaff);
else
return servercmd.music.current.response.not_playing;
});
// shuffle command
this.servant.createCommand(servercmd.music.shuffle, () => {
this.dj.shuffle();
return servercmd.music.shuffle.response.success;
});
// repeat command
this.servant.createCommand(servercmd.music.repeat, () => {
if (this.dj) {
this.dj.repeat = !this.dj.repeat;
if (this.dj.repeat)
return servercmd.music.repeat.response.repeat_true;
else
return servercmd.music.repeat.response.repeat_false;
}
});
// saves playlists and videos
this.servant.createCommand(servercmd.music.savemedia, async (msg, kwargs, argv) => {
let saveName = argv.join(' ');
let row = await this.db.get('SELECT COUNT(*) count FROM playlists WHERE name = ?', [saveName]);
if (!row || row.count === 0)
await this.db.run('INSERT INTO playlists (name, url) VALUES (?, ?)', [saveName, kwargs.url]);
else
await this.db.run('UPDATE playlists SET url = ? WHERE name = ?', [kwargs.url, saveName]);
return `Saved song/playlist as ${saveName}`;
});
// savedmedia command - prints out saved playlists and videos
this.servant.createCommand(servercmd.music.savedmedia, async () => {
let response = '';
let rows = await this.db.all('SELECT name, url FROM playlists');
for (let row of rows)
response += `[${row.name}](${row.url})\n`;
if (rows.length === 0)
return servercmd.music.savedmedia.response.no_saved;
else
return new Discord.RichEmbed()
.setTitle('Saved Songs and Playlists')
.setDescription(response)
.setFooter(`Play a saved entry with ${this.prefix}play [Entryname]`)
.setTimestamp();
});
this.servant.createCommand(servercmd.music.deletemedia, async (msg, kwargs, argv) => {
let saveName = argv.join(' ');
if (!saveName) {
return servercmd.music.deletemedia.response.no_name;
} else {
await this.db.run('DELETE FROM playlists WHERE name = ?', [saveName]);
return `Deleted ${saveName} from saved media`;
}
});
// savecmd - saves a command sequence with a name
this.servant.createCommand(servercmd.utils.savecmd, async (msg, kwargs, argv) => {
let saveName = argv.pop();
let cmdsequence = argv.join(' ').replace(/\\/g, '');
if (argv.includes(this.prefix + servercmd.utils.execute.name)) {
return servercmd.utils.savecmd.response.no_recursion;
} else if (cmdsequence.split(';').length < (config.maxCmdSequenceLength || 5)){
let row = await this.db.get('SELECT COUNT(*) count FROM commands WHERE name = ?', [saveName]);
if (!row || row.count === 0)
await this.db.run('INSERT INTO commands (name, command) VALUES (?, ?)', [saveName, cmdsequence]);
else
await this.db.run('UPDATE commands SET sequence = ? WHERE name = ?', [cmdsequence, saveName]);
return `saved command sequence as ${saveName}`;
} else {
return servercmd.utils.savecmd.response.sequence_too_long;
}
});
// savedcmd - prints saved commands
this.servant.createCommand(servercmd.utils.savedcmd, async () => {
let response = new Discord.RichEmbed()
.setTitle('Saved Commands')
.setFooter(`Execute a saved entry with ${this.prefix}execute [Entryname]`)
.setTimestamp();
let rows = await this.db.all('SELECT name, command FROM commands');
if (rows.length === 0)
return servercmd.utils.savedcmd.response.no_commands;
else
for (let row of rows)
response.addField(row.name, '`' + row.command + '`');
return response;
});
// deletecmd - deletes a command from saved commands
this.servant.createCommand(servercmd.utils.deletecmd, async (msg, kwargs) => {
await this.db.run('DELETE FROM commands WHERE name = ?', [kwargs.cmdname]);
return `Deleted command ${kwargs.cmdname}`;
});
// execute - executes a saved command
this.servant.createCommand(servercmd.utils.execute, async (msg, kwargs) => {
let row = await this.db.get('SELECT command FROM commands WHERE name = ?', [kwargs.cmdname]);
if (row) {
msg.content = row.command;
await this.handleMessage(msg);
} else {
return servercmd.utils.execute.response.not_found;
}
});
}
};

@ -201,7 +201,7 @@ describe('lib/music', function() {
"api": {} "api": {}
}); });
describe('#DJ', function () { describe('#MusicPlayer', function () {
it('connects to a VoiceChannel', function (done) { it('connects to a VoiceChannel', function (done) {
let dj = new music.DJ(mockobjects.mockVoicechannel); let dj = new music.DJ(mockobjects.mockVoicechannel);
@ -456,7 +456,7 @@ describe('lib/guilding', function*() { // deactivated because of problems with s
let gh = new guilding.GuildHandler('test', '~'); let gh = new guilding.GuildHandler('test', '~');
gh.db = new mockobjects.MockDatabase('', ()=>{}); gh.db = new mockobjects.MockDatabase('', ()=>{});
gh.ready = true; gh.ready = true;
gh.dj = new music.DJ(mockobjects.mockVoicechannel); gh.musicPlayer = new music.DJ(mockobjects.mockVoicechannel);
gh.connectAndPlay(mockobjects.mockVoicechannel, 'test', false).then(() => { gh.connectAndPlay(mockobjects.mockVoicechannel, 'test', false).then(() => {
done(); done();
}); });

@ -50,30 +50,30 @@ head
span.label.text-right Member Count: span.label.text-right Member Count:
span#guild-memberCount.text-left span#guild-memberCount.text-left
.space .space
h3.cell DJ h3.cell MusicPlayer
.cell .cell
span.label.text-right State: span.label.text-right State:
span#guild-djStatus.text-left span#guild-mpStatus.text-left
.cell .cell
span.label.text-right Repeat: span.label.text-right Repeat:
span#dj-repeat.text-left span#mp-repeat.text-left
.cell .cell
span.label.text-right Voice Channel: span.label.text-right Voice Channel:
span#dj-voiceChannel.text-left span#mp-voiceChannel.text-left
#dj-songinfo.listContainer(style='display: none') #mp-songinfo.listContainer(style='display: none')
a#songinfo-container a#songinfo-container
span#dj-songname span#mp-songname
img#dj-songImg(src='' alt='') img#mp-songImg(src='' alt='')
#dj-songProgress(style='display:none') #mp-songProgress(style='display:none')
span#dj-songCurrentTS span#mp-songCurrentTS
#dj-queue-container #mp-queue-container
span.cell.label(id='Queue Song count') span.cell.label(id='Queue Song count')
span#dj-queueCount span#mp-queueCount
| Songs in Queue | Songs in Queue
span.cell span.cell
| Next | Next
span#dj-queueDisplayCount 0 span#mp-queueDisplayCount 0
| Songs: | Songs:
#dj-songQueue #mp-songQueue
script. script.
startUpdating(); startUpdating();

@ -247,7 +247,7 @@ div.cell > *
#guild-nameAndIcon #guild-nameAndIcon
width: 50% width: 50%
#dj-songinfo #mp-songinfo
background-color: $cBackgroundVariant background-color: $cBackgroundVariant
border-radius: 20px border-radius: 20px
overflow-x: hidden overflow-x: hidden
@ -259,15 +259,15 @@ div.cell > *
padding: 10px padding: 10px
width: calc(100% - 20px) width: calc(100% - 20px)
#dj-queue-container #mp-queue-container
display: grid display: grid
padding: 0 5px 5px padding: 0 5px 5px
#dj-songname #mp-songname
font-weight: bold font-weight: bold
font-size: 120% font-size: 120%
#dj-songImg #mp-songImg
align-self: center align-self: center
width: 80% width: 80%
height: auto height: auto
@ -281,6 +281,6 @@ div.cell > *
#guildinfo:hover #guildinfo:hover
overflow-y: auto overflow-y: auto
#dj-songQueue #mp-songQueue
display: grid display: grid
max-height: 100% max-height: 100%

@ -60,7 +60,7 @@ function queryGuilds() {
guilds { guilds {
id id
name name
dj { musicPlayer {
playing playing
} }
} }
@ -71,8 +71,8 @@ function queryGuilds() {
if ($(`option[value=${guild.id}]`).length === 0) { if ($(`option[value=${guild.id}]`).length === 0) {
let option = document.createElement('option'); let option = document.createElement('option');
option.setAttribute('value', guild.id); option.setAttribute('value', guild.id);
if (guild.dj) if (guild.musicPlayer)
option.innerText = guild.dj.playing? guild.name + ' 🎶' : guild.name; option.innerText = guild.musicPlayer.playing? guild.name + ' 🎶' : guild.name;
let guildSelect = document.querySelector('#guild-select'); let guildSelect = document.querySelector('#guild-select');
guildSelect.appendChild(option); guildSelect.appendChild(option);
} }
@ -118,7 +118,7 @@ function queryGuildStatus(guildId) {
let query = `{ let query = `{
client { client {
guilds(id: "${guildId}") { guilds(id: "${guildId}") {
dj { musicPlayer {
playing playing
connected connected
repeat repeat
@ -144,29 +144,29 @@ function queryGuildStatus(guildId) {
}`; }`;
postQuery(query).then((res) => { postQuery(query).then((res) => {
let guild = res.data.client.guilds[0]; let guild = res.data.client.guilds[0];
document.querySelector('#dj-repeat').innerText = guild.dj.repeat? 'on': 'off'; document.querySelector('#mp-repeat').innerText = guild.musicPlayer.repeat? 'on': 'off';
document.querySelector('#guild-djStatus').innerText = guild.dj.connected? 'connected' : 'disconnected'; document.querySelector('#guild-mpStatus').innerText = guild.musicPlayer.connected? 'connected' : 'disconnected';
if (guild.dj.connected) { if (guild.musicPlayer.connected) {
let songinfoContainer = $('#dj-songinfo'); let songinfoContainer = $('#mp-songinfo');
songinfoContainer.show(); songinfoContainer.show();
document.querySelector('#guild-djStatus').innerText = guild.dj.playing? 'playing' : 'connected'; document.querySelector('#guild-mpStatus').innerText = guild.musicPlayer.playing? 'playing' : 'connected';
document.querySelector('#dj-voiceChannel').innerText = guild.dj.voiceChannel; document.querySelector('#mp-voiceChannel').innerText = guild.musicPlayer.voiceChannel;
if (guild.dj.playing) { if (guild.musicPlayer.playing) {
if (songinfoContainer.is(':hidden')) if (songinfoContainer.is(':hidden'))
songinfoContainer.show(); songinfoContainer.show();
document.querySelector('#guild-djStatus').innerText = guild.dj.paused? 'paused' : 'playing'; document.querySelector('#guild-mpStatus').innerText = guild.musicPlayer.paused? 'paused' : 'playing';
document.querySelector('#songinfo-container').setAttribute('href', guild.dj.currentSong.url); document.querySelector('#songinfo-container').setAttribute('href', guild.musicPlayer.currentSong.url);
document.querySelector('#dj-songname').innerText = guild.dj.currentSong.name; document.querySelector('#mp-songname').innerText = guild.musicPlayer.currentSong.name;
document.querySelector('#dj-songImg').setAttribute('src', guild.dj.currentSong.thumbnail.replace('maxresdefault', 'mqdefault')); document.querySelector('#mp-songImg').setAttribute('src', guild.musicPlayer.currentSong.thumbnail.replace('maxresdefault', 'mqdefault'));
let songSd = getSplitDuration(Date.now() - guild.dj.songStartTime); let songSd = getSplitDuration(Date.now() - guild.musicPlayer.songStartTime);
document.querySelector('#dj-songCurrentTS').innerText = `${songSd.minutes}:${songSd.seconds.toString().padStart(2, '0')}`; document.querySelector('#mp-songCurrentTS').innerText = `${songSd.minutes}:${songSd.seconds.toString().padStart(2, '0')}`;
document.querySelector('#dj-songCurrentTS').setAttribute('start-ts', guild.dj.songStartTime); document.querySelector('#mp-songCurrentTS').setAttribute('start-ts', guild.musicPlayer.songStartTime);
document.querySelector('#dj-queueCount').innerText = guild.dj.queueCount; document.querySelector('#mp-queueCount').innerText = guild.musicPlayer.queueCount;
let songContainer = document.querySelector('#dj-songQueue'); let songContainer = document.querySelector('#mp-songQueue');
$('.songEntry').remove(); $('.songEntry').remove();
for (let song of guild.dj.queue) { for (let song of guild.musicPlayer.queue) {
let songEntry = document.createElement('a'); let songEntry = document.createElement('a');
songEntry.setAttribute('href', song.url); songEntry.setAttribute('href', song.url);
songEntry.setAttribute('class', 'songEntry'); songEntry.setAttribute('class', 'songEntry');
@ -179,14 +179,14 @@ function queryGuildStatus(guildId) {
songEntry.appendChild(nameEntry); songEntry.appendChild(nameEntry);
songContainer.appendChild(songEntry); songContainer.appendChild(songEntry);
} }
document.querySelector('#dj-queueDisplayCount').innerText = document.querySelectorAll('.songEntry').length; document.querySelector('#mp-queueDisplayCount').innerText = document.querySelectorAll('.songEntry').length;
} else { } else {
if (songinfoContainer.is(':not(:hidden)')) if (songinfoContainer.is(':not(:hidden)'))
songinfoContainer.hide(); songinfoContainer.hide();
} }
} else { } else {
$('#dj-songinfo').hide(); $('#mp-songinfo').hide();
document.querySelector('#dj-voiceChannel').innerText = 'None'; document.querySelector('#mp-voiceChannel').innerText = 'None';
} }
}); });
} }
@ -302,7 +302,7 @@ function startUpdating() {
queryGuild(guildId); queryGuild(guildId);
}); });
setInterval(() => { setInterval(() => {
let songSd = getSplitDuration(Date.now() - $('#dj-songCurrentTS').attr('start-ts')); let songSd = getSplitDuration(Date.now() - $('#mp-songCurrentTS').attr('start-ts'));
document.querySelector('#dj-songCurrentTS').innerText = `${songSd.minutes}:${songSd.seconds.toString().padStart(2, '0')}`; document.querySelector('#mp-songCurrentTS').innerText = `${songSd.minutes}:${songSd.seconds.toString().padStart(2, '0')}`;
}, 500); }, 500);
} }

Loading…
Cancel
Save