diff --git a/README.md b/README.md index c671fcc..29e7f22 100644 --- a/README.md +++ b/README.md @@ -43,17 +43,19 @@ Because the play command is defined as `play [URL]`, and the queue command is de * `shuffle [true|false]` - Sets the bot to shuffle the playlist, as in pull a random song from the playlist, with some filters to prevent repeating songs. * `playlist ` - Various commands to manipulate playlists. The specific sub-commands are explained below. - * `create [URL]...` - Creates a new playlist, optionally with some starting URLs. + * `create [URL]...` - Creates a new playlist, optionally with some starting URLs. - * `delete ` - Deletes a playlist with the given name. + * `delete ` - Deletes a playlist with the given name. - * `show [NAME]` - If a name is given, shows the songs in a given playlist; otherwise it lists the names of the playlists. + * `show [PLAYLIST]` - If a name is given, shows the songs in a given playlist; otherwise it lists the names of the playlists. - * `play ` - Loads and begins playing the specified playlist. + * `play ` - Loads and begins playing the specified playlist. - * `add [URL]...` - Adds the specified URL, or multiple URLs to the playlist given by `NAME`. + * `add [URL]...` - Adds the specified URL, or multiple URLs to the playlist given by `PLAYLIST`. - * `remove ` - Removes the specified song name, or the one that most closely matches the song name given, from the playlist given by `NAME`. + * `remove ` - Removes the specified song name, or the one that most closely matches the song name given, from the playlist given by `PLAYLIST`. - * `rename ` - Renames the playlist to the new name. + * `rename ` - Renames the playlist to the new name. + + * `move ` - Moves a song from one index to another index, shifting other elements as necessary. diff --git a/src/main/java/handiebot/command/CommandHandler.java b/src/main/java/handiebot/command/CommandHandler.java index 4ea8ee2..a4a14b2 100644 --- a/src/main/java/handiebot/command/CommandHandler.java +++ b/src/main/java/handiebot/command/CommandHandler.java @@ -1,14 +1,15 @@ package handiebot.command; import com.sun.istack.internal.NotNull; +import handiebot.command.commands.music.PlayCommand; import handiebot.command.commands.music.PlaylistCommand; +import handiebot.command.commands.music.RepeatCommand; +import handiebot.command.commands.music.ShuffleCommand; import handiebot.utils.DisappearingMessage; import handiebot.view.BotLog; import handiebot.view.actions.QuitAction; -import handiebot.view.actions.music.PlayAction; import handiebot.view.actions.music.QueueListAction; import handiebot.view.actions.music.SkipAction; -import handiebot.view.actions.music.ToggleRepeatAction; import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; import sx.blah.discord.handle.obj.*; import sx.blah.discord.util.EmbedBuilder; @@ -40,7 +41,7 @@ public class CommandHandler { DisappearingMessage.deleteMessageAfter(1000, message); if (command.equals("play")){ //Play or queue a song. - new PlayAction(guild, args).actionPerformed(null); + new PlayCommand().execute(context); } else if (command.equals("skip") && args.length == 0){ //Skip the current song. new SkipAction(guild).actionPerformed(null); @@ -50,9 +51,11 @@ public class CommandHandler { } else if (command.equals("queue")){ //Display the first few items of the queue. new QueueListAction(guild, (args.length == 1) && args[0].equals("all")).actionPerformed(null); - } else if (command.equals("repeat")){ + } else if (command.equals("repeat")) { //Toggle repeat. - new ToggleRepeatAction(guild).actionPerformed(null); + new RepeatCommand().execute(context); + } else if (command.equals("shuffle")){ + new ShuffleCommand().execute(context); } else if (command.equals("clear")){ //TODO clear command. } else if (command.equals("quit")){ diff --git a/src/main/java/handiebot/command/commands/music/PlayCommand.java b/src/main/java/handiebot/command/commands/music/PlayCommand.java index f782d72..1d961ec 100644 --- a/src/main/java/handiebot/command/commands/music/PlayCommand.java +++ b/src/main/java/handiebot/command/commands/music/PlayCommand.java @@ -3,6 +3,8 @@ package handiebot.command.commands.music; import handiebot.HandieBot; import handiebot.command.CommandContext; import handiebot.command.types.ContextCommand; +import handiebot.lavaplayer.playlist.UnloadedTrack; +import handiebot.utils.DisappearingMessage; /** * @author Andrew Lalis @@ -19,7 +21,12 @@ public class PlayCommand extends ContextCommand { if (context.getArgs() == null || context.getArgs().length == 0){ HandieBot.musicPlayer.playQueue(context.getGuild()); } else { - HandieBot.musicPlayer.loadToQueue(context.getGuild(), context.getArgs()[0]); + try { + HandieBot.musicPlayer.addToQueue(context.getGuild(), new UnloadedTrack(context.getArgs()[0])); + } catch (Exception e) { + new DisappearingMessage(context.getChannel(), "Unable to queue track: "+context.getArgs()[0], 3000); + e.printStackTrace(); + } } } diff --git a/src/main/java/handiebot/command/commands/music/PlaylistCommand.java b/src/main/java/handiebot/command/commands/music/PlaylistCommand.java index 0924e02..37e505b 100644 --- a/src/main/java/handiebot/command/commands/music/PlaylistCommand.java +++ b/src/main/java/handiebot/command/commands/music/PlaylistCommand.java @@ -1,12 +1,15 @@ package handiebot.command.commands.music; +import handiebot.HandieBot; import handiebot.command.CommandContext; import handiebot.command.CommandHandler; import handiebot.command.types.ContextCommand; -import handiebot.lavaplayer.Playlist; +import handiebot.lavaplayer.playlist.Playlist; +import handiebot.lavaplayer.playlist.UnloadedTrack; import handiebot.utils.DisappearingMessage; import handiebot.view.BotLog; import sx.blah.discord.handle.obj.IChannel; +import sx.blah.discord.handle.obj.IMessage; import java.io.File; import java.util.List; @@ -38,13 +41,19 @@ public class PlaylistCommand extends ContextCommand { show(context); break; case ("add"): - + add(context); + break; + case ("play"): + play(context); break; case ("remove"): - + remove(context); break; case ("rename"): - + rename(context); + break; + case ("move"): + move(context); break; default: incorrectMainArg(context.getChannel()); @@ -69,14 +78,14 @@ public class PlaylistCommand extends ContextCommand { */ private void create(CommandContext context){ if (context.getArgs().length >= 2) { - Playlist playlist = new Playlist(context.getArgs()[1], context.getUser().getLongID()); + Playlist playlist = new Playlist(context.getArgs()[1]); playlist.save(); for (int i = 2; i < context.getArgs().length; i++){ String url = context.getArgs()[i]; playlist.loadTrack(url); - playlist.save(); } - log.log(BotLog.TYPE.INFO, "Created playlist: "+playlist.getName()+" with "+playlist.getTracks().size()+" new tracks."); + playlist.save(); + log.log(BotLog.TYPE.INFO, "Created playlist: "+playlist.getName()+" with "+playlist.getTrackCount()+" new tracks."); new DisappearingMessage(context.getChannel(), "Your playlist *"+playlist.getName()+"* has been created.\nType `"+ CommandHandler.PREFIX+"playlist play "+playlist.getName()+"` to play it.", 5000); } else { new DisappearingMessage(context.getChannel(), "You must specify a name for the new playlist.", 3000); @@ -93,6 +102,7 @@ public class PlaylistCommand extends ContextCommand { File f = new File(System.getProperty("user.home")+"/.handiebot/playlist/"+context.getArgs()[1].replace(" ", "_")+".txt"); boolean success = f.delete(); if (success){ + log.log(BotLog.TYPE.INFO, "The playlist ["+context.getArgs()[1]+"] has been deleted."); new DisappearingMessage(context.getChannel(), "The playlist *"+context.getArgs()[1]+"* has been deleted.", 5000); } else { log.log(BotLog.TYPE.ERROR, "Unable to delete playlist: "+context.getArgs()[1]); @@ -112,14 +122,164 @@ public class PlaylistCommand extends ContextCommand { */ private void show(CommandContext context){ if (context.getArgs().length > 1){ - + if (Playlist.playlistExists(context.getArgs()[1])){ + Playlist playlist = new Playlist(context.getArgs()[1]); + playlist.load(); + IMessage message = context.getChannel().sendMessage(playlist.toString()); + DisappearingMessage.deleteMessageAfter(6000, message); + } else { + new DisappearingMessage(context.getChannel(), "The playlist you specified does not exist.\nUse `"+CommandHandler.PREFIX+"playlist show` to view available playlists.", 5000); + } } else { List playlists = Playlist.getAvailablePlaylists(); StringBuilder sb = new StringBuilder("**Playlists:**\n"); for (String playlist : playlists) { sb.append(playlist).append('\n'); } - context.getChannel().sendMessage(sb.toString()); + IMessage message = context.getChannel().sendMessage(sb.toString()); + DisappearingMessage.deleteMessageAfter(6000, message); + } + } + + /** + * Attempts to add a song or multiple songs to a playlist. + * @param context The command context. + */ + private void add(CommandContext context){ + if (context.getArgs().length > 2){ + if (!Playlist.playlistExists(context.getArgs()[1])){ + new DisappearingMessage(context.getChannel(), "The playlist you entered does not exist.", 3000); + return; + } + Playlist playlist = new Playlist(context.getArgs()[1]); + playlist.load(); + for (int i = 2; i < context.getArgs().length; i++){ + playlist.loadTrack(context.getArgs()[i]); + new DisappearingMessage(context.getChannel(), "Added track to *"+playlist.getName()+"*.", 3000); + } + playlist.save(); + IMessage message = context.getChannel().sendMessage(playlist.toString()); + log.log(BotLog.TYPE.INFO, "Added song(s) to playlist ["+playlist.getName()+"]."); + DisappearingMessage.deleteMessageAfter(6000, message); + } else { + if (context.getArgs().length == 1){ + new DisappearingMessage(context.getChannel(), "You must provide the name of a playlist to add a URL to.\nUse '"+CommandHandler.PREFIX+"playlist show` to view available playlists.", 5000); + } else { + new DisappearingMessage(context.getChannel(), "You must provide at least one URL to add.", 3000); + } + } + } + + /** + * Shifts the named playlist to the active playlist and begins playback in accordance with the Music Player. + * @param context The command context. + */ + private void play(CommandContext context){ + if (context.getArgs().length == 2){ + if (!Playlist.playlistExists(context.getArgs()[1])){ + new DisappearingMessage(context.getChannel(), "The playlist you entered does not exist.", 3000); + return; + } + Playlist playlist = new Playlist(context.getArgs()[1]); + playlist.load(); + HandieBot.musicPlayer.getMusicManager(context.getGuild()).scheduler.setPlaylist(playlist); + HandieBot.musicPlayer.getMusicManager(context.getGuild()).scheduler.nextTrack(); + log.log(BotLog.TYPE.INFO, "Loaded playlist ["+playlist.getName()+"]."); + new DisappearingMessage(context.getChannel(), "Now playing from playlist: *"+playlist.getName()+"*.", 6000); + } else { + new DisappearingMessage(context.getChannel(), "You must provide a playlist to play.\nUse '"+CommandHandler.PREFIX+"playlist show` to view available playlists.", 3000); + } + } + + /** + * Attempts to rename a playlist. + * @param context The command context. + */ + private void rename(CommandContext context){ + if (context.getArgs().length == 3){ + if (!Playlist.playlistExists(context.getArgs()[1])){ + new DisappearingMessage(context.getChannel(), "The playlist you entered does not exist.", 3000); + return; + } + File f = new File(System.getProperty("user.home")+"/.handiebot/playlist/"+context.getArgs()[1].replace(" ", "_")+".txt"); + boolean success = f.renameTo(new File(System.getProperty("user.home")+"/.handiebot/playlist/"+context.getArgs()[2].replace(" ", "_")+".txt")); + if (success){ + new DisappearingMessage(context.getChannel(), "The playlist *"+context.getArgs()[1]+"* has been renamed to *"+context.getArgs()[2]+"*.", 6000); + log.log(BotLog.TYPE.INFO, "Playlist "+context.getArgs()[1]+" renamed to "+context.getArgs()[2]+"."); + } else { + new DisappearingMessage(context.getChannel(), "Unable to rename playlist.", 3000); + log.log(BotLog.TYPE.ERROR, "Unable to rename playlist "+context.getArgs()[1]+" to "+context.getArgs()[2]+"."); + } + } else { + new DisappearingMessage(context.getChannel(), "You must include the original playlist, and a new name for it.", 3000); + } + } + + /** + * Attempst to remove the song at a specified index of the playlist. + * @param context The command context. + */ + private void remove(CommandContext context){ + if (context.getArgs().length == 3){ + if (!Playlist.playlistExists(context.getArgs()[1])){ + new DisappearingMessage(context.getChannel(), "The playlist you entered does not exist.", 3000); + return; + } + Playlist playlist = new Playlist(context.getArgs()[1]); + playlist.load(); + try{ + int index = Integer.parseInt(context.getArgs()[2]); + UnloadedTrack track = playlist.getTracks().get(index); + playlist.removeTrack(track); + new DisappearingMessage(context.getChannel(), "Removed song: *"+track.getTitle()+"* from playlist **"+playlist.getName()+"**.", 6000); + log.log(BotLog.TYPE.MUSIC, "Removed song: "+track.getTitle()+" from playlist ["+playlist.getName()+"]."); + DisappearingMessage.deleteMessageAfter(6000, context.getChannel().sendMessage(playlist.toString())); + } catch (IndexOutOfBoundsException | NumberFormatException e){ + new DisappearingMessage(context.getChannel(), "Unable to remove the specified song.", 3000); + log.log(BotLog.TYPE.ERROR, "Unable to remove song from playlist: ["+playlist.getName()+"]."); + e.printStackTrace(); + } + + } else { + new DisappearingMessage(context.getChannel(), "You must provide a playlist name, followed by the index number of a song to remove.", 5000); + } + } + + /** + * Moves a song from one index to another. + * @param context The command context. + */ + private void move(CommandContext context){ + if (context.getArgs().length == 4){ + if (!Playlist.playlistExists(context.getArgs()[1])){ + new DisappearingMessage(context.getChannel(), "The playlist you entered does not exist.", 3000); + return; + } + Playlist playlist = new Playlist(context.getArgs()[1]); + playlist.load(); + int oldIndex = -1; + int newIndex = -1; + try { + oldIndex = Integer.parseInt(context.getArgs()[2])-1; + newIndex = Integer.parseInt(context.getArgs()[3])-1; + } catch (NumberFormatException e){ + new DisappearingMessage(context.getChannel(), "You must enter two integer values for the song indices.", 5000); + } + UnloadedTrack track = null; + if (oldIndex > -1 && oldIndex < playlist.getTrackCount()){ + track = playlist.getTracks().remove(oldIndex); + if (newIndex > -1 && newIndex <= playlist.getTrackCount()){ + playlist.getTracks().add(newIndex, track); + new DisappearingMessage(context.getChannel(), "Moved song *"+track.getTitle()+"* from position "+(oldIndex+1)+" to position "+(newIndex+1), 6000); + log.log(BotLog.TYPE.MUSIC, "Moved song "+track.getTitle()+" from position "+(oldIndex+1)+" to position "+(newIndex+1)); + } else { + new DisappearingMessage(context.getChannel(), "The index of the song's new position is invalid. You entered "+newIndex, 5000); + } + } else { + new DisappearingMessage(context.getChannel(), "The index of the song is invalid. You entered "+oldIndex, 5000); + } + } else { + new DisappearingMessage(context.getChannel(), "You must provide a playlist name, followed by the song index, and a new index for that song.", 5000); } } diff --git a/src/main/java/handiebot/command/commands/music/RepeatCommand.java b/src/main/java/handiebot/command/commands/music/RepeatCommand.java index f80cf05..25e0793 100644 --- a/src/main/java/handiebot/command/commands/music/RepeatCommand.java +++ b/src/main/java/handiebot/command/commands/music/RepeatCommand.java @@ -3,6 +3,10 @@ package handiebot.command.commands.music; import handiebot.HandieBot; import handiebot.command.CommandContext; import handiebot.command.types.ContextCommand; +import handiebot.utils.DisappearingMessage; +import handiebot.view.BotLog; + +import static handiebot.HandieBot.log; /** * @author Andrew Lalis @@ -22,5 +26,7 @@ public class RepeatCommand extends ContextCommand { } else { HandieBot.musicPlayer.toggleRepeat(context.getGuild()); } + log.log(BotLog.TYPE.MUSIC, context.getGuild(), "Set repeat to "+HandieBot.musicPlayer.getMusicManager(context.getGuild()).scheduler.isRepeating()); + new DisappearingMessage(context.getChannel(), "Set repeat to "+HandieBot.musicPlayer.getMusicManager(context.getGuild()).scheduler.isRepeating(), 3000); } } diff --git a/src/main/java/handiebot/command/commands/music/ShuffleCommand.java b/src/main/java/handiebot/command/commands/music/ShuffleCommand.java index fe58fa3..c4f5ebf 100644 --- a/src/main/java/handiebot/command/commands/music/ShuffleCommand.java +++ b/src/main/java/handiebot/command/commands/music/ShuffleCommand.java @@ -3,6 +3,10 @@ package handiebot.command.commands.music; import handiebot.HandieBot; import handiebot.command.CommandContext; import handiebot.command.types.ContextCommand; +import handiebot.utils.DisappearingMessage; +import handiebot.view.BotLog; + +import static handiebot.HandieBot.log; /** * @author Andrew Lalis @@ -22,5 +26,7 @@ public class ShuffleCommand extends ContextCommand { } else { HandieBot.musicPlayer.toggleShuffle(context.getGuild()); } + log.log(BotLog.TYPE.MUSIC, context.getGuild(), "Set shuffle to "+Boolean.toString(HandieBot.musicPlayer.getMusicManager(context.getGuild()).scheduler.isShuffling())); + new DisappearingMessage(context.getChannel(), "Set shuffle to "+Boolean.toString(HandieBot.musicPlayer.getMusicManager(context.getGuild()).scheduler.isShuffling()), 3000); } } diff --git a/src/main/java/handiebot/lavaplayer/MusicPlayer.java b/src/main/java/handiebot/lavaplayer/MusicPlayer.java index f38abb2..977aab1 100644 --- a/src/main/java/handiebot/lavaplayer/MusicPlayer.java +++ b/src/main/java/handiebot/lavaplayer/MusicPlayer.java @@ -1,35 +1,19 @@ package handiebot.lavaplayer; -import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; -import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import handiebot.command.CommandHandler; +import handiebot.lavaplayer.playlist.UnloadedTrack; import handiebot.utils.DisappearingMessage; +import handiebot.utils.Pastebin; import handiebot.view.BotLog; -import org.apache.commons.io.IOUtils; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.message.BasicNameValuePair; import sx.blah.discord.handle.obj.IChannel; import sx.blah.discord.handle.obj.IGuild; import sx.blah.discord.handle.obj.IMessage; import sx.blah.discord.handle.obj.IVoiceChannel; import sx.blah.discord.util.EmbedBuilder; -import java.io.IOException; -import java.io.InputStream; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -46,7 +30,6 @@ public class MusicPlayer { //Name for the message and voice channels dedicated to this bot. static String CHANNEL_NAME = "HandieBotMusic"; - private static String PASTEBIN_KEY = "769adc01154922ece448cabd7a33b57c"; private final AudioPlayerManager playerManager; @@ -78,7 +61,7 @@ public class MusicPlayer { * @param guild The guild to get the music manager for. * @return The music manager for a guild. */ - private GuildMusicManager getMusicManager(IGuild guild){ + public GuildMusicManager getMusicManager(IGuild guild){ if (!this.musicManagers.containsKey(guild)){ log.log(BotLog.TYPE.MUSIC, guild, "Creating new music manager and audio provider for guild: "+guild.getName()); this.musicManagers.put(guild, new GuildMusicManager(this.playerManager, guild)); @@ -165,141 +148,39 @@ public class MusicPlayer { * Sends a formatted message to the guild about the first few items in a queue. */ public void showQueueList(IGuild guild, boolean showAll) { - List tracks = getMusicManager(guild).scheduler.queueList(); + List tracks = getMusicManager(guild).scheduler.queueList(); if (tracks.size() == 0) { new DisappearingMessage(getChatChannel(guild), "The queue is empty. Use **"+ CommandHandler.PREFIX+"play** *URL* to add songs.", 3000); } else { - if (!showAll) { + if (tracks.size() > 10 && showAll) { + String result = Pastebin.paste("Current queue for discord server: "+guild.getName()+".", getMusicManager(guild).scheduler.getActivePlaylist().toString()); + if (result != null && result.startsWith("https://pastebin.com/")){ + log.log(BotLog.TYPE.INFO, guild, "Queue uploaded to pastebin: "+result); + new DisappearingMessage(getChatChannel(guild), "You may view the full queue by following the link: "+result, 600000); + } else { + log.log(BotLog.TYPE.ERROR, guild, "Unable to upload to pastebin: "+result); + } + } else { EmbedBuilder builder = new EmbedBuilder(); builder.withColor(255, 0, 0); StringBuilder sb = new StringBuilder(); for (int i = 0; i < (tracks.size() <= 10 ? tracks.size() : 10); i++) { - sb.append(i + 1); - sb.append(". "); - sb.append('['); - sb.append(tracks.get(i).getInfo().title); - sb.append("]("); - sb.append(tracks.get(i).getInfo().uri); - sb.append(")"); - int seconds = (int) (tracks.get(i).getInfo().length / 1000); - int minutes = seconds / 60; - seconds = seconds % 60; - String time = String.format(" [%d:%02d]\n", minutes, seconds); - sb.append(time); + sb.append(i + 1).append(". [").append(tracks.get(i).getTitle()).append("]("); + sb.append(tracks.get(i).getURL()).append(")"); + sb.append(tracks.get(i).getFormattedDuration()).append('\n'); } - builder.withTimestamp(System.currentTimeMillis()); builder.appendField("Showing " + (tracks.size() <= 10 ? tracks.size() : "the first 10") + " track" + (tracks.size() > 1 ? "s" : "") + ".", sb.toString(), false); IMessage message = getChatChannel(guild).sendMessage(builder.build()); DisappearingMessage.deleteMessageAfter(6000, message); - } else { - StringBuilder sb = new StringBuilder("Queue for Discord Server: "+guild.getName()+"\n"); - for (int i = 0; i < tracks.size(); i++){ - sb.append(i+1).append(". ").append(tracks.get(i).getInfo().title); - int seconds = (int) (tracks.get(i).getInfo().length / 1000); - int minutes = seconds / 60; - seconds = seconds % 60; - String time = String.format(" [%d:%02d]\n", minutes, seconds); - sb.append(time); - } - - HttpClient httpclient = HttpClients.createDefault(); - HttpPost httppost = new HttpPost("https://www.pastebin.com/api/api_post.php"); - - // Request parameters and other properties. - List params = new ArrayList(2); - params.add(new BasicNameValuePair("api_dev_key", PASTEBIN_KEY)); - params.add(new BasicNameValuePair("api_option", "paste")); - params.add(new BasicNameValuePair("api_paste_code", sb.toString())); - params.add(new BasicNameValuePair("api_paste_private", "0")); - params.add(new BasicNameValuePair("api_paste_name", "Music Queue for Discord Server: "+guild.getName())); - params.add(new BasicNameValuePair("api_paste_expire_date", "10M")); - //params.add(new BasicNameValuePair("api_paste_format", "text")); - params.add(new BasicNameValuePair("api_user_key", "")); - - try { - httppost.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - - //Execute and get the response. - HttpResponse response = null; - try { - response = httpclient.execute(httppost); - } catch (IOException e) { - e.printStackTrace(); - } - HttpEntity entity = response.getEntity(); - - if (entity != null) { - InputStream instream = null; - try { - instream = entity.getContent(); - } catch (IOException e) { - e.printStackTrace(); - } - try { - StringWriter writer = new StringWriter(); - IOUtils.copy(instream, writer, "UTF-8"); - String pasteURL = writer.toString(); - log.log(BotLog.TYPE.INFO, guild, "Uploaded full queue to "+pasteURL); - new DisappearingMessage(getChatChannel(guild), "You may view the full queue here. "+pasteURL, 60000); - } catch (IOException e) { - e.printStackTrace(); - } finally { - try { - instream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - } } } - /** - * Loads a URL to the queue, or outputs an error message if it fails. - * @param trackURL A string representing a youtube/soundcloud URL. - */ - public void loadToQueue(IGuild guild, String trackURL){ - this.playerManager.loadItemOrdered(getMusicManager(guild), trackURL, new AudioLoadResultHandler() { - @Override - public void trackLoaded(AudioTrack audioTrack) { - addToQueue(guild, audioTrack); - } - - @Override - public void playlistLoaded(AudioPlaylist audioPlaylist) { - if (audioPlaylist.getTracks().size() > 0){ - AudioTrack firstTrack = audioPlaylist.getSelectedTrack(); - if (firstTrack == null){ - firstTrack = audioPlaylist.getTracks().get(0); - } - addToQueue(guild, firstTrack); - } - } - - @Override - public void noMatches() { - log.log(BotLog.TYPE.ERROR, guild, "No matches found for: "+trackURL); - new DisappearingMessage(getChatChannel(guild), "Unable to find a result for: "+trackURL, 3000); - } - - @Override - public void loadFailed(FriendlyException e) { - log.log(BotLog.TYPE.ERROR, guild, "Unable to load song: "+trackURL+". "+e.getMessage()); - new DisappearingMessage(getChatChannel(guild), "Unable to load. "+e.getMessage(), 3000); - } - }); - } - /** * Adds a track to the queue and sends a message to the appropriate channel notifying users. * @param track The track to queue. */ - private void addToQueue(IGuild guild, AudioTrack track){ + public void addToQueue(IGuild guild, UnloadedTrack track){ IVoiceChannel voiceChannel = getVoiceChannel(guild); if (voiceChannel != null){ if (!voiceChannel.isConnected()) { @@ -310,7 +191,7 @@ public class MusicPlayer { //Build message. StringBuilder sb = new StringBuilder(); if (timeUntilPlay > 0) { - sb.append("Added **").append(track.getInfo().title).append("** to the queue."); + sb.append("Added **").append(track.getTitle()).append("** to the queue."); } //If there's some tracks in the queue, get the time until this one plays. if (timeUntilPlay > 0){ diff --git a/src/main/java/handiebot/lavaplayer/TrackScheduler.java b/src/main/java/handiebot/lavaplayer/TrackScheduler.java index 284290f..f06d513 100644 --- a/src/main/java/handiebot/lavaplayer/TrackScheduler.java +++ b/src/main/java/handiebot/lavaplayer/TrackScheduler.java @@ -6,6 +6,8 @@ import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; import handiebot.HandieBot; +import handiebot.lavaplayer.playlist.Playlist; +import handiebot.lavaplayer.playlist.UnloadedTrack; import handiebot.view.BotLog; import sx.blah.discord.handle.obj.IChannel; import sx.blah.discord.handle.obj.IGuild; @@ -36,10 +38,23 @@ public class TrackScheduler extends AudioEventAdapter { * @param player The audio player this scheduler uses. */ public TrackScheduler(AudioPlayer player, IGuild guild){ + super(); this.player = player; this.guild = guild; - //this.activePlaylist = new Playlist("HandieBot Active Playlist", 283652989212688384L); this.activePlaylist = new Playlist("HandieBot Active Playlist"); + //this.activePlaylist = new Playlist("HandieBot Active Playlist"); + } + + /** + * Fills the playlist with the tracks from a given playlist, or if null, + * @param playlist the playlist to load from. + */ + public void setPlaylist(Playlist playlist){ + this.activePlaylist = playlist; + } + + public Playlist getActivePlaylist(){ + return this.activePlaylist; } /** @@ -84,7 +99,7 @@ public class TrackScheduler extends AudioEventAdapter { if (currentTrack != null){ t += currentTrack.getDuration() - currentTrack.getPosition(); } - for (AudioTrack track : this.queueList()){ + for (UnloadedTrack track : this.queueList()){ t += track.getDuration(); } return t; @@ -94,7 +109,7 @@ public class TrackScheduler extends AudioEventAdapter { * Returns a list of tracks in the queue. * @return A list of tracks in the queue. */ - public List queueList(){ + public List queueList(){ return this.activePlaylist.getTracks(); } @@ -102,9 +117,9 @@ public class TrackScheduler extends AudioEventAdapter { * Add the next track to the queue or play right away if nothing is in the queue. * @param track The track to play or add to the queue. */ - public void queue(AudioTrack track){ + public void queue(UnloadedTrack track){ if (player.getPlayingTrack() == null){ - player.startTrack(track, false); + player.startTrack(track.loadAudioTrack(), false); } else { this.activePlaylist.addTrack(track); } @@ -118,7 +133,7 @@ public class TrackScheduler extends AudioEventAdapter { if (currentTrack != null){ this.player.stopTrack(); } - AudioTrack track = (this.repeat ? this.activePlaylist.getNextTrackAndRequeue(this.shuffle) : this.activePlaylist.getNextTrackAndRemove(this.shuffle)); + AudioTrack track = this.activePlaylist.loadNextTrack(this.shuffle); if (track != null) { IVoiceChannel voiceChannel = HandieBot.musicPlayer.getVoiceChannel(this.guild); if (!voiceChannel.isConnected()){ @@ -154,6 +169,9 @@ public class TrackScheduler extends AudioEventAdapter { @Override public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) { + if (this.repeat){ + this.activePlaylist.addTrack(new UnloadedTrack(track)); + } if (endReason.mayStartNext){ nextTrack(); } else { diff --git a/src/main/java/handiebot/lavaplayer/Playlist.java b/src/main/java/handiebot/lavaplayer/playlist/Playlist.java similarity index 54% rename from src/main/java/handiebot/lavaplayer/Playlist.java rename to src/main/java/handiebot/lavaplayer/playlist/Playlist.java index 070e287..cee15e1 100644 --- a/src/main/java/handiebot/lavaplayer/Playlist.java +++ b/src/main/java/handiebot/lavaplayer/playlist/Playlist.java @@ -1,10 +1,6 @@ -package handiebot.lavaplayer; +package handiebot.lavaplayer.playlist; -import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; -import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; -import handiebot.HandieBot; import handiebot.view.BotLog; import java.io.*; @@ -14,13 +10,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; -import java.util.concurrent.ExecutionException; import static handiebot.HandieBot.log; /** * @author Andrew Lalis - * A Playlist is a list of AudioTracks which a track scheduler can pull from to create a queue filled with songs. The + * 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. @@ -28,129 +23,68 @@ import static handiebot.HandieBot.log; public class Playlist { private String name; - private long creatorUID; - private List tracks; + 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. - * @param creatorUID The ID of the user who created it. - */ - public Playlist(String name, long creatorUID){ - this.name = name; - this.creatorUID = creatorUID; - this.tracks = new ArrayList<>(); - } - - /** - * Creates a playlist from a file with the given name. - * @param name The name of the file. */ public Playlist(String name){ this.name = name; - this.load(); + this.tracks = new ArrayList<>(); } - public String getName(){ + public String getName() { return this.name; } - public long getCreatorUID(){ - return this.creatorUID; + public int getTrackCount(){ + return this.tracks.size(); } - public List getTracks(){ + public List getTracks(){ return this.tracks; } - /** - * Adds a track to the end of the playlist. - * @param track The track to add. - */ - public void addTrack(AudioTrack track){ + public void addTrack(UnloadedTrack track){ this.tracks.add(track); } + public void removeTrack(UnloadedTrack track){ + this.tracks.remove(track); + } + + /** + * 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 (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 { - HandieBot.musicPlayer.getPlayerManager().loadItem(url, new AudioLoadResultHandler() { - @Override - public void trackLoaded(AudioTrack audioTrack) { - tracks.add(audioTrack); - } - - @Override - public void playlistLoaded(AudioPlaylist audioPlaylist) { - tracks.addAll(audioPlaylist.getTracks()); - } - - @Override - public void noMatches() { - log.log(BotLog.TYPE.ERROR, "No matches found for: "+url+"."); - //Do nothing. This should not happen. - } - - @Override - public void loadFailed(FriendlyException e) { - log.log(BotLog.TYPE.ERROR, "Unable to load song from URL: "+url+". "+e.getMessage()); - //Do nothing. This should not happen. - } - }).get(); - } catch (InterruptedException e) { - log.log(BotLog.TYPE.ERROR, "Loading of playlist ["+this.name+"] interrupted. "+e.getMessage()); - e.printStackTrace(); - } catch (ExecutionException e) { - log.log(BotLog.TYPE.ERROR, "Execution exception while loading playlist ["+this.name+"]. "+e.getMessage()); + UnloadedTrack track = new UnloadedTrack(url); + this.tracks.add(track); + log.log(BotLog.TYPE.MUSIC, "Added "+track.getTitle()+" to playlist ["+this.name+"]."); + } catch (Exception e) { + log.log(BotLog.TYPE.ERROR, "Unable to add "+url+" to the playlist ["+this.name+"]."); e.printStackTrace(); } } - /** - * Removes a track from the playlist. - * @param track The track to remove. - */ - public void removeTrack(AudioTrack track){ - this.tracks.remove(track); - } - - /** - * Copies all tracks from a specified playlist to this one. - * @param other The other playlist to make a copy of. - */ - public void copyFrom(Playlist other){ - this.tracks.clear(); - other.getTracks().forEach(track -> this.tracks.add(track.makeClone())); - } - - /** - * Returns the next track, i.e. the first one in the list, and removes it from the internal list. - * @return The AudioTrack that should be played next. - */ - public AudioTrack getNextTrackAndRemove(boolean shouldShuffle){ - if (this.tracks.isEmpty()){ - return null; - } - return this.tracks.remove((shouldShuffle ? getShuffledIndex(this.tracks.size()) : 0)); - } - - /** - * Returns the next track to be played, and re-adds it to the end of the playlist, as it would do in a loop. - * @return The next track to be played. - */ - public AudioTrack getNextTrackAndRequeue(boolean shouldShuffle){ - if (this.tracks.isEmpty()){ - return null; - } - AudioTrack track = this.tracks.remove((shouldShuffle ? getShuffledIndex(this.tracks.size()) : 0)); - this.tracks.add(track); - return track; - } - /** * 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 @@ -182,11 +116,9 @@ public class Playlist { File playlistFile = new File(playlistDir.getPath()+"/"+this.name.replace(" ", "_")+".txt"); log.log(BotLog.TYPE.INFO, "Saving playlist to: "+playlistFile.getAbsolutePath()); try(Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(playlistFile)))){ - writer.write(this.name+'\n'); - writer.write(Long.toString(this.creatorUID)+'\n'); writer.write(Integer.toString(this.tracks.size())+'\n'); - for (AudioTrack track : this.tracks){ - writer.write(track.getInfo().uri); + for (UnloadedTrack track : this.tracks){ + writer.write(track.toString()); writer.write('\n'); } } catch (FileNotFoundException e) { @@ -201,19 +133,18 @@ public class Playlist { * Loads the playlist from a file with the playlist's name. */ public void load(){ - String path = System.getProperty("user.home")+"/.handiebot/playlist/"+this.name.replace(" ", "_")+".txt"; + String path = System.getProperty("user.home")+"/.handiebot/playlist/"+name.replace(" ", "_")+".txt"; log.log(BotLog.TYPE.INFO, "Loading playlist from: "+path); File playlistFile = new File(path); if (playlistFile.exists()){ try { List lines = Files.readAllLines(Paths.get(playlistFile.toURI())); - this.name = lines.remove(0); - this.creatorUID = Long.parseLong(lines.remove(0)); int trackCount = Integer.parseInt(lines.remove(0)); + this.name = name; this.tracks = new ArrayList<>(trackCount); for (int i = 0; i < trackCount; i++){ - String url = lines.remove(0); - loadTrack(url); + 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, "IOException while loading playlist ["+this.name+"]. "+e.getMessage()); @@ -231,7 +162,12 @@ public class Playlist { public static List getAvailablePlaylists(){ File playlistFolder = new File(System.getProperty("user.home")+"/.handiebot/playlist"); List names = new ArrayList(Arrays.asList(playlistFolder.list())); - names.forEach(name -> name = name.replace("_", " ")); + 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; } @@ -250,4 +186,13 @@ public class Playlist { return false; } + @Override + public String toString(){ + StringBuilder sb = new StringBuilder("HandieBot Playlist: "+this.getName()+'\n'); + 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(); + } + } diff --git a/src/main/java/handiebot/lavaplayer/playlist/UnloadedTrack.java b/src/main/java/handiebot/lavaplayer/playlist/UnloadedTrack.java new file mode 100644 index 0000000..e2315c1 --- /dev/null +++ b/src/main/java/handiebot/lavaplayer/playlist/UnloadedTrack.java @@ -0,0 +1,168 @@ +package handiebot.lavaplayer.playlist; + +import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; +import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import handiebot.HandieBot; +import handiebot.view.BotLog; + +import java.util.concurrent.ExecutionException; + +import static handiebot.HandieBot.log; + +/** + * @author Andrew Lalis + * Class for describing a track without the actual audio track. + * This is useful for quickly loading playlists and only loading a track when it is needed. + */ +public class UnloadedTrack implements Cloneable { + + private String title; + private String url; + private long duration; + + /** + * Constructs a new unloaded track. + * This assumes that the url is known to be error free, so it will avoid a time consuming validation check. + * @param title The title of the track. + * @param url The url of the track, used when loading. + * @param duration The duration, in milliseconds(ms) of the song. + */ + public UnloadedTrack(String title, String url, long duration){ + this.title = title; + this.url = url; + this.duration = duration; + } + + /** + * Constructs a new unloaded track from a given url. + * Therefore, this method will take time to query youtube/soundcloud to receive a valid audio track. + * This is meant to ensure that this unloaded track is reliable. + * @param songURL The url to load from. + */ + public UnloadedTrack(String songURL) throws Exception { + this.title = null; + this.url = null; + this.duration = 0; + try { + HandieBot.musicPlayer.getPlayerManager().loadItem(songURL, new AudioLoadResultHandler() { + @Override + public void trackLoaded(AudioTrack audioTrack) { + title = audioTrack.getInfo().title; + url = audioTrack.getInfo().uri; + duration = audioTrack.getDuration(); + } + + @Override + public void playlistLoaded(AudioPlaylist audioPlaylist) { + log.log(BotLog.TYPE.ERROR, "Attempt to load playlist to create unloaded track."); + } + + @Override + public void noMatches() { + log.log(BotLog.TYPE.ERROR, "No matches found for " + songURL); + } + + @Override + public void loadFailed(FriendlyException e) { + log.log(BotLog.TYPE.ERROR, "Loading track failed for " + songURL); + } + }).get(); + } catch (InterruptedException | ExecutionException e) { + log.log(BotLog.TYPE.ERROR, "Exception occurred while loading item from URL: "+songURL); + e.printStackTrace(); + } + if (this.title == null){ + throw new Exception("Invalid URL: "+songURL); + } + } + + /** + * Constructs a new unloaded track from an already existing audio track. + * @param track The track to use. + */ + public UnloadedTrack(AudioTrack track){ + this.title = track.getInfo().title; + this.url = track.getInfo().uri; + this.duration = track.getDuration(); + } + + public String getTitle(){ + return this.title; + } + + public String getURL(){ + return this.url; + } + + public long getDuration(){ + return this.duration; + } + + /** + * Loads the real audio track from the internet, and returns it. + * @return an AudioTrack representing this track. + */ + public AudioTrack loadAudioTrack(){ + final AudioTrack[] track = {null}; + try { + HandieBot.musicPlayer.getPlayerManager().loadItem(this.url, new AudioLoadResultHandler() { + @Override + public void trackLoaded(AudioTrack audioTrack) { + track[0] = audioTrack; + } + + @Override + public void playlistLoaded(AudioPlaylist audioPlaylist) { + log.log(BotLog.TYPE.ERROR, "Attempt to load playlist to create unloaded track."); + } + + @Override + public void noMatches() { + log.log(BotLog.TYPE.ERROR, "No matches found for " + url); + } + + @Override + public void loadFailed(FriendlyException e) { + log.log(BotLog.TYPE.ERROR, "Loading track failed for " + url); + } + }).get(); + } catch (InterruptedException | ExecutionException e) { + log.log(BotLog.TYPE.ERROR, "Exception occurred while loading item from URL: "+url); + e.printStackTrace(); + } + return track[0]; + } + + /** + * Returns the duration of the track in an aesthetically pleasing way. + * Format is as follows: [mm:ss] + * @return A string representation of the duration of a track. + */ + public String getFormattedDuration(){ + int seconds = (int) (this.duration / 1000); + int minutes = seconds / 60; + seconds = seconds % 60; + return String.format("[%d:%02d]", minutes, seconds); + } + + @Override + public String toString(){ + return this.title + " / " + this.url + " / " + Long.toString(this.duration); + } + + /** + * Creates a clone of this track. + * @return A clone of this track. + */ + public UnloadedTrack clone(){ + try { + super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return new UnloadedTrack(this.title, this.url, this.duration); + } + +} diff --git a/src/main/java/handiebot/utils/Pastebin.java b/src/main/java/handiebot/utils/Pastebin.java new file mode 100644 index 0000000..46450cc --- /dev/null +++ b/src/main/java/handiebot/utils/Pastebin.java @@ -0,0 +1,56 @@ +package handiebot.utils; + +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicNameValuePair; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Andrew Lalis + * Class to easily paste to pastebin. + */ +public class Pastebin { + + private static String PASTEBIN_KEY = "769adc01154922ece448cabd7a33b57c"; + + public static String paste(String title, String content){ + HttpClient client = HttpClients.createDefault(); + HttpPost post = new HttpPost("https://www.pastebin.com/api/api_post.php"); + + List params = new ArrayList<>(); + params.add(new BasicNameValuePair("api_dev_key", PASTEBIN_KEY)); + params.add(new BasicNameValuePair("api_option", "paste")); + params.add(new BasicNameValuePair("api_paste_code", content)); + params.add(new BasicNameValuePair("api_paste_private", "0")); + params.add(new BasicNameValuePair("api_paste_name", title)); + params.add(new BasicNameValuePair("api_paste_expire_date", "10M")); + params.add(new BasicNameValuePair("api_user_key", "")); + + try { + post.setEntity(new UrlEncodedFormEntity(params, "UTF-8")); + HttpResponse response = client.execute(post); + HttpEntity entity = response.getEntity(); + if (entity != null){ + try (InputStream in = entity.getContent()){ + StringWriter writer = new StringWriter(); + IOUtils.copy(in, writer, "UTF-8"); + return writer.toString(); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/src/main/java/handiebot/view/BotWindow.java b/src/main/java/handiebot/view/BotWindow.java index 6aa1340..8c6ac07 100644 --- a/src/main/java/handiebot/view/BotWindow.java +++ b/src/main/java/handiebot/view/BotWindow.java @@ -2,10 +2,12 @@ package handiebot.view; import handiebot.HandieBot; +import javax.imageio.ImageIO; import javax.swing.*; import java.awt.*; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.io.IOException; /** * @author Andrew Lalis @@ -27,6 +29,11 @@ public class BotWindow extends JFrame { } } }); + try { + setIconImage(ImageIO.read(getClass().getClassLoader().getResourceAsStream("avatarIcon.png"))); + } catch (IOException e) { + e.printStackTrace(); + } setContentPane(view.mainPanel); setJMenuBar(new MenuBar()); setPreferredSize(new Dimension(800, 600)); diff --git a/src/main/java/handiebot/view/actions/music/PlayAction.java b/src/main/java/handiebot/view/actions/music/PlayAction.java index a0cdc9b..5a698e5 100644 --- a/src/main/java/handiebot/view/actions/music/PlayAction.java +++ b/src/main/java/handiebot/view/actions/music/PlayAction.java @@ -1,6 +1,5 @@ package handiebot.view.actions.music; -import handiebot.HandieBot; import sx.blah.discord.handle.obj.IGuild; import java.awt.event.ActionEvent; @@ -23,10 +22,6 @@ public class PlayAction extends MusicAction { @Override public void actionPerformed(ActionEvent e) { - if (this.args == null || this.args.length < 1){ - HandieBot.musicPlayer.playQueue(this.guild); - } else { - HandieBot.musicPlayer.loadToQueue(this.guild, this.args[0]); - } + System.out.println("Play action."); } }