[V3 Audio] External lavalink server settings, playlist saving & recall, bugfixes (#1528)

* Add settings for external lavalink servers

* Add external lavalink server to settings display

* Add simple error handling

* Remove future permissions conflict

Assuming base Red permissions will be accessed with the 'p' command in the future

* Update prev to set requester to user instead of id

* Various fixes

Jukebox: Plebs not in a channel will not have the song cost deducted before the warning to join a channel first.
DJ role: Finer grained permissions for plebs that are alone: they can use prev (if shuffle is off), skip, pause, resume

* Update permissions

* Add track number to footer when queueing

* Add basic playlist saving

More subcommands to come later

* Check if user is not in voice

* Restrict playlist deletion to author and mods

* Add playlist info command
This commit is contained in:
aikaterna 2018-04-16 15:42:32 -07:00 committed by Kowlin
parent 96791bd72b
commit 59276ce2a5

View File

@ -9,7 +9,7 @@ from redbot.core import Config, checks, bank
from .manager import shutdown_lavalink_server from .manager import shutdown_lavalink_server
__version__ = "0.0.4" __version__ = "0.0.5"
__author__ = ["aikaterna", "billy/bollo/ati"] __author__ = ["aikaterna", "billy/bollo/ati"]
@ -33,6 +33,7 @@ class Audio:
"dj_role": None, "dj_role": None,
"jukebox": False, "jukebox": False,
"jukebox_price": 0, "jukebox_price": 0,
"playlists": {},
"notify": False, "notify": False,
"repeat": False, "repeat": False,
"shuffle": False, "shuffle": False,
@ -116,6 +117,17 @@ class Audio:
await self.bot.change_presence(activity=discord.Activity(name='music in {} servers'.format(playing_servers), await self.bot.change_presence(activity=discord.Activity(name='music in {} servers'.format(playing_servers),
type=discord.ActivityType.playing)) type=discord.ActivityType.playing))
if event_type == lavalink.LavalinkEvents.TRACK_EXCEPTION:
message_channel = player.fetch('channel')
if message_channel:
message_channel = self.bot.get_channel(message_channel)
embed = discord.Embed(colour=message_channel.guild.me.top_role.colour, title='Track Error',
description='{}\n**[{}]({})**'.format(extra, player.current.title,
player.current.uri))
embed.set_footer(text='Skipping...')
await message_channel.send(embed=embed)
await player.skip()
@commands.group() @commands.group()
@commands.guild_only() @commands.guild_only()
async def audioset(self, ctx): async def audioset(self, ctx):
@ -186,11 +198,12 @@ class Audio:
async def settings(self, ctx): async def settings(self, ctx):
"""Show the current settings.""" """Show the current settings."""
data = await self.config.guild(ctx.guild).all() data = await self.config.guild(ctx.guild).all()
global_data = await self.config.all()
dj_role_obj = discord.utils.get(ctx.guild.roles, id=data['dj_role']) dj_role_obj = discord.utils.get(ctx.guild.roles, id=data['dj_role'])
dj_enabled = data['dj_enabled'] dj_enabled = data['dj_enabled']
jukebox = data['jukebox'] jukebox = data['jukebox']
jukebox_price = data['jukebox_price'] jukebox_price = data['jukebox_price']
status = await self.config.status()
vote_percent = data['vote_percent'] vote_percent = data['vote_percent']
msg = ('```ini\n' msg = ('```ini\n'
'----Server Settings----\n') '----Server Settings----\n')
@ -202,12 +215,13 @@ class Audio:
msg += ('Repeat: [{repeat}]\n' msg += ('Repeat: [{repeat}]\n'
'Shuffle: [{shuffle}]\n' 'Shuffle: [{shuffle}]\n'
'Song notify msgs: [{notify}]\n' 'Song notify msgs: [{notify}]\n'
'Songs as status: [{0}]\n'.format(status, **data)) 'Songs as status: [{status}]\n'.format(**global_data, **data))
if vote_percent > 0: if vote_percent > 0:
msg += ('Vote skip: [{vote_enabled}]\n' msg += ('Vote skip: [{vote_enabled}]\n'
'Skip percentage: [{vote_percent}%]\n').format(**data) 'Skip percentage: [{vote_percent}%]\n').format(**data)
msg += ('---Lavalink Settings---\n' msg += ('---Lavalink Settings---\n'
'Cog version: {}\n```'.format(__version__)) 'Cog version: [{}]\n'
'External server: [{use_external_lavalink}]```').format(__version__, **global_data)
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, description=msg) embed = discord.Embed(colour=ctx.guild.me.top_role.colour, description=msg)
return await ctx.send(embed=embed) return await ctx.send(embed=embed)
@ -291,7 +305,7 @@ class Audio:
if dj_enabled: if dj_enabled:
if not await self._can_instaskip(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author):
return await self._embed_msg(ctx, 'You need the DJ role to disconnect.') return await self._embed_msg(ctx, 'You need the DJ role to disconnect.')
if not await self._can_instaskip(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author):
return await self._embed_msg(ctx, 'There are other people listening to music.') return await self._embed_msg(ctx, 'There are other people listening to music.')
else: else:
await lavalink.get_player(ctx.guild.id).stop() await lavalink.get_player(ctx.guild.id).stop()
@ -337,7 +351,7 @@ class Audio:
dj_enabled = await self.config.guild(ctx.guild).dj_enabled() dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
vote_enabled = await self.config.guild(ctx.guild).vote_enabled() vote_enabled = await self.config.guild(ctx.guild).vote_enabled()
if dj_enabled or vote_enabled: if dj_enabled or vote_enabled:
if not await self._can_instaskip(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author):
return return
if player.current: if player.current:
@ -376,11 +390,11 @@ class Audio:
await self._can_instaskip(ctx, ctx.author)): await self._can_instaskip(ctx, ctx.author)):
return await self._embed_msg(ctx, 'You must be in the voice channel to pause the music.') return await self._embed_msg(ctx, 'You must be in the voice channel to pause the music.')
if dj_enabled: if dj_enabled:
if not await self._can_instaskip(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author):
return await self._embed_msg(ctx, 'You need the DJ role to pause songs.') return await self._embed_msg(ctx, 'You need the DJ role to pause songs.')
command = ctx.invoked_with command = ctx.invoked_with
if player.current and not player.paused and command == 'pause': if player.current and not player.paused and command != 'resume':
await player.pause() await player.pause()
embed = discord.Embed( embed = discord.Embed(
colour=ctx.guild.me.top_role.colour, title='Track Paused', colour=ctx.guild.me.top_role.colour, title='Track Paused',
@ -391,7 +405,7 @@ class Audio:
) )
return await ctx.send(embed=embed) return await ctx.send(embed=embed)
if player.paused and command == 'resume': if player.paused and command != 'pause':
await player.pause(False) await player.pause(False)
embed = discord.Embed( embed = discord.Embed(
colour=ctx.guild.me.top_role.colour, colour=ctx.guild.me.top_role.colour,
@ -449,7 +463,7 @@ class Audio:
description=queue_user_list) description=queue_user_list)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.command(aliases=['p']) @commands.command()
async def play(self, ctx, *, query): async def play(self, ctx, *, query):
"""Play a URL or search for a song.""" """Play a URL or search for a song."""
dj_enabled = await self.config.guild(ctx.guild).dj_enabled() dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
@ -465,8 +479,6 @@ class Audio:
if dj_enabled: if dj_enabled:
if not await self._can_instaskip(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author):
return await self._embed_msg(ctx, 'You need the DJ role to queue songs.') return await self._embed_msg(ctx, 'You need the DJ role to queue songs.')
if not await self._currency_check(ctx, jukebox_price):
return
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
player.store('channel', ctx.channel.id) player.store('channel', ctx.channel.id)
player.store('guild', ctx.guild.id) player.store('guild', ctx.guild.id)
@ -474,6 +486,8 @@ class Audio:
if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not
await self._can_instaskip(ctx, ctx.author)): await self._can_instaskip(ctx, ctx.author)):
return await self._embed_msg(ctx, 'You must be in the voice channel to use the play command.') return await self._embed_msg(ctx, 'You must be in the voice channel to use the play command.')
if not await self._currency_check(ctx, jukebox_price):
return
query = query.strip('<>') query = query.strip('<>')
if not query.startswith('http'): if not query.startswith('http'):
@ -485,6 +499,7 @@ class Audio:
queue_duration = await self._queue_duration(ctx) 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)
before_queue_length = len(player.queue) + 1
if 'list' in query and 'ytsearch:' not in query: if 'list' in query and 'ytsearch:' not in query:
for track in tracks: for track in tracks:
@ -492,7 +507,7 @@ class Audio:
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist Enqueued', embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist Enqueued',
description='Added {} tracks to the queue.'.format(len(tracks))) description='Added {} tracks to the queue.'.format(len(tracks)))
if not shuffle and queue_duration > 0: if not shuffle and queue_duration > 0:
embed.set_footer(text='{} until start of playlist playback'.format(queue_total_duration)) embed.set_footer(text='{} until start of playlist playback: starts at #{} in queue'.format(queue_total_duration, before_queue_length))
if not player.current: if not player.current:
await player.play() await player.play()
else: else:
@ -501,11 +516,181 @@ class Audio:
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Enqueued', embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Track Enqueued',
description='**[{}]({})**'.format(single_track.title, single_track.uri)) description='**[{}]({})**'.format(single_track.title, single_track.uri))
if not shuffle and queue_duration > 0: if not shuffle and queue_duration > 0:
embed.set_footer(text='{} until track playback'.format(queue_total_duration)) embed.set_footer(text='{} until track playback: #{} in queue'.format(queue_total_duration, before_queue_length))
if not player.current: if not player.current:
await player.play() await player.play()
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.group()
@commands.guild_only()
async def playlist(self, ctx):
"""Playlist configuration options."""
if ctx.invoked_subcommand is None:
await ctx.send_help()
@playlist.command(name='delete')
async def _playlist_delete(self, ctx, playlist_name):
"""Delete a saved playlist."""
async with self.config.guild(ctx.guild).playlists() as playlists:
try:
if playlists[playlist_name]['author'] != ctx.author.id and not await self._can_instaskip(ctx, ctx.author):
return await self._embed_msg(ctx, 'You are not the author of that playlist.')
del playlists[playlist_name]
except KeyError:
return await self._embed_msg(ctx, 'No playlist with that name.')
await self._embed_msg(ctx, '{} playlist removed.'.format(playlist_name))
@playlist.command(name='info')
async def _playlist_info(self, ctx, playlist_name):
"""Retrieve information from a saved playlist."""
playlists = await self.config.guild(ctx.guild).playlists.get_raw()
try:
author_id = playlists[playlist_name]['author']
except KeyError:
return await self._embed_msg(ctx, 'No playlist with that name.')
author_obj = self.bot.get_user(author_id)
playlist_url = playlists[playlist_name]['playlist_url']
try:
track_len = len(playlists[playlist_name]['tracks'])
except TypeError:
track_len = 1
if playlist_url is None:
playlist_url = '**Not generated from a URL.**'
else:
playlist_url = 'URL: <{}>'.format(playlist_url)
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist info for {}:'.format(playlist_name),
description='Author: **{}**\n{}'.format(author_obj,
playlist_url))
if track_len > 1:
embed.set_footer(text='{} tracks'.format(track_len))
if track_len == 1:
embed.set_footer(text='{} track'.format(track_len))
await ctx.send(embed=embed)
@playlist.command(name='list')
async def _playlist_list(self, ctx):
"""List saved playlists."""
playlists = await self.config.guild(ctx.guild).playlists.get_raw()
playlist_list = []
for playlist_name in playlists:
playlist_list.append(playlist_name)
abc_names = sorted(playlist_list, key=str.lower)
all_playlists = ', '.join(abc_names)
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlists for {}:'.format(ctx.guild.name),
description=all_playlists)
await ctx.send(embed=embed)
@playlist.command(name='queue')
async def _playlist_queue(self, ctx, playlist_name=None):
"""Save the queue to a playlist."""
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
if dj_enabled:
if not await self._can_instaskip(ctx, ctx.author):
return await self._embed_msg(ctx, 'You need the DJ role to save playlists.')
async with self.config.guild(ctx.guild).playlists() as playlists:
if playlist_name in playlists:
return await self._embed_msg(ctx, 'Playlist name already exists, try again with a different name.')
if not self._player_check(ctx):
return await self._embed_msg(ctx, 'Nothing playing.')
player = lavalink.get_player(ctx.guild.id)
tracklist = []
np_song = self._track_creator(ctx, player, 'np', None)
tracklist.append(np_song)
for track in player.queue:
queue_idx = player.queue.index(track)
track_obj = self._track_creator(ctx, player, queue_idx, None)
tracklist.append(track_obj)
if not playlist_name:
await self._embed_msg(ctx, 'Please enter a name for this playlist.')
def check(m):
return m.author == ctx.author
try:
playlist_name_msg = await ctx.bot.wait_for('message', timeout=15.0, check=check)
playlist_name = str(playlist_name_msg.content)
if len(playlist_name) > 20:
return await self._embed_msg(ctx, 'Try the command again with a shorter name.')
if playlist_name in playlists:
return await self._embed_msg(ctx, 'Playlist name already exists, try again with a different name.')
except asyncio.TimeoutError:
return await self._embed_msg(ctx, 'No playlist name entered, try again later.')
playlist_list = self._to_json(ctx, None, tracklist, playlist_name)
async with self.config.guild(ctx.guild).playlists() as playlists:
playlists[playlist_name] = playlist_list
await self._embed_msg(ctx, 'Playlist {} saved from current queue: {} tracks added.'.format(playlist_name, len(tracklist)))
@playlist.command(name='save')
async def _playlist_save(self, ctx, playlist_name, playlist_url):
"""Save a playlist from a url."""
if not await self._playlist_check(ctx):
return
player = lavalink.get_player(ctx.guild.id)
tracks = await player.get_tracks(playlist_url)
if not tracks:
return await self._embed_msg(ctx, 'Nothing found.')
tracklist = []
for track in tracks:
track_obj = self._track_creator(ctx, player, None, track)
tracklist.append(track_obj)
playlist_list = self._to_json(ctx, playlist_url, tracklist, playlist_name)
async with self.config.guild(ctx.guild).playlists() as playlists:
playlists[playlist_name] = playlist_list
return await self._embed_msg(ctx, 'Playlist {} saved: {} tracks added.'.format(playlist_name, len(tracks)))
@playlist.command(name='start')
async def _playlist_start(self, ctx, playlist_name=None):
"""Load a playlist into the queue."""
if not await self._playlist_check(ctx):
return
playlists = await self.config.guild(ctx.guild).playlists.get_raw()
try:
author_id = playlists[playlist_name]["author"]
except KeyError:
return await self._embed_msg(ctx, 'That playlist doesn\'t exist.')
author_obj = self.bot.get_user(author_id)
track_count = 0
try:
playlist_len = len(playlists[playlist_name]["tracks"])
player = lavalink.get_player(ctx.guild.id)
for track in playlists[playlist_name]["tracks"]:
player.add(author_obj, lavalink.rest_api.Track(data=track))
track_count = track_count + 1
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist Enqueued',
description='Added {} tracks to the queue.'.format(track_count))
await ctx.send(embed=embed)
if not player.current:
await player.play()
except TypeError:
await ctx.invoke(self.play, query=playlists[playlist_name]["playlist_url"])
async def _playlist_check(self, ctx):
dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
jukebox_price = await self.config.guild(ctx.guild).jukebox_price()
if dj_enabled:
if not await self._can_instaskip(ctx, ctx.author):
await self._embed_msg(ctx, 'You need the DJ role to use playlists.')
return False
if not self._player_check(ctx):
try:
await lavalink.connect(ctx.author.voice.channel)
player = lavalink.get_player(ctx.guild.id)
player.store('connect', datetime.datetime.utcnow())
except AttributeError:
await self._embed_msg(ctx, 'Connect to a voice channel first.')
return False
player = lavalink.get_player(ctx.guild.id)
player.store('channel', ctx.channel.id)
player.store('guild', ctx.guild.id)
if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not
await self._can_instaskip(ctx, ctx.author)):
await self._embed_msg(ctx, 'You must be in the voice channel to use the playlist command.')
return False
if not await self._currency_check(ctx, jukebox_price):
return False
await self._data_check(ctx)
return True
@commands.command() @commands.command()
async def prev(self, ctx): async def prev(self, ctx):
"""Skips to the start of the previously played track.""" """Skips to the start of the previously played track."""
@ -515,7 +700,7 @@ class Audio:
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
shuffle = await self.config.guild(ctx.guild).shuffle() shuffle = await self.config.guild(ctx.guild).shuffle()
if dj_enabled: if dj_enabled:
if not await self._can_instaskip(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author):
return await self._embed_msg(ctx, 'You need the DJ role to skip songs.') return await self._embed_msg(ctx, 'You need the DJ role to skip songs.')
if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not if ((not ctx.author.voice or ctx.author.voice.channel != player.channel) and not
await self._can_instaskip(ctx, ctx.author)): await self._can_instaskip(ctx, ctx.author)):
@ -526,7 +711,7 @@ class Audio:
return await self._embed_msg(ctx, 'No previous track.') return await self._embed_msg(ctx, 'No previous track.')
else: else:
last_track = await player.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]) player.add(player.fetch('prev_requester'), last_track[0])
queue_len = len(player.queue) queue_len = len(player.queue)
bump_song = player.queue[-1] bump_song = player.queue[-1]
player.queue.insert(0, bump_song) player.queue.insert(0, bump_song)
@ -557,7 +742,10 @@ class Audio:
end = start + items_per_page end = start + items_per_page
queue_list = '' queue_list = ''
arrow = await self._draw_time(ctx) try:
arrow = await self._draw_time(ctx)
except AttributeError:
return await self._embed_msg(ctx, 'There\'s nothing in the queue.')
pos = lavalink.utils.format_time(player.position) pos = lavalink.utils.format_time(player.position)
if player.current.is_stream: if player.current.is_stream:
@ -715,7 +903,7 @@ class Audio:
queue_duration = await self._queue_duration(ctx) 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: if not shuffle and queue_duration > 0:
songembed.set_footer(text='{} until start of search playback'.format(queue_total_duration)) songembed.set_footer(text='{} until start of search playback: starts at #{} in queue'.format(queue_total_duration, (len(player.queue) + 1)))
for track in tracks: for track in tracks:
player.add(ctx.author, track) player.add(ctx.author, track)
if not player.current: if not player.current:
@ -735,7 +923,7 @@ class Audio:
queue_duration = await self._queue_duration(ctx) 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: if not shuffle and queue_duration > 0:
embed.set_footer(text='{} until track playback'.format(queue_total_duration)) embed.set_footer(text='{} until track playback: #{} in queue'.format(queue_total_duration, (len(player.queue) + 1)))
player.add(ctx.author, search_choice) player.add(ctx.author, search_choice)
if not player.current: if not player.current:
await player.play() await player.play()
@ -752,7 +940,7 @@ class Audio:
await self._can_instaskip(ctx, ctx.author)): await self._can_instaskip(ctx, ctx.author)):
return await self._embed_msg(ctx, 'You must be in the voice channel to use seek.') return await self._embed_msg(ctx, 'You must be in the voice channel to use seek.')
if dj_enabled: if dj_enabled:
if not await self._can_instaskip(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author):
return await self._embed_msg(ctx, 'You need the DJ role to use seek.') return await self._embed_msg(ctx, 'You need the DJ role to use seek.')
if player.current: if player.current:
if player.current.is_stream: if player.current.is_stream:
@ -798,7 +986,7 @@ class Audio:
dj_enabled = await self.config.guild(ctx.guild).dj_enabled() dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
vote_enabled = await self.config.guild(ctx.guild).vote_enabled() vote_enabled = await self.config.guild(ctx.guild).vote_enabled()
if dj_enabled and not vote_enabled and not await self._can_instaskip(ctx, ctx.author): if dj_enabled and not vote_enabled and not await self._can_instaskip(ctx, ctx.author):
if not await self._can_instaskip(ctx, ctx.author): if not await self._is_alone(ctx, ctx.author):
return await self._embed_msg(ctx, 'You need the DJ role to skip songs.') return await self._embed_msg(ctx, 'You need the DJ role to skip songs.')
if vote_enabled: if vote_enabled:
if not await self._can_instaskip(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author):
@ -846,18 +1034,26 @@ class Audio:
is_admin = discord.utils.get(ctx.guild.get_member(member.id).roles, id=admin_role) is not None is_admin = discord.utils.get(ctx.guild.get_member(member.id).roles, id=admin_role) is not None
is_mod = discord.utils.get(ctx.guild.get_member(member.id).roles, id=mod_role) is not None is_mod = discord.utils.get(ctx.guild.get_member(member.id).roles, id=mod_role) is not None
is_bot = member.bot is True is_bot = member.bot is True
return is_active_dj or is_owner or is_server_owner or is_coowner or is_admin or is_mod or is_bot
async def _is_alone(self, ctx, member):
try: try:
nonbots = sum(not m.bot for m in ctx.guild.get_member(member.id).voice.channel.members) user_voice = ctx.guild.get_member(member.id).voice
bot_voice = ctx.guild.get_member(self.bot.user.id).voice
nonbots = sum(not m.bot for m in user_voice.channel.members)
if user_voice.channel != bot_voice.channel:
nonbots = nonbots + 1
except AttributeError: except AttributeError:
if ctx.guild.get_member(self.bot.user.id).voice is not None: if ctx.guild.get_member(self.bot.user.id).voice is not None:
nonbots = sum(not m.bot for m in ctx.guild.get_member(self.bot.user.id).voice.channel.members) nonbots = sum(not m.bot for m in ctx.guild.get_member(self.bot.user.id).voice.channel.members)
if nonbots == 1: if nonbots == 1:
nonbots = 2 nonbots = 2
else: else:
nonbots = 2 if ctx.guild.get_member(member.id).voice.channel.members == 1:
nonbots = 1
alone = nonbots <= 1 alone = nonbots <= 1
return alone
return is_active_dj or is_owner or is_server_owner or is_coowner or is_admin or is_mod or is_bot or alone
async def _has_dj_role(self, ctx, member): async def _has_dj_role(self, ctx, member):
dj_role_id = await self.config.guild(ctx.guild).dj_role() dj_role_id = await self.config.guild(ctx.guild).dj_role()
@ -902,7 +1098,7 @@ class Audio:
await self._can_instaskip(ctx, ctx.author)): await self._can_instaskip(ctx, ctx.author)):
return await self._embed_msg(ctx, 'You must be in the voice channel to stop the music.') return await self._embed_msg(ctx, 'You must be in the voice channel to stop the music.')
if vote_enabled or vote_enabled and dj_enabled: if vote_enabled or vote_enabled and dj_enabled:
if not await self._can_instaskip(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author):
return await self._embed_msg(ctx, 'There are other people listening - vote to skip instead.') return await self._embed_msg(ctx, 'There are other people listening - vote to skip instead.')
if dj_enabled and not vote_enabled: if dj_enabled and not vote_enabled:
if not await self._can_instaskip(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author):
@ -956,29 +1152,73 @@ class Audio:
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.send_help() await ctx.send_help()
@llsetup.command()
async def external(self, ctx):
"""Toggles using external lavalink servers."""
external = await self.config.use_external_lavalink()
await self.config.use_external_lavalink.set(not external)
if external:
await self.config.host.set('localhost')
await self.config.password.set('youshallnotpass')
await self.config.rest_port.set(2333)
await self.config.ws_port.set(2332)
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='External lavalink server: {}.'.format(not external))
embed.set_footer(text='Defaults reset.')
return await ctx.send(embed=embed)
else:
await self._embed_msg(ctx, 'External lavalink server: {}.'.format(not external))
@llsetup.command() @llsetup.command()
async def host(self, ctx, host): async def host(self, ctx, host):
"""Set the lavalink server host.""" """Set the lavalink server host."""
await self.config.host.set(host) await self.config.host.set(host)
await self._embed_msg(ctx, 'Host set to {}.'.format(host)) if await self._check_external():
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Host set to {}.'.format(host))
embed.set_footer(text='External lavalink server set to True.')
await ctx.send(embed=embed)
else:
await self._embed_msg(ctx, 'Host set to {}.'.format(host))
@llsetup.command() @llsetup.command()
async def password(self, ctx, passw): async def password(self, ctx, password):
"""Set the lavalink server password.""" """Set the lavalink server password."""
await self.config.passw.set(str(passw)) await self.config.password.set(str(password))
await self._embed_msg(ctx, 'Server password set to {}.'.format(passw)) if await self._check_external():
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Server password set to {}.'.format(password))
embed.set_footer(text='External lavalink server set to True.')
await ctx.send(embed=embed)
else:
await self._embed_msg(ctx, 'Server password set to {}.'.format(password))
@llsetup.command() @llsetup.command()
async def restport(self, ctx, rest_port): async def restport(self, ctx, rest_port):
"""Set the lavalink REST server port.""" """Set the lavalink REST server port."""
await self.config.rest_port.set(str(rest_port)) await self.config.rest_port.set(rest_port)
await self._embed_msg(ctx, 'REST port set to {}.'.format(rest_port)) if await self._check_external():
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='REST port set to {}.'.format(rest_port))
embed.set_footer(text='External lavalink server set to True.')
await ctx.send(embed=embed)
else:
await self._embed_msg(ctx, 'REST port set to {}.'.format(rest_port))
@llsetup.command() @llsetup.command()
async def wsport(self, ctx, rest_port): async def wsport(self, ctx, ws_port):
"""Set the lavalink websocket server port.""" """Set the lavalink websocket server port."""
await self.config.ws_port.set(str(ws_port)) await self.config.rest_port.set(ws_port)
await self._embed_msg(ctx, 'Websocket port set to {}.'.format(ws_port)) if await self._check_external():
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Websocket port set to {}.'.format(ws_port))
embed.set_footer(text='External lavalink server set to True.')
await ctx.send(embed=embed)
else:
await self._embed_msg(ctx, 'Websocket port set to {}.'.format(ws_port))
async def _check_external(self):
external = await self.config.use_external_lavalink()
if not external:
await self.config.use_external_lavalink.set(True)
return True
else:
return False
@staticmethod @staticmethod
async def _clear_react(message): async def _clear_react(message):
@ -1090,6 +1330,30 @@ class Audio:
except KeyError: except KeyError:
return False return False
def _to_json(self, ctx, playlist_url, tracklist, playlist_name):
playlist = {"author": ctx.author.id, "playlist_url": playlist_url, "tracks": tracklist}
return playlist
def _track_creator(self, ctx, player, position, other_track=None):
if position == 'np':
queued_track = player.current
elif position == None:
queued_track = other_track
else:
queued_track = player.queue[position]
track_keys = queued_track._info.keys()
track_values = queued_track._info.values()
track_id = queued_track.track_identifier
track_info = {}
for k, v in zip(track_keys, track_values):
track_info[k] = v
keys = ['track', 'info']
values = [track_id, track_info]
track_obj = {}
for key, value in zip(keys, values):
track_obj[key] = value
return track_obj
async def on_voice_state_update(self, member, before, after): async def on_voice_state_update(self, member, before, after):
if after.channel != before.channel: if after.channel != before.channel:
try: try: