|
|
|
const cmdLib = require('../../lib/command'),
|
|
|
|
utils = require('../../lib/utils'),
|
|
|
|
config = require('../../config');
|
|
|
|
|
|
|
|
function checkPermission(msg, rolePerm) {
|
|
|
|
if (!rolePerm || ['all', 'any', 'everyone'].includes(rolePerm))
|
|
|
|
return true;
|
|
|
|
if (config.owners.includes(msg.author.tag))
|
|
|
|
return true;
|
|
|
|
else if (msg.member && rolePerm && rolePerm !== 'owner' && msg.member.roles
|
|
|
|
.some(role => (role.name.toLowerCase() === rolePerm.toLowerCase() ||
|
|
|
|
role.name.toLowerCase() === 'botcommander')))
|
|
|
|
return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Music commands provide commands to control the bots music functions.
|
|
|
|
* These commands are for server music functionalities.
|
|
|
|
*/
|
|
|
|
class MusicCommandModule extends cmdLib.CommandModule {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param opts {Object} properties:
|
|
|
|
* getGuildHandler - a function to get the guild handler for a guild.
|
|
|
|
*/
|
|
|
|
constructor(opts) {
|
|
|
|
super(cmdLib.CommandScopes.Guild);
|
|
|
|
this._templateDir = __dirname;
|
|
|
|
this._getGuildHandler = opts.getGuildHandler;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Connects to a voice-channel if not connected and plays the url
|
|
|
|
* @param gh {guilding.GuildHandler}
|
|
|
|
* @param vc {Discord.VoiceChannel}
|
|
|
|
* @param url {String} The url to the YouTube media
|
|
|
|
* @param next {Boolean} Should the song be played next
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
async _connectAndPlay(gh, vc, url, next) {
|
|
|
|
if (!gh.musicPlayer.connected) {
|
|
|
|
await gh.musicPlayer.connect(vc);
|
|
|
|
return await gh.musicPlayer.playYouTube(url, next);
|
|
|
|
} else {
|
|
|
|
return await gh.musicPlayer.playYouTube(url, next);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The play function for the music commands play and playnext
|
|
|
|
* @param m {Discord.Message}
|
|
|
|
* @param k {Object} kwargs
|
|
|
|
* @param s {String} argsString
|
|
|
|
* @param t {Object} template
|
|
|
|
* @param n {Boolean} play next
|
|
|
|
* @returns {Promise<*>}
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
async _playFunction(m, k, s, t, n) {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
let vc = gh.musicPlayer.voiceChannel || m.member.voiceChannel;
|
|
|
|
let url = k['url'];
|
|
|
|
if (!vc)
|
|
|
|
return t.response.no_voicechannel;
|
|
|
|
if (!url)
|
|
|
|
return t.response.no_url;
|
|
|
|
if (!utils.YouTube.isValidEntityUrl(url)) {
|
|
|
|
url = s;
|
|
|
|
let row = await gh.db.get(gh.db.sql.select('playlists', false, ['url'],
|
|
|
|
gh.db.sql.where('name', '=', gh.db.sql.parameter(1))), [url]);
|
|
|
|
if (!row) {
|
|
|
|
this._logger.debug('Got invalid url for play command.');
|
|
|
|
return t.response.url_invalid;
|
|
|
|
} else {
|
|
|
|
let songcount = await this._connectAndPlay(gh, vc, row.url, n);
|
|
|
|
if (songcount)
|
|
|
|
return `Added ${songcount} songs to the queue.`;
|
|
|
|
else
|
|
|
|
return t.response.success;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let songcount = await this._connectAndPlay(gh, vc, url, n);
|
|
|
|
if (songcount)
|
|
|
|
return `Added ${songcount} songs to the queue.`;
|
|
|
|
else
|
|
|
|
return t.response.success;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async register(commandHandler) {
|
|
|
|
await this._loadTemplate();
|
|
|
|
|
|
|
|
let play = new cmdLib.Command(
|
|
|
|
this.template.play,
|
|
|
|
new cmdLib.Answer(async (m, k, s) => {
|
|
|
|
return await this._playFunction(m, k, s, this.template.play, false);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let playNext = new cmdLib.Command(
|
|
|
|
this.template.play_next,
|
|
|
|
new cmdLib.Answer(async (m, k, s) => {
|
|
|
|
return await this._playFunction(m, k, s, this.template.play_next, true);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let join = new cmdLib.Command(
|
|
|
|
this.template.join,
|
|
|
|
new cmdLib.Answer(async (m) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
if (m.member.voiceChannel)
|
|
|
|
await gh.musicPlayer.connect(m.member.voiceChannel);
|
|
|
|
else
|
|
|
|
return this.template.join.response.no_voicechannel;
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let stop = new cmdLib.Command(
|
|
|
|
this.template.stop,
|
|
|
|
new cmdLib.Answer(async (m) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
let vc = gh.musicPlayer.voiceChannel || m.member.voiceChannel;
|
|
|
|
if (gh.musicPlayer.connected && vc) {
|
|
|
|
let votes = gh.updateCommandVote(stop.name, m.author.tag);
|
|
|
|
let neededVotes = Math.ceil((vc.members.size - 1) / 2);
|
|
|
|
|
|
|
|
if (neededVotes <= votes.count || checkPermission(m, 'dj')) {
|
|
|
|
this._logger.debug(`Vote passed. ${votes.count} out of ${neededVotes} for stop or permission granted`);
|
|
|
|
gh.musicPlayer.stop();
|
|
|
|
gh.resetCommandVote(stop.name);
|
|
|
|
return this.template.stop.success;
|
|
|
|
} else {
|
|
|
|
this._logger.silly(`Vote count increased. ${votes.count} out of ${neededVotes} for stop`);
|
|
|
|
return `${votes.count} out of ${neededVotes} needed voted to stop.`;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return this.template.stop.not_playing;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let pause = new cmdLib.Command(
|
|
|
|
this.template.pause,
|
|
|
|
new cmdLib.Answer(async (m) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
if (gh.musicPlayer.playing) {
|
|
|
|
gh.musicPlayer.pause();
|
|
|
|
return this.template.pause.response.success;
|
|
|
|
} else {
|
|
|
|
return this.template.pause.response.not_playing;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let resume = new cmdLib.Command(
|
|
|
|
this.template.resume,
|
|
|
|
new cmdLib.Answer(async (m) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
if (gh.musicPlayer.playing) {
|
|
|
|
gh.musicPlayer.resume();
|
|
|
|
return this.template.resume.response.success;
|
|
|
|
} else {
|
|
|
|
return this.template.resume.response.not_playing;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let skip = new cmdLib.Command(
|
|
|
|
this.template.skip,
|
|
|
|
new cmdLib.Answer(async (m) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
let vc = gh.musicPlayer.voiceChannel || m.member.voiceChannel;
|
|
|
|
if (gh.musicPlayer.playing && vc) {
|
|
|
|
let votes = gh.updateCommandVote(skip.name, m.author.tag);
|
|
|
|
let neededVotes = Math.ceil((vc.members.size - 1) / 2);
|
|
|
|
|
|
|
|
if (neededVotes <= votes.count || checkPermission(m, 'dj')) {
|
|
|
|
this._logger.debug(`Vote passed. ${votes.count} out of ${neededVotes} for skip or permission granted`);
|
|
|
|
gh.musicPlayer.skip();
|
|
|
|
gh.resetCommandVote(skip.name);
|
|
|
|
return this.template.skip.response.success;
|
|
|
|
} else {
|
|
|
|
this._logger.silly(`Vote count increased. ${votes.count} out of ${neededVotes} for skip`);
|
|
|
|
return `${votes.count} out of ${neededVotes} needed voted to skip.`;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return this.template.skip.response.not_playing;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let clear = new cmdLib.Command(
|
|
|
|
this.template.clear,
|
|
|
|
new cmdLib.Answer(async (m) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
gh.musicPlayer.clear();
|
|
|
|
return this.template.clear.response.success;
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let mediaQueue = new cmdLib.Command(
|
|
|
|
this.template.media_queue,
|
|
|
|
new cmdLib.Answer(async (m) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
this._logger.debug(`Found ${gh.musicPlayer.queue.length} songs.`);
|
|
|
|
let description = '';
|
|
|
|
|
|
|
|
for (let i = 0; i < Math.min(gh.musicPlayer.queue.length, 9); i++) {
|
|
|
|
let entry = gh.musicPlayer.queue[i];
|
|
|
|
description += `[${entry.title}](${entry.url})\n`;
|
|
|
|
}
|
|
|
|
return new cmdLib.ExtendedRichEmbed(`${gh.musicPlayer.queue.length} songs in queue`)
|
|
|
|
.setDescription(description);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let mediaCurrent = new cmdLib.Command(
|
|
|
|
this.template.media_current,
|
|
|
|
new cmdLib.Answer(async (m) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
let song = gh.musicPlayer.song;
|
|
|
|
if (song)
|
|
|
|
return new cmdLib.ExtendedRichEmbed('Now playing:')
|
|
|
|
.setDescription(`[${song.title}](${song.url})`)
|
|
|
|
.setImage(utils.YouTube.getVideoThumbnailUrlFromUrl(song.url))
|
|
|
|
.setColor(0x00aaff);
|
|
|
|
else
|
|
|
|
return this.template.media_current.response.not_playing;
|
|
|
|
}, async (response) => {
|
|
|
|
let message = response.message;
|
|
|
|
let gh = await this._getGuildHandler(message.guild);
|
|
|
|
|
|
|
|
if (message.editable && gh.musicPlayer) {
|
|
|
|
let next = (song) => {
|
|
|
|
message.edit('', new cmdLib.ExtendedRichEmbed('Now playing:')
|
|
|
|
.setDescription(`[${song.title}](${song.url})`)
|
|
|
|
.setImage(utils.YouTube.getVideoThumbnailUrlFromUrl(song.url))
|
|
|
|
.setColor(0x00aaff));
|
|
|
|
if (message.id !== message.channel.lastMessageID) {
|
|
|
|
gh.musicPlayer.off('next', next);
|
|
|
|
message.delete();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
gh.musicPlayer.on('next', next);
|
|
|
|
gh.musicPlayer.on('stop', () => {
|
|
|
|
gh.musicPlayer.off('next', next);
|
|
|
|
message.delete();
|
|
|
|
});
|
|
|
|
response.on('delete', () => gh.musicPlayer.off('next', next));
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let shuffle = new cmdLib.Command(
|
|
|
|
this.template.shuffle,
|
|
|
|
new cmdLib.Answer(async (m) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
gh.musicPlayer.shuffle();
|
|
|
|
return this.template.shuffle.response.success;
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let toggleRepeat = new cmdLib.Command(
|
|
|
|
this.template.toggle_repeat,
|
|
|
|
new cmdLib.Answer(async (m) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
gh.musicPlayer.repeat = !gh.musicPlayer.repeat;
|
|
|
|
return gh.musicPlayer.repeat?
|
|
|
|
this.template.toggle_repeat.response.repeat_true :
|
|
|
|
this.template.toggle_repeat.response.repeat_false;
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let saveMedia = new cmdLib.Command(
|
|
|
|
this.template.save_media,
|
|
|
|
new cmdLib.Answer(async (m, k, s) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
let saveName = s.replace(k.url + ' ', '');
|
|
|
|
let row = await gh.db.get(gh.db.sql.select('playlists', false,
|
|
|
|
[gh.db.sql.count('*')], gh.db.sql.where('name', '=', gh.db.sql.parameter(1))), [saveName]);
|
|
|
|
if (!row || Number(row.count) === 0)
|
|
|
|
await gh.db.run(gh.db.sql.insert('playlists',
|
|
|
|
{name: gh.db.sql.parameter(1), url: gh.db.sql.parameter(2)}), [saveName, k.url]);
|
|
|
|
else
|
|
|
|
await gh.db.run(gh.db.sql.update('playlists',
|
|
|
|
{url: gh.db.sql.parameter(1)},
|
|
|
|
gh.db.sql.where('name', '=', gh.db.sql.parameter(2))), [k.url, saveName]);
|
|
|
|
return `Saved song/playlist as ${saveName}`;
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let deleteMedia = new cmdLib.Command(
|
|
|
|
this.template.delete_media,
|
|
|
|
new cmdLib.Answer(async (m, k, s) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
if (!s) {
|
|
|
|
return this.template.delete_media.response.no_name;
|
|
|
|
} else {
|
|
|
|
await gh.db.run(gh.db.sql.delete('playlists', gh.db.sql.where('name', '=', gh.db.sql.parameter(1))), [s]);
|
|
|
|
return `Deleted ${s} from saved media`;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let savedMedia = new cmdLib.Command(
|
|
|
|
this.template.saved_media,
|
|
|
|
new cmdLib.Answer(async (m) => {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
let response = '';
|
|
|
|
let rows = await gh.db.all(gh.db.sql.select('playlists', false, ['name', 'url']));
|
|
|
|
for (let row of rows)
|
|
|
|
response += `[${row.name}](${row.url})\n`;
|
|
|
|
|
|
|
|
if (rows.length === 0)
|
|
|
|
return this.template.saved_media.response.no_saved;
|
|
|
|
else
|
|
|
|
return new cmdLib.ExtendedRichEmbed('Saved Songs and Playlists')
|
|
|
|
.setDescription(response)
|
|
|
|
.setFooter(`Play a saved entry with play [Entryname]`);
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let volume = new cmdLib.Command(
|
|
|
|
this.template.volume,
|
|
|
|
new cmdLib.Answer(async (m, k) => {
|
|
|
|
let volume = Number(k.volume);
|
|
|
|
if (volume && volume <= 100 && volume >= 0) {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
gh.musicPlayer.setVolume(Math.round(volume)/100);
|
|
|
|
await gh.db.setSetting('musicPlayerVolume', Math.round(volume)/100);
|
|
|
|
return `Set music volume to **${volume}**`;
|
|
|
|
} else {
|
|
|
|
return this.template.volume.response.invalid;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
let quality = new cmdLib.Command(
|
|
|
|
this.template.quality,
|
|
|
|
new cmdLib.Answer(async (m, k) => {
|
|
|
|
let allowed = ['highest', 'lowest', 'highestaudio', 'lowestaudio'];
|
|
|
|
if (allowed.includes(k.quality)) {
|
|
|
|
let gh = await this._getGuildHandler(m.guild);
|
|
|
|
gh.musicPlayer.quality = k.quality;
|
|
|
|
await gh.db.setSetting('musicPlayerQuality', k.quality);
|
|
|
|
return `Set music quality to **${k.quality}**`;
|
|
|
|
} else {
|
|
|
|
return this.template.quality.response.invalid;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
// register commands
|
|
|
|
commandHandler
|
|
|
|
.registerCommand(play)
|
|
|
|
.registerCommand(playNext)
|
|
|
|
.registerCommand(join)
|
|
|
|
.registerCommand(stop)
|
|
|
|
.registerCommand(pause)
|
|
|
|
.registerCommand(resume)
|
|
|
|
.registerCommand(skip)
|
|
|
|
.registerCommand(clear)
|
|
|
|
.registerCommand(mediaQueue)
|
|
|
|
.registerCommand(mediaCurrent)
|
|
|
|
.registerCommand(shuffle)
|
|
|
|
.registerCommand(toggleRepeat)
|
|
|
|
.registerCommand(saveMedia)
|
|
|
|
.registerCommand(deleteMedia)
|
|
|
|
.registerCommand(savedMedia)
|
|
|
|
.registerCommand(volume)
|
|
|
|
.registerCommand(quality);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.assign(exports, {
|
|
|
|
module: MusicCommandModule
|
|
|
|
});
|