Playlist additions and cleanup (#1579)

Add playlist append, create, remove, and upload.
This commit is contained in:
aikaterna 2018-05-03 20:43:00 -07:00 committed by Kowlin
parent d61827b92c
commit 79676c4f72

View File

@ -1,16 +1,18 @@
import aiohttp
import asyncio import asyncio
import datetime import datetime
import discord import discord
import heapq import heapq
import lavalink import lavalink
import math import math
import re
import redbot.core import redbot.core
from discord.ext import commands from discord.ext import commands
from redbot.core import Config, checks, bank from redbot.core import Config, checks, bank
from .manager import shutdown_lavalink_server from .manager import shutdown_lavalink_server
__version__ = "0.0.5" __version__ = "0.0.5a"
__author__ = ["aikaterna", "billy/bollo/ati"] __author__ = ["aikaterna", "billy/bollo/ati"]
@ -46,6 +48,7 @@ class Audio:
self.config.register_guild(**default_guild) self.config.register_guild(**default_guild)
self.config.register_global(**default_global) self.config.register_global(**default_global)
self.skip_votes = {} self.skip_votes = {}
self.session = aiohttp.ClientSession()
async def init_config(self): async def init_config(self):
host = await self.config.host() host = await self.config.host()
@ -96,10 +99,11 @@ class Audio:
await self.bot.change_presence(activity=None) await self.bot.change_presence(activity=None)
if playing_servers == 1: if playing_servers == 1:
await self.bot.change_presence(activity=discord.Activity(name=get_single_title, await self.bot.change_presence(activity=discord.Activity(name=get_single_title,
type=discord.ActivityType.listening)) type=discord.ActivityType.listening))
if playing_servers > 1: if playing_servers > 1:
await self.bot.change_presence(activity=discord.Activity(name='music in {} servers'.format(playing_servers), await self.bot.change_presence(
type=discord.ActivityType.playing)) activity=discord.Activity(name='music in {} servers'.format(playing_servers),
type=discord.ActivityType.playing))
if event_type == lavalink.LavalinkEvents.QUEUE_END and notify: if event_type == lavalink.LavalinkEvents.QUEUE_END and notify:
notify_channel = player.fetch('channel') notify_channel = player.fetch('channel')
@ -113,10 +117,11 @@ class Audio:
await self.bot.change_presence(activity=None) await self.bot.change_presence(activity=None)
if playing_servers == 1: if playing_servers == 1:
await self.bot.change_presence(activity=discord.Activity(name=get_single_title, await self.bot.change_presence(activity=discord.Activity(name=get_single_title,
type=discord.ActivityType.listening)) type=discord.ActivityType.listening))
if playing_servers > 1: if playing_servers > 1:
await self.bot.change_presence(activity=discord.Activity(name='music in {} servers'.format(playing_servers), await self.bot.change_presence(
type=discord.ActivityType.playing)) activity=discord.Activity(name='music in {} servers'.format(playing_servers),
type=discord.ActivityType.playing))
if event_type == lavalink.LavalinkEvents.TRACK_EXCEPTION: if event_type == lavalink.LavalinkEvents.TRACK_EXCEPTION:
message_channel = player.fetch('channel') message_channel = player.fetch('channel')
@ -124,7 +129,7 @@ class Audio:
message_channel = self.bot.get_channel(message_channel) message_channel = self.bot.get_channel(message_channel)
embed = discord.Embed(colour=message_channel.guild.me.top_role.colour, title='Track Error', embed = discord.Embed(colour=message_channel.guild.me.top_role.colour, title='Track Error',
description='{}\n**[{}]({})**'.format(extra, player.current.title, description='{}\n**[{}]({})**'.format(extra, player.current.title,
player.current.uri)) player.current.uri))
embed.set_footer(text='Skipping...') embed.set_footer(text='Skipping...')
await message_channel.send(embed=embed) await message_channel.send(embed=embed)
await player.skip() await player.skip()
@ -143,7 +148,6 @@ class Audio:
dj_role_id = await self.config.guild(ctx.guild).dj_role() dj_role_id = await self.config.guild(ctx.guild).dj_role()
if dj_role_id is None: if dj_role_id is None:
await self._embed_msg(ctx, 'Please set a role to use with DJ mode. Enter the role name now.') await self._embed_msg(ctx, 'Please set a role to use with DJ mode. Enter the role name now.')
def check(m): def check(m):
return m.author == ctx.author return m.author == ctx.author
try: try:
@ -172,8 +176,6 @@ class Audio:
@checks.mod_or_permissions(administrator=True) @checks.mod_or_permissions(administrator=True)
async def jukebox(self, ctx, price: int): async def jukebox(self, ctx, price: int):
"""Set a price for queueing songs for non-mods. 0 to disable.""" """Set a price for queueing songs for non-mods. 0 to disable."""
jukebox = await self.config.guild(ctx.guild).jukebox()
jukebox_price = await self.config.guild(ctx.guild).jukebox_price()
if price < 0: if price < 0:
return await self._embed_msg(ctx, 'Can\'t be less than zero.') return await self._embed_msg(ctx, 'Can\'t be less than zero.')
if price == 0: if price == 0:
@ -182,7 +184,7 @@ class Audio:
else: else:
jukebox = True jukebox = True
await self._embed_msg(ctx, 'Track queueing command price set to {} {}.'.format( await self._embed_msg(ctx, 'Track queueing command price set to {} {}.'.format(
price, await bank.get_currency_name(ctx.guild))) price, await bank.get_currency_name(ctx.guild)))
await self.config.guild(ctx.guild).jukebox_price.set(price) await self.config.guild(ctx.guild).jukebox_price.set(price)
await self.config.guild(ctx.guild).jukebox.set(jukebox) await self.config.guild(ctx.guild).jukebox.set(jukebox)
@ -266,10 +268,10 @@ class Audio:
connect_dur = self._dynamic_time(int((datetime.datetime.utcnow() - connect_start).total_seconds())) connect_dur = self._dynamic_time(int((datetime.datetime.utcnow() - connect_start).total_seconds()))
try: try:
server_list.append('{} [`{}`]: **[{}]({})**'.format(p.channel.guild.name, connect_dur, server_list.append('{} [`{}`]: **[{}]({})**'.format(p.channel.guild.name, connect_dur,
p.current.title, p.current.uri)) p.current.title, p.current.uri))
except AttributeError: except AttributeError:
server_list.append('{} [`{}`]: **{}**'.format(p.channel.guild.name, connect_dur, server_list.append('{} [`{}`]: **{}**'.format(p.channel.guild.name, connect_dur,
'Nothing playing.')) 'Nothing playing.'))
if server_num == 0: if server_num == 0:
servers = 'Not connected anywhere.' servers = 'Not connected anywhere.'
else: else:
@ -286,7 +288,7 @@ class Audio:
return await self._embed_msg(ctx, 'Nothing playing.') return await self._embed_msg(ctx, 'Nothing playing.')
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
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 bump a song.') return await self._embed_msg(ctx, 'You must be in the voice channel to bump a song.')
if dj_enabled: if dj_enabled:
if not await self._can_instaskip(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author):
@ -363,6 +365,7 @@ class Audio:
def check(r, u): def check(r, u):
return r.message.id == message.id and u == ctx.message.author return r.message.id == message.id and u == ctx.message.author
try: try:
(r, u) = await self.bot.wait_for('reaction_add', check=check, timeout=10.0) (r, u) = await self.bot.wait_for('reaction_add', check=check, timeout=10.0)
except asyncio.TimeoutError: except asyncio.TimeoutError:
@ -390,7 +393,7 @@ class Audio:
return await self._embed_msg(ctx, 'Nothing playing.') return await self._embed_msg(ctx, 'Nothing playing.')
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
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 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) and not await self._is_alone(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author):
@ -487,11 +490,13 @@ class Audio:
player.store('guild', ctx.guild.id) player.store('guild', ctx.guild.id)
await self._data_check(ctx) await self._data_check(ctx)
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): if not await self._currency_check(ctx, jukebox_price):
return return
if not query:
return await self._embed_msg(ctx, 'No songs to play.')
query = query.strip('<>') query = query.strip('<>')
if not query.startswith('http'): if not query.startswith('http'):
query = 'ytsearch:{}'.format(query) query = 'ytsearch:{}'.format(query)
@ -510,7 +515,8 @@ 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: starts at #{} in queue'.format(queue_total_duration, before_queue_length)) 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:
@ -519,7 +525,8 @@ 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: #{} in queue'.format(queue_total_duration, before_queue_length)) 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)
@ -531,17 +538,60 @@ class Audio:
if ctx.invoked_subcommand is None: if ctx.invoked_subcommand is None:
await ctx.send_help() await ctx.send_help()
@playlist.command(name='append')
async def _playlist_append(self, ctx, playlist_name, *url):
"""Add a song URL, playlist link, or quick search to the end of a saved playlist."""
if not await self._playlist_check(ctx):
return
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.')
player = lavalink.get_player(ctx.guild.id)
to_append = await self._playlist_tracks(ctx, player, url)
if not to_append:
return
track_list = playlists[playlist_name]['tracks']
if track_list:
playlists[playlist_name]['tracks'] = track_list + to_append
else:
playlists[playlist_name]['tracks'] = to_append
except KeyError:
return await self._embed_msg(ctx, 'No playlist with that name.')
if playlists[playlist_name]['playlist_url'] is not None:
playlists[playlist_name]['playlist_url'] = None
if len(to_append) == 1:
track_title = to_append[0]['info']['title']
return await self._embed_msg(ctx, '{} appended to {}.'.format(track_title, playlist_name))
await self._embed_msg(ctx, '{} tracks appended to {}.'.format(len(to_append), playlist_name))
@playlist.command(name='create')
async def _playlist_create(self, ctx, playlist_name):
"""Create an empty 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.')
playlist_list = self._to_json(ctx, None, None)
playlists[playlist_name] = playlist_list
await self._embed_msg(ctx, 'Empty playlist {} created.'.format(playlist_name))
@playlist.command(name='delete') @playlist.command(name='delete')
async def _playlist_delete(self, ctx, playlist_name): async def _playlist_delete(self, ctx, playlist_name):
"""Delete a saved playlist.""" """Delete a saved playlist."""
async with self.config.guild(ctx.guild).playlists() as playlists: async with self.config.guild(ctx.guild).playlists() as playlists:
try: try:
if playlists[playlist_name]['author'] != ctx.author.id and not await self._can_instaskip(ctx, ctx.author): 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.') return await self._embed_msg(ctx, 'You are not the author of that playlist.')
del playlists[playlist_name] del playlists[playlist_name]
except KeyError: except KeyError:
return await self._embed_msg(ctx, 'No playlist with that name.') return await self._embed_msg(ctx, 'No playlist with that name.')
await self._embed_msg(ctx, '{} playlist removed.'.format(playlist_name)) await self._embed_msg(ctx, '{} playlist deleted.'.format(playlist_name))
@playlist.command(name='info') @playlist.command(name='info')
async def _playlist_info(self, ctx, playlist_name): async def _playlist_info(self, ctx, playlist_name):
@ -556,18 +606,15 @@ class Audio:
try: try:
track_len = len(playlists[playlist_name]['tracks']) track_len = len(playlists[playlist_name]['tracks'])
except TypeError: except TypeError:
track_len = 1 track_len = 0
if playlist_url is None: if playlist_url is None:
playlist_url = '**Not generated from a URL.**' playlist_url = '**Custom playlist.**'
else: else:
playlist_url = 'URL: <{}>'.format(playlist_url) playlist_url = 'URL: <{}>'.format(playlist_url)
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist info for {}:'.format(playlist_name), embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist info for {}:'.format(playlist_name),
description='Author: **{}**\n{}'.format(author_obj, description='Author: **{}**\n{}'.format(author_obj,
playlist_url)) playlist_url))
if track_len > 1: embed.set_footer(text='{} track(s)'.format(track_len))
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) await ctx.send(embed=embed)
@playlist.command(name='list') @playlist.command(name='list')
@ -597,11 +644,11 @@ class Audio:
return await self._embed_msg(ctx, 'Nothing playing.') return await self._embed_msg(ctx, 'Nothing playing.')
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
tracklist = [] tracklist = []
np_song = self._track_creator(ctx, player, 'np', None) np_song = self._track_creator(player, 'np')
tracklist.append(np_song) tracklist.append(np_song)
for track in player.queue: for track in player.queue:
queue_idx = player.queue.index(track) queue_idx = player.queue.index(track)
track_obj = self._track_creator(ctx, player, queue_idx, None) track_obj = self._track_creator(player, queue_idx)
tracklist.append(track_obj) tracklist.append(track_obj)
if not playlist_name: if not playlist_name:
await self._embed_msg(ctx, 'Please enter a name for this playlist.') await self._embed_msg(ctx, 'Please enter a name for this playlist.')
@ -616,11 +663,38 @@ class Audio:
return await self._embed_msg(ctx, 'Playlist name already exists, try again with a different name.') return await self._embed_msg(ctx, 'Playlist name already exists, try again with a different name.')
except asyncio.TimeoutError: except asyncio.TimeoutError:
return await self._embed_msg(ctx, 'No playlist name entered, try again later.') return await self._embed_msg(ctx, 'No playlist name entered, try again later.')
playlist_list = self._to_json(ctx, None, tracklist)
playlist_list = self._to_json(ctx, None, tracklist, playlist_name)
async with self.config.guild(ctx.guild).playlists() as playlists: async with self.config.guild(ctx.guild).playlists() as playlists:
playlists[playlist_name] = playlist_list playlists[playlist_name] = playlist_list
await self._embed_msg(ctx, 'Playlist {} saved from current queue: {} tracks added.'.format(playlist_name, len(tracklist))) await self._embed_msg(ctx, 'Playlist {} saved from current queue: {} tracks added.'.format(
playlist_name, len(tracklist)))
@playlist.command(name='remove')
async def _playlist_remove(self, ctx, playlist_name, url):
"""Remove a song from a playlist by url."""
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.')
except KeyError:
return await self._embed_msg(ctx, 'No playlist with that name.')
track_list = playlists[playlist_name]['tracks']
clean_list = [track for track in track_list if not url == track['info']['uri']]
if len(playlists[playlist_name]['tracks']) == len(clean_list):
return await self._embed_msg(ctx, 'URL not in playlist.')
del_count = len(playlists[playlist_name]['tracks']) - len(clean_list)
if not clean_list:
del playlists[playlist_name]
return await self._embed_msg(ctx, 'No songs left, removing playlist.')
playlists[playlist_name]['tracks'] = clean_list
if playlists[playlist_name]['playlist_url'] is not None:
playlists[playlist_name]['playlist_url'] = None
if del_count > 1:
await self._embed_msg(ctx, '{} entries have been removed from the {} playlist.'.format(
del_count, playlist_name))
else:
await self._embed_msg(ctx, 'The track has been removed from the {} playlist.'.format(playlist_name))
@playlist.command(name='save') @playlist.command(name='save')
async def _playlist_save(self, ctx, playlist_name, playlist_url): async def _playlist_save(self, ctx, playlist_name, playlist_url):
@ -628,18 +702,13 @@ class Audio:
if not await self._playlist_check(ctx): if not await self._playlist_check(ctx):
return return
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
tracks = await player.get_tracks(playlist_url) tracklist = await self._playlist_tracks(ctx, player, playlist_url)
if not tracks: playlist_list = self._to_json(ctx, playlist_url, tracklist)
return await self._embed_msg(ctx, 'Nothing found.') if tracklist is not None:
tracklist = [] async with self.config.guild(ctx.guild).playlists() as playlists:
for track in tracks: playlists[playlist_name] = playlist_list
track_obj = self._track_creator(ctx, player, None, track) return await self._embed_msg(ctx, 'Playlist {} saved: {} tracks added.'.format(
tracklist.append(track_obj) playlist_name, len(tracklist)))
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') @playlist.command(name='start')
async def _playlist_start(self, ctx, playlist_name=None): async def _playlist_start(self, ctx, playlist_name=None):
@ -647,25 +716,89 @@ class Audio:
if not await self._playlist_check(ctx): if not await self._playlist_check(ctx):
return return
playlists = await self.config.guild(ctx.guild).playlists.get_raw() playlists = await self.config.guild(ctx.guild).playlists.get_raw()
try: author_obj = self.bot.get_user(ctx.author.id)
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 track_count = 0
try: try:
playlist_len = len(playlists[playlist_name]["tracks"])
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
for track in playlists[playlist_name]["tracks"]: for track in playlists[playlist_name]["tracks"]:
player.add(author_obj, lavalink.rest_api.Track(data=track)) player.add(author_obj, lavalink.rest_api.Track(data=track))
track_count = track_count + 1 track_count = track_count + 1
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(track_count)) description='Added {} tracks to the queue.'.format(track_count))
await ctx.send(embed=embed) await ctx.send(embed=embed)
if not player.current: if not player.current:
await player.play() await player.play()
except TypeError: except TypeError:
await ctx.invoke(self.play, query=playlists[playlist_name]["playlist_url"]) await ctx.invoke(self.play, query=playlists[playlist_name]["playlist_url"])
except KeyError:
await self._embed_msg(ctx, 'That playlist doesn\'t exist.')
@checks.is_owner()
@playlist.command(name='upload')
async def _playlist_upload(self, ctx):
"""Convert a Red v2 playlist file to a playlist."""
if not await self._playlist_check(ctx):
return
player = lavalink.get_player(ctx.guild.id)
await self._embed_msg(ctx, 'Please upload the playlist file. Any other message will cancel this operation.')
def check(m):
return m.author == ctx.author
try:
file_message = await ctx.bot.wait_for('message', timeout=30.0, check=check)
except asyncio.TimeoutError:
return await self._embed_msg(ctx, 'No file detected, try again later.')
try:
file_url = file_message.attachments[0].url
except IndexError:
return await self._embed_msg(ctx, 'Upload canceled.')
v2_playlist_name = (file_url.split('/')[6]).split('.')[0]
file_suffix = file_url.rsplit('.', 1)[1]
if file_suffix != "txt":
return await self._embed_msg(ctx, 'Only playlist files can be uploaded.')
async with self.session.request('GET', file_url) as r:
v2_playlist = await r.json(content_type='text/plain')
try:
v2_playlist_url = v2_playlist["link"]
except KeyError:
v2_playlist_url = None
if (not v2_playlist_url or not self._match_yt_playlist(v2_playlist_url) or not
await player.get_tracks(v2_playlist_url)):
track_list = []
track_count = 0
async with self.config.guild(ctx.guild).playlists() as v3_playlists:
try:
if v3_playlists[v2_playlist_name]:
return await self._embed_msg(ctx, 'A playlist already exists with this name.')
except KeyError:
pass
embed1 = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Please wait, adding tracks...')
playlist_msg = await ctx.send(embed=embed1)
for song_url in v2_playlist["playlist"]:
track = await player.get_tracks(song_url)
try:
track_obj = self._track_creator(player, other_track=track[0])
track_list.append(track_obj)
track_count = track_count + 1
except IndexError:
pass
if track_count % 5 == 0:
embed2 = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Loading track {}/{}...'.format(
track_count, len(v2_playlist["playlist"])))
await playlist_msg.edit(embed=embed2)
if not track_list:
return await self._embed_msg(ctx, 'No tracks found.')
playlist_list = self._to_json(ctx, v2_playlist_url, track_list)
v3_playlists[v2_playlist_name] = playlist_list
if len(v2_playlist["playlist"]) != track_count:
bad_tracks = len(v2_playlist["playlist"]) - track_count
msg = ('Added {} tracks from the {} playlist. {} track(s) could not '
'be loaded.'.format(track_count, v2_playlist_name, bad_tracks))
else:
msg = 'Added {} tracks from the {} playlist.'.format(track_count, v2_playlist_name)
embed3 = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Playlist Saved', description=msg)
await playlist_msg.edit(embed=embed3)
else:
await ctx.invoke(self._playlist_save, v2_playlist_name, v2_playlist_url)
async def _playlist_check(self, ctx): async def _playlist_check(self, ctx):
dj_enabled = await self.config.guild(ctx.guild).dj_enabled() dj_enabled = await self.config.guild(ctx.guild).dj_enabled()
@ -686,7 +819,7 @@ class Audio:
player.store('channel', ctx.channel.id) player.store('channel', ctx.channel.id)
player.store('guild', ctx.guild.id) player.store('guild', ctx.guild.id)
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)):
await self._embed_msg(ctx, 'You must be in the voice channel to use the playlist command.') await self._embed_msg(ctx, 'You must be in the voice channel to use the playlist command.')
return False return False
if not await self._currency_check(ctx, jukebox_price): if not await self._currency_check(ctx, jukebox_price):
@ -694,6 +827,27 @@ class Audio:
await self._data_check(ctx) await self._data_check(ctx)
return True return True
async def _playlist_tracks(self, ctx, player, query):
search = False
if type(query) is tuple:
query = " ".join(query)
if not query.startswith('http'):
query = " ".join(query)
query = 'ytsearch:{}'.format(query)
search = True
tracks = await player.get_tracks(query)
if not tracks:
return await self._embed_msg(ctx, 'Nothing found.')
tracklist = []
if not search:
for track in tracks:
track_obj = self._track_creator(player, other_track=track)
tracklist.append(track_obj)
else:
track_obj = self._track_creator(player, other_track=tracks[0])
tracklist.append(track_obj)
return tracklist
@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."""
@ -706,7 +860,7 @@ class Audio:
if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(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)):
return await self._embed_msg(ctx, 'You must be in the voice channel to skip the music.') return await self._embed_msg(ctx, 'You must be in the voice channel to skip the music.')
if shuffle: if shuffle:
return await self._embed_msg(ctx, 'Turn shuffle off to use this command.') return await self._embed_msg(ctx, 'Turn shuffle off to use this command.')
@ -773,8 +927,8 @@ class Audio:
for i, track in enumerate(player.queue[start:end], start=start): for i, track in enumerate(player.queue[start:end], start=start):
req_user = track.requester req_user = track.requester
next = i + 1 _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, embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Queue for ' + ctx.guild.name,
description=queue_list) description=queue_list)
@ -802,7 +956,7 @@ class Audio:
await self._data_check(ctx) await self._data_check(ctx)
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
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 toggle repeat.') return await self._embed_msg(ctx, 'You must be in the voice channel to toggle repeat.')
await self._embed_msg(ctx, 'Repeat songs: {}.'.format(repeat)) await self._embed_msg(ctx, 'Repeat songs: {}.'.format(repeat))
@ -819,7 +973,7 @@ class Audio:
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 remove songs.') return await self._embed_msg(ctx, 'You need the DJ role to remove 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)):
return await self._embed_msg(ctx, 'You must be in the voice channel to manage the queue.') return await self._embed_msg(ctx, 'You must be in the voice channel to manage the queue.')
if index > len(player.queue) or index < 1: 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.') return await self._embed_msg(ctx, 'Song number must be greater than 1 and within the queue limit.')
@ -852,7 +1006,7 @@ class Audio:
player.store('channel', ctx.channel.id) player.store('channel', ctx.channel.id)
player.store('guild', ctx.guild.id) player.store('guild', ctx.guild.id)
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 enqueue songs.') return await self._embed_msg(ctx, 'You must be in the voice channel to enqueue songs.')
query = query.strip('<>') query = query.strip('<>')
@ -872,8 +1026,8 @@ class Audio:
end = start + items_per_page end = start + items_per_page
search_list = '' search_list = ''
for i, track in enumerate(tracks[start:end], start=start): for i, track in enumerate(tracks[start:end], start=start):
next = i + 1 _next = i + 1
search_list += '`{0}.` [**{1}**]({2})\n'.format(next, track.title, search_list += '`{0}.` [**{1}**]({2})\n'.format(_next, track.title,
track.uri) track.uri)
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Tracks Found:', description=search_list) embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Tracks Found:', description=search_list)
@ -906,7 +1060,8 @@ 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: starts at #{} in queue'.format(queue_total_duration, (len(player.queue) + 1))) 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:
@ -926,7 +1081,8 @@ 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: #{} in queue'.format(queue_total_duration, (len(player.queue) + 1))) 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()
@ -940,7 +1096,7 @@ class Audio:
return await self._embed_msg(ctx, 'Nothing playing.') return await self._embed_msg(ctx, 'Nothing playing.')
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
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 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) and not await self._is_alone(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author):
@ -973,7 +1129,7 @@ class Audio:
await self._data_check(ctx) await self._data_check(ctx)
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
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 toggle shuffle.') return await self._embed_msg(ctx, 'You must be in the voice channel to toggle shuffle.')
await self._embed_msg(ctx, 'Shuffle songs: {}.'.format(shuffle)) await self._embed_msg(ctx, 'Shuffle songs: {}.'.format(shuffle))
@ -984,7 +1140,7 @@ class Audio:
return await self._embed_msg(ctx, 'Nothing playing.') return await self._embed_msg(ctx, 'Nothing playing.')
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
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 skip the music.') return await self._embed_msg(ctx, 'You must be in the voice channel to skip the music.')
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()
@ -1052,11 +1208,11 @@ class Audio:
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
elif ctx.guild.get_member(member.id).voice.channel.members == 1:
nonbots = 1
else: else:
if ctx.guild.get_member(member.id).voice.channel.members == 1: nonbots = 0
nonbots = 1 return nonbots <= 1
alone = nonbots <= 1
return 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()
@ -1098,7 +1254,7 @@ class Audio:
return await self._embed_msg(ctx, 'Nothing playing.') return await self._embed_msg(ctx, 'Nothing playing.')
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
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 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) and not await self._is_alone(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author) and not await self._is_alone(ctx, ctx.author):
@ -1128,7 +1284,7 @@ class Audio:
if self._player_check(ctx): if self._player_check(ctx):
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
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 change the volume.') return await self._embed_msg(ctx, 'You must be in the voice channel to change the volume.')
if dj_enabled: if dj_enabled:
if not await self._can_instaskip(ctx, ctx.author) and not await self._has_dj_role(ctx, ctx.author): if not await self._can_instaskip(ctx, ctx.author) and not await self._has_dj_role(ctx, ctx.author):
@ -1165,7 +1321,8 @@ class Audio:
await self.config.password.set('youshallnotpass') await self.config.password.set('youshallnotpass')
await self.config.rest_port.set(2333) await self.config.rest_port.set(2333)
await self.config.ws_port.set(2332) 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 = discord.Embed(colour=ctx.guild.me.top_role.colour, title='External lavalink server: {}.'.format(
not external))
embed.set_footer(text='Defaults reset.') embed.set_footer(text='Defaults reset.')
return await ctx.send(embed=embed) return await ctx.send(embed=embed)
else: else:
@ -1187,7 +1344,8 @@ class Audio:
"""Set the lavalink server password.""" """Set the lavalink server password."""
await self.config.password.set(str(password)) await self.config.password.set(str(password))
if await self._check_external(): if await self._check_external():
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Server password set to {}.'.format(password)) 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.') embed.set_footer(text='External lavalink server set to True.')
await ctx.send(embed=embed) await ctx.send(embed=embed)
else: else:
@ -1209,7 +1367,8 @@ class Audio:
"""Set the lavalink websocket server port.""" """Set the lavalink websocket server port."""
await self.config.rest_port.set(ws_port) await self.config.rest_port.set(ws_port)
if await self._check_external(): if await self._check_external():
embed = discord.Embed(colour=ctx.guild.me.top_role.colour, title='Websocket port set to {}.'.format(ws_port)) 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.') embed.set_footer(text='External lavalink server set to True.')
await ctx.send(embed=embed) await ctx.send(embed=embed)
else: else:
@ -1255,7 +1414,8 @@ class Audio:
if player.volume != volume: if player.volume != volume:
await player.set_volume(volume) await player.set_volume(volume)
async def _draw_time(self, ctx): @staticmethod
async def _draw_time(ctx):
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
paused = player.paused paused = player.paused
pos = player.position pos = player.position
@ -1305,6 +1465,15 @@ class Audio:
else: else:
return 0 return 0
@staticmethod
def _match_yt_playlist(url):
yt_list_playlist = re.compile(
r'^(https?\:\/\/)?(www\.)?(youtube\.com|youtu\.?be)'
r'(\/playlist\?).*(list=)(.*)(&|$)')
if yt_list_playlist.match(url):
return True
return False
@staticmethod @staticmethod
async def _queue_duration(ctx): async def _queue_duration(ctx):
player = lavalink.get_player(ctx.guild.id) player = lavalink.get_player(ctx.guild.id)
@ -1312,7 +1481,7 @@ class Audio:
for i in range(len(player.queue)): for i in range(len(player.queue)):
if not player.queue[i].is_stream: if not player.queue[i].is_stream:
duration.append(player.queue[i].length) duration.append(player.queue[i].length)
queue_duration = sum(duration) queue_duration = sum(duration)
if not player.queue: if not player.queue:
queue_duration = 0 queue_duration = 0
try: try:
@ -1333,14 +1502,16 @@ class Audio:
except KeyError: except KeyError:
return False return False
def _to_json(self, ctx, playlist_url, tracklist, playlist_name): @staticmethod
def _to_json(ctx, playlist_url, tracklist):
playlist = {"author": ctx.author.id, "playlist_url": playlist_url, "tracks": tracklist} playlist = {"author": ctx.author.id, "playlist_url": playlist_url, "tracks": tracklist}
return playlist return playlist
def _track_creator(self, ctx, player, position, other_track=None): @staticmethod
def _track_creator(player, position=None, other_track=None):
if position == 'np': if position == 'np':
queued_track = player.current queued_track = player.current
elif position == None: elif position is None:
queued_track = other_track queued_track = other_track
else: else:
queued_track = player.queue[position] queued_track = player.queue[position]
@ -1365,6 +1536,7 @@ class Audio:
pass pass
def __unload(self): def __unload(self):
self.session.close()
lavalink.unregister_event_listener(self.event_handler) lavalink.unregister_event_listener(self.event_handler)
self.bot.loop.create_task(lavalink.close()) self.bot.loop.create_task(lavalink.close())
shutdown_lavalink_server() shutdown_lavalink_server()