diff --git a/redbot/cogs/audio/audio.py b/redbot/cogs/audio/audio.py index 59aa16312..bd4a5200c 100644 --- a/redbot/cogs/audio/audio.py +++ b/redbot/cogs/audio/audio.py @@ -1,5 +1,3 @@ -# Lavalink.py cog for Red v3 beta 7+ -# Cog base thanks to Kromatic's example cog. import asyncio import datetime import discord @@ -11,7 +9,7 @@ from redbot.core import Config, checks from .manager import shutdown_lavalink_server -__version__ = "2.0.2.9.b" +__version__ = "0.0.3a" __author__ = ["aikaterna", "billy/bollo/ati"] @@ -22,8 +20,9 @@ class Audio: default_global = { "host": 'localhost', - "port": '2332', - "passw": 'youshallnotpass', + "rest_port": '2333', + "ws_port": '2332', + "password": 'youshallnotpass', "status": False, "current_build": 0 } @@ -38,80 +37,75 @@ class Audio: self.config.register_guild(**default_guild) self.config.register_global(**default_global) - self._lavalink = None - async def init_config(self): host = await self.config.host() - passw = await self.config.passw() - port = await self.config.port() + password = await self.config.password() + rest_port = await self.config.rest_port() + ws_port = await self.config.ws_port() - try: - self._lavalink = lavalink.Client( - bot=self.bot, password=passw, host=host, port=port, loop=self.bot.loop - ) - except RuntimeError: - pass + await lavalink.initialize( + bot=self.bot, host=host, password=password, rest_port=rest_port, ws_port=ws_port + ) + lavalink.register_event_listener(self.event_handler) - self.bot.lavalink.client.register_hook(self.track_hook) - - @property - def lavalink(self): - return self._lavalink - - async def track_hook(self, player, event): - notify = await self.config.guild(self.bot.get_guild(player.fetch('guild'))).notify() + async def event_handler(self, player, event_type, extra): + notify = await self.config.guild(player.channel.guild).notify() status = await self.config.status() - playing_servers = await self._get_playing() - get_players = [p for p in self.bot.lavalink.players._players.values() if p.is_playing] try: + get_players = [p for p in lavalink.players if p.current is not None] get_single_title = get_players[0].current.title + playing_servers = len(get_players) except IndexError: - pass + playing_servers = 0 - if event == 'TrackStartEvent': + if event_type == lavalink.LavalinkEvents.TRACK_START: playing_song = player.fetch('playing_song') requester = player.fetch('requester') player.store('prev_song', playing_song) player.store('prev_requester', requester) - player.store('playing_song', self.bot.lavalink.players.get(player.fetch('guild')).current.uri) - player.store('requester', self.bot.get_user(self.bot.lavalink.players.get(player.fetch('guild')).current.requester)) + player.store('playing_song', player.current.uri) + player.store('requester', player.current.requester) - if event == 'TrackStartEvent' and notify: - c = player.fetch('channel') - if c: - c = self.bot.get_channel(c) + if event_type == lavalink.LavalinkEvents.TRACK_START and notify: + notify_channel = player.fetch('channel') + if notify_channel: + notify_channel = self.bot.get_channel(notify_channel) if player.fetch('notify_message') is not None: try: await player.fetch('notify_message').delete() except discord.errors.NotFound: pass - embed = discord.Embed(colour=c.guild.me.top_role.colour, title='Now Playing', + embed = discord.Embed(colour=notify_channel.guild.me.top_role.colour, title='Now Playing', description='**[{}]({})**'.format(player.current.title, player.current.uri)) - notify_message = await c.send(embed=embed) + notify_message = await notify_channel.send(embed=embed) player.store('notify_message', notify_message) - if event == 'TrackStartEvent' and status: - if playing_servers > 1: - await self.bot.change_presence(activity=discord.Activity(name='music in {} servers'.format(playing_servers), type=discord.ActivityType.playing)) - else: - await self.bot.change_presence(activity=discord.Activity(name=get_single_title, type=discord.ActivityType.listening)) - - if event == 'QueueEndEvent' and notify: - c = player.fetch('channel') - if c: - c = self.bot.get_channel(c) - if c: - embed = discord.Embed(colour=c.guild.me.top_role.colour, title='Queue ended.') - await c.send(embed=embed) - - if event == 'QueueEndEvent' and status: - await asyncio.sleep(1) + if event_type == lavalink.LavalinkEvents.TRACK_START and status: if playing_servers == 0: await self.bot.change_presence(activity=None) if playing_servers == 1: - await self.bot.change_presence(activity=discord.Activity(name=get_single_title, type=discord.ActivityType.listening)) + await self.bot.change_presence(activity=discord.Activity(name=get_single_title, + type=discord.ActivityType.listening)) if playing_servers > 1: - await self.bot.change_presence(activity=discord.Activity(name='music in {} servers'.format(playing_servers), type=discord.ActivityType.playing)) + await self.bot.change_presence(activity=discord.Activity(name='music in {} servers'.format(playing_servers), + type=discord.ActivityType.playing)) + + if event_type == lavalink.LavalinkEvents.QUEUE_END and notify: + notify_channel = player.fetch('channel') + if notify_channel: + notify_channel = self.bot.get_channel(notify_channel) + embed = discord.Embed(colour=notify_channel.guild.me.top_role.colour, title='Queue ended.') + await notify_channel.send(embed=embed) + + if event_type == lavalink.LavalinkEvents.QUEUE_END and status: + if playing_servers == 0: + await self.bot.change_presence(activity=None) + if playing_servers == 1: + await self.bot.change_presence(activity=discord.Activity(name=get_single_title, + type=discord.ActivityType.listening)) + if playing_servers > 1: + await self.bot.change_presence(activity=discord.Activity(name='music in {} servers'.format(playing_servers), + type=discord.ActivityType.playing)) @commands.group() @checks.is_owner() @@ -143,8 +137,7 @@ class Audio: msg += 'Repeat: [{}]\n'.format(repeat) msg += 'Shuffle: [{}]\n'.format(shuffle) msg += '---Lavalink Settings---\n' - msg += 'Cog version: {}\n'.format(__version__) - msg += 'Pip version: {}\n```'.format(lavalink.__version__) + msg += 'Cog version: {}\n```'.format(__version__) embed = discord.Embed(colour=ctx.guild.me.top_role.colour, description=msg) return await ctx.send(embed=embed) @@ -156,48 +149,43 @@ class Audio: status = await self.config.status() await self.config.status.set(not status) get_status = await self.config.status() - await self._embed_msg(ctx, 'Song titles as status: {}'.format(get_status)) + await self._embed_msg(ctx, 'Song titles as status: {}.'.format(get_status)) @commands.command() async def audiostats(self, ctx): """Audio stats.""" - server_num = await self._get_playing() - server_ids = self.bot.lavalink.players._players + server_num = len([p for p in lavalink.players if p.current is not None]) server_list = [] - for k, v in server_ids.items(): - guild_id = k - player = v - connect_start = v.fetch('connect') + for p in lavalink.players: + connect_start = p.fetch('connect') + connect_dur = self._dynamic_time(int((datetime.datetime.utcnow() - connect_start).total_seconds())) try: - connect_dur = self._dynamic_time(int((datetime.datetime.utcnow() - connect_start).total_seconds())) - except TypeError: - connect_dur = 0 - try: - server_list.append('{} [`{}`]: **[{}]({})**'.format(self.bot.get_guild(guild_id).name, connect_dur, - v.current.title, v.current.uri)) + server_list.append('{} [`{}`]: **[{}]({})**'.format(p.channel.guild.name, connect_dur, + p.current.title, p.current.uri)) except AttributeError: - pass - servers = '\n'.join(server_list) - if server_list == []: + server_list.append('{} [`{}`]: **{}**'.format(p.channel.guild.name, connect_dur, + 'Nothing playing.')) + if server_num == 0: servers = 'Not connected anywhere.' - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playing in {} servers:'.format(server_num), + else: + servers = '\n'.join(server_list) + embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Connected in {} servers:'.format(server_num), description=servers) await ctx.send(embed=embed) @commands.command() async def bump(self, ctx, index: int): """Bump a song number to the top of the queue.""" - player = self.bot.lavalink.players.get(ctx.guild.id) - - if not player.queue: - return await self._embed_msg(ctx, 'Nothing queued.') + if not self._player_check(ctx): + return await self._embed_msg(ctx, 'Nothing playing.') + player = lavalink.get_player(ctx.guild.id) if index > len(player.queue) or index < 1: return await self._embed_msg(ctx, 'Song number must be greater than 1 and within the queue limit.') bump_index = index - 1 - bump_song = self.bot.lavalink.players.get(ctx.guild.id).queue[bump_index] + bump_song = player.queue[bump_index] player.queue.insert(0, bump_song) removed = player.queue.pop(index) await self._embed_msg(ctx, 'Moved **' + removed.title + '** to the top of the queue.') @@ -205,40 +193,38 @@ class Audio: @commands.command(aliases=['dc']) async def disconnect(self, ctx): """Disconnect from the voice channel.""" - player = self.bot.lavalink.players.get(ctx.guild.id) - await player.disconnect() - player.queue.clear() - player.store('connect', None) - player.store('prev_requester', None) - player.store('prev_song', None) - player.store('playing_song', None) - player.store('requester', None) - await self.bot.lavalink.client._trigger_event("QueueEndEvent", ctx.guild.id) + if self._player_check(ctx): + await lavalink.get_player(ctx.guild.id).stop() + await lavalink.get_player(ctx.guild.id).disconnect() @commands.command(aliases=['np', 'n', 'song']) async def now(self, ctx): """Now playing.""" - expected = ['⏹', '⏸', '⏭'] + if not self._player_check(ctx): + return await self._embed_msg(ctx, 'Nothing playing.') + expected = ['⏮', '⏹', '⏸', '⏭'] emoji = { + 'prev': '⏮', 'stop': '⏹', 'pause': '⏸', 'next': '⏭' } - player = self.bot.lavalink.players.get(ctx.guild.id) + player = lavalink.get_player(ctx.guild.id) song = 'Nothing' if player.current: arrow = await self._draw_time(ctx) - pos = lavalink.Utils.format_time(player.position) - if player.current.stream: + pos = lavalink.utils.format_time(player.position) + if player.current.is_stream: dur = 'LIVE' else: - dur = lavalink.Utils.format_time(player.current.duration) - if not player.current: - song = 'Nothing.' + dur = lavalink.utils.format_time(player.current.length) + song = '**[{}]({})**\nRequested by: **{}**\n\n{}`{}`/`{}`'.format( + player.current.title, player.current.uri, + player.current.requester, arrow, pos, dur + ) else: - req_user = self.bot.get_user(player.current.requester) - song = '**[{}]({})**\nRequested by: **{}**\n\n{}`{}`/`{}`'.format(player.current.title, player.current.uri, - req_user, arrow, pos, dur) + song = 'Nothing.' + if player.fetch('np_message') is not None: try: await player.fetch('np_message').delete() @@ -253,7 +239,7 @@ class Audio: return r.message.id == message.id and u == ctx.message.author if player.current: - for i in range(3): + for i in range(4): await message.add_reaction(expected[i]) try: (r, u) = await self.bot.wait_for('reaction_add', check=check, timeout=10.0) @@ -263,7 +249,10 @@ class Audio: reacts = {v: k for k, v in emoji.items()} react = reacts[r.emoji] - if react == 'stop': + if react == 'prev': + await self._clear_react(message) + await ctx.invoke(self.prev) + elif react == 'stop': await self._clear_react(message) await ctx.invoke(self.stop) elif react == 'pause': @@ -276,28 +265,44 @@ class Audio: @commands.command(aliases=['resume']) async def pause(self, ctx): """Pause and resume.""" - player = self.bot.lavalink.players.get(ctx.guild.id) - if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)): + if not self._player_check(ctx): + return await self._embed_msg(ctx, 'Nothing playing.') + player = lavalink.get_player(ctx.guild.id) + if not ctx.author.voice or ctx.author.voice.channel != player.channel: return await self._embed_msg(ctx, 'You must be in the voice channel to pause the music.') - if not player.is_playing: - return + if player.current and not player.paused: + await player.pause() + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, title='Track Paused', + description='**[{}]({})**'.format( + player.current.title, + player.current.uri + ) + ) + return await ctx.send(embed=embed) if player.paused: - await player.set_pause(False) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Resumed', - description='**[{}]({})**'.format(player.current.title, player.current.uri)) - message = await ctx.send(embed=embed) - else: - await player.set_pause(True) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Paused', - description='**[{}]({})**'.format(player.current.title, player.current.uri)) - message = await ctx.send(embed=embed) + await player.pause(False) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title='Track Resumed', + description='**[{}]({})**'.format( + player.current.title, + player.current.uri + ) + ) + return await ctx.send(embed=embed) + + await self._embed_msg(ctx, 'Nothing playing.') @commands.command() async def percent(self, ctx): """Queue percentage.""" - queue_tracks = self.bot.lavalink.players.get(ctx.guild.id).queue + if not self._player_check(ctx): + return await self._embed_msg(ctx, 'Nothing playing.') + player = lavalink.get_player(ctx.guild.id) + queue_tracks = player.queue queue_len = len(queue_tracks) requesters = {'total': 0, 'users': {}} @@ -310,11 +315,12 @@ class Audio: requesters['users'][req_username]['songcount'] = 1 requesters['total'] += 1 - for i in range(queue_len): - req_username = self.bot.get_user(queue_tracks[i].requester).name + for track in queue_tracks: + req_username = '{}#{}'.format(track.requester.name, track.requester.discriminator) await _usercount(req_username) + try: - req_username = self.bot.get_user(self.bot.lavalink.players.get(ctx.guild.id).current.requester).name + req_username = '{}#{}'.format(player.current.requester.name, player.current.requester.discriminator) await _usercount(req_username) except AttributeError: return await self._embed_msg(ctx, 'Nothing in the queue.') @@ -334,91 +340,89 @@ class Audio: @commands.command(aliases=['p']) async def play(self, ctx, *, query): """Play a URL or search for a song.""" - player = self.bot.lavalink.players.get(ctx.guild.id) - shuffle = await self.config.guild(ctx.guild).shuffle() - - if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)): + if not ctx.author.voice: return await self._embed_msg(ctx, 'You must be in the voice channel to use the play command.') - + if not self._player_check(ctx): + await lavalink.connect(ctx.author.voice.channel) + player = lavalink.get_player(ctx.guild.id) + player.store('connect', datetime.datetime.utcnow()) + player = lavalink.get_player(ctx.guild.id) + shuffle = await self.config.guild(ctx.guild).shuffle() player.store('channel', ctx.channel.id) player.store('guild', ctx.guild.id) await self._data_check(ctx) - if not player.is_connected: - player.store('connect', datetime.datetime.utcnow()) - await player.connect(ctx.author.voice.channel.id) - query = query.strip('<>') if not query.startswith('http'): query = 'ytsearch:{}'.format(query) - tracks = await self.bot.lavalink.client.get_tracks(query) + tracks = await player.get_tracks(query) if not tracks: - return await self._embed_msg(ctx, 'Nothing found 👀') + return await self._embed_msg(ctx, 'Nothing found.') queue_duration = await self._queue_duration(ctx) - queue_total_duration = lavalink.Utils.format_time(queue_duration) + queue_total_duration = lavalink.utils.format_time(queue_duration) if 'list' in query and 'ytsearch:' not in query: for track in tracks: - player.add(requester=ctx.author.id, track=track) + player.add(ctx.author, track) embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist Enqueued', description='Added {} tracks to the queue.'.format(len(tracks))) if not shuffle and queue_duration > 0: embed.set_footer(text='{} until start of playlist playback'.format(queue_total_duration)) + if not player.current: + await player.play() else: - player.add(requester=ctx.author.id, track=tracks[0]) - track_title = tracks[0]["info"]["title"] - track_url = tracks[0]["info"]["uri"] + single_track = tracks[0] + player.add(ctx.author, single_track) embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Enqueued', - description='[**{}**]({})'.format(track_title, track_url)) + description='**[{}]({})**'.format(single_track.title, single_track.uri)) if not shuffle and queue_duration > 0: embed.set_footer(text='{} until track playback'.format(queue_total_duration)) + if not player.current: + await player.play() await ctx.send(embed=embed) - if not player.is_playing: - await player.play() - @commands.command() async def prev(self, ctx): """Skips to the start of the previously played track.""" - player = self.bot.lavalink.players.get(ctx.guild.id) + if not self._player_check(ctx): + return await self._embed_msg(ctx, 'Nothing playing.') + player = lavalink.get_player(ctx.guild.id) shuffle = await self.config.guild(ctx.guild).shuffle() - - if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)): + if not ctx.author.voice or ctx.author.voice.channel != player.channel: return await self._embed_msg(ctx, 'You must be in the voice channel to skip the music.') - if shuffle: return await self._embed_msg(ctx, 'Turn shuffle off to use this command.') - - if player.current is None: - return await self._embed_msg(ctx, 'The player is stopped.') - if player.fetch('prev_song') is None: return await self._embed_msg(ctx, 'No previous track.') else: - last_track = await self.bot.lavalink.client.get_tracks(player.fetch('prev_song')) + last_track = await player.get_tracks(player.fetch('prev_song')) player.add(player.fetch('prev_requester').id, last_track[0]) - queue_len = len(self.bot.lavalink.players.get(ctx.guild.id).queue) - bump_song = self.bot.lavalink.players.get(ctx.guild.id).queue[queue_len - 1] + queue_len = len(player.queue) + bump_song = player.queue[-1] player.queue.insert(0, bump_song) player.queue.pop(queue_len) await player.skip() - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Replaying Track', description='**[{}]({})**'.format(player.current.title, player.current.uri)) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, + title='Replaying Track', description='**[{}]({})**'.format( + player.current.title, player.current.uri + ) + ) await ctx.send(embed=embed) @commands.command(aliases=['q']) - async def queue(self, ctx, page: int = 1): + async def queue(self, ctx, page: int=1): """Lists the queue.""" + if not self._player_check(ctx): + return await self._embed_msg(ctx, 'There\'s nothing in the queue.') shuffle = await self.config.guild(ctx.guild).shuffle() repeat = await self.config.guild(ctx.guild).repeat() - player = self.bot.lavalink.players.get(ctx.guild.id) + player = lavalink.get_player(ctx.guild.id) if not player.queue: return await self._embed_msg(ctx, 'There\'s nothing in the queue.') - if player.current is None: - return await self._embed_msg(ctx, 'The player is stopped.') - items_per_page = 10 pages = math.ceil(len(player.queue) / items_per_page) start = (page - 1) * items_per_page @@ -426,34 +430,39 @@ class Audio: queue_list = '' arrow = await self._draw_time(ctx) - pos = lavalink.Utils.format_time(player.position) + pos = lavalink.utils.format_time(player.position) - if player.current.stream: + if player.current.is_stream: dur = 'LIVE' else: - dur = lavalink.Utils.format_time(player.current.duration) + dur = lavalink.utils.format_time(player.current.length) - if player.current.stream: - queue_list += '**Currently livestreaming:** [**{}**]({})\nRequested by: **{}**\n\n{}`{}`/`{}`\n\n'.format( - player.current.title, player.current.uri, self.bot.get_user(player.current.requester), arrow, pos, dur) + if player.current.is_stream: + queue_list += '**Currently livestreaming:** **[{}]({})**\nRequested by: **{}**\n\n{}`{}`/`{}`\n\n'.format( + player.current.title, + player.current.uri, + player.current.requester, + arrow, pos, dur + ) else: - queue_list += 'Playing: [**{}**]({})\nRequested by: **{}**\n\n{}`{}`/`{}`\n\n'.format(player.current.title, - player.current.uri, - self.bot.get_user( - player.current.requester), - arrow, pos, dur) + queue_list += 'Playing: **[{}]({})**\nRequested by: **{}**\n\n{}`{}`/`{}`\n\n'.format( + player.current.title, + player.current.uri, + player.current.requester, + arrow, pos, dur + ) for i, track in enumerate(player.queue[start:end], start=start): - req_user = self.bot.get_user(track.requester) + req_user = track.requester next = i + 1 - queue_list += '`{}.` [**{}**]({}), requested by **{}**\n'.format(next, track.title, track.uri, req_user) + queue_list += '`{}.` **[{}]({})**, requested by **{}**\n'.format(next, track.title, track.uri, req_user) embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Queue for ' + ctx.guild.name, description=queue_list) queue_duration = await self._queue_duration(ctx) - queue_total_duration = lavalink.Utils.format_time(queue_duration) - text = 'Page {}/{} | {} tracks, {} remaining'.format(page, pages, len(player.queue), queue_total_duration) + queue_total_duration = lavalink.utils.format_time(queue_duration) + text = 'Page {}/{} | {} tracks, {} remaining'.format(page, pages, len(player.queue) + 1, queue_total_duration) if repeat: text += ' | Repeat: \N{WHITE HEAVY CHECK MARK}' if shuffle: @@ -464,31 +473,33 @@ class Audio: @commands.command() async def repeat(self, ctx): """Toggles repeat.""" - player = self.bot.lavalink.players.get(ctx.guild.id) - if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)): - return await self._embed_msg(ctx, 'You must be in the voice channel to toggle repeat.') + repeat_msg = 'You must be in the voice channel to toggle repeat.' + if not ctx.author.voice: + return await self._embed_msg(ctx, repeat_msg) repeat = await self.config.guild(ctx.guild).repeat() await self.config.guild(ctx.guild).repeat.set(not repeat) repeat = await self.config.guild(ctx.guild).repeat() - if player.repeat != repeat: - player.repeat = repeat await self._embed_msg(ctx, 'Repeat songs: {}.'.format(repeat)) + if self._player_check(ctx): + await self._data_check(ctx) + player = lavalink.get_player(ctx.guild.id) + if ctx.author.voice.channel != player.channel: + return await self._embed_msg(ctx, repeat_msg) + @commands.command() async def remove(self, ctx, index: int): """Remove a specific song number from the queue.""" - player = self.bot.lavalink.players.get(ctx.guild.id) - + if not self._player_check(ctx): + return await self._embed_msg(ctx, 'Nothing playing.') + player = lavalink.get_player(ctx.guild.id) if not player.queue: return await self._embed_msg(ctx, 'Nothing queued.') - if index > len(player.queue) or index < 1: return await self._embed_msg(ctx, 'Song number must be greater than 1 and within the queue limit.') - - index = index - 1 + index -= 1 removed = player.queue.pop(index) - await self._embed_msg(ctx, 'Removed **' + removed.title + '** from the queue.') @commands.command() @@ -496,26 +507,26 @@ class Audio: """Pick a song with a search. Use [p]search list to queue all songs. """ - expected = ["1⃣", "2⃣", "3⃣", "4⃣", "5⃣", "⏪", "⏩"] + expected = ["1⃣", "2⃣", "3⃣", "4⃣", "5⃣"] emoji = { "one": "1⃣", "two": "2⃣", "three": "3⃣", "four": "4⃣", - "five": "5⃣", - "back": "⏪", - "next": "⏩" + "five": "5⃣" } - player = self.bot.lavalink.players.get(ctx.guild.id) + if not ctx.author.voice: + return await self._embed_msg(ctx, 'You must be in the voice channel to enqueue songs.') + if not self._player_check(ctx): + await lavalink.connect(ctx.author.voice.channel) + player = lavalink.get_player(ctx.guild.id) + player.store('connect', datetime.datetime.utcnow()) + player = lavalink.get_player(ctx.guild.id) shuffle = await self.config.guild(ctx.guild).shuffle() player.store('channel', ctx.channel.id) player.store('guild', ctx.guild.id) - - if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)): + if ctx.author.voice.channel != player.channel: return await self._embed_msg(ctx, 'You must be in the voice channel to enqueue songs.') - if not player.is_connected: - player.store('connect', datetime.datetime.utcnow()) - await player.connect(ctx.author.voice.channel.id) query = query.strip('<>') if query.startswith('sc '): @@ -523,7 +534,7 @@ class Audio: elif not query.startswith('http') or query.startswith('sc '): query = 'ytsearch:{}'.format(query) - tracks = await self.bot.lavalink.client.get_tracks(query) + tracks = await player.get_tracks(query) if not tracks: return await self._embed_msg(ctx, 'Nothing found 👀') if 'list' not in query and 'ytsearch:' or 'scsearch:' in query: @@ -532,13 +543,11 @@ class Audio: pages = math.ceil(len(tracks) / items_per_page) start = (page - 1) * items_per_page end = start + items_per_page - search_list = '' - for i, track in enumerate(tracks[start:end], start=start): next = i + 1 - search_list += '`{0}.` [**{1}**]({2})\n'.format(next, tracks[i]["info"]["title"], - tracks[i]["info"]["uri"]) + search_list += '`{0}.` [**{1}**]({2})\n'.format(next, track.title, + track.uri) embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Tracks Found:', description=search_list) embed.set_footer(text='Page {}/{} | {} search results'.format(page, pages, len(tracks))) @@ -547,7 +556,7 @@ class Audio: def check(r, u): return r.message.id == message.id and u == ctx.message.author - for i in range(7): + for i in range(5): await message.add_reaction(expected[i]) try: (r, u) = await self.bot.wait_for('reaction_add', check=check, timeout=30.0) @@ -556,157 +565,152 @@ class Audio: return reacts = {v: k for k, v in emoji.items()} react = reacts[r.emoji] - if react == 'one': - await self._search_button(ctx, message, tracks, entry=0) - elif react == 'two': - await self._search_button(ctx, message, tracks, entry=1) - elif react == 'three': - await self._search_button(ctx, message, tracks, entry=2) - elif react == 'four': - await self._search_button(ctx, message, tracks, entry=3) - elif react == 'five': - await self._search_button(ctx, message, tracks, entry=4) + choice = {'one': 0, 'two': 1, 'three': 2, 'four': 3, 'five': 4} + await self._search_button(ctx, message, tracks, entry=choice[react]) - elif react == 'back': - await self._clear_react(message) - return - elif react == 'next': - await self._clear_react(message) - return else: await self._data_check(ctx) songembed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Queued {} track(s).'.format(len(tracks))) queue_duration = await self._queue_duration(ctx) - queue_total_duration = lavalink.Utils.format_time(queue_duration) + queue_total_duration = lavalink.utils.format_time(queue_duration) if not shuffle and queue_duration > 0: songembed.set_footer(text='{} until start of search playback'.format(queue_total_duration)) for track in tracks: - player.add(requester=ctx.author.id, track=track) - if not player.is_playing: + player.add(ctx.author, track) + if not player.current: await player.play() message = await ctx.send(embed=songembed) async def _search_button(self, ctx, message, tracks, entry: int): - player = self.bot.lavalink.players.get(ctx.guild.id) + player = lavalink.get_player(ctx.guild.id) shuffle = await self.config.guild(ctx.guild).shuffle() await self._clear_react(message) - player.add(requester=ctx.author.id, track=tracks[entry]) - track_title = tracks[entry]["info"]["title"] - track_url = tracks[entry]["info"]["uri"] + search_choice = tracks[entry] embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Enqueued', - description='[**{}**]({})'.format(track_title, track_url)) + description='**[{}]({})**'.format(search_choice.title, search_choice.uri)) queue_duration = await self._queue_duration(ctx) - queue_total_duration = lavalink.Utils.format_time(queue_duration) - if not shuffle: + queue_total_duration = lavalink.utils.format_time(queue_duration) + if not shuffle and queue_duration > 0: embed.set_footer(text='{} until track playback'.format(queue_total_duration)) - if not player.is_playing: + player.add(ctx.author, search_choice) + if not player.current: await player.play() return await ctx.send(embed=embed) @commands.command() - async def seek(self, ctx, seconds: int = 5): + async def seek(self, ctx, seconds: int=30): """Seeks ahead or behind on a track by seconds.""" - player = self.bot.lavalink.players.get(ctx.guild.id) - if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)): + if not self._player_check(ctx): + return await self._embed_msg(ctx, 'Nothing playing.') + player = lavalink.get_player(ctx.guild.id) + if not ctx.author.voice or ctx.author.voice.channel != player.channel: return await self._embed_msg(ctx, 'You must be in the voice channel to use seek.') - if player.is_playing: - if player.current.stream: + if player.current: + if player.current.is_stream: return await self._embed_msg(ctx, 'Can\'t seek on a stream.') else: time_sec = seconds * 1000 seek = player.position + time_sec - if abs(time_sec) > player.position: + if seek <= 0: await self._embed_msg(ctx, 'Moved {}s to 00:00:00'.format(seconds)) else: - await self._embed_msg(ctx, 'Moved {}s to {}'.format(seconds, lavalink.Utils.format_time(seek))) + await self._embed_msg(ctx, 'Moved {}s to {}'.format(seconds, lavalink.utils.format_time(seek))) return await player.seek(seek) + else: + await self._embed_msg(ctx, 'Nothing playing.') @commands.command() async def shuffle(self, ctx): """Toggles shuffle.""" - player = self.bot.lavalink.players.get(ctx.guild.id) - if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)): - return await self._embed_msg(ctx, 'You must be in the voice channel to toggle shuffle.') + shuffle_msg = 'You must be in the voice channel to toggle shuffle.' + if not ctx.author.voice: + return await self._embed_msg(ctx, shuffle_msg) shuffle = await self.config.guild(ctx.guild).shuffle() await self.config.guild(ctx.guild).shuffle.set(not shuffle) shuffle = await self.config.guild(ctx.guild).shuffle() - if player.shuffle != shuffle: - player.shuffle = shuffle await self._embed_msg(ctx, 'Shuffle songs: {}.'.format(shuffle)) + if self._player_check(ctx): + await self._data_check(ctx) + player = lavalink.get_player(ctx.guild.id) + if ctx.author.voice.channel != player.channel: + return await self._embed_msg(ctx, shuffle_msg) + @commands.command(aliases=['forceskip', 'fs']) async def skip(self, ctx): """Skips to the next track.""" - player = self.bot.lavalink.players.get(ctx.guild.id) + if not self._player_check(ctx): + return await self._embed_msg(ctx, 'Nothing playing.') + else: + player = lavalink.get_player(ctx.guild.id) - if player.current is None: - return await self._embed_msg(ctx, 'The player is stopped.') + if not player.current: + return await self._embed_msg(ctx, 'There\'s nothing in the queue.') if not player.queue: - pos = player.position - dur = player.current.duration - remain = dur - pos - time_remain = lavalink.Utils.format_time(remain) - if player.current.stream: + pos, dur = player.position, player.current.length + time_remain = lavalink.utils.format_time(dur - pos) + if player.current.is_stream: embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='There\'s nothing in the queue.') embed.set_footer(text='Currently livestreaming {}'.format(player.current.title)) - return await ctx.send(embed=embed) - elif player.current.track: + else: embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='There\'s nothing in the queue.') embed.set_footer(text='{} left on {}'.format(time_remain, player.current.title)) - return await ctx.send(embed=embed) - else: - return await self._embed_msg(ctx, 'There\'s nothing in the queue.') + return await ctx.send(embed=embed) - if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)): + if not ctx.author.voice or ctx.author.voice.channel != player.channel: return await self._embed_msg(ctx, 'You must be in the voice channel to skip the music.') - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Skipped', - description='**[{}]({})**'.format(player.current.title, player.current.uri)) - message = await ctx.send(embed=embed) + embed = discord.Embed( + colour=ctx.guild.me.top_role.colour, title='Track Skipped', + description='**[{}]({})**'.format( + player.current.title, player.current.uri + ) + ) + await ctx.send(embed=embed) await player.skip() @commands.command(aliases=['s']) async def stop(self, ctx): """Stops playback and clears the queue.""" - player = self.bot.lavalink.players.get(ctx.guild.id) - if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)): + if not self._player_check(ctx): + return await self._embed_msg(ctx, 'Nothing playing.') + player = lavalink.get_player(ctx.guild.id) + if not ctx.author.voice or ctx.author.voice.channel != player.channel: return await self._embed_msg(ctx, 'You must be in the voice channel to stop the music.') if player.is_playing: await self._embed_msg(ctx, 'Stopping...') - player.queue.clear() await player.stop() player.store('prev_requester', None) player.store('prev_song', None) player.store('playing_song', None) player.store('requester', None) - await self.bot.lavalink.client._trigger_event("QueueEndEvent", ctx.guild.id) - @commands.command(aliases=['vol']) - async def volume(self, ctx, vol: int = None): + @commands.command() + async def volume(self, ctx, vol: int=None): """Sets the volume, 1% - 150%.""" - player = self.bot.lavalink.players.get(ctx.guild.id) - if not ctx.author.voice or (player.is_connected and ctx.author.voice.channel.id != int(player.channel_id)): - return await self._embed_msg(ctx, 'You must be in the voice channel to change the volume.') if not vol: vol = await self.config.guild(ctx.guild).volume() embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Current Volume:', description=str(vol) + '%') - if not player.is_playing: + if not self._player_check(ctx): embed.set_footer(text='Nothing playing.') return await ctx.send(embed=embed) - if int(vol) > 150: + if vol > 150: vol = 150 await self.config.guild(ctx.guild).volume.set(vol) - await player.set_volume(vol) + if self._player_check: + await lavalink.get_player(ctx.guild.id).set_volume(vol) else: await self.config.guild(ctx.guild).volume.set(vol) - await player.set_volume(vol) - embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Volume:', description=str(vol) + '%') - if not player.is_playing: + if self._player_check: + await lavalink.get_player(ctx.guild.id).set_volume(vol) + embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Volume:', + description=str(vol) + '%') + if not self._player_check(ctx): embed.set_footer(text='Nothing playing.') await ctx.send(embed=embed) @@ -732,11 +736,18 @@ class Audio: await self._embed_msg(ctx, 'Server password set to {}.'.format(get_passw)) @llsetup.command() - async def port(self, ctx, port): - """Set the lavalink server port.""" - await self.config.port.set(str(port)) - get_port = await self.config.port() - await self._embed_msg(ctx, 'Port set to {}.'.format(get_port)) + async def restport(self, ctx, rest_port): + """Set the lavalink REST server port.""" + await self.config.rest_port.set(str(rest_port)) + get_rest_port = await self.config.rest_port() + await self._embed_msg(ctx, 'REST port set to {}.'.format(get_rest_port)) + + @llsetup.command() + async def wsport(self, ctx, rest_port): + """Set the lavalink websocket server port.""" + await self.config.ws_port.set(str(ws_port)) + get_rest_port = await self.config.ws_port() + await self._embed_msg(ctx, 'Websocket port set to {}.'.format(get_ws_port)) async def _clear_react(self, message): try: @@ -745,7 +756,7 @@ class Audio: return async def _data_check(self, ctx): - player = self.bot.lavalink.players.get(ctx.guild.id) + player = lavalink.get_player(ctx.guild.id) shuffle = await self.config.guild(ctx.guild).shuffle() repeat = await self.config.guild(ctx.guild).repeat() volume = await self.config.guild(ctx.guild).volume() @@ -754,13 +765,13 @@ class Audio: if player.shuffle != shuffle: player.shuffle = shuffle if player.volume != volume: - player.volume = volume + await player.set_volume(volume) async def _draw_time(self, ctx): - player = self.bot.lavalink.players.get(ctx.guild.id) + player = lavalink.get_player(ctx.guild.id) paused = player.paused pos = player.position - dur = player.current.duration + dur = player.current.length sections = 12 loc_time = round((pos / dur) * sections) bar = '\N{BOX DRAWINGS HEAVY HORIZONTAL}' @@ -795,21 +806,25 @@ class Audio: embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title=title) await ctx.send(embed=embed) - async def _get_playing(self): - return len([p for p in self.bot.lavalink.players._players.values() if p.is_playing]) + async def _get_playing(self, ctx): + if self._player_check(ctx): + player = lavalink.get_player(ctx.guild.id) + return len([player for p in lavalink.players if p.is_playing]) + else: + return 0 async def _queue_duration(self, ctx): - player = self.bot.lavalink.players.get(ctx.guild.id) + player = lavalink.get_player(ctx.guild.id) duration = [] for i in range(len(player.queue)): - if not player.queue[i].stream: - duration.append(player.queue[i].duration) + if not player.queue[i].is_stream: + duration.append(player.queue[i].length) queue_duration = sum(duration) if player.queue == []: queue_duration = 0 try: - if not player.current.stream: - remain = player.current.duration - player.position + if not player.current.is_stream: + remain = player.current.length - player.position else: remain = 0 except AttributeError: @@ -817,7 +832,14 @@ class Audio: queue_total_duration = remain + queue_duration return queue_total_duration + def _player_check(self, ctx): + try: + lavalink.get_player(ctx.guild.id) + return True + except KeyError: + return False + def __unload(self): - self.bot.lavalink.ws._ws.close() - self.bot.lavalink.client.destroy() + lavalink.unregister_event_listener(self.event_handler) + self.bot.loop.create_task(lavalink.close()) shutdown_lavalink_server() diff --git a/requirements.txt b/requirements.txt index 5f737fa50..577d7bff7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,3 @@ colorama==0.3.9 aiohttp-json-rpc==0.8.7 pyyaml==3.12 Red-Trivia -lavalink==2.0.2.9 diff --git a/setup.py b/setup.py index 012b36c35..c731a3a2f 100644 --- a/setup.py +++ b/setup.py @@ -130,6 +130,6 @@ setup( ], 'mongo': ['motor'], 'docs': ['sphinx', 'sphinxcontrib-asyncio', 'sphinx_rtd_theme'], - 'voice': ['PyNaCl'] + 'voice': ['red-lavalink>=0.0.4'] } )