Finished playlists:

* playlists are saved globally.
* playlists are lists of links, titles and durations
* songs are loaded only when they should be played.
This commit is contained in:
Andrew Lalis 2017-06-23 14:15:36 +02:00
parent 54deb2fb61
commit 2db68aac61
13 changed files with 534 additions and 280 deletions

View File

@ -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 <create|show|play|delete|add|remove|rename>` - Various commands to manipulate playlists. The specific sub-commands are explained below.
* `create <NAME> [URL]...` - Creates a new playlist, optionally with some starting URLs.
* `create <PLAYLIST> [URL]...` - Creates a new playlist, optionally with some starting URLs.
* `delete <NAME>` - Deletes a playlist with the given name.
* `delete <PLAYLIST>` - 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 <NAME>` - Loads and begins playing the specified playlist.
* `play <PLAYLIST>` - Loads and begins playing the specified playlist.
* `add <NAME> <URL> [URL]...` - Adds the specified URL, or multiple URLs to the playlist given by `NAME`.
* `add <PLAYLIST> <URL> [URL]...` - Adds the specified URL, or multiple URLs to the playlist given by `PLAYLIST`.
* `remove <NAME> <SONGNAME>` - Removes the specified song name, or the one that most closely matches the song name given, from the playlist given by `NAME`.
* `remove <PLAYLIST> <SONGNUMBER>` - Removes the specified song name, or the one that most closely matches the song name given, from the playlist given by `PLAYLIST`.
* `rename <NAME> <NEWNAME>` - Renames the playlist to the new name.
* `rename <PLAYLIST> <NEWNAME>` - Renames the playlist to the new name.
* `move <PLAYLIST> <SONGNUMBER> <NEWNUMBER>` - Moves a song from one index to another index, shifting other elements as necessary.

View File

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

View File

@ -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();
}
}
}

View File

@ -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<String> 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);
}
}

View File

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

View File

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

View File

@ -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<AudioTrack> tracks = getMusicManager(guild).scheduler.queueList();
List<UnloadedTrack> 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<NameValuePair> params = new ArrayList<NameValuePair>(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){

View File

@ -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<AudioTrack> queueList(){
public List<UnloadedTrack> 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 {

View File

@ -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<AudioTrack> tracks;
private List<UnloadedTrack> 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<AudioTrack> getTracks(){
public List<UnloadedTrack> 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<String> 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<String> getAvailablePlaylists(){
File playlistFolder = new File(System.getProperty("user.home")+"/.handiebot/playlist");
List<String> names = new ArrayList<String>(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();
}
}

View File

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

View File

@ -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<NameValuePair> 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;
}
}

View File

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

View File

@ -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.");
}
}