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:
docker:
# specify the version you desire here
- image: circleci/node:10.11
- image: circleci/node:10.15
# Specify service dependencies here if necessary
# 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.

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

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

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

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

@ -4,6 +4,7 @@ const Discord = require("discord.js"),
yttl = require('get-youtube-title'),
args = require('args-parser')(process.argv),
config = require('../config.json'),
utils = require('./utils.js'),
ytapiKey = args.ytapi || config.ytapikey;
/* Variable Definition */
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 {boolean}
@ -100,84 +112,83 @@ exports.DJ = class {
* @param playnext
*/
playYouTube(url, playnext) {
/** Commented because it causes an connection overflow error.
* 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\-]+/);
let plist = utils.YouTube.getPlaylistIdFromUrl(url);
if (plist) {
logger.debug(`Adding playlist ${plist} to queue`);
ypi(ytapiKey, plist).then(items => {
for (let i = 0; i < items.length; i++) {
let vurl = `https://www.youtube.com/watch?v=${items[i].resourceId.videoId}`;
this.queue.push({'url': vurl, 'title': null});
yttl(vurl.replace(/http(s)?:\/\/(www.)?youtube.com\/watch\?v=/g, ''), (err, title) => {
if (err) {
logger.debug(JSON.stringify(err));
} else {
try {
logger.debug(`Found title: ${title} for ${vurl}`);
this.queue.find((el) => {
return (el.url === vurl);
}).title = title;
} catch (error) {
logger.verbose(JSON.stringify(error));
}
}
});
let firstSong = utils.YouTube.getVideoUrlFromId(items.shift().resourceId.videoId);
this.getVideoName(firstSong).then((title) => { // getting the first song to start playing music
if (this.repeat) // listen on repeat
this.queue.push({'url': firstSong, 'title': title}); // put the current song back at the end of the queue
this.playYouTube(firstSong); // call with single url that gets queued if a song is already playing
}).catch((err) => logger.error(err.message));
for (let item of items) {
let vurl = utils.YouTube.getVideoUrlFromId(item.resourceId.videoId);
this.getVideoName(vurl).then((title) => {
this.queue.push({'url': vurl, 'title': title});
}).catch((err) => logger.error(err.message));
}
this.current = this.queue.shift();
if (this.repeat) this.queue.push(this.current);
this.playYouTube(this.current.url);
logger.debug(`Added ${items.length} songs to the queue`);
});
return;
}
if (!this.playing || !this.disp) {
logger.debug(`Playing ${url}`);
this.disp = this.conn.playStream(ytdl(url, {
filter: 'audioonly',
quality: this.quality,
liveBuffer: 40000
}), {volume: this.volume});
this.disp.on('end', (reason) => {
if (reason !== 'stop') {
this.playing = false;
this.current = null;
if (this.queue.length > 0) {
this.current = this.queue.shift();
if (this.repeat) this.queue.push(this.current);
this.playYouTube(this.current.url);
} else {
this.stop();
}
}
});
this.playing = true;
} else {
logger.debug(`Added ${url} to the queue`);
if (playnext) {
this.queue.unshift({'url': url, 'title': null});
if (!this.playing || !this.disp) {
logger.debug(`Playing ${url}`);
this.getVideoName(url).then((title) => {
this.current = ({'url': url, 'title': title});
this.disp = this.conn.playStream(ytdl(url, {
filter: 'audioonly', quality: this.quality, liveBuffer: 40000
}), {volume: this.volume});
this.disp.on('end', (reason) => { // end event triggers the next song to play when the reason is not stop
if (reason !== 'stop') {
this.playing = false;
this.current = null;
if (this.queue.length > 0) {
this.current = this.queue.shift();
if (this.repeat) // listen on repeat
this.queue.push(this.current);
this.playYouTube(this.current.url);
} else {
this.stop();
}
}
});
this.playing = true;
});
} else {
this.queue.push({'url': url, 'title': null});
logger.debug(`Added ${url} to the queue`);
if (playnext) {
this.getVideoName(url).then((title) => {
this.queue.unshift({'url': url, 'title': title});
}).catch((err) => logger.error(err.message));
} else {
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) {
logger.debug(JSON.stringify(err));
reject(err);
} else {
try {
logger.debug(`Found title: ${title} for ${url}`);
this.queue.find((el) => {
return (el.url === url);
}).title = title;
} catch (error) {
console.verbose(JSON.stringify(error));
}
resolve(title);
}
});
}
});
}
/**

@ -71,4 +71,86 @@ exports.dirExistence = function (path, callback) {
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