From 54deb2fb61b8d90b7ac5306b2620c561fe72238e Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Thu, 22 Jun 2017 17:04:10 +0200 Subject: [PATCH 1/2] Added new command framework and rudimentary playlist functionality --- README.md | 52 +++++++- pom.xml | 15 +-- src/main/java/handiebot/HandieBot.java | 5 +- .../handiebot/command/CommandContext.java | 41 ++++++ .../handiebot/command/CommandHandler.java | 4 +- src/main/java/handiebot/command/Commands.java | 27 ++++ .../command/commands/HelpCommand.java | 35 +++++ .../command/commands/music/PlayCommand.java | 26 ++++ .../commands/music/PlaylistCommand.java | 126 ++++++++++++++++++ .../command/commands/music/QueueCommand.java | 21 +++ .../command/commands/music/RepeatCommand.java | 26 ++++ .../commands/music/ShuffleCommand.java | 26 ++++ .../command/commands/music/SkipCommand.java | 22 +++ .../java/handiebot/command/types/Command.java | 19 +++ .../command/types/ContextCommand.java | 17 +++ .../command/types/StaticCommand.java | 15 +++ .../lavaplayer/GuildMusicManager.java | 2 +- .../handiebot/lavaplayer/MusicPlayer.java | 114 ++++++++++++++-- .../java/handiebot/lavaplayer/Playlist.java | 107 ++++++++++----- .../handiebot/lavaplayer/TrackScheduler.java | 5 +- src/main/resources/avatarIcon.png | Bin 0 -> 9597 bytes src/main/resources/icon.png | Bin 71593 -> 0 bytes src/main/resources/icon.svg | 108 --------------- src/main/resources/iconTemplate.png | Bin 0 -> 40392 bytes 24 files changed, 638 insertions(+), 175 deletions(-) create mode 100644 src/main/java/handiebot/command/CommandContext.java create mode 100644 src/main/java/handiebot/command/Commands.java create mode 100644 src/main/java/handiebot/command/commands/HelpCommand.java create mode 100644 src/main/java/handiebot/command/commands/music/PlayCommand.java create mode 100644 src/main/java/handiebot/command/commands/music/PlaylistCommand.java create mode 100644 src/main/java/handiebot/command/commands/music/QueueCommand.java create mode 100644 src/main/java/handiebot/command/commands/music/RepeatCommand.java create mode 100644 src/main/java/handiebot/command/commands/music/ShuffleCommand.java create mode 100644 src/main/java/handiebot/command/commands/music/SkipCommand.java create mode 100644 src/main/java/handiebot/command/types/Command.java create mode 100644 src/main/java/handiebot/command/types/ContextCommand.java create mode 100644 src/main/java/handiebot/command/types/StaticCommand.java create mode 100644 src/main/resources/avatarIcon.png delete mode 100644 src/main/resources/icon.png delete mode 100644 src/main/resources/icon.svg create mode 100644 src/main/resources/iconTemplate.png diff --git a/README.md b/README.md index 480d668..c671fcc 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,59 @@ # HandieBot +![AvatarIcon](/src/main/resources/avatarIcon.png) + HandieBot is a bot for Discord, written using Java, the [Discord4J](https://github.com/austinv11/Discord4J) library, and the [Lavaplayer](https://github.com/sedmelluq/lavaplayer) library for sound processing. It is a fully-fledged bot with the ability to manage playlists, get music from URLs, and perform other necessary functions for a clean and enjoyable user experience. +## Description + +This Bot is designed to run as one executable Jar file, to represent one Discord bot. The bot itself keeps track of which servers (`guilds`) that it's connected to, and can independently handle requests from each one, provided it has enough bandwidth. + +In each guild, the bot will use, or create both a voice and a text channel for it to use. These values are set in the source code as `HandieBotMusic` and `handiebotmusic`, respectively. From these channels, the bot will send messages about what song it's currently playing, responses to player requests, and any possible errors that occur. The voice channel is specifically only for playing music, and the bot will try to only connect when it is doing so. + ## Commands -### `play ` +HandieBot contains some commands, most of which should be quite intuitive to the user. However, for completions' sake, the format for commands is as follows: -Issuing the `play` command attempts to load a song from a given URL, and append it to the active queue. The bot will tell the user quite obviously if their link does not work, or if there was an internal error. If there are already some songs in the queue, then this will also, if successful, tell the user approximately how long it will be until their song is played. +All commands begin with a prefix, which will not be shown with all the following commands, as it can be configured by users. This prefix is by default `!`. + +`command [optional arguments] ` + +In particular, if the optional argument is shown as capital letters, this means that you must give a value, but if the optional argument is given in lowercase letters, simply write this argument. For example, the following commands are valid: + +```text +play +play https://www.youtube.com/watch?v=9bZkp7q19f0 +queue +queue all +``` + +Because the play command is defined as `play [URL]`, and the queue command is defined as `queue [all]`. +### Music + +* `play [URL]` - Starts playback from the queue, or if a URL is defined, then it will attempt to play that song, or add it to the queue, depending on if a song is already playing. If a song is already playing, you should receive an estimate of when your song should begin playing. + +* `skip` - If a song is playing, the bot will skip it and play the next song in the queue. + +* `queue [all]` - Lists up to the first 10 items on the queue, if no argument is given. If you add `all`, the bot will upload a list to [PasteBin](http://pastebin.com) of the entire queue, and give you + +* `repeat [true|false]` - Sets the bot to repeat the playlist, as in once a song is removed from the queue to be played, it is added back to the end of the playlist. + +* `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. + + * `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. + + * `play ` - Loads and begins playing the specified playlist. + + * `add [URL]...` - Adds the specified URL, or multiple URLs to 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 `NAME`. + + * `rename ` - Renames the playlist to the new name. + diff --git a/pom.xml b/pom.xml index 21b6797..dea48d3 100644 --- a/pom.xml +++ b/pom.xml @@ -4,9 +4,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - net.agspace.handiebot - handiebot - 1.0-SNAPSHOT + com.github.andrewlalis + HandieBot + 1.1.0 @@ -21,10 +21,6 @@ jar - - 1.1.0 - - jcenter @@ -47,11 +43,6 @@ lavaplayer 1.2.39 - - com.github.kennedyoliveira - pastebin4j - ${pastebin4j.version} - \ No newline at end of file diff --git a/src/main/java/handiebot/HandieBot.java b/src/main/java/handiebot/HandieBot.java index e3b05f8..7e4ca6e 100644 --- a/src/main/java/handiebot/HandieBot.java +++ b/src/main/java/handiebot/HandieBot.java @@ -16,13 +16,15 @@ import sx.blah.discord.util.RateLimitException; /** * @author Andrew Lalis * Main Class for the discord bot. Contains client loading information and general event processing. + * Most variables are static here because this is the main file for the Bot across many possible guilds it could + * be runnnig on, so it is no problem to have only one copy. */ public class HandieBot { public static final String APPLICATION_NAME = "HandieBot"; private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY"; - private static IDiscordClient client; + public static IDiscordClient client; public static View view; private static BotWindow window; public static BotLog log; @@ -38,6 +40,7 @@ public class HandieBot { @EventSubscriber public void onReady(ReadyEvent event){ log.log(BotLog.TYPE.INFO, "HandieBot initialized."); + //client.changeAvatar(Image.forStream("png", getClass().getClassLoader().getResourceAsStream("avatarIcon.png"))); } public static void main(String[] args) throws DiscordException, RateLimitException { diff --git a/src/main/java/handiebot/command/CommandContext.java b/src/main/java/handiebot/command/CommandContext.java new file mode 100644 index 0000000..03fbaec --- /dev/null +++ b/src/main/java/handiebot/command/CommandContext.java @@ -0,0 +1,41 @@ +package handiebot.command; + +import sx.blah.discord.handle.obj.IChannel; +import sx.blah.discord.handle.obj.IGuild; +import sx.blah.discord.handle.obj.IUser; + +/** + * @author Andrew Lalis + * Class to hold important data for a command, such as user, channel, and guild. + */ +public class CommandContext { + + private IUser user; + private IChannel channel; + private IGuild guild; + private String[] args; + + public CommandContext(IUser user, IChannel channel, IGuild guild, String[] args){ + this.user = user; + this.channel = channel; + this.guild = guild; + this.args = args; + } + + public IUser getUser(){ + return this.user; + } + + public IChannel getChannel(){ + return this.channel; + } + + public IGuild getGuild(){ + return this.guild; + } + + public String[] getArgs(){ + return this.args; + } + +} diff --git a/src/main/java/handiebot/command/CommandHandler.java b/src/main/java/handiebot/command/CommandHandler.java index 642682a..4ea8ee2 100644 --- a/src/main/java/handiebot/command/CommandHandler.java +++ b/src/main/java/handiebot/command/CommandHandler.java @@ -1,6 +1,7 @@ package handiebot.command; import com.sun.istack.internal.NotNull; +import handiebot.command.commands.music.PlaylistCommand; import handiebot.utils.DisappearingMessage; import handiebot.view.BotLog; import handiebot.view.actions.QuitAction; @@ -34,6 +35,7 @@ public class CommandHandler { IGuild guild = event.getGuild(); String command = extractCommand(message); String[] args = extractArgs(message); + CommandContext context = new CommandContext(user, channel, guild, args); if (guild != null && command != null){ DisappearingMessage.deleteMessageAfter(1000, message); if (command.equals("play")){ @@ -58,7 +60,7 @@ public class CommandHandler { new QuitAction(guild).actionPerformed(null); } else if (command.equals("playlist")){ //Do playlist actions. - //TODO perform actions! + new PlaylistCommand().execute(context); } else if (command.equals("prefix") && args.length == 1){ //Set the prefix to the first argument. if (args[0].length() != 1){ diff --git a/src/main/java/handiebot/command/Commands.java b/src/main/java/handiebot/command/Commands.java new file mode 100644 index 0000000..67a1aba --- /dev/null +++ b/src/main/java/handiebot/command/Commands.java @@ -0,0 +1,27 @@ +package handiebot.command; + +import handiebot.command.commands.music.*; +import handiebot.command.types.Command; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Andrew Lalis + * Class to hold a list of commands, as static definitions that can be called upon by {@code CommandHandler}. + */ +public class Commands { + + public static List commands = new ArrayList(); + + static { + //Music commands. + commands.add(new PlayCommand()); + commands.add(new QueueCommand()); + commands.add(new SkipCommand()); + commands.add(new RepeatCommand()); + commands.add(new ShuffleCommand()); + commands.add(new PlaylistCommand()); + } + +} diff --git a/src/main/java/handiebot/command/commands/HelpCommand.java b/src/main/java/handiebot/command/commands/HelpCommand.java new file mode 100644 index 0000000..6142cf2 --- /dev/null +++ b/src/main/java/handiebot/command/commands/HelpCommand.java @@ -0,0 +1,35 @@ +package handiebot.command.commands; + +import handiebot.command.CommandContext; +import handiebot.command.types.ContextCommand; +import sx.blah.discord.handle.obj.IPrivateChannel; +import sx.blah.discord.util.EmbedBuilder; + +import java.awt.*; + +/** + * @author Andrew Lalis + * Class for sending help/command info to a user if they so desire it. + */ +public class HelpCommand extends ContextCommand { + + public HelpCommand() { + super("help"); + } + + @Override + public void execute(CommandContext context) { + IPrivateChannel pm = context.getUser().getOrCreatePMChannel(); + EmbedBuilder builder = new EmbedBuilder(); + + builder.withAuthorName("HandieBot"); + builder.withAuthorUrl("https://github.com/andrewlalis/HandieBot"); + builder.withAuthorIcon("https://github.com/andrewlalis/HandieBot/blob/master/src/main/resources/icon.png"); + + builder.withColor(new Color(255, 0, 0)); + builder.withDescription("I'm a discord bot that can manage music, as well as some other important functions which will be implemented later on. Some commands are shown below."); + builder.appendField("Commands:", "play, skip, help", false); + + pm.sendMessage(builder.build()); + } +} diff --git a/src/main/java/handiebot/command/commands/music/PlayCommand.java b/src/main/java/handiebot/command/commands/music/PlayCommand.java new file mode 100644 index 0000000..f782d72 --- /dev/null +++ b/src/main/java/handiebot/command/commands/music/PlayCommand.java @@ -0,0 +1,26 @@ +package handiebot.command.commands.music; + +import handiebot.HandieBot; +import handiebot.command.CommandContext; +import handiebot.command.types.ContextCommand; + +/** + * @author Andrew Lalis + * Command to play a song from the queue or load a new song. + */ +public class PlayCommand extends ContextCommand { + + public PlayCommand() { + super("play"); + } + + @Override + public void execute(CommandContext context) { + if (context.getArgs() == null || context.getArgs().length == 0){ + HandieBot.musicPlayer.playQueue(context.getGuild()); + } else { + HandieBot.musicPlayer.loadToQueue(context.getGuild(), context.getArgs()[0]); + } + } + +} diff --git a/src/main/java/handiebot/command/commands/music/PlaylistCommand.java b/src/main/java/handiebot/command/commands/music/PlaylistCommand.java new file mode 100644 index 0000000..0924e02 --- /dev/null +++ b/src/main/java/handiebot/command/commands/music/PlaylistCommand.java @@ -0,0 +1,126 @@ +package handiebot.command.commands.music; + +import handiebot.command.CommandContext; +import handiebot.command.CommandHandler; +import handiebot.command.types.ContextCommand; +import handiebot.lavaplayer.Playlist; +import handiebot.utils.DisappearingMessage; +import handiebot.view.BotLog; +import sx.blah.discord.handle.obj.IChannel; + +import java.io.File; +import java.util.List; + +import static handiebot.HandieBot.log; + +/** + * @author Andrew Lalis + * Command to manipulate playlists. + */ +public class PlaylistCommand extends ContextCommand { + + public PlaylistCommand(){ + super("playlist"); + } + + @Override + public void execute(CommandContext context) { + String[] args = context.getArgs(); + if (args.length > 0){ + switch (args[0]){ + case ("create"): + create(context); + break; + case ("delete"): + delete(context); + break; + case ("show"): + show(context); + break; + case ("add"): + + break; + case ("remove"): + + break; + case ("rename"): + + break; + default: + incorrectMainArg(context.getChannel()); + break; + } + } else { + incorrectMainArg(context.getChannel()); + } + } + + /** + * Error message to show if the main argument is incorrect. + * @param channel The channel to show the error message in. + */ + private void incorrectMainArg(IChannel channel){ + new DisappearingMessage(channel, "Please use one of the following actions: \n``", 5000); + } + + /** + * Creates a new playlist. + * @param context The important data such as user and arguments to be passed. + */ + private void create(CommandContext context){ + if (context.getArgs().length >= 2) { + Playlist playlist = new Playlist(context.getArgs()[1], context.getUser().getLongID()); + 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."); + 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); + } + } + + /** + * Attempts to delete a playlist. + * @param context The context of the command. + */ + private void delete(CommandContext context){ + if (context.getArgs().length == 2){ + if (Playlist.playlistExists(context.getArgs()[1])){ + File f = new File(System.getProperty("user.home")+"/.handiebot/playlist/"+context.getArgs()[1].replace(" ", "_")+".txt"); + boolean success = f.delete(); + if (success){ + 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]); + new DisappearingMessage(context.getChannel(), "The playlist was not able to be deleted.", 3000); + } + } else { + new DisappearingMessage(context.getChannel(), "The name you entered is not a playlist.\nType `"+CommandHandler.PREFIX+"playlist show` to list the playlists available.", 5000); + } + } else { + new DisappearingMessage(context.getChannel(), "You must specify the name of a playlist to delete.", 3000); + } + } + + /** + * Displays the list of playlists, or a specific playlist's songs. + * @param context The data to be passed, containing channel and arguments. + */ + private void show(CommandContext context){ + if (context.getArgs().length > 1){ + + } 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()); + } + } + +} diff --git a/src/main/java/handiebot/command/commands/music/QueueCommand.java b/src/main/java/handiebot/command/commands/music/QueueCommand.java new file mode 100644 index 0000000..85b8919 --- /dev/null +++ b/src/main/java/handiebot/command/commands/music/QueueCommand.java @@ -0,0 +1,21 @@ +package handiebot.command.commands.music; + +import handiebot.HandieBot; +import handiebot.command.CommandContext; +import handiebot.command.types.ContextCommand; + +/** + * @author Andrew Lalis + * Queue command to display the active queue. + */ +public class QueueCommand extends ContextCommand { + public QueueCommand() { + super("queue"); + } + + @Override + public void execute(CommandContext context) { + HandieBot.musicPlayer.showQueueList(context.getGuild(), (context.getArgs() != null && context.getArgs()[0].equals("all"))); + } + +} diff --git a/src/main/java/handiebot/command/commands/music/RepeatCommand.java b/src/main/java/handiebot/command/commands/music/RepeatCommand.java new file mode 100644 index 0000000..f80cf05 --- /dev/null +++ b/src/main/java/handiebot/command/commands/music/RepeatCommand.java @@ -0,0 +1,26 @@ +package handiebot.command.commands.music; + +import handiebot.HandieBot; +import handiebot.command.CommandContext; +import handiebot.command.types.ContextCommand; + +/** + * @author Andrew Lalis + * Command to toggle repeating of the active playlist. + */ +public class RepeatCommand extends ContextCommand { + + public RepeatCommand(){ + super("repeat"); + } + + @Override + public void execute(CommandContext context) { + if (context.getArgs().length == 1){ + boolean shouldRepeat = Boolean.getBoolean(context.getArgs()[0].toLowerCase()); + HandieBot.musicPlayer.setRepeat(context.getGuild(), shouldRepeat); + } else { + HandieBot.musicPlayer.toggleRepeat(context.getGuild()); + } + } +} diff --git a/src/main/java/handiebot/command/commands/music/ShuffleCommand.java b/src/main/java/handiebot/command/commands/music/ShuffleCommand.java new file mode 100644 index 0000000..fe58fa3 --- /dev/null +++ b/src/main/java/handiebot/command/commands/music/ShuffleCommand.java @@ -0,0 +1,26 @@ +package handiebot.command.commands.music; + +import handiebot.HandieBot; +import handiebot.command.CommandContext; +import handiebot.command.types.ContextCommand; + +/** + * @author Andrew Lalis + * Command to set shuffling of the active playlist. + */ +public class ShuffleCommand extends ContextCommand { + + public ShuffleCommand(){ + super("shuffle"); + } + + @Override + public void execute(CommandContext context) { + if (context.getArgs().length == 1){ + boolean shouldShuffle = Boolean.getBoolean(context.getArgs()[0].toLowerCase()); + HandieBot.musicPlayer.setShuffle(context.getGuild(), shouldShuffle); + } else { + HandieBot.musicPlayer.toggleShuffle(context.getGuild()); + } + } +} diff --git a/src/main/java/handiebot/command/commands/music/SkipCommand.java b/src/main/java/handiebot/command/commands/music/SkipCommand.java new file mode 100644 index 0000000..69ed235 --- /dev/null +++ b/src/main/java/handiebot/command/commands/music/SkipCommand.java @@ -0,0 +1,22 @@ +package handiebot.command.commands.music; + +import handiebot.HandieBot; +import handiebot.command.CommandContext; +import handiebot.command.types.ContextCommand; + +/** + * @author Andrew Lalis + * Skips the current song, if there is one playing. + */ +public class SkipCommand extends ContextCommand { + + public SkipCommand() { + super("skip"); + } + + @Override + public void execute(CommandContext context) { + HandieBot.musicPlayer.skipTrack(context.getGuild()); + } + +} diff --git a/src/main/java/handiebot/command/types/Command.java b/src/main/java/handiebot/command/types/Command.java new file mode 100644 index 0000000..7f6ca26 --- /dev/null +++ b/src/main/java/handiebot/command/types/Command.java @@ -0,0 +1,19 @@ +package handiebot.command.types; + +/** + * @author Andrew Lalis + * Basic type of command. + */ +public abstract class Command { + + private String name; + + public Command(String name){ + this.name = name; + } + + public String getName(){ + return this.name; + }; + +} diff --git a/src/main/java/handiebot/command/types/ContextCommand.java b/src/main/java/handiebot/command/types/ContextCommand.java new file mode 100644 index 0000000..6ac4e91 --- /dev/null +++ b/src/main/java/handiebot/command/types/ContextCommand.java @@ -0,0 +1,17 @@ +package handiebot.command.types; + +import handiebot.command.CommandContext; + +/** + * @author Andrew Lalis + * Type of command which requires a guild to function properly. + */ +public abstract class ContextCommand extends Command { + + public ContextCommand(String s) { + super(s); + } + + public abstract void execute(CommandContext context); + +} diff --git a/src/main/java/handiebot/command/types/StaticCommand.java b/src/main/java/handiebot/command/types/StaticCommand.java new file mode 100644 index 0000000..60274dc --- /dev/null +++ b/src/main/java/handiebot/command/types/StaticCommand.java @@ -0,0 +1,15 @@ +package handiebot.command.types; + +/** + * @author Andrew Lalis + * Class for commands which require no context, so execute on a global scale. + */ +public abstract class StaticCommand extends Command { + + public StaticCommand(String s) { + super(s); + } + + public abstract void execute(); + +} diff --git a/src/main/java/handiebot/lavaplayer/GuildMusicManager.java b/src/main/java/handiebot/lavaplayer/GuildMusicManager.java index bd5320f..54f8268 100644 --- a/src/main/java/handiebot/lavaplayer/GuildMusicManager.java +++ b/src/main/java/handiebot/lavaplayer/GuildMusicManager.java @@ -16,7 +16,7 @@ public class GuildMusicManager { public GuildMusicManager(AudioPlayerManager manager, IGuild guild){ this.player = manager.createPlayer(); - this.scheduler = new TrackScheduler(this.player, guild, manager); + this.scheduler = new TrackScheduler(this.player, guild); this.player.addListener(this.scheduler); } diff --git a/src/main/java/handiebot/lavaplayer/MusicPlayer.java b/src/main/java/handiebot/lavaplayer/MusicPlayer.java index 79296f6..f38abb2 100644 --- a/src/main/java/handiebot/lavaplayer/MusicPlayer.java +++ b/src/main/java/handiebot/lavaplayer/MusicPlayer.java @@ -10,12 +10,26 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import handiebot.command.CommandHandler; import handiebot.utils.DisappearingMessage; 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; @@ -55,6 +69,10 @@ public class MusicPlayer { this.voiceChannels = new HashMap<>(); } + public AudioPlayerManager getPlayerManager(){ + return this.playerManager; + } + /** * Gets the music manager specific to a particular guild. * @param guild The guild to get the music manager for. @@ -113,14 +131,40 @@ public class MusicPlayer { */ public void toggleRepeat(IGuild guild){ GuildMusicManager musicManager = this.getMusicManager(guild); - musicManager.scheduler.setRepeat(!musicManager.scheduler.isRepeating()); } + /** + * Sets the repeating of songs for a particular guild. + * @param guild The guild to set repeat for. + * @param value True to repeat, false otherwise. + */ + public void setRepeat(IGuild guild, boolean value){ + getMusicManager(guild).scheduler.setRepeat(value); + } + + /** + * Toggles shuffling for a specific guild. + * @param guild The guild to toggle shuffling for. + */ + public void toggleShuffle(IGuild guild){ + GuildMusicManager musicManager = this.getMusicManager(guild); + musicManager.scheduler.setShuffle(!musicManager.scheduler.isShuffling()); + } + + /** + * Sets shuffling for a specific guild. + * @param guild The guild to set shuffling for. + * @param value The value to set. True for shuffling, false for linear play. + */ + public void setShuffle(IGuild guild, boolean value){ + getMusicManager(guild).scheduler.setShuffle(value); + } + /** * Sends a formatted message to the guild about the first few items in a queue. */ - public void showQueueList(IGuild guild, boolean showAll){ + public void showQueueList(IGuild guild, boolean showAll) { 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); @@ -157,18 +201,60 @@ public class MusicPlayer { String time = String.format(" [%d:%02d]\n", minutes, seconds); sb.append(time); } - //TODO: get pastebin working. - /* - PasteBin pasteBin = new PasteBin(new AccountCredentials(PASTEBIN_KEY)); - Paste paste = new Paste(PASTEBIN_KEY); - paste.setTitle("Music Queue for Discord Server: "+guild.getName()); - paste.setContent(sb.toString()); - paste.setExpiration(PasteExpiration.ONE_HOUR); - paste.setVisibility(PasteVisibility.PUBLIC); - final String pasteURL = pasteBin.createPaste(paste); - log.log(BotLog.TYPE.INFO, guild, "Uploaded full queue to "+pasteURL); - new DisappearingMessage(getChatChannel(guild), "You may view the full queue here. "+pasteURL, 60000); - */ + + 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(); + } + } + } + } } } diff --git a/src/main/java/handiebot/lavaplayer/Playlist.java b/src/main/java/handiebot/lavaplayer/Playlist.java index 3d2a8f8..070e287 100644 --- a/src/main/java/handiebot/lavaplayer/Playlist.java +++ b/src/main/java/handiebot/lavaplayer/Playlist.java @@ -1,16 +1,17 @@ package handiebot.lavaplayer; import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler; -import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; 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.*; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutionException; @@ -21,6 +22,8 @@ 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 * 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 { @@ -44,9 +47,9 @@ public class Playlist { * Creates a playlist from a file with the given name. * @param name The name of the file. */ - public Playlist(String name, AudioPlayerManager playerManager){ + public Playlist(String name){ this.name = name; - this.load(playerManager); + this.load(); } public String getName(){ @@ -69,6 +72,44 @@ public class Playlist { this.tracks.add(track); } + /** + * 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()); + e.printStackTrace(); + } + } + /** * Removes a track from the playlist. * @param track The track to remove. @@ -159,7 +200,7 @@ public class Playlist { /** * Loads the playlist from a file with the playlist's name. */ - public void load(AudioPlayerManager playerManager){ + public void load(){ String path = System.getProperty("user.home")+"/.handiebot/playlist/"+this.name.replace(" ", "_")+".txt"; log.log(BotLog.TYPE.INFO, "Loading playlist from: "+path); File playlistFile = new File(path); @@ -172,41 +213,41 @@ public class Playlist { this.tracks = new ArrayList<>(trackCount); for (int i = 0; i < trackCount; i++){ String url = lines.remove(0); - playerManager.loadItem(url, new AudioLoadResultHandler() { - @Override - public void trackLoaded(AudioTrack audioTrack) { - tracks.add(audioTrack); - } - - @Override - public void playlistLoaded(AudioPlaylist audioPlaylist) { - //Do nothing. This should not happen. - } - - @Override - public void noMatches() { - System.out.println("No matches for: "+url); - //Do nothing. This should not happen. - } - - @Override - public void loadFailed(FriendlyException e) { - System.out.println("Load failed: "+e.getMessage()); - //Do nothing. This should not happen. - } - }).get(); + loadTrack(url); } } catch (IOException e) { log.log(BotLog.TYPE.ERROR, "IOException while loading playlist ["+this.name+"]. "+e.getMessage()); e.printStackTrace(); - } 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()); - e.printStackTrace(); } + } else { + log.log(BotLog.TYPE.ERROR, "The playlist ["+this.name+"] does not exist."); } } + /** + * 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"); + List names = new ArrayList(Arrays.asList(playlistFolder.list())); + names.forEach(name -> name = name.replace("_", " ")); + 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; + } + } diff --git a/src/main/java/handiebot/lavaplayer/TrackScheduler.java b/src/main/java/handiebot/lavaplayer/TrackScheduler.java index 30d6d6b..284290f 100644 --- a/src/main/java/handiebot/lavaplayer/TrackScheduler.java +++ b/src/main/java/handiebot/lavaplayer/TrackScheduler.java @@ -1,7 +1,6 @@ package handiebot.lavaplayer; import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; -import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; @@ -36,11 +35,11 @@ public class TrackScheduler extends AudioEventAdapter { * Constructs a new track scheduler with the given player. * @param player The audio player this scheduler uses. */ - public TrackScheduler(AudioPlayer player, IGuild guild, AudioPlayerManager playerManager){ + public TrackScheduler(AudioPlayer player, IGuild guild){ this.player = player; this.guild = guild; //this.activePlaylist = new Playlist("HandieBot Active Playlist", 283652989212688384L); - this.activePlaylist = new Playlist("HandieBot Active Playlist", playerManager); + this.activePlaylist = new Playlist("HandieBot Active Playlist"); } /** diff --git a/src/main/resources/avatarIcon.png b/src/main/resources/avatarIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..e18afd713f1493df8327c75b38b0a738081943d2 GIT binary patch literal 9597 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

iUuE-mCAa!inoWsfM>1O*L>algzw zy}b`ZYxTZTEzL3U4a+n&oX9U@t|W0|eY}C;wef)&p7^E%-dbVTyrPlCSMZ_=DfK0r z`(1&&VezOz8wIBorihxpg2e^jaEpa3d>c-DV{rg}UQrWo+gcXtpa5_oSb4E=kibDX z(fvyzHh5b_jn=EbG}6sH{a!>bPs!I|t!2UXu5JE1WL>QYUmak@%&FTM6daw{$r|f} zIx#JM9x!h6KRVk1)Au2zZk^uMoh*+&$a7?AkSI9i+bHPl2;Ot(Izt5NIGvyFQLsUc z3wfHo;#vpfl$I`_)9UTQuESl!VB!|-px^ILvUC5+O*~|S{;&f;kXqMj{QWGq9laS^ zm3(&rxVd3|FTdWX73}9NedY5e8)5<$vCLbPE z!ke>bxgT95!f53R9Wfgk94V+Y1mEw+&VyOdBX&NruepvKh;sG$j1~myrK_Fh`1_Rk z3Dy0p;OLsr6UskI2nv`ZD;as(2EnN?OXS%q4m#cc(TjxCCmT;2?v8NTX9$Z&B0P{% z=_L|^9)UGP_i$PDX;7K+kCP7@dxyYV2qn8LH~hrL&vPz&!;JQXq?WEij9z3^3Y zTcuF-8kgnKqlb+M?WBXPNluf`=}QH8+R(=c7Zk$KF=#XKa9$ca_0S`P7i^Qrr7 z{y#7E|0V+vjk`3AcqIGB&$bcBnoRj0)|@v-Q%k*tC7U`@Oja#I9?LbQ2HABx^Vo*# zDjHhr0~uMXf%Q${Yi2?&d7QeY{+K*=$0!BFWF7$es0wCjACH$-yyohJPfLIJ6Pk-_ zm#q|Pe>b(aJ@vC~U%YbL^cuHOkhD?vfn%k^xY-ogwW!|B{4hHMOYx$%V3nz@cOJ& zE!L~MKRoQ|oWR|mR6VI;HQ;Jblh8=w0dS>9;68Hb2U#>g`G0<$Z97f>D7@wr<4B4M SsetQGAf)C!jRrOQ=>GvGX!v#j literal 0 HcmV?d00001 diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png deleted file mode 100644 index cb2d22082939d6a483eeeea0e70e8cfa954c5608..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 71593 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 diff --git a/src/main/resources/icon.svg b/src/main/resources/icon.svg deleted file mode 100644 index 5babeb4..0000000 --- a/src/main/resources/icon.svg +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - diff --git a/src/main/resources/iconTemplate.png b/src/main/resources/iconTemplate.png new file mode 100644 index 0000000000000000000000000000000000000000..dac4cef2cc46408ad9a5c8c09e3b2ff070591a0b GIT binary patch literal 40392 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 Cqf^TO literal 0 HcmV?d00001 -- 2.34.1 From 2db68aac61a102ce1474181ae4b79f03190847db Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Fri, 23 Jun 2017 14:15:36 +0200 Subject: [PATCH 2/2] Finished playlists: * playlists are saved globally. * playlists are lists of links, titles and durations * songs are loaded only when they should be played. --- README.md | 16 +- .../handiebot/command/CommandHandler.java | 13 +- .../command/commands/music/PlayCommand.java | 9 +- .../commands/music/PlaylistCommand.java | 178 +++++++++++++++++- .../command/commands/music/RepeatCommand.java | 6 + .../commands/music/ShuffleCommand.java | 6 + .../handiebot/lavaplayer/MusicPlayer.java | 155 ++------------- .../handiebot/lavaplayer/TrackScheduler.java | 30 ++- .../lavaplayer/{ => playlist}/Playlist.java | 163 ++++++---------- .../lavaplayer/playlist/UnloadedTrack.java | 168 +++++++++++++++++ src/main/java/handiebot/utils/Pastebin.java | 56 ++++++ src/main/java/handiebot/view/BotWindow.java | 7 + .../view/actions/music/PlayAction.java | 7 +- 13 files changed, 534 insertions(+), 280 deletions(-) rename src/main/java/handiebot/lavaplayer/{ => playlist}/Playlist.java (54%) create mode 100644 src/main/java/handiebot/lavaplayer/playlist/UnloadedTrack.java create mode 100644 src/main/java/handiebot/utils/Pastebin.java 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."); } } -- 2.34.1