Merge pull request #25 from Trivernis/develop

Closes #24 #23
pull/27/head
Trivernis 6 years ago committed by GitHub
commit 35dc749ad0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -7,7 +7,7 @@ jobs:
build: build:
docker: docker:
# specify the version you desire here # specify the version you desire here
- image: circleci/node:10.11 - image: circleci/node:10.15
# Specify service dependencies here if necessary # Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images # CircleCI maintains a library of pre-built images

@ -1,4 +1,4 @@
discordbot [![CircleCI](https://circleci.com/gh/Trivernis/discordbot.js.svg?style=svg)](https://circleci.com/gh/Trivernis/discordbot.js) [![CodeFactor](https://www.codefactor.io/repository/github/trivernis/discordbot.js/badge)](https://www.codefactor.io/repository/github/trivernis/discordbot.js) discordbot [![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0) [![CircleCI](https://circleci.com/gh/Trivernis/discordbot.js.svg?style=shield)](https://circleci.com/gh/Trivernis/discordbot.js) [![CodeFactor](https://www.codefactor.io/repository/github/trivernis/discordbot.js/badge)](https://www.codefactor.io/repository/github/trivernis/discordbot.js)
=== ===
A bot that does the discord thing. A bot that does the discord thing.

@ -11,8 +11,8 @@ const Discord = require("discord.js"),
prefix = args.prefix || config.prefix, prefix = args.prefix || config.prefix,
gamepresence = args.game || config.presence; gamepresence = args.game || config.presence;
let presences = [], let presences = [], // loaded from presences.txt file if the file exists
rotator = null; rotator = null; // an interval id to stop presence duration if needed
function main() { function main() {
utils.Cleanup(() => { utils.Cleanup(() => {
@ -23,6 +23,7 @@ function main() {
guilding.setLogger(logger); guilding.setLogger(logger);
cmd.init(prefix); cmd.init(prefix);
registerCommands(); registerCommands();
utils.dirExistence('./data', () => { utils.dirExistence('./data', () => {
fs.exists('./data/presences.txt', (exist) => { fs.exists('./data/presences.txt', (exist) => {
if (exist) { if (exist) {
@ -37,15 +38,16 @@ function main() {
} }
}) })
}); });
// log the errors instead of letting the program crash registerCallbacks();
client.on('error', (err) => {
logger.error(err.message);
});
client.login(authToken).then(() => { client.login(authToken).then(() => {
logger.debug("Logged in"); logger.debug("Logged in");
}); });
} }
/**
* registeres global commands
*/
function registerCommands() { function registerCommands() {
// useless test command // useless test command
cmd.createGlobalCommand(prefix + 'repeatafterme', (msg, argv, args) => { cmd.createGlobalCommand(prefix + 'repeatafterme', (msg, argv, args) => {
@ -106,6 +108,36 @@ function rotatePresence() {
logger.debug(`Presence rotation to ${pr}`); logger.debug(`Presence rotation to ${pr}`);
} }
/**
* Sends the answer recieved from the commands callback.
* Handles the sending differently depending on the type of the callback return
* @param msg
* @param answer
*/
function 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) {
answer
.then((answer) => answerMessage(msg, answer))
.catch((error) => answerMessage(msg, error));
} else {
(this.mention)? msg.reply(answer) : msg.channel.send(answer);
}
} else {
logger.warn(`Empty answer won't be send.`);
}
}
/**
* Registeres callbacks for client events
*/
function registerCallbacks() {
client.on('error', (err) => {
logger.error(err.message);
});
client.on('ready', () => { client.on('ready', () => {
logger.info(`logged in as ${client.user.tag}!`); logger.info(`logged in as ${client.user.tag}!`);
client.user.setPresence({game: {name: gamepresence, type: "PLAYING"}, status: 'online'}); client.user.setPresence({game: {name: gamepresence, type: "PLAYING"}, status: 'online'});
@ -120,13 +152,7 @@ client.on('message', msg => {
logger.verbose(`<${msg.author.tag}>: ${msg.content}`); logger.verbose(`<${msg.author.tag}>: ${msg.content}`);
if (!msg.guild) { if (!msg.guild) {
let reply = cmd.parseMessage(msg); let reply = cmd.parseMessage(msg);
if (reply) { answerMessage(msg, reply);
if (reply.isPrototypeOf(Discord.RichEmbed)) {
msg.channel.send('', reply);
} else {
msg.channel.send(reply)
}
}
} else { } else {
guilding.getHandler(msg.guild, prefix).handleMessage(msg); guilding.getHandler(msg.guild, prefix).handleMessage(msg);
} }
@ -134,6 +160,7 @@ client.on('message', msg => {
logger.error(err.stack); logger.error(err.stack);
} }
}); });
}
// Executing the main function // Executing the main function
if (typeof require !== 'undefined' && require.main === module) { if (typeof require !== 'undefined' && require.main === module) {

@ -103,7 +103,10 @@
"name": "np", "name": "np",
"permission": "all", "permission": "all",
"description": "Shows the currently playing song.", "description": "Shows the currently playing song.",
"category": "Music" "category": "Music",
"response": {
"not_playing": "I'm not playing music at the moment."
}
}, },
"shuffle": { "shuffle": {
"name": "shuffle", "name": "shuffle",

@ -1,16 +1,14 @@
/* Module definition */ /* Module definition */
/* Variable Definition */ /* Variable Definition */
let logger = require('winston'), const Discord = require('discord.js'),
globCommands = {},
ownerCommands = {},
config = require('../config.json'),
args = require('args-parser')(process.argv), args = require('args-parser')(process.argv),
config = require('../config.json'),
gcmdTempl = require('../commands/globalcommands'), gcmdTempl = require('../commands/globalcommands'),
scmdTempl = require('../commands/servercommands'), scmdTempl = require('../commands/servercommands');
Discord = require('discord.js');
/* Function Definition */ let logger = require('winston'),
globCommands = {};
/** /**
* @type {Servant} * @type {Servant}

@ -92,7 +92,7 @@ exports.GuildHandler = class {
(this.mention)? msg.reply(answer) : msg.channel.send(answer); (this.mention)? msg.reply(answer) : msg.channel.send(answer);
} }
} else { } else {
logger.warning(`Empty answer won't be send.`); logger.warn(`Empty answer won't be send.`);
} }
} }
/** /**
@ -130,7 +130,7 @@ exports.GuildHandler = class {
this.dj.connect(vc).then(() => { this.dj.connect(vc).then(() => {
this.dj.playYouTube(url, next); this.dj.playYouTube(url, next);
resolve(); resolve();
}); }).catch((err) => reject(err));
} else { } else {
this.dj.playYouTube(url, next); this.dj.playYouTube(url, next);
resolve(); resolve();
@ -155,37 +155,33 @@ exports.GuildHandler = class {
reject(servercmd.music.play.response.no_voicechannel); reject(servercmd.music.play.response.no_voicechannel);
if (!url) if (!url)
reject(servercmd.music.play.response.no_url); reject(servercmd.music.play.response.no_url);
if (!url.match(/http/g)) { if (!utils.YouTube.isValidEntityUrl(url)) {
if (argv && argv.length > 0) if (argv && argv.length > 0)
url += ' ' + argv.join(' '); // join to get the whole expression behind the command url += ' ' + argv.join(' '); // join to get the whole expression behind the command
this.db.get('SELECT url FROM playlists WHERE name = ?', [url], (err, row) => { this.db.get('SELECT url FROM playlists WHERE name = ?', [url], (err, row) => {
if (err) { if (err)
console.error(err.message); console.error(err.message);
}
if (!row) { if (!row) {
reject(servercmd.music.play.response.url_invalid); reject(servercmd.music.play.response.url_invalid);
logger.verbose('Got invalid url for play command.'); logger.verbose('Got invalid url for play command.');
} else { } else {
url = row.url; url = row.url;
try {
this.connectAndPlay(vc, url).then(() => { this.connectAndPlay(vc, url).then(() => {
resolve(servercmd.music.play.response.success); resolve(servercmd.music.play.response.success);
}); }).catch((err) => {
} catch (err) {
logger.error(err.message); logger.error(err.message);
reject(servercmd.music.play.response.failure); reject(servercmd.music.play.response.failure);
} });
} }
}); });
} else { } else {
try {
this.connectAndPlay(vc, url).then(() => { this.connectAndPlay(vc, url).then(() => {
resolve(servercmd.music.play.response.success); resolve(servercmd.music.play.response.success);
}); }).catch((err) => {
} catch (err) {
logger.error(err.message); logger.error(err.message);
reject(servercmd.music.play.response.failure); reject(servercmd.music.play.response.failure);
} });
} }
}) })
}); });
@ -197,35 +193,32 @@ exports.GuildHandler = class {
if (!this.dj.connected) this.dj.voiceChannel = vc; if (!this.dj.connected) this.dj.voiceChannel = vc;
let url = kwargs['url']; let url = kwargs['url'];
if (!url) reject(servercmd.music.playnext.response.no_url); if (!url) reject(servercmd.music.playnext.response.no_url);
if (!url.match(/http/g)) { if (!utils.YouTube.isValidEntityUrl(url)) {
if (argv) if (argv)
url += ' ' + argv.join(' '); url += ' ' + argv.join(' ');
this.db.get('SELECT url FROM playlists WHERE name = ?', [url], (err, row) => { this.db.get('SELECT url FROM playlists WHERE name = ?', [url], (err, row) => {
if (err) { if (err)
console.error(err.message); console.error(err.message);
}
if (!row) { if (!row) {
reject(servercmd.music.play.response.url_invalid); reject(servercmd.music.play.response.url_invalid);
} } else {
url = row.url; url = row.url;
try {
this.connectAndPlay(url, true).then(() => { this.connectAndPlay(url, true).then(() => {
resolve(servercmd.music.playnext.response.success); resolve(servercmd.music.playnext.response.success);
}); }).catch((err) => {
} catch (err) {
logger.error(err.message); logger.error(err.message);
reject(servercmd.music.play.response.failure); reject(servercmd.music.play.response.failure);
});
} }
}); });
} else { } else {
try {
this.connectAndPlay(url, true).then(() => { this.connectAndPlay(url, true).then(() => {
resolve(servercmd.music.playnext.response.success); resolve(servercmd.music.playnext.response.success);
}); }).catch((err) => {
} catch (err) {
logger.error(err); logger.error(err);
reject(servercmd.music.playnext.response.failure); reject(servercmd.music.playnext.response.failure);
} });
} }
}) })
}); });
@ -284,7 +277,15 @@ exports.GuildHandler = class {
// np command // np command
this.servant.createCommand(servercmd.music.current, () => { this.servant.createCommand(servercmd.music.current, () => {
let song = this.dj.song; let song = this.dj.song;
return `Playing: ${song.title}\n ${song.url}`; 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 // shuffle command

@ -4,6 +4,7 @@ const Discord = require("discord.js"),
yttl = require('get-youtube-title'), yttl = require('get-youtube-title'),
args = require('args-parser')(process.argv), args = require('args-parser')(process.argv),
config = require('../config.json'), config = require('../config.json'),
utils = require('./utils.js'),
ytapiKey = args.ytapi || config.ytapikey; ytapiKey = args.ytapi || config.ytapikey;
/* Variable Definition */ /* Variable Definition */
let logger = require('winston'); let logger = require('winston');
@ -46,6 +47,17 @@ exports.DJ = class {
}) })
} }
/**
* Defining setter for listenOnRepeat to include the current song into the repeating loop.
* @param value
*/
set listenOnRepeat(value) {
this.repeat = value;
if (this.current) {
this.queue.push(this.current);
}
}
/** /**
* Returns if a connection exists * Returns if a connection exists
* @returns {boolean} * @returns {boolean}
@ -100,55 +112,44 @@ exports.DJ = class {
* @param playnext * @param playnext
*/ */
playYouTube(url, playnext) { playYouTube(url, playnext) {
/** Commented because it causes an connection overflow error. let plist = utils.YouTube.getPlaylistIdFromUrl(url);
* TODO: Decide to either fix this with promises or ignore it because connection checks are performed by the guild handler.**/
/*
if (!this.connected) {
this.connect().then(this.playYouTube(url));
}
*/
let plist = url.match(/(?<=\?list=)[\w\-]+/);
if (plist) { if (plist) {
logger.debug(`Adding playlist ${plist} to queue`); logger.debug(`Adding playlist ${plist} to queue`);
ypi(ytapiKey, plist).then(items => { ypi(ytapiKey, plist).then(items => {
for (let i = 0; i < items.length; i++) { let firstSong = utils.YouTube.getVideoUrlFromId(items.shift().resourceId.videoId);
let vurl = `https://www.youtube.com/watch?v=${items[i].resourceId.videoId}`;
this.queue.push({'url': vurl, 'title': null}); this.getVideoName(firstSong).then((title) => { // getting the first song to start playing music
yttl(vurl.replace(/http(s)?:\/\/(www.)?youtube.com\/watch\?v=/g, ''), (err, title) => { if (this.repeat) // listen on repeat
if (err) { this.queue.push({'url': firstSong, 'title': title}); // put the current song back at the end of the queue
logger.debug(JSON.stringify(err)); this.playYouTube(firstSong); // call with single url that gets queued if a song is already playing
} else { }).catch((err) => logger.error(err.message));
try { for (let item of items) {
logger.debug(`Found title: ${title} for ${vurl}`); let vurl = utils.YouTube.getVideoUrlFromId(item.resourceId.videoId);
this.queue.find((el) => { this.getVideoName(vurl).then((title) => {
return (el.url === vurl); this.queue.push({'url': vurl, 'title': title});
}).title = title; }).catch((err) => logger.error(err.message));
} catch (error) { }
logger.verbose(JSON.stringify(error)); logger.debug(`Added ${items.length} songs to the queue`);
}
}
});
}
this.current = this.queue.shift();
if (this.repeat) this.queue.push(this.current);
this.playYouTube(this.current.url);
}); });
return; } else {
}
if (!this.playing || !this.disp) { if (!this.playing || !this.disp) {
logger.debug(`Playing ${url}`); logger.debug(`Playing ${url}`);
this.getVideoName(url).then((title) => {
this.current = ({'url': url, 'title': title});
this.disp = this.conn.playStream(ytdl(url, { this.disp = this.conn.playStream(ytdl(url, {
filter: 'audioonly', filter: 'audioonly', quality: this.quality, liveBuffer: 40000
quality: this.quality,
liveBuffer: 40000
}), {volume: this.volume}); }), {volume: this.volume});
this.disp.on('end', (reason) => {
this.disp.on('end', (reason) => { // end event triggers the next song to play when the reason is not stop
if (reason !== 'stop') { if (reason !== 'stop') {
this.playing = false; this.playing = false;
this.current = null; this.current = null;
if (this.queue.length > 0) { if (this.queue.length > 0) {
this.current = this.queue.shift(); this.current = this.queue.shift();
if (this.repeat) this.queue.push(this.current); if (this.repeat) // listen on repeat
this.queue.push(this.current);
this.playYouTube(this.current.url); this.playYouTube(this.current.url);
} else { } else {
this.stop(); this.stop();
@ -156,28 +157,38 @@ exports.DJ = class {
} }
}); });
this.playing = true; this.playing = true;
});
} else { } else {
logger.debug(`Added ${url} to the queue`); logger.debug(`Added ${url} to the queue`);
if (playnext) { if (playnext) {
this.queue.unshift({'url': url, 'title': null}); this.getVideoName(url).then((title) => {
this.queue.unshift({'url': url, 'title': title});
}).catch((err) => logger.error(err.message));
} else { } else {
this.queue.push({'url': url, 'title': null}); this.getVideoName(url).then((title) => {
this.queue.push({'url': url, 'title': title});
}).catch((err) => logger.error(err.message));
}
}
} }
yttl(url.replace(/http(s)?:\/\/(www.)?youtube.com\/watch\?v=/g, ''), (err, title) => { }
/**
* Gets the name of the YouTube Video at url
* @param url
* @returns {Promise<>}
*/
getVideoName(url) {
return new Promise((resolve, reject) => {
yttl(utils.YouTube.getVideoIdFromUrl(url), (err, title) => {
if (err) { if (err) {
logger.debug(JSON.stringify(err)); logger.debug(JSON.stringify(err));
reject(err);
} else { } else {
try { resolve(title);
logger.debug(`Found title: ${title} for ${url}`);
this.queue.find((el) => {
return (el.url === url);
}).title = title;
} catch (error) {
console.verbose(JSON.stringify(error));
}
} }
}); });
} });
} }
/** /**

@ -72,3 +72,85 @@ exports.dirExistence = function (path, callback) {
} }
}) })
}; };
exports.YouTube = class {
/**
* returns if an url is a valid youtube url (without checking for an entity id)
* @param url
* @returns {boolean}
*/
static isValidUrl(url) {
return /https?:\/\/www.youtube.com\/(watch\?v=|playlist\?list=)/g.test(url) ||
/https?:\/\/youtu.be\//g.test(url);
}
/**
* returns if an url is a valid youtube url for an entity
* @param url
* @returns {boolean}
*/
static isValidEntityUrl(url) {
return /https?:\/\/www.youtube.com\/(watch\?v=.+?|playlist\?list=.+?)/g.test(url) ||
/https?:\/\/youtu.be\/.+?/g.test(url);
}
/**
* Returns if an url is a valid youtube url for a playlist
* @param url
* @returns {boolean}
*/
static isValidPlaylistUrl(url) {
return /https?:\/\/www.youtube.com\/playlist\?list=.+?/g.test(url);
}
/**
* Returns if an url is a valid youtube url for a video
* @param url
* @returns {boolean}
*/
static isValidVideoUrl(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
* @param url
* @returns {RegExpMatchArray}
*/
static getPlaylistIdFromUrl(url) {
let matches = url.match(/(?<=\?list=)[\w\-]+/);
if (matches)
return matches[0];
else
return null;
}
/**
* Returns the id for a youtube video stripped from the url
* @param url
*/
static getVideoIdFromUrl(url) {
let matches = url.match(/(?<=\?v=)[\w\-]+/);
if (matches)
return matches[0];
else
return null;
}
/**
* Returns the youtube video url for a video id by string concatenation
* @param id
*/
static getVideoUrlFromId(id) {
return `https://www.youtube.com/watch?v=${id}`;
}
/**
* Returns the youtube video thumbnail for a video url
* @param url
* @returns {string}
*/
static getVideoThumbnailUrlFromUrl(url) {
return `https://i3.ytimg.com/vi/${exports.YouTube.getVideoIdFromUrl(url)}/maxresdefault.jpg`
}
};
Loading…
Cancel
Save