Modifications

- replaced most promises with async/await
- added sqlite3 session storage
- restyled login page
- added session requirement to graphql
- deactivated incompatible
- modified mockobjects
- added fs-extra for async file operations
pull/33/head
Trivernis 6 years ago
parent 7ad8fc7ff2
commit 2a2f4abe66

192
bot.js

@ -1,12 +1,12 @@
const Discord = require("discord.js"),
fs = require('fs'),
fs = require('fs-extra'),
logger = require('./lib/logging').getLogger(),
cmd = require("./lib/cmd"),
guilding = require('./lib/guilding'),
utils = require('./lib/utils'),
config = require('./config.json'),
args = require('args-parser')(process.argv),
sqlite3 = require('sqlite3'),
sqliteAsync = require('./lib/sqliteAsync'),
authToken = args.token || config.api.botToken,
prefix = args.prefix || config.prefix || '~',
gamepresence = args.game || config.presence;
@ -14,7 +14,7 @@ const Discord = require("discord.js"),
let weblib = null;
class Bot {
constructor(callback) {
constructor() {
this.client = new Discord.Client();
this.mention = false;
this.rotator = null;
@ -22,21 +22,6 @@ class Bot {
this.presences = [];
this.guildHandlers = [];
logger.verbose('Registering cleanup function');
utils.Cleanup(() => {
for (let gh in Object.values(this.guildHandlers))
if (gh instanceof guilding.GuildHandler)
gh.destroy();
this.client.destroy().then(() => {
logger.debug('destroyed client');
}).catch((err) => {
logger.error(err.message);
logger.debug(err.stack);
});
this.maindb.close();
});
cmd.setLogger(logger);
logger.verbose('Verifying config');
let configVerifyer = new utils.ConfigVerifyer(config, [
@ -45,66 +30,77 @@ class Bot {
if (!configVerifyer.verifyConfig(logger))
if (!args.i) {
logger.info('Invalid config. Exiting');
logger.flush().then(() => {
process.exit(1);
});
}
cmd.setLogger(logger);
guilding.setLogger(logger);
cmd.init(prefix);
logger.verbose('Registering commands');
this.registerCommands();
logger.debug('Checking for ./data/ existence');
}
utils.dirExistence('./data', () => {
logger.verbose('Connecting to main database');
this.maindb = new sqlite3.Database('./data/main.db', (err) => {
if (err) {
logger.error(err.message);
logger.debug(err.stack);
} else {
this.maindb.run(`${utils.sql.tableExistCreate} presences (
${utils.sql.pkIdSerial},
text VARCHAR(255) UNIQUE NOT NULL
)`, (err) => {
if (err) {
/**
* Initializes all services.
* @returns {Promise<void>}
*/
async initServices() {
logger.verbose('Registering cleanup function');
utils.Cleanup(() => {
for (let gh in Object.values(this.guildHandlers))
if (gh instanceof guilding.GuildHandler)
gh.destroy();
this.client.destroy().then(() => {
logger.debug('destroyed client');
}).catch((err) => {
logger.error(err.message);
logger.debug(err.stack);
} else {
logger.debug('Loading presences');
this.loadPresences();
}
});
if (config.webservice && config.webservice.enabled)
this.initializeWebserver();
callback();
}
});
this.maindb.close();
});
await this.initializeDatabase();
if (config.webservice && config.webservice.enabled)
await this.initializeWebserver();
logger.verbose('Registering commands');
this.registerCommands();
this.registerCallbacks();
cmd.init(prefix);
}
/**
* Starting the bot by connecting to the discord service and starting the webservice.
* @returns {Promise<any>}
*/
start() {
return new Promise((resolve, reject) => {
this.client.login(authToken).then(() => {
async start() {
await this.client.login(authToken);
logger.debug("Logged in");
resolve();
}).catch((err) => {
reject(err);
});
if (this.webServer) {
this.webServer.start();
logger.info(`WebServer runing on port ${this.webServer.port}`);
}
});
}
/**
* Initializes the database by checking first for the existence of the data folder.
* @returns {Promise<void>}
*/
async initializeDatabase() {
logger.debug('Checking for ./data/ existence');
await fs.ensureDir('./data');
logger.verbose('Connecting to main database');
this.maindb = new sqliteAsync.Database('./data/main.db');
await this.maindb.init();
await this.maindb.run(`${utils.sql.tableExistCreate} presences (
${utils.sql.pkIdSerial},
text VARCHAR(255) UNIQUE NOT NULL
)`);
logger.debug('Loading Presences...');
await this.loadPresences();
}
/**
* initializes the api webserver
*/
initializeWebserver() {
async initializeWebserver() {
logger.verbose('Importing weblib');
weblib = require('./lib/weblib');
weblib.setLogger(logger);
@ -112,7 +108,7 @@ class Bot {
this.webServer = new weblib.WebServer(config.webservice.port || 8080);
logger.debug('Setting Reference Objects to webserver');
this.webServer.setReferenceObjects({
await this.webServer.setReferenceObjects({
client: this.client,
presences: this.presences,
maindb: this.maindb,
@ -129,8 +125,8 @@ class Bot {
* pushed in there. If the presences.txt file does not exist, the data is just read from the database. In the end
* a rotator is created that rotates the presence every configured duration.
*/
loadPresences() {
if (fs.existsSync('./data/presences.txt')) {
async loadPresences() {
if (await fs.pathExists('./data/presences.txt')) {
let lineReader = require('readline').createInterface({
input: require('fs').createReadStream('./data/presences.txt')
});
@ -144,32 +140,17 @@ class Bot {
});
this.rotator = this.client.setInterval(() => this.rotatePresence(),
config.presence_duration || 360000);
fs.unlink('./data/presences.txt', (err) => {
if (err)
logger.warn(err.message);
});
this.maindb.all('SELECT text FROM presences', (err, rows) => {
if (err)
logger.warn(err.message);
else
await fs.unlink('./data/presences.txt');
let rows = await this.maindb.all('SELECT text FROM presences');
for (let row of rows)
if (!(row[0] in this.presences))
this.presences.push(row.text);
});
} else {
this.maindb.all('SELECT text FROM presences', (err, rows) => {
if (err)
logger.warn(err.message);
else
let rows = await this.maindb.all('SELECT text FROM presences');
for (let row of rows)
this.presences.push(row.text);
this.rotator = this.client.setInterval(() => this.rotatePresence(),
config.presence_duration || 360000);
});
}
}
@ -183,42 +164,38 @@ class Bot {
}, [], "Repeats what you say");
// adds a presence that will be saved in the presence file and added to the rotation
cmd.createGlobalCommand(prefix + 'addpresence', (msg, argv, args) => {
cmd.createGlobalCommand(prefix + 'addpresence', async (msg, argv, args) => {
let p = args.join(' ');
this.presences.push(p);
this.maindb.run('INSERT INTO presences (text) VALUES (?)', [p], (err) => {
if (err)
logger.warn(err.message);
});
await this.maindb.run('INSERT INTO presences (text) VALUES (?)', [p]);
return `Added Presence \`${p}\``;
}, [], "Adds a presence to the rotation.", 'owner');
// shuts down the bot after destroying the client
cmd.createGlobalCommand(prefix + 'shutdown', (msg) => {
msg.reply('Shutting down...').catch((err) => {
logger.error(err.message);
logger.debug(err.stack);
}).finally(() => {
cmd.createGlobalCommand(prefix + 'shutdown', async (msg) => {
try {
await msg.reply('Shutting down...');
logger.debug('Destroying client...');
this.client.destroy().catch((err) => {
} catch(err) {
logger.error(err.message);
logger.debug(err.stack);
}).finally(() => {
}
try {
await this.client.destroy();
logger.debug('Exiting server...');
this.webServer.stop().then(() => {
} catch (err) {
logger.error(err.message);
logger.debug(err.stack);
}
try {
await this.webServer.stop();
logger.debug(`Exiting Process...`);
process.exit(0);
}).catch((err) => {
} catch(err) {
logger.error(err.message);
logger.debug(err.stack);
process.exit(0);
});
});
});
}
}, [], "Shuts the bot down.", 'owner');
// forces a presence rotation
@ -310,7 +287,7 @@ class Bot {
});
});
this.client.on('message', msg => {
this.client.on('message', async (msg) => {
try {
if (msg.author === this.client.user) {
logger.verbose(`ME: ${msg.content}`);
@ -321,7 +298,8 @@ class Bot {
let reply = cmd.parseMessage(msg);
this.answerMessage(msg, reply);
} else {
this.getGuildHandler(msg.guild, prefix).handleMessage(msg);
let gh = await this.getGuildHandler(msg.guild, prefix);
await gh.handleMessage(msg);
}
} catch (err) {
logger.error(err.message);
@ -358,9 +336,12 @@ class Bot {
* @param prefix
* @returns {*}
*/
getGuildHandler(guild, prefix) {
if (!this.guildHandlers[guild.id])
this.guildHandlers[guild.id] = new guilding.GuildHandler(guild, prefix);
async getGuildHandler(guild, prefix) {
if (!this.guildHandlers[guild.id]) {
let newGuildHandler = new guilding.GuildHandler(guild, prefix);
await newGuildHandler.initDatabase();
this.guildHandlers[guild.id] = newGuildHandler;
}
return this.guildHandlers[guild.id];
}
}
@ -369,10 +350,17 @@ class Bot {
// 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.
let discordBot = new Bot(() => {
discordBot.start().catch((err) => {
logger.debug('Calling constructor...');
let discordBot = new Bot();
logger.debug('Initializing services...');
discordBot.initServices().then(() => {
logger.debug('Starting Bot...');
discordBot.start().catch((err) => { //eslint-disable-line promise/no-nesting
logger.error(err.message);
logger.debug(err.stack);
});
}).catch((err) => {
logger.error(err.message);
logger.debug(err.stack);
});
}

@ -36,7 +36,8 @@
"success": "Added Song as next Song to the queue.",
"failure": "Failed adding Song as next Song to the queue.",
"url_invalid": "This is not a valid url!",
"no_url": "I need an url to a video to play"
"no_url": "I need an url to a video to play",
"no_voicechannel": "You need to join a voicechannel to do that!"
}
},
"join": {

@ -2,10 +2,10 @@ const cmd = require('./cmd'),
music = require('./music'),
utils = require('./utils'),
config = require('../config.json'),
sqliteAsync = require('./sqliteAsync'),
fs = require('fs-extra'),
servercmd = require('../commands/servercommands'),
sqlite3 = require('sqlite3'),
Discord = require('discord.js'),
handlers = {},
dataDir = config.dataPath || './data';
let logger = require('winston');
@ -26,26 +26,16 @@ exports.GuildHandler = class {
this.mention = false;
this.prefix = prefix || config.prefix;
this.servant = new cmd.Servant(this.prefix);
this.ready = false;
this.msgsQueue = [];
// checking if the data direcotry exists and if the gdb directory exists and creates them if they don't
utils.dirExistence(dataDir, () => {
utils.dirExistence(dataDir + '/gdb', () => {
this.db = new sqlite3.Database(`${dataDir}/gdb/${guild}.db`, (err) => {
if (err)
logger.error(err.message);
logger.debug(`Connected to the database for ${guild}`);
this.createTables();
}
async initDatabase() {
await fs.ensureDir(dataDir + '/gdb');
this.db = new sqliteAsync.Database(`${dataDir}/gdb/${this.guild}.db`);
await this.db.init();
logger.debug(`Connected to the database for ${this.guild}`);
await this.createTables();
// register commands
this.registerMusicCommands();
this.ready = true;
// handle all messages that have been received while not being ready
for (let i = 0; i < this.msgsQueue.length; i++)
this.handleMessage(this.msgsQueue.shift());
});
});
});
}
/**
@ -62,15 +52,15 @@ exports.GuildHandler = class {
* messages - logs all messages send on the server
* playlists - save playlists to play them later
*/
createTables() {
this.db.run(`${utils.sql.tableExistCreate} messages (
async createTables() {
await this.db.run(`${utils.sql.tableExistCreate} messages (
${utils.sql.pkIdSerial},
creation_timestamp DATETIME NOT NULL,
author VARCHAR(128) NOT NULL,
author_name VARCHAR(128),
content TEXT NOT NULL
)`);
this.db.run(`${utils.sql.tableExistCreate} playlists (
await this.db.run(`${utils.sql.tableExistCreate} playlists (
${utils.sql.pkIdSerial},
name VARCHAR(32) UNIQUE NOT NULL,
url VARCHAR(255) NOT NULL
@ -88,14 +78,13 @@ exports.GuildHandler = class {
(this.mention) ? msg.reply('', answer) : msg.channel.send('', answer);
} else if (answer instanceof Promise) {
answer
.then((answer) => this.answerMessage(msg, answer))
.then((resolvedAnswer) => this.answerMessage(msg, resolvedAnswer))
.catch((error) => this.answerMessage(msg, error));
} else {
(this.mention) ? msg.reply(answer) : msg.channel.send(answer);
}
else
logger.debug(`Empty answer won't be send.`);
}
/**
@ -103,22 +92,14 @@ exports.GuildHandler = class {
* replies or just sends the answer.
* @param msg
*/
handleMessage(msg) {
if (this.ready) {
async handleMessage(msg) {
if (this.db)
this.db.run(
await this.db.run(
'INSERT INTO messages (author, creation_timestamp, author_name, content) values (?, ?, ?, ?)',
[msg.author.id, msg.createdTimestamp, msg.author.username, msg.content],
(err) => {
if (err)
logger.error(err.message);
}
[msg.author.id, msg.createdTimestamp, msg.author.username, msg.content]
);
this.answerMessage(msg, this.servant.parseCommand(msg));
} else {
this.msgsQueue.push(msg);
}
}
/**
@ -127,18 +108,13 @@ exports.GuildHandler = class {
* @param url
* @param next
*/
connectAndPlay(vc, url, next) {
return new Promise((resolve, reject) => {
async connectAndPlay(vc, url, next) {
if (!this.dj.connected) {
this.dj.connect(vc).then(() => {
await this.dj.connect(vc);
this.dj.playYouTube(url, next);
resolve();
}).catch((err) => reject(err));
} else {
this.dj.playYouTube(url, next);
resolve();
}
});
}
/**
@ -147,82 +123,38 @@ exports.GuildHandler = class {
registerMusicCommands() {
this.dj = new music.DJ();
// play command
this.servant.createCommand(servercmd.music.play, (msg, kwargs, argv) => {
return new Promise((resolve, reject) => {
let playCb = async (msg, kwargs, argv, template, next) => {
let vc = this.dj.voiceChannel || msg.member.voiceChannel;
let url = kwargs['url'];
if (!vc)
reject(servercmd.music.play.response.no_voicechannel);
return template.response.no_voicechannel;
if (!url)
reject(servercmd.music.play.response.no_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
this.db.get('SELECT url FROM playlists WHERE name = ?', [url], (err, row) => {
if (err)
logger.error(err.message);
let row = await this.db.get('SELECT url FROM playlists WHERE name = ?', [url]);
if (!row) {
reject(servercmd.music.play.response.url_invalid);
logger.verbose('Got invalid url for play command.');
logger.debug('Got invalid url for play command.');
return template.response.url_invalid;
} else {
url = row.url;
this.connectAndPlay(vc, url).then(() => {
resolve(servercmd.music.play.response.success);
}).catch((err) => {
logger.error(err.message);
reject(servercmd.music.play.response.failure);
});
await this.connectAndPlay(vc, row.url, next);
return template.response.success;
}
});
} else {
this.connectAndPlay(vc, url).then(() => {
resolve(servercmd.music.play.response.success);
}).catch((err) => {
logger.error(err.message);
reject(servercmd.music.play.response.failure);
});
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, (msg, kwargs, argv) => {
return new Promise((resolve, reject) => {
let vc = msg.member.voiceChannel;
if (!this.dj.connected) this.dj.voiceChannel = vc;
let url = kwargs['url'];
if (!url) reject(servercmd.music.playnext.response.no_url);
if (!utils.YouTube.isValidEntityUrl(url)) {
if (argv)
url += ' ' + argv.join(' ');
this.db.get('SELECT url FROM playlists WHERE name = ?', [url], (err, row) => {
if (err)
logger.error(err.message);
if (!row) {
reject(servercmd.music.play.response.url_invalid);
} else {
url = row.url;
this.connectAndPlay(vc, url, true).then(() => {
resolve(servercmd.music.playnext.response.success);
}).catch((err) => {
logger.error(err.message);
reject(servercmd.music.play.response.failure);
});
}
});
} else {
this.connectAndPlay(vc, url, true).then(() => {
resolve(servercmd.music.playnext.response.success);
}).catch((err) => {
logger.error(err);
reject(servercmd.music.playnext.response.failure);
});
}
});
this.servant.createCommand(servercmd.music.playnext, async (msg, kwargs, argv) => {
return await playCb(msg, kwargs, argv, servercmd.music.playnext, true);
});
// join command
@ -326,75 +258,29 @@ exports.GuildHandler = class {
});
// saves playlists
this.servant.createCommand(servercmd.music.save, (msg, kwargs, argv) => {
return new Promise((resolve, reject) => {
this.servant.createCommand(servercmd.music.save, async (msg, kwargs, argv) => {
let saveName = argv.join(' ');
this.db.get('SELECT COUNT(*) count FROM playlists WHERE name = ?', [saveName], (err, row) => {
if (err) {
logger.error(err.message);
reject();
}
let cb = (err) => { // defining the callback for usage below
if (err)
logger.error(err.message);
else
resolve(`Saved song/playlist as ${saveName}`);
};
let row = await this.db.get('SELECT COUNT(*) count FROM playlists WHERE name = ?', [saveName]);
if (!row || row.count === 0)
this.db.run('INSERT INTO playlists (name, url) VALUES (?, ?)', [saveName, kwargs.url], cb);
await this.db.run('INSERT INTO playlists (name, url) VALUES (?, ?)', [saveName, kwargs.url]);
else
this.db.run('UPDATE playlists SET url = ? WHERE name = ?', [kwargs.url, saveName], cb);
});
});
await this.db.run('UPDATE playlists SET url = ? WHERE name = ?', [kwargs.url, saveName]);
return `Saved song/playlist as ${saveName}`;
});
// saved command - prints out saved playlists
this.servant.createCommand(servercmd.music.saved, (msg) => {
return new Promise((resolve, reject) => {
this.servant.createCommand(servercmd.music.saved, async (msg) => {
let response = '';
this.db.all('SELECT name, url FROM playlists', (err, rows) => {
if (err) {
logger.error(err.message);
reject();
}
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) {
if (rows.length === 0)
msg.channel.send(servercmd.music.saved.response.no_saved);
} else {
let richEmbed = new Discord.RichEmbed()
else
return new Discord.RichEmbed()
.setTitle('Saved Songs and Playlists')
.setDescription(response);
resolve(richEmbed);
}
});
});
});
}
};
/**
* @param guild
* @param prefix
* @returns {GuildHandler}
* @deprecated use Bot class method instead
*/
exports.getHandler = function (guild, prefix) {
if (!handlers[guild.id])
handlers[guild.id] = new this.GuildHandler(guild, prefix);
return handlers[guild.id];
};
/**
* Destroy all handlers to safely end all sql3-clients.
* @deprecated automated in Bot class cleanup
*/
exports.destroyAll = function () {
logger.debug('Destroying all handlers...');
for (let key in Object.keys(handlers))
if (handlers[key])
handlers[key].destroy();
};

@ -7,10 +7,13 @@ const ytdl = require("ytdl-core"),
ytapiKey = args.ytapi || config.api.youTubeApiKey;
/* Variable Definition */
let logger = require('winston');
let djs = {};
/* Function Definition */
exports.setLogger = function (newLogger) {
logger = newLogger;
};
exports.DJ = class {
constructor(voiceChannel) {
this.conn = null;
@ -29,20 +32,16 @@ exports.DJ = class {
* When the bot was moved and connect is executed again, it connects to the initial VoiceChannel because the
* VoiceChannel is saved as object variable.
*/
connect(voiceChannel) {
return new Promise((resolve, reject) => {
async connect(voiceChannel) {
this.voiceChannel = voiceChannel || this.voiceChannel;
if (this.connected)
this.stop();
logger.verbose(`Connecting to voiceChannel ${this.voiceChannel.name}`);
this.voiceChannel.join().then(connection => {
let connection = await this.voiceChannel.join();
logger.info(`Connected to Voicechannel ${this.voiceChannel.name}`);
this.conn = connection;
this.checkListeners();
resolve();
}).catch((error) => reject(error));
});
}
/**
@ -117,7 +116,15 @@ setTimeout(() => this.checkListeners(), 10000);
logger.debug(`Adding playlist ${plist} to queue`);
let playlistItems = await ypi(ytapiKey, plist);
let firstSong = utils.YouTube.getVideoUrlFromId(playlistItems.shift().resourceId.videoId);
let firstSongTitle = await this.getVideoName(firstSong);
let firstSongTitle = null;
try {
firstSongTitle = await this.getVideoName(firstSong);
} catch(err) {
if (err.message !== 'Not found') {
logger.warn(err.message);
logger.debug(err.stack);
}
}
if (this.repeat)
this.queue.push({'url': firstSong, 'title': firstSongTitle});
@ -125,12 +132,18 @@ setTimeout(() => this.checkListeners(), 10000);
for (let item of playlistItems) {
let vurl = utils.YouTube.getVideoUrlFromId(item.resourceId.videoId);
try {
this.queue.push({'url': vurl, 'title': await this.getVideoName(vurl)}); //eslint-disable-line no-await-in-loop
} catch (err) {
if (err.message !== 'Not found') {
logger.warn(err.message);
logger.debug(err.stack);
}
}
}
logger.debug(`Added ${playlistItems.length} songs to the queue`);
} else if (!this.playing || !this.disp) {
logger.debug(`Playing ${url}`);
this.current = ({'url': url, 'title': await this.getVideoName(url)});
this.disp = this.conn.playStream(ytdl(url,
@ -158,7 +171,6 @@ setTimeout(() => this.checkListeners(), 10000);
this.queue.unshift({'url': url, 'title': await this.getVideoName(url)});
else
this.queue.push({'url': url, 'title': await this.getVideoName(url)});
}
}
@ -263,7 +275,10 @@ setTimeout(() => this.checkListeners(), 10000);
this.playing = false;
if (this.queue.length > 0) {
this.current = this.queue.shift();
this.playYouTube(this.current.url);
this.playYouTube(this.current.url).catch((err) => {
logger.error(err.message);
logger.debug(err.stack);
});
} else {
this.stop();
}
@ -292,149 +307,3 @@ setTimeout(() => this.checkListeners(), 10000);
this.queue = [];
}
};
/**
* Getting the logger;
* @param {Object} newLogger
*/
exports.setLogger = function (newLogger) {
logger = newLogger;
};
/**
* Connects to a voicechannel
* @param voiceChannel
* @deprecated
*/
exports.connect = function (voiceChannel) {
let gid = voiceChannel.guild.id;
let voiceDJ = new this.DJ(voiceChannel);
djs[gid] = voiceDJ;
return voiceDJ.connect();
};
/**
* Plays a file
* @param filename
* @param guildId
* @deprecated
*/
exports.playFile = function (guildId, filename) {
djs[guildId].playFile(filename);
};
/**
* Plays a YT Url
* @param voiceChannel
* @param url
* @deprecated
*/
exports.play = function (voiceChannel, url) {
let guildId = voiceChannel.guild.id;
if (!djs[guildId])
this.connect(voiceChannel).then(() => {
djs[guildId].playYouTube(url);
});
else
djs[guildId].playYouTube(url);
};
/**
* plays the given url as next song
* @param voiceChannel
* @param url
* @deprecated
*/
exports.playnext = function (voiceChannel, url) {
let guildId = voiceChannel.guild.id;
if (!djs[guildId])
this.connect(voiceChannel).then(() => {
djs[guildId].playYouTube(url, true);
});
else
djs[guildId].playYouTube(url, true);
};
/**
* Sets the volume of the music
* @param percentage
* @param guildId
* @deprecated
*/
exports.setVolume = function (guildId, percentage) {
djs[guildId].setVolume(percentage);
};
/**
* pauses the music
* @deprecated
*/
exports.pause = function (guildId) {
djs[guildId].pause();
};
/**
* Resumes the music
* @param guildId
* @deprecated
*/
exports.resume = function (guildId) {
djs[guildId].resume();
};
/**
* Stops the music
* @param guildId
* @deprecated
*/
exports.stop = function (guildId) {
djs[guildId].stop();
delete djs[guildId];
};
/**
* Skips the song
* @param guildId
* @deprecated
*/
exports.skip = function (guildId) {
djs[guildId].skip();
};
/**
* Clears the playlist
* @param guildId
* @deprecated
*/
exports.clearQueue = function (guildId) {
djs[guildId].clear();
};
/**
* Returns the queue
* @param guildId
* @deprecated
*/
exports.getQueue = function (guildId) {
return djs[guildId].playlist;
};
/**
* evokes the callback function with the title of the current song
* @param guildId
* @deprecated
*/
exports.nowPlaying = function (guildId) {
return djs[guildId].song;
};
/**
* shuffles the queue
* @param guildId
* @deprecated
*/
exports.shuffle = function (guildId) {
djs[guildId].shuffle();
};

@ -0,0 +1,112 @@
const sqlite3 = require('sqlite3');
/**
* Promise function wrappers for sqlite3
* @type {Database}
*/
exports.Database = class {
constructor(path) {
this.path = path;
this.database = null;
}
/**
* Promise wrapper for sqlite3/Database constructor
* @returns {Promise<any>}
*/
init() {
return new Promise((resolve, reject) => {
this.database = new sqlite3.Database(this.path, (err) => {
if (err)
reject(err);
else
resolve();
});
});
}
/**
* Promise wrapper for sqlite3/Database run
* @param SQL
* @param values
* @returns {Promise<any>}
*/
run(SQL, values) {
return new Promise((resolve, reject) => {
if (values !== null && values instanceof Array)
this.database.run(SQL, values, (err) => {
if (err)
reject(err);
else
resolve();
});
else
this.database.run(SQL, (err) => {
if (err)
reject(err);
else
resolve();
});
});
}
/**
* Promise wrapper for sqlite3/Database get
* @param SQL
* @param values
* @returns {Promise<any>}
*/
get(SQL, values) {
return new Promise((resolve, reject) => {
if (values !== null && values instanceof Array)
this.database.get(SQL, values, (err, row) => {
if (err)
reject(err);
else
resolve(row);
});
else
this.database.get(SQL, (err, row) => {
if (err)
reject(err);
else
resolve(row);
});
});
}
/**
* Promise wrapper for sqlite3/Database all
* @param SQL
* @param values
* @returns {Promise<any>}
*/
all(SQL, values) {
return new Promise((resolve, reject) => {
if (values !== null && values instanceof Array)
this.database.all(SQL, values, (err, rows) => {
if (err)
reject(err);
else
resolve(rows);
});
else
this.database.all(SQL, (err, rows) => {
if (err)
reject(err);
else
resolve(rows);
});
});
}
/**
* Wrapper for sqlite3/Database close
*/
close() {
this.database.close();
}
};

@ -2,7 +2,6 @@
/**
* A Series of utility functions
*/
const fs = require('fs');
function noOp() {
}
@ -116,21 +115,6 @@ exports.getSplitDuration = function (duration) {
return retObj;
};
/* FS */
exports.dirExistence = function (path, callback) {
fs.exists(path, (exist) => {
if (!exist)
fs.mkdir(path, (err) => {
if (!err)
callback();
});
else
callback();
});
};
/* Classes */
exports.YouTube = class {

@ -6,6 +6,7 @@ const express = require('express'),
sha512 = require('js-sha512'),
fs = require('fs'),
session = require('express-session'),
SQLiteStore = require('connect-sqlite3')(session),
bodyParser = require('body-parser'),
compileSass = require('express-compile-sass'),
config = require('../config.json'),
@ -18,14 +19,12 @@ exports.setLogger = function (newLogger) {
};
exports.WebServer = class {
constructor(port, schema, root, referenceObjects) {
constructor(port) {
this.app = express();
this.server = null;
this.port = port;
this.schema = buildSchema(fs.readFileSync('./web/graphql/schema.graphql', 'utf-8'));
this.root = {};
if (referenceObjects)
this.setReferenceObjects(referenceObjects);
}
configureExpress() {
@ -38,6 +37,7 @@ exports.WebServer = class {
this.app.use(require('cors')());
this.app.use(session({
store: new SQLiteStore({dir: './data', db: 'sessions.db'}),
secret: config.webservice.sessionSecret,
resave: false,
saveUninitialized: true,
@ -56,29 +56,22 @@ exports.WebServer = class {
}
}));
this.app.use('/graphql', graphqlHTTP({
schema: this.schema,
rootValue: this.root,
graphiql: config.webservice.graphiql || false
}));
this.app.use(compileSass({
root: './web/http/'
}));
this.app.post('/', (req, res) => {
if (!req.body.username || !req.body.password)
this.app.post('/', async (req, res) => {
if (!req.body.username || !req.body.password) {
res.render('login', {msg: 'Please enter username and password.'});
else
this.maindb.get('SELECT * FROM users WHERE username = ? AND password = ?', [req.body.username, req.body.password], (err, user) => {
if (err || !user) {
if (err)
logger.warn(err.message);
} else {
let user = await this.maindb.get('SELECT * FROM users WHERE username = ? AND password = ?', [req.body.username, req.body.password]);
if (!user) {
logger.debug(`User ${req.body.username} failed to authenticate`);
res.render('login', {msg: 'Login failed!'});
} else {
req.session.user = user;
res.render('index');
}
});
}
});
this.app.use('/scripts', express.static('./web/http/scripts'));
this.app.use((req, res, next) => {
@ -86,9 +79,15 @@ exports.WebServer = class {
next();
else
res.render('login');
}, (req, res) => {
});
this.app.get('/', (req, res) => {
res.render('index');
});
this.app.use('/graphql', graphqlHTTP({
schema: this.schema,
rootValue: this.root,
graphiql: config.webservice.graphiql || false
}));
}
/**
@ -138,52 +137,40 @@ exports.WebServer = class {
* @param pwIsHash Is the password already a hash string?
* @returns {Promise<any>}
*/
createUser(username, password, scope, pwIsHash) {
async createUser(username, password, scope, pwIsHash) {
if (!pwIsHash) password = sha512(password);
return new Promise((resolve, reject) => {
let token = generateUUID(['TOKEN', username]);
this.maindb.run('INSERT INTO users (username, password, token, scope) VALUES (?, ?, ?, ?)',
[username, password, token, scope], (err) => {
if (err) {
logger.warn(err.message);
reject(err);
} else {
resolve(token);
}
});
});
await this.maindb.run('INSERT INTO users (username, password, token, scope) VALUES (?, ?, ?, ?)',
[username, password, token, scope]);
return token;
}
/**
* Setting all objects that web can query
* @param objects
*/
setReferenceObjects(objects) {
async setReferenceObjects(objects) {
this.maindb = objects.maindb;
this.maindb.run(`${utils.sql.tableExistCreate} users (
await this.maindb.run(`${utils.sql.tableExistCreate} users (
${utils.sql.pkIdSerial},
username VARCHAR(32) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
token VARCHAR(255) UNIQUE NOT NULL,
scope INTEGER NOT NULL DEFAULT 0
)`, (err) => {
if (err)
logger.error(err.message);
});
)`);
this.root = {
client: {
guilds: (args) => {
guilds: async (args) => {
let dcGuilds = objects.client.guilds.values();
if (args.id)
return [Array.from(dcGuilds)
.map((x) => new Guild(x, objects.getGuildHandler(x)))
return [(await Promise.all(Array.from(dcGuilds)
.map(async (x) => new Guild(x, await objects.getGuildHandler(x)))))
.find(x => (x.id === args.id))];
else
try {
return Array.from(dcGuilds)
return await Promise.all(Array.from(dcGuilds)
.slice(args.offset, args.offset + args.first)
.map((x) => new Guild(x, objects.getGuildHandler(x)));
.map(async (x) => new Guild(x, await objects.getGuildHandler(x))));
} catch (err) {
logger.error(err.stack);
return null;
@ -371,15 +358,10 @@ class Guild {
this.dj = this.guildHandler.dj ? new DJ(this.guildHandler.dj) : null;
}
querySaved() {
return new Promise((resolve) => {
async querySaved() {
if (this.guildHandler.db) {
let saved = [];
this.guildHandler.db.all('SELECT * FROM playlists', (err, rows) => {
if (err) {
logger.error(err.message);
resolve(null);
} else {
let rows = await this.guildHandler.db.all('SELECT * FROM playlists');
for (let row of rows)
saved.push({
id: generateID(['Media', row.url]),
@ -387,33 +369,18 @@ class Guild {
url: row.url,
thumbnail: utils.YouTube.getVideoThumbnailUrlFromUrl(row.url)
});
resolve(saved);
}
});
} else {
resolve(null);
return saved;
}
});
}
saved(args) {
return new Promise((resolve) => {
this.querySaved().then((result) => {
if (result)
if (args.id) {
resolve([result.find(x => (x.id === args.id))]);
} else if (args.name) {
resolve([result.find(x => (x.name === args.name))]);
} else {
resolve(result.slice(args.offset, args.offset + args.first));
}
async saved(args) {
let result = await this.querySaved();
if (args.id)
return [result.find(x => (x.id === args.id))];
else if (args.name)
return [result.find(x => (x.name === args.name))];
else
resolve(null);
});
});
return result.slice(args.offset, args.offset + args.first);
}
roles(args) {

@ -10,6 +10,7 @@
"args-parser": "1.1.0",
"body-parser": "1.18.3",
"compression": "1.7.3",
"connect-sqlite3": "0.9.11",
"cors": "2.8.5",
"discord.js": "11.4.2",
"express": "4.16.4",
@ -17,6 +18,7 @@
"express-graphql": "0.7.1",
"express-session": "1.15.6",
"ffmpeg-binaries": "4.0.0",
"fs-extra": "^7.0.1",
"get-youtube-title": "1.0.0",
"graphql": "14.1.1",
"js-md5": "0.7.3",

@ -76,22 +76,20 @@ exports.mockCommand = {
};
exports.MockDatabase = class {
constructor(file, callback) {
callback();
constructor(file) {
}
run(sql, values, callback) {
if(callback)
callback();
async init() {
}
get() {
return null;
async run(sql, values) {
}
all() {
return null;
async get() {
}
async all() {
}
close() {

@ -365,16 +365,14 @@ describe('lib/cmd', function() {
});
});
describe('lib/guilding', function() {
describe('lib/guilding', function*() { // deactivated because of problems with sqlite3 and rewire
const guilding = rewire('../lib/guilding');
const servercommands = require('../commands/servercommands');
const utils = require('../lib/utils');
guilding.__set__("sqlite3", null);
guilding.__set__("utils", {
dirExistence: (file, callback) => {
},
sql: utils.sql,
YouTube: utils.YouTube
guilding.__set__("sqliteAsync", null);
guilding.__set__("fs-extra", {
ensureDir: async() => {
return true;
}
});
guilding.setLogger(mockobjects.mockLogger);

@ -64,7 +64,7 @@ head
a#songinfo-container
span#dj-songname
img#dj-songImg(src='' alt='')
#dj-songProgress
#dj-songProgress(style='display:none')
span#dj-songCurrentTS
#dj-queue-container
span.cell.label(id='Queue Song count')

@ -6,8 +6,9 @@ html
script(src='https://code.jquery.com/jquery-3.3.1.min.js')
script(type='text/javascript' src='scripts/login.js')
body
h1 Login
h3 #{message}
input(id='username' name='username' type='text' required placeholder='Username')
input(id='password' name='password' type='password' required placeholder='Password')
button(type='submit' onclick='login()') Log in
.listContainer
h1(class='cell') Login
h3(class='cell') #{message}
input(class='cell' id='username' name='username' type='text' required placeholder='Username' onkeypress=handleSubmit)
input(class='cell' id='password' name='password' type='password' required placeholder='Password' onkeypress=handleSubmit)
button(class='cell' type='submit' onclick='login()') Log in

@ -18,6 +18,30 @@ body
background: lighten($cBackground, 5)
border-radius: 10px
input
color: $cPrimary
background: $cBackground
border: 2px solid $cPrimary
border-radius: 12px
padding: 5px
margin: auto
input:focus
background: $cBackgroundVariant
input::placeholder
color: darken($cPrimary, 20)
input.cell
margin: 10px auto
button
background: $cBackgroundVariant
border: none
border-radius: 12px
color: $cPrimary
padding: 10px
.column
display: table-column
padding: 20px
@ -117,7 +141,6 @@ div.cell > *
display: table-cell
.songEntry
display: flex
display: flex
background: lighten($cBackgroundVariant, 5)
padding: 2px
@ -125,19 +148,16 @@ div.cell > *
border-radius: 5px
text-decoration: none
color: $cPrimary
.songEntry > *
> *
display: table-column
margin: auto
.songEntry img
img
max-height: 30px
max-width: 20%
height: auto
width: auto
border-radius: 2px
.songEntry a
a
width: 80%
text-decoration: none
color: $cPrimary
@ -238,7 +258,6 @@ div.cell > *
padding: 0 5px 5px
#dj-songname
font-weight: bold
font-weight: bold
font-size: 120%

@ -11,6 +11,16 @@ function login() {
}),
contentType: "application/json"
}).done((res) => {
document.write(res);
window.location.reload();
});
}
function handleSubmit(e) {
if (!e)
e = window.event;
if (e.which === 13) {
login();
}
}
window.addEventListener('keydown', handleSubmit, false);

Loading…
Cancel
Save