Merge branch 'develop' into renovate/winston-3.x

pull/28/head
Trivernis 6 years ago committed by GitHub
commit 3bc3557796
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -30,6 +30,10 @@ jobs:
name: Installing dependencies name: Installing dependencies
command: npm install command: npm install
- run:
name: Installing dependencies
command: npm install --save-dev
- run: - run:
name: installing additional dependencies name: installing additional dependencies
command: npm install sqlite3 command: npm install sqlite3
@ -41,17 +45,9 @@ jobs:
- run: - run:
name: Creating config file name: Creating config file
command: echo {} >> config.json command: echo {\"api\":{}} >> config.json
# run tests! # run tests!
- run: - run:
name: Testing ./lib/music name: Unit testing
command: node ./testscripts/musicTest.js command: npm test
- run:
name: Testing ./lib/cmd
command: node ./testscripts/cmdTest.js
- run:
name: Testing ./lib/guilding
command: node ./testscripts/guildingTest.js

1
.gitignore vendored

@ -1,5 +1,6 @@
.log .log
.idea .idea
.nyc_output
data data
package-lock.json package-lock.json
node_modules node_modules

@ -3,19 +3,21 @@ discordbot [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blu
A bot that does the discord thing. A bot that does the discord thing.
`node bot.js [--token=<DiscordBotToken>] [--ytapi=<GoogleApiKey>] [--owner=<DiscordTag>] [--prefix=<Char>] [--game=<String>]` `node bot.js [--token=<DiscordBotToken>] [--ytapi=<GoogleApiKey>] [--owner=<DiscordTag>] [--prefix=<Char>] [--game=<String>] [-i=<Boolen>]`
The arguments are optional because the token and youtube-api-key that the bot needs to run can also be defined in the config.json in the bot's directory: The arguments are optional because the token and youtube-api-key that the bot needs to run can also be defined in the config.json in the bot's directory:
```json5 ```json5
// config.json // config.json
{ {
"prefix": "_", "prefix": "_",
"token": "DISCORD BOT TOKEN", "presence": "STRING", // this will be shown when no presences are set in data/presences.txt
"ytapikey": "YOUTUBE API KEY", "presence_duration": 300000, // how long does the bot have one presence
"presence": "THE DEFAULT GAME IF NO presences.txt IS FOUND IN ./data/", "api": {
"presence_duration": 300000, "botToken": "YOUR DISCORD BOT TOKEN",
"youTubeApiKey": "YOUR YOUTUBE API KEY"
},
"owners": [ "owners": [
"SPECIFY A LIST OF BOT-OWNERS" "DISCORD NAME" // specify a list of bot owners that can use the owner commands
], ],
"music": { "music": {
"timeout": 300000 "timeout": 300000
@ -23,6 +25,17 @@ The arguments are optional because the token and youtube-api-key that the bot ne
} }
``` ```
If the keys are missing from the config file, the bot exits. This behaviour can be deactivated by setting the `-i` commandline flag.
Keys
---
You can get the API-Keys here:
[Discord Bot Token](https://discordapp.com/developers)
[YouTube API Key](https://console.developers.google.com)
Features Features
--- ---
@ -32,6 +45,11 @@ At the moment the bot can...
- [x] ...log stuff in a database - [x] ...log stuff in a database
- [ ] ...transform into a cow - [ ] ...transform into a cow
Presences
---
You can add presences to the bot either by owner command `addpresence` or by providing a presences.txt file in the data directory. Each line represents a presence. <p style='color: f00'> When all lines are loaded by the bot, the file gets deleted.</p>
Ideas Ideas
--- ---
- command replies saved in file (server specific file and global file) - command replies saved in file (server specific file and global file)

117
bot.js

@ -7,36 +7,59 @@ const Discord = require("discord.js"),
config = require('./config.json'), config = require('./config.json'),
client = new Discord.Client(), client = new Discord.Client(),
args = require('args-parser')(process.argv), args = require('args-parser')(process.argv),
authToken = args.token || config.token, sqlite3 = require('sqlite3'),
prefix = args.prefix || config.prefix, authToken = args.token || config.api.botToken,
prefix = args.prefix || config.prefix || '~',
gamepresence = args.game || config.presence; gamepresence = args.game || config.presence;
let presences = [], // loaded from presences.txt file if the file exists let presences = [], // loaded from presences.txt file if the file exists
rotator = null; // an interval id to stop presence duration if needed rotator = null, // an interval id to stop presence duration if needed
maindb = null;
function main() { function main() {
logger.verbose('Registering cleanup function');
utils.Cleanup(() => { utils.Cleanup(() => {
guilding.destroyAll(); guilding.destroyAll();
client.destroy(); client.destroy();
}); });
cmd.setLogger(logger); cmd.setLogger(logger);
logger.verbose('Verifying config');
let configVerifyer = new utils.ConfigVerifyer(config, [
"api.botToken", "api.youTubeApiKey"
]);
if (!configVerifyer.verifyConfig(logger)) {
if (!args.i) {
logger.info('Invalid config. Exiting');
process.exit(1);
}
}
guilding.setLogger(logger); guilding.setLogger(logger);
cmd.init(prefix); cmd.init(prefix);
logger.verbose('Registering commands');
registerCommands(); registerCommands();
logger.debug('Checking for ./data/ existence')
utils.dirExistence('./data', () => { utils.dirExistence('./data', () => {
fs.exists('./data/presences.txt', (exist) => { logger.verbose('Connecting to main database');
if (exist) { maindb = new sqlite3.Database('./data/main.db', (err) => {
logger.debug('Loading presences from file...'); if (err) {
let lineReader = require('readline').createInterface({ logger.error(err.message);
input: require('fs').createReadStream('./data/presences.txt') } else {
}); maindb.run(`${utils.sql.tableExistCreate} presences (
lineReader.on('line', (line) => { ${utils.sql.pkIdSerial},
presences.push(line); text VARCHAR(255) UNIQUE NOT NULL
)`, (err) => {
if (err) {
logger.error(err.message);
} else {
logger.debug('Loading presences');
loadPresences();
}
}); });
rotator = client.setInterval(() => rotatePresence(), config.presence_duration);
} }
}) });
}); });
registerCallbacks(); registerCallbacks();
@ -45,6 +68,55 @@ function main() {
}); });
} }
/**
* If a data/presences.txt exists, it is read and each line is put into the presences array.
* Each line is also stored in the main.db database. After the file is completely read, it get's deleted.
* Then the data is read from the database and if the presence doesn't exist in the presences array, it get's
* 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.
*/
function loadPresences() {
if(fs.existsSync('./data/presences.txt')) {
let lineReader = require('readline').createInterface({
input: require('fs').createReadStream('./data/presences.txt')
});
lineReader.on('line', (line) => {
maindb.run('INSERT INTO presences (text) VALUES (?)', [line], (err) => {
if(err) {
logger.warn(err.message);
}
});
presences.push(line);
});
rotator = client.setInterval(() => rotatePresence(), config.presence_duration || 360000);
fs.unlink('./data/presences.txt', (err) => {
if (err)
logger.warn(err.message);
});
maindb.all('SELECT text FROM presences', (err, rows) => {
if (err) {
logger.warn(err.message);
} else {
for(let row of rows) {
if (!row[0] in presences)
presences.push(row.text);
}
}
})
} else {
maindb.all('SELECT text FROM presences', (err, rows) => {
if (err) {
logger.warn(err.message);
} else {
for(let row of rows) {
presences.push(row.text);
}
}
rotator = client.setInterval(() => rotatePresence(), config.presence_duration || 360000);
})
}
}
/** /**
* registeres global commands * registeres global commands
*/ */
@ -58,15 +130,20 @@ function registerCommands() {
cmd.createGlobalCommand(prefix + 'addpresence', (msg, argv, args) => { cmd.createGlobalCommand(prefix + 'addpresence', (msg, argv, args) => {
let p = args.join(' '); let p = args.join(' ');
presences.push(p); presences.push(p);
fs.writeFile('./data/presences.txt', presences.join('\n'), (err) => {
maindb.run('INSERT INTO presences (text) VALUES (?)', [p], (err) => {
if (err)
logger.warn(err.message);
}); });
return `Added Presence \`${p}\``; return `Added Presence \`${p}\``;
}, [], "Adds a presence to the rotation.", 'owner'); }, [], "Adds a presence to the rotation.", 'owner');
// shuts down the bot after destroying the client // shuts down the bot after destroying the client
cmd.createGlobalCommand(prefix + 'shutdown', (msg) => { cmd.createGlobalCommand(prefix + 'shutdown', (msg) => {
msg.reply('Shutting down...').finally(() => { msg.reply('Shutting down...').finally(() => {
logger.debug('Destroying client...'); logger.debug('Destroying client...');
client.destroy().finally(() => { client.destroy().finally(() => {
logger.debug(`Exiting Process...`); logger.debug(`Exiting Process...`);
process.exit(0); process.exit(0);
@ -92,7 +169,14 @@ function registerCommands() {
// returns the time the bot is running // returns the time the bot is running
cmd.createGlobalCommand(prefix + 'uptime', () => { cmd.createGlobalCommand(prefix + 'uptime', () => {
return `Uptime: \`${client.uptime / 1000} s\`` let uptime = utils.getSplitDuration(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 uptime of the bot', 'owner'); }, [], 'Returns the uptime of the bot', 'owner');
// returns the numbe of guilds, the bot has joined // returns the numbe of guilds, the bot has joined
@ -101,6 +185,9 @@ function registerCommands() {
}, [], 'Returns the number of guilds the bot has joined', 'owner'); }, [], 'Returns the number of guilds the bot has joined', 'owner');
} }
/**
* changes the presence of the bot by using one stored in the presences array
*/
function rotatePresence() { function rotatePresence() {
let pr = presences.shift(); let pr = presences.shift();
presences.push(pr); presences.push(pr);

@ -1,6 +1,6 @@
const cmd = require('./cmd'), const cmd = require('./cmd'),
music = require('./music'), music = require('./music'),
utils = require('./utils.js'), utils = require('./utils'),
config = require('../config.json'), config = require('../config.json'),
servercmd = require('../commands/servercommands'), servercmd = require('../commands/servercommands'),
sqlite3 = require('sqlite3'), sqlite3 = require('sqlite3'),
@ -12,6 +12,7 @@ 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);
}; };
/** /**
@ -59,17 +60,15 @@ exports.GuildHandler = class {
* playlists - save playlists to play them later * playlists - save playlists to play them later
*/ */
createTables() { createTables() {
let createCmd = 'CREATE TABLE IF NOT EXISTS'; this.db.run(`${utils.sql.tableExistCreate} messages (
let autoIdPK = 'id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL'; ${utils.sql.pkIdSerial},
this.db.run(`${createCmd} messages (
${autoIdPK},
creation_timestamp DATETIME NOT NULL, creation_timestamp DATETIME NOT NULL,
author VARCHAR(128) NOT NULL, author VARCHAR(128) NOT NULL,
author_name VARCHAR(128), author_name VARCHAR(128),
content TEXT NOT NULL content TEXT NOT NULL
)`); )`);
this.db.run(`${createCmd} playlists ( this.db.run(`${utils.sql.tableExistCreate} playlists (
${autoIdPK}, ${utils.sql.pkIdSerial},
name VARCHAR(32) UNIQUE NOT NULL, name VARCHAR(32) UNIQUE NOT NULL,
url VARCHAR(255) NOT NULL url VARCHAR(255) NOT NULL
)`); )`);
@ -264,14 +263,15 @@ exports.GuildHandler = class {
// playlist command // playlist command
this.servant.createCommand(servercmd.music.playlist, () => { this.servant.createCommand(servercmd.music.playlist, () => {
let songs = this.dj.playlist; logger.debug(`found ${this.dj.queue.length} songs`);
logger.debug(`found ${songs.length} songs`); let describtion = '';
let songlist = `**${songs.length} Songs in playlist**\n`; for (let i = 0; i < Math.min(this.dj.queue.length, 9); i++) {
for (let i = 0; i < songs.length; i++) { let entry = this.dj.queue[i];
if (i > 10) break; describtion += `[${entry.title}](${entry.url})\n`;
songlist += songs[i] + '\n';
} }
return songlist; return new Discord.RichEmbed()
.setTitle(`${this.dj.queue.length} songs in queue`)
.setDescription(describtion);
}); });
// np command // np command

@ -5,7 +5,7 @@ const Discord = require("discord.js"),
args = require('args-parser')(process.argv), 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.ytapikey; ytapiKey = args.ytapi || config.api.youTubeApiKey;
/* Variable Definition */ /* Variable Definition */
let logger = require('winston'); let logger = require('winston');
let djs = {}; let djs = {};
@ -276,18 +276,6 @@ exports.DJ = class {
} }
} }
/**
* Returns the title for each song saved in the queue
* @returns {Array}
*/
get playlist() {
let songs = [];
this.queue.forEach((entry) => {
songs.push(entry.title);
});
return songs;
}
/** /**
* Returns the song saved in the private variable 'current' * Returns the song saved in the private variable 'current'
* @returns {null|*} * @returns {null|*}

@ -15,17 +15,38 @@ let sysData = {};
* @return {String} A string that represents the file-extension. * @return {String} A string that represents the file-extension.
*/ */
exports.getExtension = function (filename) { exports.getExtension = function (filename) {
if (!filename) return null; if (!filename)
return null;
try { try {
let exts = filename.match(/\.[a-z]+/g); // get the extension by using regex let exts = filename.match(/\.\w+/g); // get the extension by using regex
if (exts) return exts[exts.length - 1]; // return the found extension if (exts)
else return null; // return null if no extension could be found return exts.pop(); // return the found extension
else
return null; // return null if no extension could be found
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return null; return null;
} }
}; };
/**
* Walks the path to the objects attribute and returns the value.
* @param object
* @param attributePath
* @returns {undefined/Object}
*/
exports.objectDeepFind = function (object, attributePath) {
let current = object,
paths = attributePath.split('.');
for (let path of paths) {
if (current[path] !== undefined && current[path] !== null)
current = current[path];
else
return undefined;
}
return current;
};
/** /**
* lets you define a cleanup for your program exit * lets you define a cleanup for your program exit
* @param {Function} callback the cleanup function * @param {Function} callback the cleanup function
@ -58,6 +79,21 @@ exports.Cleanup = function Cleanup(callback) {
}); });
}; };
exports.getSplitDuration = function (duration) {
let dur = duration;
let retObj = {};
retObj.milliseconds = dur % 1000;
dur = Math.round(dur / 1000);
retObj.seconds = dur % 60;
dur = Math.round(dur / 60);
retObj.minutes = dur % 60;
dur = Math.round(dur / 60);
retObj.hours = dur % 24;
dur = Math.round(dur / 24);
retObj.days = dur;
return retObj;
};
/* FS */ /* FS */
exports.dirExistence = function (path, callback) { exports.dirExistence = function (path, callback) {
@ -73,6 +109,8 @@ exports.dirExistence = function (path, callback) {
}) })
}; };
/* Classes */
exports.YouTube = class { exports.YouTube = class {
/** /**
* returns if an url is a valid youtube url (without checking for an entity id) * returns if an url is a valid youtube url (without checking for an entity id)
@ -80,8 +118,8 @@ exports.YouTube = class {
* @returns {boolean} * @returns {boolean}
*/ */
static isValidUrl(url) { static isValidUrl(url) {
return /https?:\/\/www.youtube.com\/(watch\?v=|playlist\?list=)/g.test(url) || return /https?:\/\/(www\.)?youtube\.com\/(watch\?v=|playlist\?list=)/g.test(url) ||
/https?:\/\/youtu.be\//g.test(url); /https?:\/\/youtu\.be\//g.test(url);
} }
/** /**
@ -90,8 +128,8 @@ exports.YouTube = class {
* @returns {boolean} * @returns {boolean}
*/ */
static isValidEntityUrl(url) { static isValidEntityUrl(url) {
return /https?:\/\/www.youtube.com\/(watch\?v=.+?|playlist\?list=.+?)/g.test(url) || return /https?:\/\/(www\.)?youtube\.com\/(watch\?v=.+?|playlist\?list=.+?)/g.test(url) ||
/https?:\/\/youtu.be\/.+?/g.test(url); /https?:\/\/youtu\.be\/.+?/g.test(url);
} }
/** /**
@ -100,7 +138,7 @@ exports.YouTube = class {
* @returns {boolean} * @returns {boolean}
*/ */
static isValidPlaylistUrl(url) { static isValidPlaylistUrl(url) {
return /https?:\/\/www.youtube.com\/playlist\?list=.+?/g.test(url); return /https?:\/\/(www\.)?youtube\.com\/playlist\?list=.+?/g.test(url);
} }
/** /**
@ -109,15 +147,17 @@ exports.YouTube = class {
* @returns {boolean} * @returns {boolean}
*/ */
static isValidVideoUrl(url) { static isValidVideoUrl(url) {
return /https?:\/\/www.youtube.com\/watch\?v=.+?/g.test(url) || /https?:\/\/youtu.be\/.+?/g.test(url); return /https?:\/\/(www\.)?youtube\.com\/watch\?v=.+?/g.test(url) || /https?:\/\/youtu\.be\/.+?/g.test(url);
} }
/** /**
* Returns the id for a youtube video stripped from the url * Returns the id for a youtube video stripped from the url
* @param url * @param url
* @returns {RegExpMatchArray} * @returns {String}
*/ */
static getPlaylistIdFromUrl(url) { static getPlaylistIdFromUrl(url) {
if (!exports.YouTube.isValidPlaylistUrl(url))
return null;
let matches = url.match(/(?<=\?list=)[\w\-]+/); let matches = url.match(/(?<=\?list=)[\w\-]+/);
if (matches) if (matches)
return matches[0]; return matches[0];
@ -128,14 +168,22 @@ exports.YouTube = class {
/** /**
* Returns the id for a youtube video stripped from the url * Returns the id for a youtube video stripped from the url
* @param url * @param url
* @return {String}
*/ */
static getVideoIdFromUrl(url) { static getVideoIdFromUrl(url) {
let matches = url.match(/(?<=\?v=)[\w\-]+/); if (!exports.YouTube.isValidVideoUrl(url))
if (matches) return null;
return matches[0]; let matches1 = url.match(/(?<=\?v=)[\w\-]+/);
if (matches1)
return matches1[0];
else {
let matches2 = url.match(/(?<=youtu\.be\/)[\w\-]+/);
if (matches2)
return matches2[0];
else else
return null; return null;
} }
}
/** /**
* Returns the youtube video url for a video id by string concatenation * Returns the youtube video url for a video id by string concatenation
@ -154,3 +202,43 @@ exports.YouTube = class {
return `https://i3.ytimg.com/vi/${exports.YouTube.getVideoIdFromUrl(url)}/maxresdefault.jpg` return `https://i3.ytimg.com/vi/${exports.YouTube.getVideoIdFromUrl(url)}/maxresdefault.jpg`
} }
}; };
exports.ConfigVerifyer = class {
/**
* @param confObj
* @param required {Array} the attributes that are required for the bot to work
*/
constructor(confObj, required) {
this.config = confObj;
this.requiredAttributes = required;
}
/**
* @param logger set the logger to log to
*/
verifyConfig(logger) {
let missing = [];
for (let reqAttr of this.requiredAttributes) {
if (exports.objectDeepFind(this.config, reqAttr) === undefined)
missing.push(reqAttr);
}
this.missingAttributes = missing;
this.logMissing(logger);
return this.missingAttributes.length === 0;
}
/**
* Promts the user which attributes are missing
* @param logger
*/
logMissing(logger) {
if (this.missingAttributes.length > 0) {
logger.error(`Missing required Attributes ${this.missingAttributes.join(', ')}`);
}
}
};
exports.sql = {
tableExistCreate: 'CREATE TABLE IF NOT EXISTS',
pkIdSerial: 'id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE NOT NULL'
};

@ -2,7 +2,9 @@
"name": "discordbot", "name": "discordbot",
"version": "1.0.0", "version": "1.0.0",
"scripts": { "scripts": {
"start": "node bot.js" "start": "node bot.js",
"test": "mocha --exit",
"test-unit": "NODE_ENV=test mocha '/**/*.spec.js'"
}, },
"dependencies": { "dependencies": {
"args-parser": "1.1.0", "args-parser": "1.1.0",
@ -15,5 +17,13 @@
"winston-daily-rotate-file": "3.6.0", "winston-daily-rotate-file": "3.6.0",
"youtube-playlist-info": "1.1.2", "youtube-playlist-info": "1.1.2",
"ytdl-core": "0.29.1" "ytdl-core": "0.29.1"
},
"devDependencies": {
"assert": "^1.4.1",
"chai": "^4.2.0",
"mocha": "^5.2.0",
"nyc": "^13.1.0",
"rewire": "^4.0.1",
"sinon": "^7.2.3"
} }
} }

@ -1,7 +1,8 @@
exports.mockLogger = { exports.mockLogger = {
error: msg => raise(msg), error: msg => {
throw new Error(msg);
},
warn: msg => console.error("warn: ", msg), warn: msg => console.error("warn: ", msg),
warning: msg => console.error("warn: ", msg),
info: msg => console.log("info: ", msg), info: msg => console.log("info: ", msg),
verbose: msg => console.log("verbose: ", msg), verbose: msg => console.log("verbose: ", msg),
debug: msg => console.log("debug: ", msg) debug: msg => console.log("debug: ", msg)
@ -48,4 +49,51 @@ exports.mockVoicechannel = {
exports.mockChannel = { exports.mockChannel = {
send: (msg) => console.log('Send: ', msg) send: (msg) => console.log('Send: ', msg)
} };
exports.mockCommand = {
"name": "test",
"permission": "all",
"description": "Tests everything",
"category": "Test",
"response": {
"success": "Testing successful"
},
"textReply": () => {
return 'test';
},
"promiseReply": () => {
return new Promise((rs, rj) => {
rs('test');
});
},
"richEmbedReply": () => {
return {embed: {
title: 'rich embed'
}};
}
};
exports.MockDatabase = class {
constructor(file, callback) {
callback();
}
run(sql, values, callback) {
if(callback) {
callback();
}
}
get() {
return null;
}
all() {
return null
}
close() {
return true;
}
};

@ -0,0 +1,10 @@
const sinon = require('sinon'),
chai = require('chai');
beforeEach(() => {
this.sandbox = sinon.createSandbox();
});
afterEach(() => {
this.sandbox.restore();
});

@ -0,0 +1,487 @@
const mockobjects = require('./mockobjects.js'),
sinon = require('sinon'),
assert = require('assert'),
rewire = require('rewire');
let Discord = require("discord.js");
mockobjects.mockLogger = {
error: () => {},
warn: () => {},
info: () => {},
verbose: () => {},
debug: () => {}
};
describe('lib/utils', function() {
const utils = require('../lib/utils.js');
describe('#getSplitDuration', function() {
it('returns an object from milliseconds', function() {
assert(utils.getSplitDuration(1000).seconds === 1);
assert(utils.getSplitDuration(360000).minutes === 6);
assert(utils.getSplitDuration(3600000).hours === 1);
assert(utils.getSplitDuration(100).milliseconds === 100);
});
});
describe('#getExtension', function() {
it('returns the correct extension for a filename', function(done) {
assert(utils.getExtension('test.txt') === '.txt');
assert(utils.getExtension('test.tar.gz') === '.gz');
assert(utils.getExtension('../lib/utils.js') === '.js');
assert(utils.getExtension('.gitignore') === '.gitignore');
done();
});
it('returns null if the file has no extension or is no file', function(done) {
assert(utils.getExtension('filenameisstrange') === null);
assert(utils.getExtension('...') === null);
assert(utils.getExtension(Object.create({})) === null);
assert(utils.getExtension(null) === null);
done();
});
});
describe('#YouTube', function() {
it('returns if an url is valid', function(done) {
assert(utils.YouTube.isValidUrl('https://www.youtube.com/watch?v=VID-ID'));
assert(utils.YouTube.isValidUrl('https://youtube.com/playlist?list=PL-ID'));
assert(utils.YouTube.isValidUrl('https://youtube.com/watch?v='));
assert(utils.YouTube.isValidUrl('https://www.youtube.com/playlist?list='));
assert(utils.YouTube.isValidUrl('https://youtu.be/VIDID'));
assert(utils.YouTube.isValidUrl('https://youtu.be/'));
assert(utils.YouTube.isValidUrl('http://youtube.com/watch?v=VID-ID'));
assert(utils.YouTube.isValidUrl('http://youtube.com/playlist?list=PL-ID'));
assert(utils.YouTube.isValidUrl('http://youtube.com/watch?v='));
assert(utils.YouTube.isValidUrl('http://youtube.com/playlist?list='));
assert(!utils.YouTube.isValidUrl('https://github.com'));
assert(!utils.YouTube.isValidUrl('notevenanurl'));
done();
});
it('returns if an url is a valid entity url', function(done) {
assert(utils.YouTube.isValidEntityUrl('https://youtube.com/watch?v=VID-ID'));
assert(utils.YouTube.isValidEntityUrl('https://youtube.com/playlist?list=PL-ID'));
assert(utils.YouTube.isValidEntityUrl('https://youtu.be/VIDID'));
assert(utils.YouTube.isValidEntityUrl('http://www.youtube.com/watch?v=VID-ID'));
assert(utils.YouTube.isValidEntityUrl('http://youtube.com/playlist?list=PL-ID'));
assert(!utils.YouTube.isValidEntityUrl('https://youtube.com/watch?v='));
assert(!utils.YouTube.isValidEntityUrl('https://youtube.com/playlist?list='));
assert(!utils.YouTube.isValidEntityUrl('https://youtu.be/'));
assert(!utils.YouTube.isValidEntityUrl('https://github.com'));
assert(!utils.YouTube.isValidEntityUrl('notevenanurl'));
done();
});
it('returns if an url is a valid playlist url', function(done) {
assert(!utils.YouTube.isValidPlaylistUrl('https://youtube.com/watch?v=VID-ID'));
assert(utils.YouTube.isValidPlaylistUrl('https://youtube.com/playlist?list=PL-ID'));
assert(!utils.YouTube.isValidPlaylistUrl('https://youtu.be/VIDID'));
assert(!utils.YouTube.isValidPlaylistUrl('http://www.youtube.com/watch?v=VID-ID'));
assert(utils.YouTube.isValidPlaylistUrl('http://youtube.com/playlist?list=PL-ID'));
assert(!utils.YouTube.isValidPlaylistUrl('http://youtube.com/playlist?list='));
assert(!utils.YouTube.isValidPlaylistUrl('https://github.com'));
assert(!utils.YouTube.isValidPlaylistUrl('notevenanurl'));
done();
});
it('returns if an url is a valid video url', function(done) {
assert(utils.YouTube.isValidVideoUrl('https://youtube.com/watch?v=VID-ID'));
assert(!utils.YouTube.isValidVideoUrl('https://youtube.com/playlist?list=PL-ID'));
assert(utils.YouTube.isValidVideoUrl('https://youtu.be/VIDID'));
assert(utils.YouTube.isValidVideoUrl('http://www.youtube.com/watch?v=VID-ID'));
assert(!utils.YouTube.isValidVideoUrl('http://youtube.com/playlist?list=PL-ID'));
assert(!utils.YouTube.isValidVideoUrl('https://youtube.com/watch?v='));
assert(!utils.YouTube.isValidVideoUrl('https://youtu.be/'));
assert(!utils.YouTube.isValidVideoUrl('https://github.com'));
assert(!utils.YouTube.isValidVideoUrl('notevenanurl'));
done();
});
it('returns the id for a playlist url', function(done) {
let getPlId = utils.YouTube.getPlaylistIdFromUrl;
assert('PL-ID' === getPlId('https://youtube.com/playlist?list=PL-ID'));
assert('PL-ID' === getPlId('http://youtube.com/playlist?list=PL-ID'));
assert('PL-ID' === getPlId('https://www.youtube.com/playlist?list=PL-ID'));
assert('PL-ID' === getPlId('https://www.youtube.com/playlist?list=PL-ID'));
assert(null === getPlId('https://www.youtube.com/playlist?list='));
done();
});
it('returns the id for a video url', function(done) {
let getVidId = utils.YouTube.getVideoIdFromUrl;
assert('VID-ID' === getVidId('https://youtube.com/watch?v=VID-ID'));
assert('VID-ID' === getVidId('http://youtube.com/watch?v=VID-ID'));
assert('VID-ID' === getVidId('https://www.youtube.com/watch?v=VID-ID'));
assert('VID-ID' === getVidId('https://youtu.be/VID-ID'));
assert(null === getVidId('https://www.faketube.com/watch?v=VID-ID'));
assert(null === getVidId('tu.be/VID-ID'));
assert(null === getVidId('https://youtube.com/watch?v='));
assert(null === getVidId('https://youtu.be/'));
done();
});
it('returns the video url for an id', function(done) {
let getVid4Id = utils.YouTube.getVideoUrlFromId;
assert('https://www.youtube.com/watch?v=VID-ID', getVid4Id('VID-ID'));
assert('https://www.youtube.com/watch?v=12345567885432', getVid4Id('12345567885432'));
done();
});
it('returns the thumbnail url for a video url', function(done) {
let getVid4Id = utils.YouTube.getVideoUrlFromId;
let getTh4Id = utils.YouTube.getVideoThumbnailUrlFromUrl;
assert('https://i3.ytimg.com/vi/VIDID/maxresdefault.jpg', getTh4Id(getVid4Id('VIDID')));
assert('https://i3.ytimg.com/vi/1234/maxresdefault.jpg', getTh4Id(getVid4Id('1234')));
done();
})
});
describe('#ConfigVerifyer', function() {
it('verifies correct configs', function(done) {
const testObj = {
'key1': {
'key2': 'value2',
'key3': 'value3'
},
'key4': [],
'key5': false,
'key6': 'a longer string',
'key7': {
'key8': [{
'key9': 'okay...'
}]
}
};
let confVer = new utils.ConfigVerifyer(testObj, ['key1', 'key1.key3']);
assert(confVer.verifyConfig(mockobjects.mockLogger));
confVer = new utils.ConfigVerifyer(testObj, ['key1', 'key1.key2', 'key7.key8.0.key9']);
assert(confVer.verifyConfig(mockobjects.mockLogger));
confVer = new utils.ConfigVerifyer(testObj, ['key4', 'key1.key2', 'key5', 'key7']);
assert(confVer.verifyConfig(mockobjects.mockLogger));
done();
});
it('rejects invalid configs', function(done) {
const testObj = {
};
let modifiedMockLogger = mockobjects.mockLogger;
modifiedMockLogger.error = (msg) => {};
let confVer = new utils.ConfigVerifyer(testObj, ['key1', 'key1.key3']);
assert(!confVer.verifyConfig(modifiedMockLogger));
confVer = new utils.ConfigVerifyer(testObj, ['key1', 'key1.key2', 'key7.key8.0.key9']);
assert(!confVer.verifyConfig(modifiedMockLogger));
done();
})
});
});
describe('lib/music', function() {
const music = rewire('../lib/music');
const Readable = require('stream').Readable;
music.__set__("logger", mockobjects.mockLogger);
music.__set__("yttl", (id, cb) => {
cb(null, 'test');
});
music.__set__('ytdl', () => {
let s = new Readable();
s._read = () => {};
s.push('chunkofdataabc');
s.push(null);
return s;
});
describe('#DJ', function () {
it('connects to a VoiceChannel', function (done) {
let dj = new music.DJ(mockobjects.mockVoicechannel);
dj.connect().then(()=> {
assert(dj.connected);
done();
});
});
it('listens on Repeat', function () {
let dj = new music.DJ(mockobjects.mockVoicechannel);
dj.current = {'url': '', 'title': ''};
dj.listenOnRepeat = true;
assert(dj.repeat);
assert(dj.queue.length > 0);
});
it('plays Files', function (done) {
let dj = new music.DJ(mockobjects.mockVoicechannel);
dj.connect().then(() => {
dj.playFile();
assert(dj.playing);
done();
});
});
it('plays YouTube urls', function (done) {
let dj = new music.DJ(mockobjects.mockVoicechannel);
dj.connect().then(() => {
dj.playYouTube('http://www.youtube.com/watch?v=ABCDEFGHIJK');
setTimeout(() => {
assert(dj.playing);
done();
}, 100);
});
});
it('gets the video name', function (done) {
let dj = new music.DJ(mockobjects.mockVoicechannel);
dj.getVideoName('http://www.youtube.com/watch?v=ABCDEFGHIJK').then((name) => {
assert(name === 'test');
done();
})
});
it('sets the volume', function(done) {
let dj = new music.DJ(mockobjects.mockVoicechannel);
dj.connect().then(() => {
dj.playFile();
dj.setVolume(100);
assert(dj.volume === 100);
done();
})
});
it('pauses playback', function(done) {
let dj = new music.DJ(mockobjects.mockVoicechannel);
dj.connect().then(() => {
dj.playFile();
dj.pause();
done();
})
});
it('resumes playback', function(done) {
let dj = new music.DJ(mockobjects.mockVoicechannel);
dj.connect().then(() => {
dj.playFile();
dj.resume();
done();
})
});
it('stops playback', function(done) {
let dj = new music.DJ(mockobjects.mockVoicechannel);
dj.connect().then(() => {
dj.playFile();
assert(dj.playing);
dj.stop();
assert(!dj.conn && !dj.disp);
done();
});
});
it('skips songs', function(done) {
let dj = new music.DJ(mockobjects.mockVoicechannel);
dj.connect().then(() => {
dj.playYouTube('http://www.youtube.com/watch?v=ABCDEFGHIJK');
dj.playYouTube('http://www.youtube.com/watch?v=ABCDEFGHIJK');
dj.playYouTube('http://www.youtube.com/watch?v=ABCDEFGHIJK');
dj.playYouTube('http://www.youtube.com/watch?v=ABCDEFGHIJK');
dj.skip();
dj.skip();
done();
});
});
it('returns a playlist', function(done) {
let dj = new music.DJ(mockobjects.mockVoicechannel);
dj.connect().then(() => {
dj.queue = [{
'title': 'title',
'url': 'http://www.youtube.com/watch?v=ABCDEFGHIJK'}, {
'title': 'title',
'url': 'http://www.youtube.com/watch?v=ABCDEFGHIJK'}];
assert(dj.playlist.length > 0);
done();
}).catch(() => done());
});
it('clears the queue', function(done) {
let dj = new music.DJ(mockobjects.mockVoicechannel);
dj.connect().then(() => {
dj.queue = [{
'title': 'title',
'url': 'http://www.youtube.com/watch?v=ABCDEFGHIJK'}, {
'title': 'title',
'url': 'http://www.youtube.com/watch?v=ABCDEFGHIJK'}];
dj.clear();
assert(dj.queue.length === 0);
done();
}).catch(() => done());
})
});
});
describe('lib/cmd', function() {
const cmd = rewire('../lib/cmd');
cmd.__set__("logger", mockobjects.mockLogger);
describe('#Servant', function() {
it('creates commands', function() {
let servant = new cmd.Servant('');
servant.createCommand(mockobjects.mockCommand, mockobjects.mockCommand.textReply);
assert(servant.commands['test']);
servant.createCommand(mockobjects.mockCommand, mockobjects.mockCommand.promiseReply);
assert(servant.commands['test']);
servant.createCommand(mockobjects.mockCommand, mockobjects.mockCommand.richEmbedReply);
assert(servant.commands['test']);
});
it('removes commands', function() {
let servant = new cmd.Servant('');
servant.createCommand(mockobjects.mockCommand, mockobjects.mockCommand.textReply);
assert(servant.commands['test']);
servant.removeCommand('test');
assert(!servant.commands['test'])
});
it('parses commands', function() {
let spy = sinon.spy();
let servant = new cmd.Servant('');
servant.createCommand(mockobjects.mockCommand, spy);
assert(servant.commands['test']);
assert(!spy.called);
servant.parseCommand({
content: 'test',
author: {
tag: undefined
}
});
assert(spy.called);
});
});
});
describe('lib/guilding', function() {
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.setLogger(mockobjects.mockLogger);
describe('#GuildHandler', function() {
it('initializes', function() {
let gh = new guilding.GuildHandler('test', '');
gh.db = new mockobjects.MockDatabase('', ()=>{});
gh.createTables();
gh.registerMusicCommands();
gh.ready = true;
assert(gh.ready);
});
it('destroyes itself', function() {
let gh = new guilding.GuildHandler('test', '');
gh.db = new mockobjects.MockDatabase('', ()=>{});
gh.createTables();
gh.registerMusicCommands();
gh.ready = true;
gh.destroy();
assert(!gh.dj.conn);
});
it('answers messages', function() {
let gh = new guilding.GuildHandler('test', '');
gh.db = new mockobjects.MockDatabase('', ()=>{});
gh.createTables();
gh.registerMusicCommands();
gh.ready = true;
let msgSpy = sinon.spy();
gh.answerMessage({
content: 'test',
author: {
tag: undefined
},
reply: msgSpy,
channel: {
send: msgSpy
}
}, 'Answer');
assert(msgSpy.called);
});
it('handles messages', function() {
let gh = new guilding.GuildHandler('test', '~');
gh.db = new mockobjects.MockDatabase('', ()=>{});
gh.ready = true;
let cbSpy = sinon.spy();
gh.servant.createCommand(mockobjects.mockCommand, cbSpy);
assert(gh.servant.commands['~test']);
gh.handleMessage({
content: '~test',
author: {
tag: undefined
}});
assert(cbSpy.called);
});
it('connects and plays', function(done) {
const music = rewire('../lib/music');
const Readable = require('stream').Readable;
music.__set__("logger", mockobjects.mockLogger);
music.__set__("yttl", (id, cb) => {
cb(null, 'test');
});
music.__set__('ytdl', () => {
let s = new Readable();
s._read = () => {};
s.push('chunkofdataabc');
s.push(null);
return s;
});
let gh = new guilding.GuildHandler('test', '~');
gh.db = new mockobjects.MockDatabase('', ()=>{});
gh.ready = true;
gh.dj = new music.DJ(mockobjects.mockVoicechannel);
gh.connectAndPlay(mockobjects.mockVoicechannel, 'test', false).then(() => {
done();
})
});
it('handles all servercommands', function() {
let gh = new guilding.GuildHandler('test', '~');
gh.db = new mockobjects.MockDatabase('', ()=>{});
gh.registerMusicCommands();
gh.ready = true;
let msgSpy = sinon.spy();
let msg = {
content: 'test',
author: {
tag: undefined
},
reply: msgSpy,
channel: {
send: msgSpy
}
};
for (let category of Object.keys(servercommands)) {
for (let command of Object.keys(servercommands[category])) {
msg.content = '~' + command;
gh.handleMessage(msg);
}
}
assert(msgSpy.called);
});
});
});

@ -1,39 +0,0 @@
const cmd = require("../lib/cmd.js"),
mockobjects = require("./mockobjects.js"),
servercmd = require('../commands/servercommands');
function main() {
cmd.setLogger(mockobjects.mockLogger);
console.log('Creating new servant instance');
let servant = new cmd.Servant('#');
console.log('registering all music commands...');
for (let [key, value] of Object.entries(servercmd.music)) {
servant.createCommand(value, () => {
console.log(` - invoked ${value.name} callback`);
});
}
console.log('parsing and deleting all music commands...');
for (let [key, value] of Object.entries(servercmd.music)) {
servant.parseCommand({
content: '#' + value.name,
author: {
tag: undefined
}
});
servant.removeCommand(value.name);
}
process.exit(0);
}
if (typeof require !== "undefined" && require.main === module) {
process.on("unhandledRejection", (reason, p) => {
console.error("Unhandled Rejection at: Promise", p, "reason:", reason);
throw Error("Promise rejection");
});
setTimeout(() => process.exit(1), 60000);
main();
}

@ -1,44 +0,0 @@
const guilding = require("../lib/guilding.js")
music = require("../lib/music.js"),
mockobjects = require("./mockobjects.js"),
servercmd = require("../commands/servercommands");
function main() {
guilding.setLogger(mockobjects.mockLogger);
music.setLogger(mockobjects.mockLogger);
console.log('Creating guildHandler instance');
let guildHandler = new guilding.GuildHandler('TEST', '#');
guildHandler.dj = new music.DJ(mockobjects.mockVoicechannel);
setTimeout(() => {
for (let [key, value] of Object.entries(servercmd.music)) {
guildHandler.handleMessage({
content: '#' + value.name + ' arg1 arg2 arg3 arg4',
author: {
tag: undefined,
id: 0,
createdTimestamp: new Date(),
username: 'TEST'
},
member: {
voiceChannel: mockobjects.mockVoicechannel
},
channel: mockobjects.mockChannel,
reply: mockobjects.mockChannel.send
});
}
guildHandler.destroy();
process.exit(0);
}, 1000);
}
if (typeof require !== "undefined" && require.main === module) {
process.on("unhandledRejection", (reason, p) => {
console.error("Unhandled Rejection at: Promise", p, "reason:", reason);
throw Error("Promise rejection");
});
setTimeout(() => process.exit(1), 60000);
main();
}

@ -1,33 +0,0 @@
const music = require('../lib/music.js'),
mockobjects = require('./mockobjects.js');
function main() {
let dj = new music.DJ(mockobjects.mockVoicechannel)
music.setLogger(mockobjects.mockLogger);
dj.connect().then(() => {
console.log('connected', dj.connected);
dj.playFile('test');
dj.playYouTube('https://www.youtube.com/watch?v=TEST');
dj.setVolume(1);
dj.pause();
dj.resume();
dj.skip();
dj.stop();
dj.shuffle();
console.log('dj.playlist: ', dj.playlist);
console.log('dj.song: ', dj.song);
dj.clear();
process.exit(0);
});
}
// Executing the main function
if (typeof require !== 'undefined' && require.main === module) {
process.on('unhandledRejection', (reason, p) => {
console.error('Unhandled Rejection at: Promise', p, 'reason:', reason);
throw Error('Promise rejection');
});
setTimeout(() => process.exit(1), 60000);
main();
}
Loading…
Cancel
Save