MusicBot icon indicating copy to clipboard operation
MusicBot copied to clipboard

[Feature] Differents autoplaylists per servers

Open Zenrac opened this issue 7 years ago • 70 comments

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.

Zenrac avatar Feb 08 '17 16:02 Zenrac

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 avatar Feb 11 '17 19:02 Selkea

@Selkea - that's awesome, thank you! Stolen :)

TomLongAshfords avatar Feb 12 '17 14:02 TomLongAshfords

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)

Zenrac avatar Feb 13 '17 18:02 Zenrac

#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)       

Selkea avatar Feb 14 '17 00:02 Selkea

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.

Selkea avatar Feb 14 '17 03:02 Selkea

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.

Selkea avatar Feb 14 '17 04:02 Selkea

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 ?

Zenrac avatar Feb 17 '17 18:02 Zenrac

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.

Selkea avatar Feb 17 '17 22:02 Selkea

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 with player.autoplaylist
  • Replaced self.config.auto_playlist_file with player.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)

Selkea avatar Feb 18 '17 02:02 Selkea

Thanks! But i've added all ur changes, and i've the following issue : image

Zenrac avatar Feb 20 '17 13:02 Zenrac

@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.

Selkea avatar Feb 20 '17 14:02 Selkea

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)

Selkea avatar Feb 20 '17 20:02 Selkea

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 image

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

DevanFischer avatar Feb 24 '17 06:02 DevanFischer

@DevFischer could you show me what error it is you're getting, or show me what your pladd and/or plrem look like?

Selkea avatar Feb 24 '17 13:02 Selkea

@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 image

DevanFischer avatar Feb 24 '17 13:02 DevanFischer

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)

Selkea avatar Feb 24 '17 15:02 Selkea

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 image and the songs are not in the playlist and "autoplaylist" is not the playlist i have loaded. any ideas?

DevanFischer avatar Feb 24 '17 16:02 DevanFischer

@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.

Selkea avatar Feb 24 '17 18:02 Selkea

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 avatar Feb 27 '17 01:02 Zenrac

@zenrac awesome thank you!

DevanFischer avatar Feb 27 '17 02:02 DevanFischer

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 ?

Zenrac avatar Mar 07 '17 06:03 Zenrac

image getting this error

OFFRANKED avatar Mar 07 '17 22:03 OFFRANKED

image

OFFRANKED avatar Mar 07 '17 22:03 OFFRANKED

ooh it's working now but when I use loadpl it doesn't autoplay songs from it

OFFRANKED avatar Mar 07 '17 23:03 OFFRANKED

@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)

Selkea avatar Mar 08 '17 01:03 Selkea

thanks

OFFRANKED avatar Mar 08 '17 01:03 OFFRANKED

It looks great! Thx

Zenrac avatar Mar 08 '17 14:03 Zenrac

awesome thank you, added code to files and working great!

DevanFischer avatar Mar 08 '17 20:03 DevanFischer

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)

Selkea avatar Mar 09 '17 19:03 Selkea

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.

rustymike avatar Apr 29 '17 16:04 rustymike