MusicBot
MusicBot copied to clipboard
[Feature] Differents autoplaylists per servers
Hey again !
What about the fact to makes differents autoplaylist with differents songs per server ? And why not adding some command such as : !createplaylist, !addtoplaylist [song] !removetoplaylist [song] .....
And it can be even better if we could choose to enable/disable the autoplaylist on one server, or not.
I wrote this function to add songs to the playlist some time ago, feel free to use it if you like.
async def cmd_pladd(self, player, song_url=None):
"""
Usage:
{command_prefix}pladd current song
{command_prefix}pladd song_url
Adds a song to the autoplaylist.
"""
#No url provided
if not song_url:
#Check if there is something playing
if not player._current_entry:
raise exceptions.CommandError('There is nothing playing.', expire_in=20)
#Get the url of the current entry
else:
song_url = player._current_entry.url
title = player._current_entry.title
else:
#Get song info from url
info = await self.downloader.safe_extract_info(player.playlist.loop, song_url, download=False, process=False)
#Verify proper url
if not info:
raise exceptions.CommandError('Invalid url. Please insure link is a valid YouTube, SoundCloud or BandCamp url.', expire_in=20)
else:
title = info.get('title', '')
#Verify that the song isn't already in our playlist
for url in self.autoplaylist:
if song_url == url:
return Response("Song already present in autoplaylist.", delete_after=30)
self.autoplaylist.append(song_url)
write_file(self.config.auto_playlist_file, self.autoplaylist)
self.autoplaylist = load_file(self.config.auto_playlist_file)
return Response("Added %s to autoplaylist." % title, delete_after=30)
@Selkea - that's awesome, thank you! Stolen :)
Nice, btw, there is the oposite command (remove) Any idea about how to create different playlist for different servers ?
async def cmd_plremove(self, player, song_url=None):
"""
Usage:
{command_prefix}plremove current song
{command_prefix}plremove song_url
Remove a song from the autoplaylist.
"""
#No url provided
if not song_url:
#Check if there is something playing
if not player._current_entry:
raise exceptions.CommandError('There is nothing playing.', expire_in=20)
#Get the url of the current entry
else:
song_url = player._current_entry.url
title = player._current_entry.title
else:
#Get song info from url
info = await self.downloader.safe_extract_info(player.playlist.loop, song_url, download=False, process=False)
#Verify proper url
if not info:
raise exceptions.CommandError('Invalid url. Please insure link is a valid YouTube, SoundCloud or BandCamp url.', expire_in=20)
else:
title = info.get('title', '')
#Verify that the song is in our playlist
for url in self.autoplaylist:
if song_url != url:
return Response("Song not present in autoplaylist.", delete_after=30)
self.autoplaylist.remove(song_url)
write_file(self.config.auto_playlist_file, self.autoplaylist)
self.autoplaylist = load_file(self.config.auto_playlist_file)
return Response("Removed %s from the autoplaylist." % title, delete_after=30)
#Verify that the song is in our playlist
for url in self.autoplaylist:
if song_url != url:
return Response("Song not present in autoplaylist.", delete_after=30)
This check won't work unless the song you are trying to remove is the only one in your autoplaylist. It'll always return that the song isn't present even if it is, and you'll never reach the end of the function that actually removes it.
Instead I would do this:
#Verify that the song is in our playlist
for url in self.autoplaylist:
if song_url == url:
self.autoplaylist.remove(song_url)
write_file(self.config.auto_playlist_file, self.autoplaylist)
self.autoplaylist = load_file(self.config.auto_playlist_file)
return Response("Removed %s from the autoplaylist." % title, delete_after=30)
return Response("Song not present in autoplaylist.", delete_after=30)
After doing some research the best way I found to implement creating a new playlist was:
async def cmd_newpl(self, file_name=None):
"""
Usage:
{command_prefix}newpl file_name
Creates a new autoplaylist.
"""
#Verify file name was provided
if not file_name:
raise exceptions.CommandError('Please specify a file name for the new playlist.', expire_in=20)
#Create a new file
else:
#Append the file extension and make everything lowercase (important for checking against already existing files)
fileName = (file_name + ".txt").lower()
#Get the filepath, and set it to the autoplaylist directory
savePath = os.path.join(os.path.dirname(__file__), os.pardir) + "\config\\"
#Check to make sure there isn't already a file with the same name
for root, dirs, files in os.walk(savePath):
if fileName in files:
raise exceptions.CommandError("%s already exists in %s." % (fileName, savePath), expire_in=20)
write_file(savePath + fileName, "")
return Response("Created new playlist called %s." % file_name)
However even if you create new playlists, you'll need to make modifications to the previous commands (pladd/plrem) to ensure they are writing to the proper files, as well as a new command to load the playlist you want.
Wrote function to load playlists on demand.
async def cmd_loadpl(self, file_name=None):
"""
Usage:
{command_prefix}loadpl file_name
Loads an existing autoplaylist.
"""
#Verify file name was provided
if not file_name:
raise exceptions.CommandError('Please specify which playlist to load.', expire_in=20)
#Create a new file
else:
#Append the file extension and make everything lowercase (important for checking against already existing files)
fileName = (file_name + ".txt").lower()
#Get the filepath, and set it to the autoplaylist directory
savePath = os.path.join(os.path.dirname(__file__), os.pardir) + "\config\\"
#Check to locate existing file
for root, dirs, files in os.walk(savePath):
if fileName in files:
self.autoplaylist = load_file(savePath + fileName)
return Response("Loaded the %s playlist." % fileName, delete_after=30)
else:
raise exceptions.CommandError('Could not find %s, please ensure the spelling is correct and that the file exists.' % fileName, expire_in=20)
This requires that you modify the following lines in both pladd/plrem:
write_file(self.config.auto_playlist_file, self.autoplaylist)
self.autoplaylist = load_file(self.config.auto_playlist_file)
to
write_file(self.autoplaylist, self.autoplaylist)
self.autoplaylist = load_file(self.autoplaylist)
Note that self.autoplaylist will always be the config default (autoplaylist.txt) until you chose to load a different playlist, and will return to the default if the bot is restarted. Furthermore loading different playlist changes it for every server.
It's amazing ! But the problem is to load differents playlists on differents servers. I don't know how it can be possible . I tried to add a command to enable/disable the autoplaylist only on one server. It looks like the blacklist command. We have to create a file named "serverautoplaylist" for example and add a command like that:
unfortunately, in vain, I don't know how to do that... I don't have much knowledge. Maybe you have an idea ?
The problem as it stands, is that bot keeps track of the autoplaylist, instead of the player. You won't be able to fix that by making a new function, you'd have to make modifications to the player and the bot so that the player keeps track of it's own playlist and that the bot references the proper playlist when an event occurs or a function is called.
Here's what I did.
In player.py
- Added inclusion
from musicbot.config import ConfigDefaults
- Added attribute
self.autoplaylist = bot.autoplaylist
in MusicPlayer class - Added attribute
self.autoplaylist_file = ConfigDefaults.auto_playlist_file
in MusicPlayer class
In bot.py
- Replaced
self.autoplaylist
withplayer.autoplaylist
- Replaced
self.config.auto_playlist_file
withplayer.autoplaylist_file
P.S. It's very important that you change all instances of these lines (Ctrl + H) except for in __init__
__init__:
- Moved empty playlist check to
on_player_finished_playing
cmd_loadpl:
- Added
player
argument to function - Added
player.autoplaylist_file = savePath + fileName
after loading the file
Note that same as before, the default autoplaylist for all servers will be the same. However, you will now be able to load different playlist independantly to different servers.
and as a free bonus, a command to list existing playlist you can choose from:
async def cmd_listpl(self):
"""
Usage:
{command_prefix}listpl
Gives a list of existing autoplaylists.
"""
#Get the filepath, and set it to the autoplaylist directory
savePath = os.path.join(os.path.dirname(__file__), os.pardir) + "\config\\"
listOfPlaylists = []
#Check to locate existing file
for root, dirs, files in os.walk(savePath):
for file in files:
if file.endswith(".txt"):
listOfPlaylists.append(file[:-4])
else:
pass
#Remove settings files
listOfPlaylists.remove("blacklist")
listOfPlaylists.remove("whitelist")
return Response("Playlists available: %s." % ", ".join(listOfPlaylists), delete_after=30)
Thanks! But i've added all ur changes, and i've the following issue :
@zenrac That may have been an issue with the replace all, I forgot that instance shouldn't change, just replace that particular line back to self.autoplaylist = load_file(self.config.auto_playlist_file)
and you should be fine.
Make sure yours looks similar to mine.
async def cmd_loadpl(self, player, file_name=None):
"""
Usage:
{command_prefix}loadpl file_name
Loads an existing autoplaylist.
"""
#Verify file name was provided
if file_name:
#Append the file extension and make everything lowercase (important for checking against already existing files)
fileName = (file_name + ".txt").lower()
#Get the filepath, and set it to the autoplaylist directory
savePath = os.path.join(os.path.dirname(__file__), os.pardir) + "\config\\"
#Check to locate existing file
for root, dirs, files in os.walk(savePath):
if fileName in files:
player.autoplaylist = load_file(savePath + fileName)
player.autoplaylist_file = savePath + fileName
return Response("Loaded the %s playlist." % file_name, delete_after=30)
else:
raise exceptions.CommandError('Could not find %s, please ensure the spelling is correct and that the file exists.' % file_name, expire_in=20)
else:
raise exceptions.CommandError('Please specify which playlist to load.', expire_in=20)
made the changes in player.py, bot.py, and cmd_loadpl
got the same error as @zenrac so i did the change you said . now i get this error
UPDATE
i fixed the problem listed above, i can create and load new playlists using the commands but i cant add or remove to the playlists using pladd
or plremove
@DevFischer could you show me what error it is you're getting, or show me what your pladd
and/or plrem
look like?
@Selkea sure!
async def cmd_pladd(self, player, song_url=None):
"""
Usage:
{command_prefix}pladd current song
{command_prefix}pladd song_url
Adds a song to the autoplaylist.
"""
#No url provided
if not song_url:
#Check if there is something playing
if not player._current_entry:
raise exceptions.CommandError('There is nothing playing.', expire_in=20)
#Get the url of the current entry
else:
song_url = player._current_entry.url
title = player._current_entry.title
else:
#Get song info from url
info = await self.downloader.safe_extract_info(player.playlist.loop, song_url, download=False, process=False)
#Verify proper url
if not info:
raise exceptions.CommandError('Invalid url. Please insure link is a valid YouTube, SoundCloud or BandCamp url.', expire_in=20)
else:
title = info.get('title', '')
#Verify that the song isn't already in our playlist
for url in player.autoplaylist:
if song_url == url:
return Response("Song already present in autoplaylist.", delete_after=30)
player.autoplaylist.append(song_url)
write_file(player.autoplaylist, player.autoplaylist)
player.autoplaylist = load_file(player.autoplaylist)
return Response("Added %s to autoplaylist." % title, delete_after=30)
async def cmd_plremove(self, player, song_url=None):
"""
Usage:
{command_prefix}plremove current song
{command_prefix}plremove song_url
Remove a song from the autoplaylist.
"""
#No url provided
if not song_url:
#Check if there is something playing
if not player._current_entry:
raise exceptions.CommandError('There is nothing playing.', expire_in=20)
#Get the url of the current entry
else:
song_url = player._current_entry.url
title = player._current_entry.title
else:
#Get song info from url
info = await self.downloader.safe_extract_info(player.playlist.loop, song_url, download=False, process=False)
#Verify proper url
if not info:
raise exceptions.CommandError('Invalid url. Please insure link is a valid YouTube, SoundCloud or BandCamp url.', expire_in=20)
else:
title = info.get('title', '')
#Verify that the song is in our playlist
for url in player.autoplaylist:
if song_url == url:
player.autoplaylist.remove(song_url)
write_file(player.autoplaylist, player.autoplaylist)
player.autoplaylist = load_file(player.autoplaylist)
return Response("Removed %s from the autoplaylist." % title, delete_after=30)
return Response("Song not present in autoplaylist.", delete_after=30)
this is what happens when i try to pladd
to a playlist i loaded with loadpl
I see what the issue is, replace both instances of these lines in pladd
and plrem
and you should be good to go :)
write_file(player.autoplaylist, player.autoplaylist)
player.autoplaylist = load_file(player.autoplaylist)
To
write_file(player.autoplaylist_file, player.autoplaylist)
player.autoplaylist = load_file(player.autoplaylist_file)
ok so i made the changes im no longer getting the error in my cmd, thank you for that, but im getting this message in the discord chat
and the songs are not in the playlist and "autoplaylist" is not the playlist i have loaded. any ideas?
@DevFischer The only way you could get that message is if the song is in your autoplaylist, otherwise you'd never enter that statement, so I would double check and make sure your checking the proper autoplaylist you're trying to add it to.
As for your second question, the message you receive is static, it does not change depending on which playlist is loaded. I left it that way because whatever playlist you load is still an autoplaylist, however if you want to make it more descriptive, you could change it to include which autoplaylist it is you're adding it to.
This is my update for the "loadpl" command: It allows to start a song if nothing is queued.
async def cmd_loadpl(self, player, file_name=None):
"""
Usage:
{command_prefix}loadpl file_name
Loads an existing autoplaylist.
"""
#Verify file name was provided
if file_name:
#Append the file extension and make everything lowercase (important for checking against already existing files)
fileName = (file_name + ".txt").lower()
#Get the filepath, and set it to the autoplaylist directory
savePath = os.path.join(os.path.dirname(__file__), os.pardir) + "\config\\"
#Check to locate existing file
for root, dirs, files in os.walk(savePath):
if fileName in files:
player.autoplaylist = load_file(savePath + fileName)
player.autoplaylist_file = savePath + fileName
song_url = random.choice(player.autoplaylist)
if not player.playlist.entries and not player.current_entry and self.config.auto_playlist: #if nothing is queued, start a song
await player.playlist.add_entry(song_url, channel=None, author=None)
return Response("Loaded the %s playlist." % file_name, delete_after=30)
else:
raise exceptions.CommandError('Could not find %s, please ensure the spelling is correct and that the file exists.' % file_name, expire_in=20)
else:
raise exceptions.CommandError('Please specify which playlist to load.', expire_in=20)
@zenrac awesome thank you!
Now i've a problem with the "not info" check, i tried the following command and it work : !pladd hello But "hello" isn't an URL and it creates bugs in the playlist Any Idea to fix it ?
getting this error
ooh it's working now but when I use loadpl it doesn't autoplay songs from it
@OFFRANKED if the autoplaylist you load has nothing in it when the bot in looking for the next entry to play, it will deactivate the autoplaylist for safety purposes, assuming you've followed the instructions above. You can get around this by either switching to a new autoplaylist while there is a current entry playing and adding a song before it ends, or manually editing the text file and adding an url that way before loading it in.
@zenrac, @DevFischer , @DukeMcAwesome I've made a small adjustment to how I check for a valid url.
See below:
async def cmd_pladd(self, player, song_url=None):
"""
Usage:
{command_prefix}pladd current song
{command_prefix}pladd song_url
Adds a song to the autoplaylist.
"""
#No url provided
if not song_url:
#Check if there is something playing and get the information
if player._current_entry:
song_url = player._current_entry.url
title = player._current_entry.title
else:
raise exceptions.CommandError('There is nothing playing.', expire_in=20)
else:
#Get song info from url
info = await self.downloader.safe_extract_info(player.playlist.loop, song_url, download=False, process=False)
title = info.get('title', '')
#Verify proper url
if not title:
raise exceptions.CommandError('Invalid url. Please insure link is a valid YouTube, SoundCloud or BandCamp url.', expire_in=20)
#Verify song isn't already in our playlist
for url in player.autoplaylist:
if song_url == url:
return Response("Song already present in autoplaylist.", delete_after=30)
player.autoplaylist.append(song_url)
write_file(player.autoplaylist_file, player.autoplaylist)
player.autoplaylist = load_file(player.autoplaylist_file)
return Response("Added %s to autoplaylist." % title, delete_after=30)
I've also made a modification to ensure safer file creation in cmd_newpl
This requires that you add a new function in utils.py
def illegal_char(string, chars):
illegal = re.compile(chars)
if illegal.search(string):
return True
else:
return False
And include it in bot.py
from musicbot.utils import load_file, write_file, sane_round_int, illegal_char
cmd_newpl
async def cmd_newpl(self, file_name=None):
"""
Usage:
{command_prefix}newpl file_name
Creates a new autoplaylist.
"""
#Verify file name was provided
if file_name:
#Verify no illegal characters
if illegal_char(file_name, "[/\\:*?\"<>|]"):
raise exceptions.CommandError('Please ensure there are no illegal characters in the filename.', expire_in=20)
#Create new file
else:
#Append the file extension and make everything lowercase (important for checking against already existing files)
fileName = (file_name + ".txt").lower()
#Get the filepath, and set it to the autoplaylist directory
savePath = os.path.join(os.path.dirname(__file__), os.pardir) + "\config\\"
#Check to make sure there isn't already a file with the same name
for root, dirs, files in os.walk(savePath):
for file in files:
if fileName in file:
raise exceptions.CommandError("%s already exists in %s." % (fileName, savePath), expire_in=20)
write_file(savePath + fileName, "")
return Response("Created new playlist called %s." % file_name, delete_after=30)
#Inform user
else:
raise exceptions.CommandError('Please specify a file name for the new playlist.', expire_in=20)
thanks
It looks great! Thx
awesome thank you, added code to files and working great!
Made a slight modification to cmd_newpl to allow users to add an url to the new autoplaylist during file creation, to avoid having to work around the autoplaylist disabling itself, and improved the url verification to prevent runtime errors.
async def cmd_newpl(self, player, file_name=None, song_url=""):
"""
Usage:
{command_prefix}newpl file_name [song_url]
Creates a new autoplaylist.
"""
#Verify file name was provided
if file_name:
#Verify no illegal characters
if illegal_char(file_name, "[/\\:*?\"<>|]"):
raise exceptions.CommandError('Please ensure there are no illegal characters in the file name.', expire_in=20)
#Create new file
else:
if song_url:
#Get song info from url
info = await self.downloader.safe_extract_info(player.playlist.loop, song_url, download=False, process=False)
try:
title = info.get('title', '')
except (AttributeError, TypeError, ValueError):
raise exceptions.CommandError('Invalid url. Please insure link is a valid YouTube, SoundCloud or BandCamp url.', expire_in=20)
#Verify proper url
if not title:
raise exceptions.CommandError('Invalid url. Please insure link is a valid YouTube, SoundCloud or BandCamp url.', expire_in=20)
#Append the file extension and make everything lowercase (important for checking against already existing files)
fileName = (file_name + ".txt").lower()
#Get the filepath, and set it to the autoplaylist directory
savePath = os.path.join(os.path.dirname(__file__), os.pardir) + "\config\\"
#Check to make sure there isn't already a file with the same name
for root, dirs, files in os.walk(savePath):
for file in files:
if fileName in file:
raise exceptions.CommandError("%s already exists in %s." % (fileName, savePath), expire_in=20)
write_file(savePath + fileName, [song_url])
return Response("Created new autoplaylist called %s." % file_name, delete_after=30)
#Inform user
else:
raise exceptions.CommandError('Please specify a file name for the new playlist.', expire_in=20)
The following lines should also be added/replace the url verification in cmd_pladd in the same way it is used in cmd_newpl
info = await self.downloader.safe_extract_info(player.playlist.loop, song_url, download=False, process=False)
try:
title = info.get('title', '')
except (AttributeError, TypeError, ValueError):
raise exceptions.CommandError('Invalid url. Please insure link is a valid YouTube, SoundCloud or BandCamp url.', expire_in=20)
Sorry i'm a total noob at this programming stuff... anyway you can provide the files so i can just add to my folders? if not its cool.