package handiebot.lavaplayer.playlist; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import handiebot.utils.Pastebin; import handiebot.view.BotLog; import sx.blah.discord.api.internal.json.objects.EmbedObject; import sx.blah.discord.util.EmbedBuilder; import java.awt.*; import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; import static handiebot.HandieBot.log; import static handiebot.HandieBot.resourceBundle; /** * @author Andrew Lalis * A Playlist is a list of Tracks which a track scheduler can pull from to create a queue filled with songs. The * playlist is persistent, i.e. it is saved into a file. * Be careful, though, as the playlist is not saved in this class, but must be saved manually by whoever is operating * on the playlist. */ public class Playlist { private String name; private List tracks; /** * Creates an empty playlist template. * Depending on the circumstances, you may need to call {@code load()} to fill the playlist from a file. * @param name The name of the playlist. */ public Playlist(String name){ this.name = name; this.tracks = new ArrayList<>(); } public String getName() { return this.name; } public void setName(String name){ this.name = name; } public int getTrackCount(){ return this.tracks.size(); } public List getTracks(){ return this.tracks; } public void addTrack(UnloadedTrack track){ this.tracks.add(track); } public void removeTrack(UnloadedTrack track){ this.tracks.remove(track); } /** * Copies all the tracks from another playlist onto this one. * @param playlist A playlist. */ public void copy(Playlist playlist){ this.getTracks().clear(); for (UnloadedTrack track : playlist.getTracks()){ this.tracks.add(track.clone()); } } /** * Clears the list of tracks. */ public void clear(){ this.tracks.clear(); } /** * Loads and returns the audio track that's first on the list. * This removes that track from the playlist. * @param shouldShuffle If this is true, the track returned will be chosen randomly. * @return The AudioTrack corresponding to the next UnloadedTrack in the list. */ public AudioTrack loadNextTrack(boolean shouldShuffle){ if (this.getTrackCount() == 0){ return null; } if (shouldShuffle){ return this.tracks.remove(getShuffledIndex(this.tracks.size())).loadAudioTrack(); } else { return this.tracks.remove(0).loadAudioTrack(); } } /** * Attempts to load a track or playlist from a URL, and add it to the tracks list. * @param url The URL to get the song/playlist from. */ public void loadTrack(String url){ try { UnloadedTrack track = new UnloadedTrack(url); this.tracks.add(track); log.log(BotLog.TYPE.MUSIC, MessageFormat.format(resourceBundle.getString("playlist.loadTrack.log"), track.getTitle(), this.name)); } catch (Exception e) { log.log(BotLog.TYPE.ERROR, MessageFormat.format(resourceBundle.getString("playlist.loadTrack.error"), url, this.name)); e.printStackTrace(); } } /** * Gets a 'shuffled index' from a given list length. That means: * - A random number from 0 to (listLength-1) - threshold*(listLength), where threshold is some percentage of * recent songs that should be ignored; for example, the most recent 20% of the playlist can be ignored. * @param listLength The number of items in a potential list to choose from. * @return A pseudo-random choice as to which item to pick from the list. */ private static int getShuffledIndex(int listLength){ float threshold = 0.2f; int trueLength = listLength - (int)(threshold*(float)listLength); Random rand = new Random(); return rand.nextInt(trueLength); } /** * Saves the playlist to a file in its name. The playlists are saved into a file in the user's home directory. */ public void save(){ String homeDir = System.getProperty("user.home"); File playlistDir = new File(homeDir+"/.handiebot/playlist"); if (!playlistDir.exists()){ if (!playlistDir.mkdirs()){ log.log(BotLog.TYPE.ERROR, MessageFormat.format(resourceBundle.getString("playlist.save.error.directory"), playlistDir.getPath())); return; } } File playlistFile = new File(playlistDir.getPath()+"/"+this.name.replace(" ", "_")+".txt"); try(Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(playlistFile)))){ writer.write(Integer.toString(this.tracks.size())+'\n'); for (UnloadedTrack track : this.tracks){ writer.write(track.toString()); writer.write('\n'); } } catch (FileNotFoundException e) { log.log(BotLog.TYPE.ERROR, MessageFormat.format(resourceBundle.getString("playlist.save.error.fileNotFound"), this.name)); e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * Loads the playlist from a file with the playlist's name. */ public void load(){ String path = System.getProperty("user.home")+"/.handiebot/playlist/"+name.replace(" ", "_")+".txt"; File playlistFile = new File(path); if (playlistFile.exists()){ try { List lines = Files.readAllLines(Paths.get(playlistFile.toURI())); int trackCount = Integer.parseInt(lines.remove(0)); this.tracks = new ArrayList<>(trackCount); for (int i = 0; i < trackCount; i++){ String[] words = lines.remove(0).split(" / "); this.tracks.add(new UnloadedTrack(words[0], words[1], Long.parseLong(words[2]))); } } catch (IOException e) { log.log(BotLog.TYPE.ERROR, MessageFormat.format(resourceBundle.getString("playlist.load.error.IOException"), this.name, e.getMessage())); e.printStackTrace(); } } else { log.log(BotLog.TYPE.ERROR, MessageFormat.format(resourceBundle.getString("playlist.load.error.exists"), this.name)); } } /** * Returns a list of all playlists, or essentially all playlist files. * @return A list of all playlists. */ public static List getAvailablePlaylists(){ File playlistFolder = new File(System.getProperty("user.home")+"/.handiebot/playlist"); @SuppressWarnings("ConstantConditions") List names = new ArrayList<>(Arrays.asList(playlistFolder.list())); for (int i = 0; i < names.size(); i++){ String name = names.get(i); name = name.replace(".txt", ""); name = name.replace("_", " "); names.set(i, name); } return names; } /** * Returns true if a playlist exists. * @param name The name of the playlist. * @return True if the playlist exists. */ public static boolean playlistExists(String name){ List names = getAvailablePlaylists(); for (String n : names){ if (n.equals(name)){ return true; } } return false; } @Override public String toString(){ StringBuilder sb = new StringBuilder("Playlist: "+this.getName()+'\n'); if (this.getTrackCount() == 0){ sb.append(resourceBundle.getString("playlist.empty")); } else { for (int i = 0; i < this.getTrackCount(); i++) { sb.append(i + 1).append(". ").append(this.tracks.get(i).getTitle()).append(" ").append(this.tracks.get(i).getFormattedDuration()).append("\n"); } } return sb.toString(); } public EmbedObject getEmbed(){ EmbedBuilder eb = new EmbedBuilder(); eb.withTitle(this.getName()); eb.withFooterText(this.getTrackCount()+" tracks."); eb.withColor(Color.red); StringBuilder sb = new StringBuilder(); boolean needsPastebin = this.getTrackCount() > 20; for (int i = 0; i < this.getTrackCount(); i++){ sb.append(i+1).append(". ").append(this.tracks.get(i).getTitle()).append(' ').append(this.tracks.get(i).getFormattedDuration()).append('\n'); } if (needsPastebin){ String result = Pastebin.paste(this.getName(), sb.toString()); eb.withUrl(result); eb.withDescription(resourceBundle.getString("playlist.embedTooLarge")); } else { eb.withDescription(sb.toString()); } return eb.build(); } }