UNPKG

discord-dynamic-music-bot-addon

Version:
1,048 lines (1,047 loc) 63.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const discord_js_1 = require("discord.js"); const messages_1 = require("./messages"); const Youtube_1 = require("./Youtube"); const language_1 = require("./language"); const GuildPlayer_1 = require("./GuildPlayer"); const yt_core_discord_1 = require("./yt-core-discord"); const youtubeTester = new RegExp(/http(?:s?):\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\\_]*)(&(amp;)?‌​[\w\\?‌​=]*)?/g); const urlTester = new RegExp(/https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,}/g); const DEFAULT_PLAYER_UPDATE = 10; const DEFAULT_WAIT_TIME_BETWEEN_TRACKS = 2; const DEFAULT_SELF_DELETE_TIME = 5; const DEFAULT_LEAVE_TIME = 20; const guildPlayer = new WeakMap(); const playerUpdateRate = new WeakMap(); const selfDeleteTime = new WeakMap(); const leaveVoiceChannelAfter = new WeakMap(); const leaveVoiceChannelAfterAllMembersLeft = new WeakMap(); const maxTrackLength = new WeakMap(); const discordClient = new WeakMap(); const youtube = new WeakMap(); const autoQueryDetection = new WeakMap(); const autoPlaylistDetection = new WeakMap(); const waitTimeBetweenTracks = new WeakMap(); const maxItemsInPlayList = new WeakMap(); const maxUserItemsInPlayList = new WeakMap(); const playlistParseWait = new WeakMap(); const multipleParser = new WeakMap(); const playlistParse = new WeakMap(); const votePercentage = new WeakMap(); const coolDown = new WeakMap(); const deleteUserMessage = new WeakMap(); const hardDeleteUserMessage = new WeakMap(); const reactionButtons = new WeakMap(); const destroyed = new WeakMap(); const userCoolDownSet = new WeakMap(); const suggestReplay = new WeakMap(); const PLAYER_VERSION = '2.1.9 build'; class YoutubePlayer { /** * Constructor that constructs * @param {string} string youtube api key * @param {PlayerLanguage} PlayerLanguage PlayerLanguage */ constructor(youtubeApiKey, options) { if (!options) options = {}; if (options && typeof options !== 'object') throw new TypeError('options must be an object!'); if (!youtubeApiKey) { youtubeApiKey = ''; console.warn('YouTube Api key has not been not provided! Rich embeds are going to contain less information about the video!'); } else { try { const youtubeClass = new Youtube_1.Youtube(youtubeApiKey); youtube.set(this, youtubeClass); } catch (error) { console.warn('YouTube Api key that has been provided is not valid! Rich embeds are going to contain less information about the video!'); } } if (typeof youtubeApiKey !== 'string') throw new TypeError(`Expected string got ${typeof youtubeApiKey}`); guildPlayer.set(this, {}); if (options && options.language) this.defaultLanguage = options.language; else { language_1.playerLanguage.set(this, new language_1.Language()); } this.playerUpdateRate = (options && options.messageUpdateRate !== undefined) ? options.messageUpdateRate : DEFAULT_PLAYER_UPDATE; this.selfDelete = (options && options.selfDeleteTime !== undefined) ? options.selfDeleteTime : DEFAULT_SELF_DELETE_TIME; this.waitTimeBetweenTracks = (options && options.waitTimeBetweenTracks !== undefined) ? options.waitTimeBetweenTracks : DEFAULT_WAIT_TIME_BETWEEN_TRACKS; this.deleteUserMessages = (options && options.deleteUserMessage !== undefined) ? options.deleteUserMessage : true; this.reactionButtons = (options && options.reactionButtons !== undefined) ? options.reactionButtons : true; this.parsePlaylistUrl = (options && options.playlistParse !== undefined) ? options.playlistParse : false; this.maxTrackLength = (options && options.maxTrackLength !== undefined) ? options.maxTrackLength : 60 * 3; this.maxItemsInPlaylist = (options && options.maxItemsInPlayList !== undefined) ? options.maxItemsInPlayList : 100; this.maxUsersItemsInPlaylist = (options && options.maxUserItemsInPlayList !== undefined) ? options.maxUserItemsInPlayList : 10; this.votePercentage = (options && options.votePercentage !== undefined) ? options.votePercentage : 60; this.playListWaitTime = (options && options.playlistParseWait !== undefined) ? options.playlistParseWait : 2; this.leaveVoiceChannelAfter = (options && options.leaveVoiceChannelAfter !== undefined) ? options.leaveVoiceChannelAfter : DEFAULT_LEAVE_TIME; this.leaveVoiceChannelAfterWhenNoPlayersInChannel = (options && options.leaveVoiceChannelAfterAllMembersLeft !== undefined) ? options.leaveVoiceChannelAfterAllMembersLeft : DEFAULT_LEAVE_TIME; this.userCoolDown = (options && options.coolDown !== undefined) ? options.coolDown : 5; this.autoQuerySearch = (options && options.autoQueryDetection !== undefined) ? options.autoQueryDetection : true; this.autoPlaylistDetection = (options && options.autoPlaylistDetection !== undefined) ? options.autoPlaylistDetection : false; this.multipleVideoParser = (options && options.multipleParser !== undefined) ? options.multipleParser : true; this.hardDeleteUserMessages = (options && options.hardDeleteUserMessage !== undefined) ? options.hardDeleteUserMessage : false; this.suggestReplayButtons = (options && options.suggestReplay !== undefined) ? options.suggestReplay : 20; destroyed.set(this, false); userCoolDownSet.set(this, new Set()); } /** * Should delete user messages? * @param {boolean} boolean if set to true if possible the messages sent by user are going to be deleted. */ set deleteUserMessages(trueFalse) { if (typeof trueFalse !== 'boolean') throw new Error(`Expected boolean got ${typeof trueFalse}`); deleteUserMessage.set(this, trueFalse); } /** * Should parse playlist links. * @param {boolean} boolean. */ set parsePlaylistUrl(trueFalse) { if (typeof trueFalse !== 'boolean') throw new Error(`Expected boolean got ${typeof trueFalse}`); playlistParse.set(this, trueFalse); } /** * Should auto detect playlist link? Requires autoQueryDetection to be enabled. * @param {boolean} boolean. */ set autoPlaylistDetection(trueFalse) { if (typeof trueFalse !== 'boolean') throw new Error(`Expected boolean got ${typeof trueFalse}`); if (trueFalse && !playlistParse.get(this)) throw new Error(`playlistParse has to be enabled in order for this function to work`); if (trueFalse && !autoQueryDetection.get(this)) throw new Error(`autoQueryDetection has to be enabled in order for this function to work`); autoPlaylistDetection.set(this, trueFalse); } /** * how much this should wait to parse next playlist time * @param {number} number seconds */ set playListWaitTime(seconds) { if (typeof seconds !== 'number') throw new Error(`Expected number got ${typeof seconds}`); playlistParseWait.set(this, seconds); } /** * Should search if none of the player commands where found in message * @param {boolean} boolean Enables/disables autoQuerySearch */ set autoQuerySearch(trueFalse) { if (typeof trueFalse !== 'boolean') throw new Error(`Expected boolean got ${typeof trueFalse}`); autoQueryDetection.set(this, trueFalse); } /** * how much items can be in playlist. * @param {number} number playlist limit. */ set maxItemsInPlaylist(items) { if (typeof items !== 'number') throw new Error(`Expected number got ${typeof items}`); if (items < 1) throw new Error('max video length cannot be lower than 1 item'); maxItemsInPlayList.set(this, items); } /** * How much track can a user have in playlist * @param {number} number user playlist limit */ set maxUsersItemsInPlaylist(items) { if (typeof items !== 'number') throw new Error(`Expected number got ${typeof items}`); if (items < 1) throw new Error('max video length cannot be lower than 1 item'); maxUserItemsInPlayList.set(this, items); } /** * User command cool down. How much does bot needs to wait before accepting new command * @param {number} number time */ set userCoolDown(seconds) { if (typeof seconds !== 'number') throw new Error(`Expected number got ${typeof seconds}`); if (seconds < 0) throw new Error('cooldown cannot be lower than 0'); coolDown.set(this, seconds); } /** * Allow multiple videos parsing. This also enables playlist parsing * @param {boolean} bool time */ set multipleVideoParser(bool) { if (typeof bool !== 'boolean') throw new Error(`Expected boolean got ${typeof bool}`); multipleParser.set(this, bool); } /** * Set wait time between tracks * @param {number} number how much should player wait. */ set waitTimeBetweenTracks(seconds) { if (typeof seconds !== 'number') throw new Error(`Expected number got ${typeof seconds}`); waitTimeBetweenTracks.set(this, seconds * 1000); } /** * max track length * @param {number} number max track length */ set maxTrackLength(seconds) { if (typeof seconds !== 'number') throw new Error(`Expected number got ${typeof seconds}`); if (seconds < 0.0834) throw new Error('max video length cannot be lower than 5 seconds'); maxTrackLength.set(this, seconds); } /** * Set player edit/update rate * @param {number} number how fast/slow should player message be updated. */ set playerUpdateRate(seconds) { if (typeof seconds !== 'number') throw new Error(`Expected number got ${typeof seconds}`); if (seconds < 5) throw new Error('update rate cannot be lower than 5 seconds'); playerUpdateRate.set(this, seconds * 1000); } /** * When bot runs out of songs how long should wait before disconnecting voice channel * @param {number} number in seconds. If set to 0 it will leave immediately. */ set leaveVoiceChannelAfter(seconds) { if (typeof seconds !== 'number') throw new Error(`Expected number got ${typeof seconds}`); leaveVoiceChannelAfter.set(this, seconds * 1000); } /** * When all uses leave voice channel how long should bot wait before destroying player * @param {number} number in seconds. If set to 0 it will leave immediately . */ set leaveVoiceChannelAfterWhenNoPlayersInChannel(seconds) { if (typeof seconds !== 'number') throw new Error(`Expected number got ${typeof seconds}`); leaveVoiceChannelAfterAllMembersLeft.set(this, seconds * 1000); } /** * percentage of vote for to be executed * @param {number} number vote percentage */ set votePercentage(percentage) { if (typeof percentage !== 'number') throw new Error(`Expected number got ${typeof percentage}`); if (percentage < 0) throw new Error(`Number cannot be lower than 0`); if (percentage > 100) throw new Error(`Number cannot be higher than 100`); votePercentage.set(this, percentage / 100); } /** * Should message be garbage collected * @param {number} seconds if 0 no others numbers are seconds */ set selfDelete(seconds) { if (typeof seconds !== 'number') throw new Error(`Expected number got ${typeof seconds}`); if (seconds < 0) throw new Error('Cannot be below 0'); selfDeleteTime.set(this, seconds * 1000); } /** * Delete every message in channels when player is active * @param {boolean} boolean */ set hardDeleteUserMessages(bool) { if (typeof bool !== 'boolean') throw new Error(`Expected boolean got ${typeof bool}`); hardDeleteUserMessage.set(this, bool); } /** * Custom player language pack * @param {Language} languePack Custom langue pack */ set defaultLanguage(playerLang) { if (typeof playerLang !== 'object') throw new Error(`Expected object got ${typeof playerLang}`); language_1.playerLanguage.set(this, new language_1.Language(playerLang)); } /** * Create play buttons with that you can control player without use of any other command * @param {bool} boolean enable/disable */ set reactionButtons(bool) { if (typeof bool !== 'boolean') throw new Error(`Expected boolean got ${typeof bool}`); reactionButtons.set(this, bool); } /** * How much before the song should show the replay song button * reaction button feature has to be enabled in order for this to work * @param {seconds} seconds if 0 the button is not going to show up */ set suggestReplayButtons(seconds) { if (typeof seconds !== 'number') throw new Error(`Expected number got ${typeof seconds}`); if (seconds < 0) throw new Error('Cannot be below 0'); if (!reactionButtons.get(this)) throw new Error(`reactionButtons feature has to be enabled in order for this feature to work`); suggestReplay.set(this, seconds * 1000); } /** * @param {Message} Message Discord message * @param {string} String prefix * @param {playerLang} PlayerLang object * @returns {boolean} It's going to return true if command is valid. */ onMessagePrefix(message, prefix, playerLang) { if (!prefix) throw new Error('Prefix cannot be undefined'); if (typeof prefix !== 'string') throw new Error('Prefix must be string'); return this.onMessage(message, message.cleanContent.slice(prefix.length).trim(), prefix, playerLang); } /** * Destroys player and makes entire class useless * If no call back if provided it going to return Promise; * @param {function} callback call function */ async destroy(callback) { destroyed.set(this, true); if (!callback) { return new Promise(resolve => { this.destroy(() => { resolve(); }); }); } const client = discordClient.get(this); if (!client) return callback(); const theGuildData = guildPlayer.get(this); const keys = Object.keys(theGuildData); for (const key of keys) { const guild = client.guilds.resolve(key); if (!guild) break; const guildPlayer = getGuildPlayer(this, guild); const lang = guildPlayer ? guildPlayer.language : language_1.playerLanguage.get(this); await destroyGuildPlayer(this, guild, lang, true); } callback(); } /** * @param {Message} Message Discord message * @param {string} String Discord message without prefix * @param {string} String Optional just for help command * @returns {boolean} It's going to return true if command is valid. */ onMessage(message, messageContentWithOutPrefix, prefix, playerLang) { if (!message.guild || message.author.bot || !message.content) return false; if (destroyed.get(this)) return false; stealAndSetClient(this, message.client); const userCoolDown = userCoolDownSet.get(this); if (prefix) language_1.playerLanguage.get(this).setPrefix(prefix); const guildPlayer = getGuildPlayer(this, message.guild); let lang; if (playerLang) lang = new language_1.Language(playerLang); else if (guildPlayer) lang = guildPlayer.language; else lang = language_1.playerLanguage.get(this); const args = messageContentWithOutPrefix.split(' '); if (!lang.lang.commands.playerCommands.includes(args[0].toLowerCase())) { return false; } const language = lang.getLang(); const commands = language.commands; const channel = message.channel; if (!message.guild || !message.guild.me) return false; const me = channel.permissionsFor(message.guild.me); if (!me) return false; if (!me.has('SEND_MESSAGES')) return false; let checker = messageContentWithOutPrefix.replace(/ {2}/g, ' '); if (checker.indexOf(' ') === -1) return false; const selfDeleteT = selfDeleteTime.get(this); if (commendChecker(removeFistWord(checker), commands.help)) { playerHelp(this, message, lang); delUserMessage(this, message); return true; } if (commendChecker(removeFistWord(checker), commands.version)) { playerVersion(this, message); delUserMessage(this, message); return true; } if (guildPlayer && guildPlayer.playerMessage && message.member && guildPlayer.playerMessage.channel !== message.channel) { const channel = guildPlayer.playerMessage.channel; const permissions = channel.permissionsFor(message.member); if (permissions && !permissions.has('VIEW_CHANNEL')) messages_1.errorInfo(message.channel, language.player.wrongChannelNoAccess.replace(/<CHANNEL>/, channel.toString()), language.error, selfDeleteT); else if (permissions && !permissions.has('SEND_MESSAGES')) messages_1.errorInfo(message.channel, language.player.wrongChannelNoPermissions.replace(/<CHANNEL>/, channel.toString()), language.error, selfDeleteT); else messages_1.errorInfo(message.channel, language.player.wrongChannel.replace(/<CHANNEL>/, channel.toString()), language.error, selfDeleteT); return true; } if (userCoolDown.has(message.author.id)) { messages_1.errorInfo(message.channel, language.sendingMessageToQuickly.replace(/<TIME>/g, coolDown.get(this).toString()), language.error, selfDeleteT); return true; } else { userCoolDown.add(message.author.id); setTimeout(() => { userCoolDown.delete(message.author.id); }, coolDown.get(this) * 1000); } if (commendChecker(checker, commands.playerCommands, false)) { playerHelp(this, message, lang); return true; } if (commendChecker(checker, commands.version, false)) { playerVersion(this, message); return true; } if (commendChecker(checker, commands.playerCommands)) { checker = removeFistWord(checker); } else return false; // just to throw object out of stack so we can return boolean value to the user setTimeout(() => { if (!message.member || !message.member.voice) return; const voiceChannel = message.member.voice.channel; if (!voiceChannel) { return messages_1.errorInfo(message.channel, language.notInVoiceChannel, language.error, selfDeleteTime.get(this)); } else if (!voiceChannel.joinable) { return messages_1.errorInfo(message.channel, language.cannotConnect, language.error, selfDeleteTime.get(this)); } let executed = false; if (commendChecker(checker, commands.destroy)) { if (!isSomethingPlaying(this, message, lang)) return; if (guildPlayer && message.guild && message.guild.voice && message.guild.voice.channel && guildPlayer.canExecute(message.member)) message.guild.voice.channel.leave(); executed = true; return; } else if (commendChecker(checker, commands.search)) { delUserMessage(this, message); const spaceIndex = messageContentWithOutPrefix.indexOf(' ', 2); youtubeLuckSearch(this, message, messageContentWithOutPrefix.slice(spaceIndex).trim(), lang); executed = true; return; } else if (commendChecker(checker, commands.url)) { const YTUrls = checker.match(youtubeTester); if (YTUrls) addYoutubeToQueue(this, message, YTUrls, undefined, lang); else messages_1.errorInfo(message.channel, language.onlyYoutubeLinks, language.error, selfDeleteTime.get(this)); executed = true; } else if (commendChecker(checker, commands.next)) { if (!isSomethingPlaying(this, message, lang)) return; commandNextTrack(this, message, lang); executed = true; return; } else if (commendChecker(checker, commands.previous)) { if (!isSomethingPlaying(this, message, lang)) return; commandPreviousTrack(this, message, lang); executed = true; return; } else if (commendChecker(checker, commands.pause)) { if (!isSomethingPlaying(this, message, lang)) return; commandPauseOrReplyTrack(this, message, lang); executed = true; return; } else if (commendChecker(checker, commands.resume)) { if (!isSomethingPlaying(this, message, lang)) return; commandPauseOrReplyTrack(this, message, lang); executed = true; return; } else if (commendChecker(checker, commands.replay)) { if (!isSomethingPlaying(this, message, lang)) return; commandReplayTrack(this, message, lang); executed = true; return; } else if (commendChecker(checker, commands.replay)) { if (!isSomethingPlaying(this, message, lang)) return; commandReplayTrack(this, message, lang); executed = true; return; } else if (commendChecker(checker, commands.loop)) { if (!isSomethingPlaying(this, message, lang)) return; commandLoopTrack(this, message, lang); executed = true; return; } else if (commendChecker(checker, commands.playlist)) { playlistCommand(this, message, checker, commands, lang); executed = true; return; } else { if (autoQueryDetection.get(this)) { const YTUrls = checker.match(youtubeTester); const urls = checker.match(urlTester); const playlistId = /[&?]list=([^&]+)/i.test(checker); if (YTUrls && urls && playlistId && playlistParse.get(this)) parsePlayList(this, message, urls[0], lang, true); else if (YTUrls) addYoutubeToQueue(this, message, YTUrls, undefined, lang); else if (checker.match(urlTester)) { messages_1.errorInfo(message.channel, language.error, language.onlyYoutubeLinks); } else youtubeLuckSearch(this, message, checker, lang); } else messages_1.errorInfo(message.channel, lang.incorrectUse(), language.error, selfDeleteT); } if (executed) delUserMessage(this, message); }); return true; } } exports.YoutubePlayer = YoutubePlayer; function delUserMessage(youtubePlayer, message) { if (!message.guild || !message.guild.me) return; const canDelete = deleteUserMessage.get(youtubePlayer); if (!canDelete) return; const channel = message.channel; const permissions = channel.permissionsFor(message.guild.me); if (permissions && permissions.has('MANAGE_MESSAGES')) message.delete().catch(err => { message.client.emit('error', err); }); } function commendChecker(messageContent, aliases, includes = true) { if (includes) messageContent = getFirstWord(messageContent); for (const command of aliases) { if (messageContent.toLowerCase() === command.toLowerCase()) { return true; } } return false; } function getFirstWord(text) { const spaceIndex = text.indexOf(' '); if (spaceIndex !== -1) { text = text.slice(0, spaceIndex).trim(); } return text; } function removeFistWord(text) { const spaceIndex = text.indexOf(' '); if (spaceIndex !== -1) { text = text.slice(spaceIndex).trim(); } return text; } function isSomethingPlaying(youtubePlayer, message, lang) { if (!message.guild) return; const language = lang.getLang(); const guildData = getGuildPlayer(youtubePlayer, message.guild); const selfDeleteT = selfDeleteTime.get(youtubePlayer); if (!guildData) { messages_1.errorInfo(message.channel, language.player.nothingPlaying, language.error, selfDeleteT); return false; } return true; } function playlistCommand(youtubePlayer, message, checker, commands, lang) { if (!message.guild) return; const args = checker.split(' '); const language = lang.getLang(); const guildPlayer = getOrSetGuildPlayer(youtubePlayer, message, lang); const selfDelete = selfDeleteTime.get(youtubePlayer); const shouldBeNumber = parseInt(args[2]); if (!guildPlayer) return; if (commendChecker(args[1], commands.playlistCommands.parse)) { if (!playlistParse.get(youtubePlayer)) { return messages_1.errorInfo(message.channel, language.player.featureDisabled, language.error, selfDelete); } if (youtubeTester.test(checker)) { const playlistId = /[&?]list=([^&]+)/i.test(checker); const urls = checker.match(urlTester); if (playlistId && urls) return parsePlayList(youtubePlayer, message, urls[0], lang); else return messages_1.errorInfo(message.channel, language.playlistNotFound, language.error, selfDelete); } else if (args[2]) { return messages_1.errorInfo(message.channel, language.onlyYoutubeLinks, language.error, selfDelete); } else return messages_1.errorInfo(message.channel, lang.incorrectUse(), language.error, selfDelete); } else if (commendChecker(args[1], commands.playlistCommands.remove)) { if (isNaN(shouldBeNumber)) { return messages_1.errorInfo(message.channel, lang.incorrectUse(), language.error, selfDelete); } else { const item = guildPlayer.playlist.find(i => i.index === shouldBeNumber); if (item) { if ((item.message && item.message.author === message.author) || canExecute(youtubePlayer, message, lang)) { guildPlayer.removeItemFromPlaylist(item); } else return messages_1.errorInfo(message.channel, language.missingPermission, language.error, selfDelete); } else return messages_1.errorInfo(message.channel, language.player.playlistUnableToFindItem, language.error, selfDelete); } } else if (commendChecker(args[1], commands.playlistCommands.force)) { if (canExecute(youtubePlayer, message, lang)) { if (isNaN(shouldBeNumber)) { return messages_1.errorInfo(message.channel, lang.incorrectUse(), language.error, selfDelete); } else { let item = guildPlayer.playlist.find(i => i.index === shouldBeNumber); if (!item) item = guildPlayer.previous.find(i => i.index === shouldBeNumber); if (item) { guildPlayer.setSong(item); guildPlayer.nextTrack(); return messages_1.info(message.channel, language.player.forceReplay, language.info, selfDeleteTime.get(youtubePlayer)); } else return messages_1.errorInfo(message.channel, language.player.playlistUnableToFindItem, language.error, selfDeleteTime.get(youtubePlayer)); } } else return messages_1.errorInfo(message.channel, language.missingPermission, language.error, selfDeleteTime.get(youtubePlayer)); } else if (commendChecker(args[1], commands.playlistCommands.shuffle)) { if (canExecute(youtubePlayer, message, lang)) { if (guildPlayer.shuffle()) return messages_1.info(message.channel, language.player.playlistShuffled, language.info, selfDeleteTime.get(youtubePlayer)); else return messages_1.errorInfo(message.channel, language.player.playlistNothingToShuffle, language.error, selfDeleteTime.get(youtubePlayer)); } } else if (commendChecker(args[1], commands.playlistCommands.sort)) { if (canExecute(youtubePlayer, message, lang)) { if (guildPlayer.sort()) return messages_1.info(message.channel, language.player.playlistSorted, language.info, selfDeleteTime.get(youtubePlayer)); else return messages_1.errorInfo(message.channel, language.player.playlistAlreadySorted, language.error, selfDeleteTime.get(youtubePlayer)); } } else { return messages_1.errorInfo(message.channel, lang.incorrectUse(), language.error); } } function stealAndSetClient(youtubePlayer, client) { if (!discordClient.get(youtubePlayer)) { discordClient.set(youtubePlayer, client); client.on('voiceStateUpdate', guildMember => { if (!guildMember.guild || !guildMember.guild.voice || !guildMember.guild.voice.channel) return; const voiceChannel = guildMember.guild.voice.channel; if (!voiceChannel) { client.emit('debug', 'Bot has been disconnected from the voice channel'); const guildPlayer = getGuildPlayer(youtubePlayer, guildMember.guild); const lang = guildPlayer ? guildPlayer.language : language_1.playerLanguage.get(youtubePlayer); destroyGuildPlayer(youtubePlayer, guildMember.guild, lang); } else { const theGuildPlayer = getGuildPlayer(youtubePlayer, guildMember.guild); if (!theGuildPlayer) return; const members = !!voiceChannel.members.filter(m => !m.user.bot).size; if (theGuildPlayer.timeOutPlayerLeaveAllMemberLeft) { clearTimeout(theGuildPlayer.timeOutPlayerLeaveAllMemberLeft); theGuildPlayer.timeOutPlayerLeaveAllMemberLeft = undefined; } if (!members) { theGuildPlayer.timeOutPlayerLeaveAllMemberLeft = setTimeout(() => { if (guildMember.guild && guildMember.guild.voice && guildMember.guild.voice.connection && guildMember.guild.voice.connection.channel) guildMember.guild.voice.connection.channel.leave(); }, leaveVoiceChannelAfterAllMembersLeft.get(youtubePlayer)); } } }); const emojiWatcher = async (theGuildPlayer, guildMembers, messageReaction, message) => { switch (messageReaction.emoji.name) { case '⏸️': if (theGuildPlayer.playerMessage === message && theGuildPlayer.setVote(guildMembers, 'votePauseResume')) theGuildPlayer.recreateOrRecreatePlayerButtons(); return; case '▶️': if (theGuildPlayer.playerMessage === message && theGuildPlayer.setVote(guildMembers, 'votePauseResume')) theGuildPlayer.recreateOrRecreatePlayerButtons(); return; case '⏮️': return theGuildPlayer.playerMessage === message && theGuildPlayer.setVote(guildMembers, 'votePrevious'); case '⏭️': return theGuildPlayer.playerMessage === message && theGuildPlayer.setVote(guildMembers, 'voteNext'); case '🔁': if (theGuildPlayer.playerMessage === message && theGuildPlayer.setVote(guildMembers, 'voteReplay')) theGuildPlayer.recreateOrRecreatePlayerButtons(); return; case '🔂': return theGuildPlayer.setVote(guildMembers, 'voteLoop'); case '❎': if (theGuildPlayer.playerMessage === message && message.guild && message.guild.voice && message.guild.voice.channel && theGuildPlayer.canExecute(guildMembers)) message.guild.voice.channel.leave(); else theGuildPlayer.removeFromPlayListByMessage(message); break; // case '🔀': // if (theGuildPlayer.setVoteShuffle(guildMembers)) recreateOrRecreatePlayerButtons(theGuildPlayer); // break; default: break; } }; const onMessageReactionAddOrRemove = async (messageReaction) => { const channel = messageReaction.message.channel; const guild = channel.guild; if (!reactionButtons.get(youtubePlayer)) return; if (client.user !== messageReaction.message.author) return; const theGuildPlayer = getGuildPlayer(youtubePlayer, guild); if (!theGuildPlayer) return; const guildMembers = theGuildPlayer.getFromVoiceAndMessageReactions(messageReaction); if (!guildMembers.length) return; emojiWatcher(theGuildPlayer, guildMembers, messageReaction, messageReaction.message); }; client.on('messageReactionAdd', async (messageReaction) => { if (!messageReaction.message.guild) return; const channel = messageReaction.message.channel; const guild = channel.guild; if (!guild || !guild.me) return; const channelPermissions = channel.permissionsFor(guild.me); if (channelPermissions && channelPermissions.has('MANAGE_MESSAGES')) { const theGuildPlayer = getGuildPlayer(youtubePlayer, messageReaction.message.guild); if (theGuildPlayer) { const pMsg = theGuildPlayer.playerMessage; const playlistFiltered = theGuildPlayer.playlist.filter(p => p.message); const msgs = playlistFiltered.map(u => u.message); const message = [...msgs, pMsg]; if (!message.includes(messageReaction.message)) return; } if (theGuildPlayer && theGuildPlayer.playerMessage) { await removeUsersThatShouldNotReact(theGuildPlayer, messageReaction); if (!['⏸️', '▶️', '⏮️', '⏭️', '🔁', '🔂', '❎'].includes(messageReaction.emoji.name)) { const users = messageReaction.users.cache.map(u => u); for (const user of users.map(u => u)) { try { messageReaction.users.remove(user); } catch (_) { break; } } } } onMessageReactionAddOrRemove(messageReaction); } }); client.on('messageReactionRemove', async (messageReaction) => { if (!messageReaction.message || !messageReaction.message.guild) return; const channel = messageReaction.message.channel; const guild = channel.guild; if (!guild || !guild.me) return; const theGuildPlayer = getGuildPlayer(youtubePlayer, messageReaction.message.guild); if (theGuildPlayer) { const pMsg = theGuildPlayer.playerMessage; const playlistFiltered = theGuildPlayer.playlist.filter(p => p.message); const msgs = playlistFiltered.map(u => u.message); const message = [...msgs, pMsg]; if (!message.includes(messageReaction.message)) return; } const channelPermissions = channel.permissionsFor(guild.me); if (channelPermissions && channelPermissions.has('MANAGE_MESSAGES')) onMessageReactionAddOrRemove(messageReaction); }); client.on('messageDelete', async (message) => { if (message.author !== client.user) return; if (!message.guild) return; const guildPlayer = getGuildPlayer(youtubePlayer, message.guild); if (!guildPlayer) return; if (guildPlayer.playerMessage === message) { guildPlayer.deletePlayerMessage(); await guildPlayer.updatePlayer(); return; } if (guildPlayer.removeFromPlayListByMessage(message, true) && guildPlayer.length < 2) guildPlayer.recreateOrRecreatePlayerButtons(); }); client.on('guildDelete', guild => { const guildPlayer = getGuildPlayer(youtubePlayer, guild); const lang = guildPlayer ? guildPlayer.language : language_1.playerLanguage.get(youtubePlayer); destroyGuildPlayer(youtubePlayer, guild, lang); }); client.on('channelDelete', channel => { const guildData = guildPlayer.get(youtubePlayer); for (const k of Object.entries(guildData)) { if (k[1].playerChannel === channel) { const pc = k[1].playerChannel; const voice = pc.guild.voice; if (voice && voice.channel) voice.channel.leave(); return; } } }); client.on('channelUpdate', channel => { const guildData = guildPlayer.get(youtubePlayer); for (const k of Object.entries(guildData)) { if (k[1].playerChannel === channel) { const me = k[1].playerChannel.guild.me; if (!me) continue; const permissions = k[1].playerChannel.permissionsFor(me); if (permissions && !permissions.has('SEND_MESSAGES')) { const voice = k[1].playerChannel.guild.voice; if (voice && voice.channel) voice.channel.leave(); return; } } } }); client.on('message', m => { const msg = m; if (!msg.guild) return; if (msg.author === msg.client.user) return; if (!hardDeleteUserMessage.get(youtubePlayer)) return; const player = getGuildPlayer(youtubePlayer, msg.guild); if (!!player && player.playerChannel === msg.channel) { msg.delete().catch(err => { msg.client.emit('error', err); }); } }); client.on('voiceStateUpdate', voiceChannel => { if (!voiceChannel.guild.voice || !voiceChannel.guild.voice.channel) { const guildPlayer = getGuildPlayer(youtubePlayer, voiceChannel.guild); if (!guildPlayer) return; const language = language_1.playerLanguage.get(youtubePlayer); destroyGuildPlayer(youtubePlayer, voiceChannel.guild, language); } }); } } async function removeUsersThatShouldNotReact(guildPlayer, messageReaction) { const guildMembers = guildPlayer.getFromVoiceAndMessageReactions(messageReaction); const messageGuildMembersReaction = guildPlayer.getGuildMembersFromReactions(messageReaction); const membersThatShouldNotReact = messageGuildMembersReaction.filter(g => !guildMembers.includes(g)); for (const member of membersThatShouldNotReact) { try { await messageReaction.users.remove(member); } catch (err) { messageReaction.message.client.emit('error', err); } } } async function addYoutubeToQueue(youtubePlayer, message, urls, playlist = '', lang) { if (!message.guild || !message.member) return; const player = getOrSetGuildPlayer(youtubePlayer, message, lang); const language = lang.getLang(); if (isPlaylistFull(message, youtubePlayer, lang)) return; if (!multipleParser.get(youtubePlayer)) urls.length = 1; const playlistProgress = (index) => { return language.player.parsingPlaylist.replace(/<URL>/g, `\n<${playlist}>`) + ` ${index}/${urls.length}`; }; const msgForInfoMsg = playlist ? playlistProgress(0) : language.player.searching.replace(/<URL>/g, `\n<${urls.join('>\n <')}>`); const infoMessage = await messages_1.info(message.channel, msgForInfoMsg, language.info); for (let i = 0; i < urls.length; i++) { const youtubeClass = youtube.get(youtubePlayer); if ((isPlaylistFull(message, youtubePlayer, lang))) { player.deletePlayerMessage(); await player.updatePlayer(); break; } const title = player.isAlreadyOnPlaylistByUrl(urls[i]); if (title) { if (player.currentPlayListItem && player.currentPlayListItem.videoInfo.title === title) await messages_1.errorInfo(message.channel, `${message.author} ${language.isCurrentlyPlaying}\n${title}`, language.error, selfDeleteTime.get(youtubePlayer)); else await messages_1.errorInfo(message.channel, `${message.author} ${language.alreadyOnPlaylist}\n${title}`, language.error, selfDeleteTime.get(youtubePlayer)); await infoMessage.delete().catch((e) => { infoMessage.client.emit('error', e); }); player.deletePlayerMessage(); await player.updatePlayer(); break; } try { const videoInfo = await yt_core_discord_1.getYTInfo(urls[i]); const maxTLength = maxTrackLength.get(youtubePlayer); if (parseInt(videoInfo.length_seconds) / 60 > maxTLength) { await messages_1.errorInfo(message.channel, ` ${message.author} ${language.toLongTrack.replace(/<TRACKURL>/g, `<${videoInfo.video_url}>`).replace(/<MAXLENGTH>/g, maxTrackLength.toString())}`, language.error, selfDeleteTime.get(youtubePlayer)); await infoMessage.delete().catch((e) => { infoMessage.client.emit('error', e); }); player.deletePlayerMessage(); await player.updatePlayer(); break; } const playlistItem = { videoInfo, stream: await yt_core_discord_1.getStream(videoInfo), videoData: youtubeClass ? await youtubeClass.getVideoInfo(urls[i]) : undefined, submitted: new Date(Date.now()), submitter: message.member, }; if (playlist && infoMessage) { const m = Array.isArray(infoMessage) ? infoMessage[0] : infoMessage; const embed = messages_1.Embeds.infoEmbed(playlistProgress(i)); if (messages_1.canEmbed(message.channel)) await m.edit(embed); else m.edit(await messages_1.stringifyRichEmbed(embed, message.guild)); } if (player.push(playlistItem)) { await startPlayer(youtubePlayer, message, lang); if (i === urls.length - 1) await sendQueueVideoInfo(youtubePlayer, message, playlistItem, lang, false, true); else await sendQueueVideoInfo(youtubePlayer, message, playlistItem, lang, false, false); } if (i !== urls.length - 1) await wait(playlistParseWait.get(youtubePlayer)); } catch (error) { messages_1.errorInfo(message.channel, error.toString(), language.error, selfDeleteTime.get(youtubePlayer)); break; } } if (infoMessage) infoMessage.delete().catch(() => { }); } async function youtubeLuckSearch(youtubePlayer, message, query, lang) { if (!message.member || !message.guild) return; const player = getOrSetGuildPlayer(youtubePlayer, message, lang); const language = player.language.getLang(); const youtubeAPI = youtube.get(youtubePlayer); if (isPlaylistFull(message, youtubePlayer, lang)) return; let url = ''; const infoMessage = await messages_1.info(message.channel, language.player.searching.replace(/<URL>/g, `\`${query}\``), language.info); let playlistItem; try { const result = youtubeAPI ? await youtubeAPI.searchOnLuck(query) : await yt_core_discord_1.searchYTVideo(query); const lengthSeconds = typeof result.length_seconds === 'string' ? parseInt(result.length_seconds) : result.length_seconds; url = `${result.video_url} `; const maxTrackLengthMinutes = maxTrackLength.get(youtubePlayer); if (lengthSeconds > maxTrackLengthMinutes * 60) { await messages_1.errorInfo(message.channel, `${message.author} ${language.toLongTrack.replace(/<TRACK_URL>/g, `${result.video_url}`).replace(/<MAX_LENGTH>/g, maxTrackLengthMinutes.toString())}`, language.error, selfDeleteTime.get(youtubePlayer)); await infoMessage.delete().catch((e) => { infoMessage.client.emit('error', e); }); player.deletePlayerMessage(); await player.updatePlayer(); return; } const title = player.isAlreadyOnPlaylistById(result.video_id); if (title) { await messages_1.errorInfo(message.channel, `${message.author} ${language.alreadyOnPlaylist}\n${title}`, language.error, selfDeleteTime.get(youtubePlayer)); await infoMessage.delete().catch((e) => { infoMessage.client.emit('error', e); }); player.deletePlayerMessage(); await player.updatePlayer(); return; } const videoInfo = youtubeAPI ? await yt_core_discord_1.getYTInfo(result.video_url) : result; playlistItem = { videoInfo, stream: await yt_core_discord_1.getStream(videoInfo), videoData: youtubeAPI ? result : undefined, submitted: new Date(Date.now()), submitter: message.member, }; if (!player.push(playlistItem)) { messages_1.errorInfo(message.channel, `Unable to add to playlist`, language.error, selfDeleteTime.get(youtubePlayer)); return; } } catch (error) { messages_1.errorInfo(message.channel, `${url}${language.foundVideoUnavailable}`, language.error, selfDeleteTime.get(youtubePlayer)); message.client.emit('error', error); return; } infoMessage.delete().catch((e) => { infoMessage.client.emit('error', e); }); await startPlayer(youtubePlayer, message, lang); await sendQueueVideoInfo(youtubePlayer, message, playlistItem, lang, true); } async function parsePlayList(youtubePlayer, message, playlistUrl, lang, failsafe = false) { const language = lang.getLang(); if (isPlaylistFull(message, youtubePlayer, lang)) return; try { const playlist = await yt_core_discord_1.parsePlaylist(playlistUrl); const urls = playlist.items.map((i) => i.url_simple); if (urls.length) addYoutubeToQueue(youtubePlayer, message, urls, playlistUrl, lang); else throw new Error('playlist not found'); } catch (error) { const url = playlistUrl.match(youtubeTester); if (failsafe && url) addYoutubeToQueue(youtubePlayer, message, url, undefined, lang); else { messages_1.errorInfo(message.channel, `${language.playListParseFail}`, language.error, selfDeleteTime.get(youtubePlayer)); } } } function isPlaylistFull(message, youtubePlayer, lang, exceeded = false) { const language = lang.getLang(); const player = getOrSetGuildPlayer(youtubePlayer, message, lang); if (!player) return false; if (player.length > maxItemsInPlayList.get(youtubePlayer) - 1) {