From c2e443410fbf60c2ac32c7813792560ab2f8e2e2 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Sun, 18 Jun 2017 11:33:58 +0200 Subject: [PATCH 01/10] Added abstraction of music functions to Music Player Removed most of the junk code from the main class. --- src/main/java/handiebot/HandieBot.java | 97 +----------- .../handiebot/command/CommandHandler.java | 6 +- .../handiebot/lavaplayer/MusicPlayer.java | 145 ++++++++++++++++++ 3 files changed, 155 insertions(+), 93 deletions(-) create mode 100644 src/main/java/handiebot/lavaplayer/MusicPlayer.java diff --git a/src/main/java/handiebot/HandieBot.java b/src/main/java/handiebot/HandieBot.java index 274ccce..ae486ec 100644 --- a/src/main/java/handiebot/HandieBot.java +++ b/src/main/java/handiebot/HandieBot.java @@ -1,24 +1,16 @@ package handiebot; -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.GuildMusicManager; +import handiebot.lavaplayer.MusicPlayer; import sx.blah.discord.api.ClientBuilder; import sx.blah.discord.api.IDiscordClient; import sx.blah.discord.api.events.EventSubscriber; -import sx.blah.discord.handle.audio.IAudioManager; import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; -import sx.blah.discord.handle.obj.IChannel; -import sx.blah.discord.handle.obj.IGuild; -import sx.blah.discord.handle.obj.IVoiceChannel; import sx.blah.discord.util.DiscordException; -import sx.blah.discord.util.MissingPermissionsException; import sx.blah.discord.util.RateLimitException; import java.util.HashMap; @@ -45,6 +37,7 @@ public class HandieBot { private final AudioPlayerManager playerManager; private final Map musicManagers; + private MusicPlayer musicPlayer; private HandieBot() { this.musicManagers = new HashMap<>(); @@ -53,20 +46,11 @@ public class HandieBot { AudioSourceManagers.registerLocalSource(playerManager); this.commandHandler = new CommandHandler(this); + this.musicPlayer = new MusicPlayer(); } - private synchronized GuildMusicManager getGuildAudioPlayer(IGuild guild) { - long guildId = Long.parseLong(guild.getID()); - GuildMusicManager musicManager = musicManagers.get(guildId); - - if (musicManager == null) { - musicManager = new GuildMusicManager(playerManager); - musicManagers.put(guildId, musicManager); - } - - guild.getAudioManager().setAudioProvider(musicManager.getAudioProvider()); - - return musicManager; + public MusicPlayer getMusicPlayer(){ + return this.musicPlayer; } @EventSubscriber @@ -74,76 +58,5 @@ public class HandieBot { this.commandHandler.handleCommand(event); } - public void loadAndPlay(final IChannel channel, final String trackUrl) { - GuildMusicManager musicManager = getGuildAudioPlayer(channel.getGuild()); - playerManager.loadItemOrdered(musicManager, trackUrl, new AudioLoadResultHandler() { - @Override - public void trackLoaded(AudioTrack track) { - sendMessageToChannel(channel, "Adding to queue " + track.getInfo().title); - - play(channel.getGuild(), musicManager, track); - } - - @Override - public void playlistLoaded(AudioPlaylist playlist) { - AudioTrack firstTrack = playlist.getSelectedTrack(); - - if (firstTrack == null) { - firstTrack = playlist.getTracks().get(0); - } - - sendMessageToChannel(channel, "Adding to queue " + firstTrack.getInfo().title + " (first track of playlist " + playlist.getName() + ")"); - - play(channel.getGuild(), musicManager, firstTrack); - } - - @Override - public void noMatches() { - sendMessageToChannel(channel, "Nothing found by " + trackUrl); - } - - @Override - public void loadFailed(FriendlyException exception) { - sendMessageToChannel(channel, "Could not play: " + exception.getMessage()); - } - }); - } - - private void play(IGuild guild, GuildMusicManager musicManager, AudioTrack track) { - connectToFirstVoiceChannel(guild.getAudioManager()); - - musicManager.scheduler.queue(track); - } - - public void skipTrack(IChannel channel) { - GuildMusicManager musicManager = getGuildAudioPlayer(channel.getGuild()); - musicManager.scheduler.nextTrack(); - - sendMessageToChannel(channel, "Skipped to next track."); - } - - private void sendMessageToChannel(IChannel channel, String message) { - try { - channel.sendMessage(message); - } catch (Exception e) { - e.printStackTrace(); - } - } - - private static void connectToFirstVoiceChannel(IAudioManager audioManager) { - for (IVoiceChannel voiceChannel : audioManager.getGuild().getVoiceChannels()) { - if (voiceChannel.isConnected()) { - return; - } - } - - for (IVoiceChannel voiceChannel : audioManager.getGuild().getVoiceChannels()) { - try { - voiceChannel.join(); - } catch (MissingPermissionsException e) { - e.printStackTrace(); - } - } - } } diff --git a/src/main/java/handiebot/command/CommandHandler.java b/src/main/java/handiebot/command/CommandHandler.java index 9a8a36b..58e0113 100644 --- a/src/main/java/handiebot/command/CommandHandler.java +++ b/src/main/java/handiebot/command/CommandHandler.java @@ -35,9 +35,13 @@ public class CommandHandler { String[] args = extractArgs(message); if (guild != null && command != null){ if (command.equals("play") && args.length == 1){ - this.bot.loadAndPlay(channel, args[0]); + this.bot.getMusicPlayer().loadToQueue(guild, args[0]); + } else if (command.equals("skip") && args.length == 0){ + this.bot.getMusicPlayer().skipTrack(guild); } else if (command.equals("help")){ this.sendHelpInfo(user); + } else if (command.equals("playnow") && args.length == 1){ + } } } diff --git a/src/main/java/handiebot/lavaplayer/MusicPlayer.java b/src/main/java/handiebot/lavaplayer/MusicPlayer.java new file mode 100644 index 0000000..ef70a9a --- /dev/null +++ b/src/main/java/handiebot/lavaplayer/MusicPlayer.java @@ -0,0 +1,145 @@ +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 sx.blah.discord.handle.obj.IChannel; +import sx.blah.discord.handle.obj.IGuild; +import sx.blah.discord.handle.obj.IVoiceChannel; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Andrew Lalis + * This class is a container for all the music related functions, and contains methods for easy playback and queue + * management. + */ +public class MusicPlayer { + + private static String CHANNEL_NAME = "music"; + + private final AudioPlayerManager playerManager; + private final Map musicManagers; + + public MusicPlayer(){ + this.musicManagers = new HashMap<>(); + this.playerManager = new DefaultAudioPlayerManager(); + AudioSourceManagers.registerLocalSource(playerManager); + AudioSourceManagers.registerRemoteSources(playerManager); + } + + /** + * Loads a URL to the queue, or outputs an error message if it fails. + * @param guild The guild to load the URL to. + * @param trackURL A string representing a youtube/soundcloud URL. + */ + public void loadToQueue(IGuild guild, String trackURL){ + GuildMusicManager musicManager = this.getGuildMusicManager(guild); + this.playerManager.loadItemOrdered(musicManager, trackURL, new AudioLoadResultHandler() { + @Override + public void trackLoaded(AudioTrack audioTrack) { + addToQueue(guild, musicManager, 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, musicManager,firstTrack); + } + } + + @Override + public void noMatches() { + getMessageChannel(guild).sendMessage("Unable to find a result for: "+trackURL); + } + + @Override + public void loadFailed(FriendlyException e) { + getMessageChannel(guild).sendMessage("Unable to load. "+e.getMessage()); + } + }); + } + + /** + * Adds a track to the queue and sends a message to the appropriate channel notifying users. + * @param guild The guild to queue the track in. + * @param musicManager The music manager to use. + * @param track The track to queue. + */ + public void addToQueue(IGuild guild, GuildMusicManager musicManager, AudioTrack track){ + IVoiceChannel voiceChannel = this.connectToMusicChannel(guild); + if (voiceChannel != null){ + musicManager.scheduler.queue(track); + IChannel channel = this.getMessageChannel(guild); + channel.sendMessage("Added ["+track.getInfo().title+"] to the queue."); + } + + } + + /** + * Skips the current track. + * @param guild The guild to perform the skip on. + */ + public void skipTrack(IGuild guild){ + this.getGuildMusicManager(guild).scheduler.nextTrack(); + this.getMessageChannel(guild).sendMessage("Skipping the current track."); + } + + /** + * Gets or creates a music manager for a specific guild. + * @param guild The guild to get a manager for. + * @return A Music Manager for the guild. + */ + private synchronized GuildMusicManager getGuildMusicManager(IGuild guild){ + long guildId = Long.parseLong(guild.getStringID()); + GuildMusicManager musicManager = this.musicManagers.get(guildId); + if (musicManager == null){ + musicManager = new GuildMusicManager(this.playerManager); + musicManagers.put(guildId, musicManager); + } + guild.getAudioManager().setAudioProvider(musicManager.getAudioProvider()); + return musicManager; + } + + /** + * Searches for and attempts to connect to a channel called 'music'. + * @param guild the guild to get voice channels from. + * @return The voice channel the bot is now connected to. + */ + private IVoiceChannel connectToMusicChannel(IGuild guild){ + for (IVoiceChannel voiceChannel : guild.getVoiceChannelsByName(CHANNEL_NAME)){ + if (!voiceChannel.isConnected()) { + voiceChannel.join(); + return voiceChannel; + } + } + IVoiceChannel voiceChannel = guild.createVoiceChannel(CHANNEL_NAME); + voiceChannel.join(); + return voiceChannel; + } + + /** + * Returns a 'music' message channel where the bot can post info on playing songs, user requests, + * etc. + * @param guild The guild to get channels from. + * @return The channel with that name. + */ + private IChannel getMessageChannel(IGuild guild){ + List channels = guild.getChannelsByName(CHANNEL_NAME); + if (channels.size() == 1){ + return channels.get(0); + } + return guild.createChannel(CHANNEL_NAME); + } + +} From cda6199fc7780296029dd7d8b1f0f93e09bf10d7 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Sun, 18 Jun 2017 12:30:09 +0200 Subject: [PATCH 02/10] Added repeat and queue listing, still lots of work that needs to be done. --- .../handiebot/command/CommandHandler.java | 8 ++- .../handiebot/lavaplayer/MusicPlayer.java | 57 +++++++++++++++++-- .../handiebot/lavaplayer/TrackScheduler.java | 36 +++++++++++- 3 files changed, 91 insertions(+), 10 deletions(-) diff --git a/src/main/java/handiebot/command/CommandHandler.java b/src/main/java/handiebot/command/CommandHandler.java index 58e0113..cae19a9 100644 --- a/src/main/java/handiebot/command/CommandHandler.java +++ b/src/main/java/handiebot/command/CommandHandler.java @@ -14,7 +14,7 @@ import java.awt.*; */ public class CommandHandler { - private static String PREFIX = "!"; + public static String PREFIX = "!"; private final HandieBot bot; @@ -40,8 +40,10 @@ public class CommandHandler { this.bot.getMusicPlayer().skipTrack(guild); } else if (command.equals("help")){ this.sendHelpInfo(user); - } else if (command.equals("playnow") && args.length == 1){ - + } else if (command.equals("queue") && args.length == 0){ + this.bot.getMusicPlayer().showQueueList(guild); + } else if (command.equals("repeat")){ + this.bot.getMusicPlayer().toggleRepeat(guild); } } } diff --git a/src/main/java/handiebot/lavaplayer/MusicPlayer.java b/src/main/java/handiebot/lavaplayer/MusicPlayer.java index ef70a9a..039da59 100644 --- a/src/main/java/handiebot/lavaplayer/MusicPlayer.java +++ b/src/main/java/handiebot/lavaplayer/MusicPlayer.java @@ -7,9 +7,11 @@ 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 sx.blah.discord.handle.obj.IChannel; import sx.blah.discord.handle.obj.IGuild; import sx.blah.discord.handle.obj.IVoiceChannel; +import sx.blah.discord.util.EmbedBuilder; import java.util.HashMap; import java.util.List; @@ -34,6 +36,49 @@ public class MusicPlayer { AudioSourceManagers.registerRemoteSources(playerManager); } + /** + * Toggles the playlist's repeating. + * @param guild The guild to perform the action on. + */ + public void toggleRepeat(IGuild guild){ + GuildMusicManager musicManager = this.getGuildMusicManager(guild); + musicManager.scheduler.setRepeat(!musicManager.scheduler.isRepeating()); + this.getMessageChannel(guild).sendMessage("**Repeat** is now *"+(musicManager.scheduler.isRepeating() ? "On" : "Off")+"*."); + } + + /** + * Sends a formatted message to the guild about the first few items in a queue. + * @param guild The guild to show the queue for. + */ + public void showQueueList(IGuild guild){ + GuildMusicManager musicManager = this.getGuildMusicManager(guild); + List tracks = musicManager.scheduler.queueList(); + if (tracks.size() == 0) { + this.getMessageChannel(guild).sendMessage("The queue is empty. Use **"+ CommandHandler.PREFIX+"play** *URL* to add songs."); + } 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; + sb.append(minutes); + sb.append(":"); + sb.append(seconds % 60); + sb.append("]\n"); + } + builder.appendField("Showing " + (tracks.size() <= 10 ? tracks.size() : "the first 10") + " tracks.", sb.toString(), false); + this.getMessageChannel(guild).sendMessage(builder.build()); + } + } + /** * Loads a URL to the queue, or outputs an error message if it fails. * @param guild The guild to load the URL to. @@ -81,7 +126,7 @@ public class MusicPlayer { if (voiceChannel != null){ musicManager.scheduler.queue(track); IChannel channel = this.getMessageChannel(guild); - channel.sendMessage("Added ["+track.getInfo().title+"] to the queue."); + channel.sendMessage("Added **"+track.getInfo().title+"** to the queue."); } } @@ -117,11 +162,11 @@ public class MusicPlayer { * @return The voice channel the bot is now connected to. */ private IVoiceChannel connectToMusicChannel(IGuild guild){ - for (IVoiceChannel voiceChannel : guild.getVoiceChannelsByName(CHANNEL_NAME)){ - if (!voiceChannel.isConnected()) { - voiceChannel.join(); - return voiceChannel; - } + List voiceChannels = guild.getVoiceChannelsByName(CHANNEL_NAME); + if (voiceChannels.size() == 1){ + if (!voiceChannels.get(0).isConnected()) + voiceChannels.get(0).join(); + return voiceChannels.get(0); } IVoiceChannel voiceChannel = guild.createVoiceChannel(CHANNEL_NAME); voiceChannel.join(); diff --git a/src/main/java/handiebot/lavaplayer/TrackScheduler.java b/src/main/java/handiebot/lavaplayer/TrackScheduler.java index 555a6f4..18fff1a 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 java.util.ArrayList; +import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -16,6 +18,8 @@ public class TrackScheduler extends AudioEventAdapter { private final AudioPlayer player; private final BlockingQueue queue; + private boolean repeat = false; + private AudioTrack currentTrack = null; /** * Constructs a new track scheduler with the given player. @@ -26,6 +30,22 @@ public class TrackScheduler extends AudioEventAdapter { this.queue = new LinkedBlockingQueue<>(); } + /** + * Sets whether or not songs get placed back into the queue once they're played. + * @param value True if the playlist should repeat. + */ + public void setRepeat(boolean value){ + this.repeat = value; + } + + /** + * Returns whether or not repeating is enabled. + * @return True if repeating, false otherwise. + */ + public boolean isRepeating(){ + return this.repeat; + } + /** * 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. @@ -41,7 +61,12 @@ public class TrackScheduler extends AudioEventAdapter { * Starts the next track, stopping the current one if it's playing. */ public void nextTrack(){ - player.startTrack(queue.poll(), false); + AudioTrack track = queue.poll(); + player.startTrack(track, false); + this.currentTrack = track; + if (this.repeat){ + this.queue.add(track); + } } @Override @@ -49,10 +74,19 @@ public class TrackScheduler extends AudioEventAdapter { if (endReason.mayStartNext){ nextTrack(); } else { + this.currentTrack = null; System.out.println(endReason.toString()); } } + /** + * Returns a list of tracks in the queue. + * @return A list of tracks in the queue. + */ + public List queueList(){ + return new ArrayList<>(this.queue); + } + @Override public void onTrackException(AudioPlayer player, AudioTrack track, FriendlyException exception){ exception.printStackTrace(); From 0ceedf293dc02c56e21224af59e300026ff46565 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Sun, 18 Jun 2017 23:44:53 +0200 Subject: [PATCH 03/10] Lots! of changes * Removed custom listener; it's unneeded. * Organized music player. * I now pass the guild to the Track Scheduler so it too can send messages. * Added disappearing messages. --- src/main/java/handiebot/HandieBot.java | 30 +-- .../handiebot/command/CommandHandler.java | 13 +- .../lavaplayer/AudioLoadResultHandler.java | 40 ---- .../lavaplayer/GuildMusicManager.java | 5 +- .../handiebot/lavaplayer/MusicPlayer.java | 185 ++++++++++-------- .../handiebot/lavaplayer/TrackScheduler.java | 65 ++++-- .../handiebot/utils/DisappearingMessage.java | 44 +++++ 7 files changed, 221 insertions(+), 161 deletions(-) delete mode 100644 src/main/java/handiebot/lavaplayer/AudioLoadResultHandler.java create mode 100644 src/main/java/handiebot/utils/DisappearingMessage.java diff --git a/src/main/java/handiebot/HandieBot.java b/src/main/java/handiebot/HandieBot.java index ae486ec..e8f01f3 100644 --- a/src/main/java/handiebot/HandieBot.java +++ b/src/main/java/handiebot/HandieBot.java @@ -1,10 +1,6 @@ package handiebot; -import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; -import com.sedmelluq.discord.lavaplayer.player.DefaultAudioPlayerManager; -import com.sedmelluq.discord.lavaplayer.source.AudioSourceManagers; import handiebot.command.CommandHandler; -import handiebot.lavaplayer.GuildMusicManager; import handiebot.lavaplayer.MusicPlayer; import sx.blah.discord.api.ClientBuilder; import sx.blah.discord.api.IDiscordClient; @@ -13,8 +9,7 @@ import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedE import sx.blah.discord.util.DiscordException; import sx.blah.discord.util.RateLimitException; -import java.util.HashMap; -import java.util.Map; +import java.util.logging.Logger; /** * @author Andrew Lalis @@ -22,29 +17,16 @@ import java.util.Map; */ public class HandieBot { + public static Logger log = Logger.getLogger("HandieBotLog"); + private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY"; private static IDiscordClient client; private CommandHandler commandHandler; - - public static void main(String[] args) throws DiscordException, RateLimitException { - System.out.println("Logging bot in..."); - client = new ClientBuilder().withToken(TOKEN).build(); - client.getDispatcher().registerListener(new HandieBot()); - client.login(); - } - - private final AudioPlayerManager playerManager; - private final Map musicManagers; private MusicPlayer musicPlayer; private HandieBot() { - this.musicManagers = new HashMap<>(); - this.playerManager = new DefaultAudioPlayerManager(); - AudioSourceManagers.registerRemoteSources(playerManager); - AudioSourceManagers.registerLocalSource(playerManager); - this.commandHandler = new CommandHandler(this); this.musicPlayer = new MusicPlayer(); } @@ -58,5 +40,11 @@ public class HandieBot { this.commandHandler.handleCommand(event); } + public static void main(String[] args) throws DiscordException, RateLimitException { + System.out.println("Logging bot in."); + client = new ClientBuilder().withToken(TOKEN).build(); + client.getDispatcher().registerListener(new HandieBot()); + client.login(); + } } diff --git a/src/main/java/handiebot/command/CommandHandler.java b/src/main/java/handiebot/command/CommandHandler.java index cae19a9..d65643e 100644 --- a/src/main/java/handiebot/command/CommandHandler.java +++ b/src/main/java/handiebot/command/CommandHandler.java @@ -2,6 +2,7 @@ package handiebot.command; import com.sun.istack.internal.NotNull; import handiebot.HandieBot; +import handiebot.utils.DisappearingMessage; import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; import sx.blah.discord.handle.obj.*; import sx.blah.discord.util.EmbedBuilder; @@ -34,16 +35,24 @@ public class CommandHandler { String command = extractCommand(message); String[] args = extractArgs(message); if (guild != null && command != null){ + DisappearingMessage.deleteMessageAfter(2000, message); if (command.equals("play") && args.length == 1){ + //Play or queue a song. this.bot.getMusicPlayer().loadToQueue(guild, args[0]); } else if (command.equals("skip") && args.length == 0){ + //Skip the current song. this.bot.getMusicPlayer().skipTrack(guild); } else if (command.equals("help")){ - this.sendHelpInfo(user); + //Send a PM to the user with help info. + this.sendHelpInfo(user);//TODO finish the help command and fill in with new descriptions each time. } else if (command.equals("queue") && args.length == 0){ + //Display the first few items of the queue. this.bot.getMusicPlayer().showQueueList(guild); } else if (command.equals("repeat")){ - this.bot.getMusicPlayer().toggleRepeat(guild); + //Toggle repeat. + //TODO implement repeat command. + } else if (command.equals("clear")){ + //TODO clear command. } } } diff --git a/src/main/java/handiebot/lavaplayer/AudioLoadResultHandler.java b/src/main/java/handiebot/lavaplayer/AudioLoadResultHandler.java deleted file mode 100644 index a07d21d..0000000 --- a/src/main/java/handiebot/lavaplayer/AudioLoadResultHandler.java +++ /dev/null @@ -1,40 +0,0 @@ -package handiebot.lavaplayer; - -import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; -import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; -import com.sedmelluq.discord.lavaplayer.track.AudioTrack; - -/** - * @author Andrew Lalis - */ -public class AudioLoadResultHandler implements com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler { - - private TrackScheduler scheduler; - - public AudioLoadResultHandler(TrackScheduler scheduler){ - this.scheduler = scheduler; - } - - @Override - public void trackLoaded(AudioTrack audioTrack) { - System.out.println("Adding to queue "+ audioTrack.getInfo().title); - scheduler.queue(audioTrack); - } - - @Override - public void playlistLoaded(AudioPlaylist audioPlaylist) { - System.out.println("Adding playlist to queue."); - audioPlaylist.getTracks().forEach(track -> this.scheduler.queue(track)); - } - - @Override - public void noMatches() { - System.out.println("No matches!"); - } - - @Override - public void loadFailed(FriendlyException e) { - System.out.println("Load failed."); - e.printStackTrace(); - } -} diff --git a/src/main/java/handiebot/lavaplayer/GuildMusicManager.java b/src/main/java/handiebot/lavaplayer/GuildMusicManager.java index 06af150..54f8268 100644 --- a/src/main/java/handiebot/lavaplayer/GuildMusicManager.java +++ b/src/main/java/handiebot/lavaplayer/GuildMusicManager.java @@ -2,6 +2,7 @@ package handiebot.lavaplayer; import com.sedmelluq.discord.lavaplayer.player.AudioPlayer; import com.sedmelluq.discord.lavaplayer.player.AudioPlayerManager; +import sx.blah.discord.handle.obj.IGuild; /** * @author Andrew Lalis @@ -13,9 +14,9 @@ public class GuildMusicManager { public final TrackScheduler scheduler; - public GuildMusicManager(AudioPlayerManager manager){ + public GuildMusicManager(AudioPlayerManager manager, IGuild guild){ this.player = manager.createPlayer(); - this.scheduler = new TrackScheduler(this.player); + 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 039da59..088244e 100644 --- a/src/main/java/handiebot/lavaplayer/MusicPlayer.java +++ b/src/main/java/handiebot/lavaplayer/MusicPlayer.java @@ -8,14 +8,17 @@ 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.utils.DisappearingMessage; 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.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; /** * @author Andrew Lalis @@ -24,37 +27,94 @@ import java.util.Map; */ public class MusicPlayer { - private static String CHANNEL_NAME = "music"; + //Name for the message and voice channels dedicated to this bot. + public static String CHANNEL_NAME = "HandieBotMusic"; private final AudioPlayerManager playerManager; - private final Map musicManagers; + + /* + Mappings of music managers, channels and voice channels for each guild. + */ + private Map musicManagers; + private Map chatChannels; + private Map voiceChannels; public MusicPlayer(){ - this.musicManagers = new HashMap<>(); + //Initialize player manager. this.playerManager = new DefaultAudioPlayerManager(); AudioSourceManagers.registerLocalSource(playerManager); AudioSourceManagers.registerRemoteSources(playerManager); + + //Initialize all maps. + this.musicManagers = new HashMap<>(); + this.chatChannels = new HashMap<>(); + this.voiceChannels = new HashMap<>(); } /** - * Toggles the playlist's repeating. - * @param guild The guild to perform the action on. + * Gets the music manager specific to a particular guild. + * @param guild The guild to get the music manager for. + * @return The music manager for a guild. */ - public void toggleRepeat(IGuild guild){ - GuildMusicManager musicManager = this.getGuildMusicManager(guild); - musicManager.scheduler.setRepeat(!musicManager.scheduler.isRepeating()); - this.getMessageChannel(guild).sendMessage("**Repeat** is now *"+(musicManager.scheduler.isRepeating() ? "On" : "Off")+"*."); + private GuildMusicManager getMusicManager(IGuild guild){ + if (!this.musicManagers.containsKey(guild)){ + System.out.println("Registering guild, creating audio provider."); + this.musicManagers.put(guild, new GuildMusicManager(this.playerManager, guild)); + guild.getAudioManager().setAudioProvider(this.musicManagers.get(guild).getAudioProvider()); + } + return this.musicManagers.get(guild); + } + + /** + * Gets the chat channel specific to a particular guild. This channel is used send updates about playback and + * responses to people's commands. If none exists, the bot will attempt to make a channel. + * @param guild The guild to get the channel from. + * @return A message channel on a particular guild that is specifically for music. + */ + private IChannel getChatChannel(IGuild guild){ + if (!this.chatChannels.containsKey(guild)){ + List channels = guild.getChannelsByName(CHANNEL_NAME.toLowerCase()); + if (channels.isEmpty()){ + System.out.println("Found "+channels.size()+" matches for message channel, creating new one."); + this.chatChannels.put(guild, guild.createChannel(CHANNEL_NAME.toLowerCase())); + } else { + this.chatChannels.put(guild, channels.get(0)); + } + } + return this.chatChannels.get(guild); + } + + /** + * Gets the voice channel associated with a particular guild. This channel is used for audio playback. If none + * exists, the bot will attempt to make a voice channel. + * @param guild The guild to get the channel from. + * @return The voice channel on a guild that is for this bot. + */ + private IVoiceChannel getVoiceChannel(IGuild guild){ + if (!this.voiceChannels.containsKey(guild)){ + List channels = guild.getVoiceChannelsByName(CHANNEL_NAME); + if (channels.isEmpty()){ + System.out.println("Found "+channels.size()+" matches for voice channel, creating new one."); + this.voiceChannels.put(guild, guild.createVoiceChannel(CHANNEL_NAME)); + } else { + this.voiceChannels.put(guild, channels.get(0)); + } + } + IVoiceChannel vc = this.voiceChannels.get(guild); + if (!vc.isConnected()){ + System.out.println("Joined voice channel."); + vc.join(); + } + return vc; } /** * Sends a formatted message to the guild about the first few items in a queue. - * @param guild The guild to show the queue for. */ public void showQueueList(IGuild guild){ - GuildMusicManager musicManager = this.getGuildMusicManager(guild); - List tracks = musicManager.scheduler.queueList(); + List tracks = getMusicManager(guild).scheduler.queueList(); if (tracks.size() == 0) { - this.getMessageChannel(guild).sendMessage("The queue is empty. Use **"+ CommandHandler.PREFIX+"play** *URL* to add songs."); + new DisappearingMessage(getChatChannel(guild), "The queue is empty. Use **"+ CommandHandler.PREFIX+"play** *URL* to add songs.", 3000); } else { EmbedBuilder builder = new EmbedBuilder(); builder.withColor(255, 0, 0); @@ -74,22 +134,22 @@ public class MusicPlayer { sb.append(seconds % 60); sb.append("]\n"); } - builder.appendField("Showing " + (tracks.size() <= 10 ? tracks.size() : "the first 10") + " tracks.", sb.toString(), false); - this.getMessageChannel(guild).sendMessage(builder.build()); + builder.withTimestamp(System.currentTimeMillis()); + builder.appendField("Showing " + (tracks.size() <= 10 ? tracks.size() : "the first 10") + " track"+(tracks.size() > 1 ? "s" : "")+".", sb.toString(), false); + getChatChannel(guild).sendMessage(builder.build()); } } /** * Loads a URL to the queue, or outputs an error message if it fails. - * @param guild The guild to load the URL to. * @param trackURL A string representing a youtube/soundcloud URL. */ public void loadToQueue(IGuild guild, String trackURL){ - GuildMusicManager musicManager = this.getGuildMusicManager(guild); - this.playerManager.loadItemOrdered(musicManager, trackURL, new AudioLoadResultHandler() { + this.playerManager.loadItemOrdered(getMusicManager(guild), trackURL, new AudioLoadResultHandler() { @Override public void trackLoaded(AudioTrack audioTrack) { - addToQueue(guild, musicManager, audioTrack); + System.out.println("Track successfully loaded: "+audioTrack.getInfo().title); + addToQueue(guild, audioTrack); } @Override @@ -99,92 +159,59 @@ public class MusicPlayer { if (firstTrack == null){ firstTrack = audioPlaylist.getTracks().get(0); } - addToQueue(guild, musicManager,firstTrack); + addToQueue(guild, firstTrack); } } @Override public void noMatches() { - getMessageChannel(guild).sendMessage("Unable to find a result for: "+trackURL); + new DisappearingMessage(getChatChannel(guild), "Unable to find a result for: "+trackURL, 3000); } @Override public void loadFailed(FriendlyException e) { - getMessageChannel(guild).sendMessage("Unable to load. "+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 guild The guild to queue the track in. - * @param musicManager The music manager to use. * @param track The track to queue. */ - public void addToQueue(IGuild guild, GuildMusicManager musicManager, AudioTrack track){ - IVoiceChannel voiceChannel = this.connectToMusicChannel(guild); + public void addToQueue(IGuild guild, AudioTrack track){ + IVoiceChannel voiceChannel = getVoiceChannel(guild); if (voiceChannel != null){ - musicManager.scheduler.queue(track); - IChannel channel = this.getMessageChannel(guild); - channel.sendMessage("Added **"+track.getInfo().title+"** to the queue."); + //Check to make sure sound can be played. + if (guild.getAudioManager().getAudioProvider() == null){ + new DisappearingMessage(getChatChannel(guild), "Audio provider not set. Please try again.", 3000); + guild.getAudioManager().setAudioProvider(getMusicManager(guild).getAudioProvider()); + return; + } + long timeUntilPlay = getMusicManager(guild).scheduler.getTimeUntilDone(); + getMusicManager(guild).scheduler.queue(track); + //Build message. + StringBuilder sb = new StringBuilder(); + sb.append("Added **").append(track.getInfo().title).append("** to the queue."); + //If there's some tracks in the queue, get the time until this one plays. + if (timeUntilPlay != 0){ + sb.append(String.format("\nTime until play: %d min, %d sec", + TimeUnit.MILLISECONDS.toMinutes(timeUntilPlay), + TimeUnit.MILLISECONDS.toSeconds(timeUntilPlay) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeUntilPlay)) + )); + } + IMessage message = getChatChannel(guild).sendMessage(sb.toString()); + DisappearingMessage.deleteMessageAfter(3000, message); } } /** * Skips the current track. - * @param guild The guild to perform the skip on. */ public void skipTrack(IGuild guild){ - this.getGuildMusicManager(guild).scheduler.nextTrack(); - this.getMessageChannel(guild).sendMessage("Skipping the current track."); - } - - /** - * Gets or creates a music manager for a specific guild. - * @param guild The guild to get a manager for. - * @return A Music Manager for the guild. - */ - private synchronized GuildMusicManager getGuildMusicManager(IGuild guild){ - long guildId = Long.parseLong(guild.getStringID()); - GuildMusicManager musicManager = this.musicManagers.get(guildId); - if (musicManager == null){ - musicManager = new GuildMusicManager(this.playerManager); - musicManagers.put(guildId, musicManager); - } - guild.getAudioManager().setAudioProvider(musicManager.getAudioProvider()); - return musicManager; - } - - /** - * Searches for and attempts to connect to a channel called 'music'. - * @param guild the guild to get voice channels from. - * @return The voice channel the bot is now connected to. - */ - private IVoiceChannel connectToMusicChannel(IGuild guild){ - List voiceChannels = guild.getVoiceChannelsByName(CHANNEL_NAME); - if (voiceChannels.size() == 1){ - if (!voiceChannels.get(0).isConnected()) - voiceChannels.get(0).join(); - return voiceChannels.get(0); - } - IVoiceChannel voiceChannel = guild.createVoiceChannel(CHANNEL_NAME); - voiceChannel.join(); - return voiceChannel; - } - - /** - * Returns a 'music' message channel where the bot can post info on playing songs, user requests, - * etc. - * @param guild The guild to get channels from. - * @return The channel with that name. - */ - private IChannel getMessageChannel(IGuild guild){ - List channels = guild.getChannelsByName(CHANNEL_NAME); - if (channels.size() == 1){ - return channels.get(0); - } - return guild.createChannel(CHANNEL_NAME); + getMusicManager(guild).scheduler.nextTrack(); + new DisappearingMessage(getChatChannel(guild), "Skipping the current track.", 3000); } } diff --git a/src/main/java/handiebot/lavaplayer/TrackScheduler.java b/src/main/java/handiebot/lavaplayer/TrackScheduler.java index 18fff1a..863d43c 100644 --- a/src/main/java/handiebot/lavaplayer/TrackScheduler.java +++ b/src/main/java/handiebot/lavaplayer/TrackScheduler.java @@ -5,6 +5,8 @@ import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; +import sx.blah.discord.handle.obj.IChannel; +import sx.blah.discord.handle.obj.IGuild; import java.util.ArrayList; import java.util.List; @@ -18,15 +20,18 @@ public class TrackScheduler extends AudioEventAdapter { private final AudioPlayer player; private final BlockingQueue queue; + private boolean repeat = false; - private AudioTrack currentTrack = null; + + private IGuild guild; /** * Constructs a new track scheduler with the given player. * @param player The audio player this scheduler uses. */ - public TrackScheduler(AudioPlayer player){ + public TrackScheduler(AudioPlayer player, IGuild guild){ this.player = player; + this.guild = guild; this.queue = new LinkedBlockingQueue<>(); } @@ -46,13 +51,38 @@ public class TrackScheduler extends AudioEventAdapter { return this.repeat; } + /** + * Returns the time until the bot is done playing sound, at the current rate. + * @return The milliseconds until music stops. + */ + public long getTimeUntilDone(){ + long t = 0; + AudioTrack currentTrack = this.player.getPlayingTrack(); + if (currentTrack != null){ + t += currentTrack.getDuration() - currentTrack.getPosition(); + } + for (AudioTrack track : this.queueList()){ + t += track.getDuration(); + } + return t; + } + + /** + * Returns a list of tracks in the queue. + * @return A list of tracks in the queue. + */ + public List queueList(){ + return new ArrayList<>(this.queue); + } + /** * 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){ - if (!player.startTrack(track, true)){ - System.out.println("Unable to start track immediately, adding to queue."); + if (player.getPlayingTrack() == null){ + player.startTrack(track, false); + } else { queue.offer(track); } } @@ -63,28 +93,28 @@ public class TrackScheduler extends AudioEventAdapter { public void nextTrack(){ AudioTrack track = queue.poll(); player.startTrack(track, false); - this.currentTrack = track; if (this.repeat){ this.queue.add(track); } } @Override - public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) { - if (endReason.mayStartNext){ - nextTrack(); - } else { - this.currentTrack = null; - System.out.println(endReason.toString()); + public void onTrackStart(AudioPlayer player, AudioTrack track) { + System.out.println("Started audio track: "+track.getInfo().title); + List channels = this.guild.getChannelsByName(MusicPlayer.CHANNEL_NAME.toLowerCase()); + if (channels.size() > 0){ + channels.get(0).sendMessage("Now playing: **"+track.getInfo().title+"**."); } } - /** - * Returns a list of tracks in the queue. - * @return A list of tracks in the queue. - */ - public List queueList(){ - return new ArrayList<>(this.queue); + @Override + public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) { + System.out.println("Track ended."); + if (endReason.mayStartNext){ + nextTrack(); + } else { + System.out.println(endReason.toString()); + } } @Override @@ -96,4 +126,5 @@ public class TrackScheduler extends AudioEventAdapter { public void onTrackStuck(AudioPlayer player, AudioTrack track, long thresholdMs) { super.onTrackStuck(player, track, thresholdMs); } + } diff --git a/src/main/java/handiebot/utils/DisappearingMessage.java b/src/main/java/handiebot/utils/DisappearingMessage.java new file mode 100644 index 0000000..caf5382 --- /dev/null +++ b/src/main/java/handiebot/utils/DisappearingMessage.java @@ -0,0 +1,44 @@ +package handiebot.utils; + +import sx.blah.discord.handle.obj.IChannel; +import sx.blah.discord.handle.obj.IMessage; + +/** + * @author Andrew Lalis + * Creates a message on a channel that will disappear after some time. + */ +public class DisappearingMessage extends Thread implements Runnable { + + /** + * Creates a new disappearing message that times out after some time. + * @param channel The channel to write the message in. + * @param message The message content. + * @param timeout How long until the message is deleted. + */ + public DisappearingMessage(IChannel channel, String message, long timeout){ + IMessage sentMessage = channel.sendMessage(message); + try { + sleep(timeout); + } catch (InterruptedException e) { + e.printStackTrace(); + } + sentMessage.delete(); + } + + /** + * Deletes a message after a set amount of time. + * @param timeout The delay until deletion, in milliseconds. + * @param message The message to delete. + */ + public static void deleteMessageAfter(long timeout, IMessage message){ + new Thread(() -> { + try { + sleep(timeout); + message.delete(); + } catch (InterruptedException e){ + e.printStackTrace(); + } + }).start(); + } + +} From 3bc2404b44951bef541a2a312b607c48792622c8 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Sun, 18 Jun 2017 23:53:09 +0200 Subject: [PATCH 04/10] Added play documentation to readme. --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b5d65c6..480d668 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,11 @@ # HandieBot -Discord Bot for music and other utilities, built with Discord4J and Lavaplayer. + +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. + +## Commands + +### `play ` + +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. From 4f97ce6e8768c0dba3e21b2e22425a07825408b0 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Tue, 20 Jun 2017 15:18:37 +0200 Subject: [PATCH 05/10] Stabilized playback, added playlist class, among other improvements. --- src/main/java/handiebot/HandieBot.java | 2 +- .../handiebot/command/CommandHandler.java | 16 +- .../handiebot/lavaplayer/MusicPlayer.java | 40 ++-- .../java/handiebot/lavaplayer/Playlist.java | 189 ++++++++++++++++++ .../handiebot/lavaplayer/TrackScheduler.java | 50 +++-- 5 files changed, 265 insertions(+), 32 deletions(-) create mode 100644 src/main/java/handiebot/lavaplayer/Playlist.java diff --git a/src/main/java/handiebot/HandieBot.java b/src/main/java/handiebot/HandieBot.java index e8f01f3..93a3210 100644 --- a/src/main/java/handiebot/HandieBot.java +++ b/src/main/java/handiebot/HandieBot.java @@ -21,7 +21,7 @@ public class HandieBot { private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY"; - private static IDiscordClient client; + public static IDiscordClient client; private CommandHandler commandHandler; private MusicPlayer musicPlayer; diff --git a/src/main/java/handiebot/command/CommandHandler.java b/src/main/java/handiebot/command/CommandHandler.java index d65643e..88d9a9d 100644 --- a/src/main/java/handiebot/command/CommandHandler.java +++ b/src/main/java/handiebot/command/CommandHandler.java @@ -35,10 +35,14 @@ public class CommandHandler { String command = extractCommand(message); String[] args = extractArgs(message); if (guild != null && command != null){ - DisappearingMessage.deleteMessageAfter(2000, message); - if (command.equals("play") && args.length == 1){ + DisappearingMessage.deleteMessageAfter(1000, message); + if (command.equals("play")){ //Play or queue a song. - this.bot.getMusicPlayer().loadToQueue(guild, args[0]); + if (args.length == 1) { + this.bot.getMusicPlayer().loadToQueue(guild, args[0]); + } else if (args.length == 0){ + this.bot.getMusicPlayer().playQueue(guild); + } } else if (command.equals("skip") && args.length == 0){ //Skip the current song. this.bot.getMusicPlayer().skipTrack(guild); @@ -53,6 +57,12 @@ public class CommandHandler { //TODO implement repeat command. } else if (command.equals("clear")){ //TODO clear command. + } else if (command.equals("quit")){ + //Quit the application. + channel.sendMessage("Quitting HandieBot functions."); + this.bot.getMusicPlayer().quit(guild); + } else if (command.equals("playlist")){ + //Do playlist actions. } } } diff --git a/src/main/java/handiebot/lavaplayer/MusicPlayer.java b/src/main/java/handiebot/lavaplayer/MusicPlayer.java index 088244e..04071d1 100644 --- a/src/main/java/handiebot/lavaplayer/MusicPlayer.java +++ b/src/main/java/handiebot/lavaplayer/MusicPlayer.java @@ -100,12 +100,7 @@ public class MusicPlayer { this.voiceChannels.put(guild, channels.get(0)); } } - IVoiceChannel vc = this.voiceChannels.get(guild); - if (!vc.isConnected()){ - System.out.println("Joined voice channel."); - vc.join(); - } - return vc; + return this.voiceChannels.get(guild); } /** @@ -136,7 +131,8 @@ public class MusicPlayer { } builder.withTimestamp(System.currentTimeMillis()); builder.appendField("Showing " + (tracks.size() <= 10 ? tracks.size() : "the first 10") + " track"+(tracks.size() > 1 ? "s" : "")+".", sb.toString(), false); - getChatChannel(guild).sendMessage(builder.build()); + IMessage message = getChatChannel(guild).sendMessage(builder.build()); + DisappearingMessage.deleteMessageAfter(6000, message); } } @@ -182,11 +178,8 @@ public class MusicPlayer { public void addToQueue(IGuild guild, AudioTrack track){ IVoiceChannel voiceChannel = getVoiceChannel(guild); if (voiceChannel != null){ - //Check to make sure sound can be played. - if (guild.getAudioManager().getAudioProvider() == null){ - new DisappearingMessage(getChatChannel(guild), "Audio provider not set. Please try again.", 3000); - guild.getAudioManager().setAudioProvider(getMusicManager(guild).getAudioProvider()); - return; + if (!voiceChannel.isConnected()) { + voiceChannel.join(); } long timeUntilPlay = getMusicManager(guild).scheduler.getTimeUntilDone(); getMusicManager(guild).scheduler.queue(track); @@ -206,6 +199,17 @@ public class MusicPlayer { } + /** + * If possible, try to begin playing from the track scheduler's queue. + */ + public void playQueue(IGuild guild){ + IVoiceChannel vc = this.getVoiceChannel(guild); + if (!vc.isConnected()){ + vc.join(); + } + getMusicManager(guild).scheduler.nextTrack(); + } + /** * Skips the current track. */ @@ -214,4 +218,16 @@ public class MusicPlayer { new DisappearingMessage(getChatChannel(guild), "Skipping the current track.", 3000); } + /** + * Stops playback and disconnects from the voice channel, to cease music actions. + * @param guild The guild to quit from. + */ + public void quit(IGuild guild){ + getMusicManager(guild).scheduler.quit(); + IVoiceChannel vc = this.getVoiceChannel(guild); + if (vc.isConnected()){ + vc.leave(); + } + } + } diff --git a/src/main/java/handiebot/lavaplayer/Playlist.java b/src/main/java/handiebot/lavaplayer/Playlist.java new file mode 100644 index 0000000..abb6c08 --- /dev/null +++ b/src/main/java/handiebot/lavaplayer/Playlist.java @@ -0,0 +1,189 @@ +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.tools.FriendlyException; +import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; +import com.sedmelluq.discord.lavaplayer.track.AudioTrack; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * @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. + */ +public class Playlist { + + private String name; + private long creatorUID; + + List tracks; + + /** + * Creates an empty playlist template. + * @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(); + } + + public String getName(){ + return this.name; + } + + public long getCreatorUID(){ + return this.creatorUID; + } + + 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){ + this.tracks.add(track); + } + + /** + * Removes a track from the playlist. + * @param track The track to remove. + */ + public void removeTrack(AudioTrack track){ + this.tracks.remove(track); + } + + /** + * 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){ + 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 + */ + public AudioTrack getNextTrackAndRequeue(boolean shouldShuffle){ + 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 + * recent songs that should be ignored; for example, the most recent 20% of the playlist can be ignored. + * - A greater likelihood for numbers closer to 0 (those which have not been played in a while). + * @param listLength The number of items in a potential list to choose from. + * @return A pseudo-random choice as to which item to pick from the list. + */ + public static int getShuffledIndex(int listLength){ + float threshold = 0.2f; + int trueLength = listLength - (int)threshold*listLength; + Random rand = new Random(); + //TODO Add in a small gradient in chance for a song to be picked. + return rand.nextInt(trueLength); + } + + /** + * Saves the playlist to a file in its name. The playlists are saved into a file in the user's home directory. + */ + public void save(){ + String homeDir = System.getProperty("user.home"); + File playlistDir = new File(homeDir+"/.handiebot/playlist"); + if (!playlistDir.exists()){ + if (!playlistDir.mkdirs()){ + System.out.println("Unable to make directory: "+playlistDir.getPath()); + return; + } + } + File playlistFile = new File(playlistDir.getPath()+"/"+this.name.replace(" ", "_")+".txt"); + System.out.println("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); + writer.write('\n'); + } + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** + * Loads the playlist from a file with the playlist's name. + */ + public void load(){//TODO Make load work!!! + String path = System.getProperty("user.home")+"/.handiebot/playlist/"+this.name.replace(" ", "_")+".txt"; + 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.tracks = new ArrayList<>(trackCount); + AudioPlayerManager pm = new DefaultAudioPlayerManager(); + for (int i = 0; i < trackCount; i++){ + System.out.println("Loading item "+i); + String url = lines.remove(0); + pm.loadItem(url, new AudioLoadResultHandler() { + @Override + public void trackLoaded(AudioTrack audioTrack) { + System.out.println("Added track"); + tracks.add(audioTrack); + } + + @Override + public void playlistLoaded(AudioPlaylist audioPlaylist) { + System.out.println("Playlist loaded."); + //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. + } + }); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } + +} diff --git a/src/main/java/handiebot/lavaplayer/TrackScheduler.java b/src/main/java/handiebot/lavaplayer/TrackScheduler.java index 863d43c..1fb86e0 100644 --- a/src/main/java/handiebot/lavaplayer/TrackScheduler.java +++ b/src/main/java/handiebot/lavaplayer/TrackScheduler.java @@ -8,10 +8,7 @@ import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; import sx.blah.discord.handle.obj.IChannel; import sx.blah.discord.handle.obj.IGuild; -import java.util.ArrayList; import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; /** * @author Andrew Lalis @@ -19,9 +16,11 @@ import java.util.concurrent.LinkedBlockingQueue; public class TrackScheduler extends AudioEventAdapter { private final AudioPlayer player; - private final BlockingQueue queue; + + private Playlist activePlaylist; private boolean repeat = false; + private boolean shuffle = false; private IGuild guild; @@ -32,7 +31,8 @@ public class TrackScheduler extends AudioEventAdapter { public TrackScheduler(AudioPlayer player, IGuild guild){ this.player = player; this.guild = guild; - this.queue = new LinkedBlockingQueue<>(); + this.activePlaylist = new Playlist("HandieBot Active Playlist", 283652989212688384L); + //this.activePlaylist = new Playlist("HandieBot Active Playlist"); } /** @@ -51,6 +51,22 @@ public class TrackScheduler extends AudioEventAdapter { return this.repeat; } + /** + * Sets whether or not to randomize the next track to be played. + * @param value True if shuffled should become active. + */ + public void setShuffle(boolean value){ + this.shuffle = value; + } + + /** + * Returns whether or not shuffling is active. + * @return True if shuffling is active, false otherwise. + */ + public boolean isShuffling(){ + return this.shuffle; + } + /** * Returns the time until the bot is done playing sound, at the current rate. * @return The milliseconds until music stops. @@ -72,7 +88,7 @@ public class TrackScheduler extends AudioEventAdapter { * @return A list of tracks in the queue. */ public List queueList(){ - return new ArrayList<>(this.queue); + return this.activePlaylist.getTracks(); } /** @@ -83,7 +99,8 @@ public class TrackScheduler extends AudioEventAdapter { if (player.getPlayingTrack() == null){ player.startTrack(track, false); } else { - queue.offer(track); + this.activePlaylist.addTrack(track); + this.activePlaylist.save(); } } @@ -91,11 +108,16 @@ public class TrackScheduler extends AudioEventAdapter { * Starts the next track, stopping the current one if it's playing. */ public void nextTrack(){ - AudioTrack track = queue.poll(); + AudioTrack track = (this.repeat ? this.activePlaylist.getNextTrackAndRequeue(this.shuffle) : this.activePlaylist.getNextTrackAndRemove(this.shuffle)); + this.activePlaylist.save(); player.startTrack(track, false); - if (this.repeat){ - this.queue.add(track); - } + } + + /** + * If the user wishes to quit, stop the currently played track. + */ + public void quit(){ + this.player.stopTrack(); } @Override @@ -111,6 +133,7 @@ public class TrackScheduler extends AudioEventAdapter { public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) { System.out.println("Track ended."); if (endReason.mayStartNext){ + System.out.println("Moving to next track."); nextTrack(); } else { System.out.println(endReason.toString()); @@ -122,9 +145,4 @@ public class TrackScheduler extends AudioEventAdapter { exception.printStackTrace(); } - @Override - public void onTrackStuck(AudioPlayer player, AudioTrack track, long thresholdMs) { - super.onTrackStuck(player, track, thresholdMs); - } - } From 8cc95e4d6855f43d4fe97315a6cb8f683a2b8e26 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Tue, 20 Jun 2017 15:32:26 +0200 Subject: [PATCH 06/10] Added the framework for a console. --- .../handiebot/command/CommandHandler.java | 10 +++- src/main/java/handiebot/view/View.form | 49 +++++++++++++++++++ src/main/java/handiebot/view/View.java | 12 +++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/main/java/handiebot/view/View.form create mode 100644 src/main/java/handiebot/view/View.java diff --git a/src/main/java/handiebot/command/CommandHandler.java b/src/main/java/handiebot/command/CommandHandler.java index 88d9a9d..ad68b2f 100644 --- a/src/main/java/handiebot/command/CommandHandler.java +++ b/src/main/java/handiebot/command/CommandHandler.java @@ -63,6 +63,14 @@ public class CommandHandler { this.bot.getMusicPlayer().quit(guild); } else if (command.equals("playlist")){ //Do playlist actions. + //TODO perform actions! + } else if (command.equals("prefix") && args.length == 1){ + //Set the prefix to the first argument. + if (args[0].length() != 1){ + new DisappearingMessage(channel, "You may only set the prefix to 1 character. To do otherwise is simply foolish.", 3000); + } else { + setPrefix(args[0]); + } } } } @@ -121,7 +129,7 @@ public class CommandHandler { * Sets the prefix used to identify commands. * @param prefix The prefix appended to the beginning of commands. */ - public void setPrefix(String prefix){ + public static void setPrefix(String prefix){ PREFIX = prefix; } diff --git a/src/main/java/handiebot/view/View.form b/src/main/java/handiebot/view/View.form new file mode 100644 index 0000000..3fc93a6 --- /dev/null +++ b/src/main/java/handiebot/view/View.form @@ -0,0 +1,49 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/main/java/handiebot/view/View.java b/src/main/java/handiebot/view/View.java new file mode 100644 index 0000000..1644382 --- /dev/null +++ b/src/main/java/handiebot/view/View.java @@ -0,0 +1,12 @@ +package handiebot.view; + +import javax.swing.*; + +/** + * @author Andrew Lalis + */ +public class View { + private JPanel panel1; + private JTextArea outputArea; + private JTextField commandField; +} From 338294fa4fcc7412dffb6dba19d1be4883e569eb Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Wed, 21 Jun 2017 08:07:09 +0200 Subject: [PATCH 07/10] Added command line to the GUI, safeguards for quitting, and more! --- pom.xml | 4 +- src/main/java/handiebot/HandieBot.java | 37 +++++++--- .../handiebot/command/CommandHandler.java | 10 +-- .../handiebot/lavaplayer/MusicPlayer.java | 32 ++++++-- .../java/handiebot/lavaplayer/Playlist.java | 6 ++ .../handiebot/lavaplayer/TrackScheduler.java | 21 ++++-- src/main/java/handiebot/view/BotLog.java | 73 +++++++++++++++++++ src/main/java/handiebot/view/BotWindow.java | 37 ++++++++++ .../handiebot/view/CommandLineListener.java | 47 ++++++++++++ src/main/java/handiebot/view/MenuBar.java | 20 +++++ src/main/java/handiebot/view/View.form | 17 +++-- src/main/java/handiebot/view/View.java | 13 +++- .../handiebot/view/actions/ActionItem.java | 16 ++++ .../handiebot/view/actions/QuitAction.java | 17 +++++ 14 files changed, 313 insertions(+), 37 deletions(-) create mode 100644 src/main/java/handiebot/view/BotLog.java create mode 100644 src/main/java/handiebot/view/BotWindow.java create mode 100644 src/main/java/handiebot/view/CommandLineListener.java create mode 100644 src/main/java/handiebot/view/MenuBar.java create mode 100644 src/main/java/handiebot/view/actions/ActionItem.java create mode 100644 src/main/java/handiebot/view/actions/QuitAction.java diff --git a/pom.xml b/pom.xml index 9beb865..5ceabfe 100644 --- a/pom.xml +++ b/pom.xml @@ -13,8 +13,8 @@ org.apache.maven.plugins maven-compiler-plugin - 1.7 - 1.7 + 1.8 + 1.8 diff --git a/src/main/java/handiebot/HandieBot.java b/src/main/java/handiebot/HandieBot.java index 93a3210..9c06591 100644 --- a/src/main/java/handiebot/HandieBot.java +++ b/src/main/java/handiebot/HandieBot.java @@ -2,6 +2,9 @@ package handiebot; import handiebot.command.CommandHandler; import handiebot.lavaplayer.MusicPlayer; +import handiebot.view.BotLog; +import handiebot.view.BotWindow; +import handiebot.view.View; import sx.blah.discord.api.ClientBuilder; import sx.blah.discord.api.IDiscordClient; import sx.blah.discord.api.events.EventSubscriber; @@ -9,30 +12,26 @@ import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedE import sx.blah.discord.util.DiscordException; import sx.blah.discord.util.RateLimitException; -import java.util.logging.Logger; - /** * @author Andrew Lalis * Main Class for the discord bot. Contains client loading information and general event processing. */ public class HandieBot { - public static Logger log = Logger.getLogger("HandieBotLog"); - + public static final String APPLICATION_NAME = "HandieBot"; private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY"; public static IDiscordClient client; + public static View view; + public static BotWindow window; + public static BotLog log; private CommandHandler commandHandler; - private MusicPlayer musicPlayer; + public static MusicPlayer musicPlayer; private HandieBot() { this.commandHandler = new CommandHandler(this); - this.musicPlayer = new MusicPlayer(); - } - public MusicPlayer getMusicPlayer(){ - return this.musicPlayer; } @EventSubscriber @@ -41,10 +40,28 @@ public class HandieBot { } public static void main(String[] args) throws DiscordException, RateLimitException { - System.out.println("Logging bot in."); + + musicPlayer = new MusicPlayer(); + + view = new View(); + log = new BotLog(view.getOutputArea()); + window = new BotWindow(view); + + log.log(BotLog.TYPE.INFO, "Logging client in."); client = new ClientBuilder().withToken(TOKEN).build(); client.getDispatcher().registerListener(new HandieBot()); client.login(); + + } + + /** + * Safely shuts down the bot on all guilds. + */ + public static void quit(){ + musicPlayer.quitAll(); + client.logout(); + window.dispose(); + } } diff --git a/src/main/java/handiebot/command/CommandHandler.java b/src/main/java/handiebot/command/CommandHandler.java index ad68b2f..da1392e 100644 --- a/src/main/java/handiebot/command/CommandHandler.java +++ b/src/main/java/handiebot/command/CommandHandler.java @@ -39,19 +39,19 @@ public class CommandHandler { if (command.equals("play")){ //Play or queue a song. if (args.length == 1) { - this.bot.getMusicPlayer().loadToQueue(guild, args[0]); + this.bot.musicPlayer.loadToQueue(guild, args[0]); } else if (args.length == 0){ - this.bot.getMusicPlayer().playQueue(guild); + this.bot.musicPlayer.playQueue(guild); } } else if (command.equals("skip") && args.length == 0){ //Skip the current song. - this.bot.getMusicPlayer().skipTrack(guild); + this.bot.musicPlayer.skipTrack(guild); } else if (command.equals("help")){ //Send a PM to the user with help info. this.sendHelpInfo(user);//TODO finish the help command and fill in with new descriptions each time. } else if (command.equals("queue") && args.length == 0){ //Display the first few items of the queue. - this.bot.getMusicPlayer().showQueueList(guild); + this.bot.musicPlayer.showQueueList(guild); } else if (command.equals("repeat")){ //Toggle repeat. //TODO implement repeat command. @@ -60,7 +60,7 @@ public class CommandHandler { } else if (command.equals("quit")){ //Quit the application. channel.sendMessage("Quitting HandieBot functions."); - this.bot.getMusicPlayer().quit(guild); + this.bot.musicPlayer.quit(guild); } else if (command.equals("playlist")){ //Do playlist actions. //TODO perform actions! diff --git a/src/main/java/handiebot/lavaplayer/MusicPlayer.java b/src/main/java/handiebot/lavaplayer/MusicPlayer.java index 04071d1..364ba99 100644 --- a/src/main/java/handiebot/lavaplayer/MusicPlayer.java +++ b/src/main/java/handiebot/lavaplayer/MusicPlayer.java @@ -9,6 +9,7 @@ import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import handiebot.command.CommandHandler; import handiebot.utils.DisappearingMessage; +import handiebot.view.BotLog; import sx.blah.discord.handle.obj.IChannel; import sx.blah.discord.handle.obj.IGuild; import sx.blah.discord.handle.obj.IMessage; @@ -20,6 +21,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import static handiebot.HandieBot.log; + /** * @author Andrew Lalis * This class is a container for all the music related functions, and contains methods for easy playback and queue @@ -58,7 +61,7 @@ public class MusicPlayer { */ private GuildMusicManager getMusicManager(IGuild guild){ if (!this.musicManagers.containsKey(guild)){ - System.out.println("Registering guild, creating audio provider."); + log.log(BotLog.TYPE.MUSIC, "Creating new music manager and audio provider for guild: "+guild.getName()); this.musicManagers.put(guild, new GuildMusicManager(this.playerManager, guild)); guild.getAudioManager().setAudioProvider(this.musicManagers.get(guild).getAudioProvider()); } @@ -75,7 +78,7 @@ public class MusicPlayer { if (!this.chatChannels.containsKey(guild)){ List channels = guild.getChannelsByName(CHANNEL_NAME.toLowerCase()); if (channels.isEmpty()){ - System.out.println("Found "+channels.size()+" matches for message channel, creating new one."); + log.log(BotLog.TYPE.MUSIC, "No chat channel found, creating a new one."); this.chatChannels.put(guild, guild.createChannel(CHANNEL_NAME.toLowerCase())); } else { this.chatChannels.put(guild, channels.get(0)); @@ -94,7 +97,7 @@ public class MusicPlayer { if (!this.voiceChannels.containsKey(guild)){ List channels = guild.getVoiceChannelsByName(CHANNEL_NAME); if (channels.isEmpty()){ - System.out.println("Found "+channels.size()+" matches for voice channel, creating new one."); + log.log(BotLog.TYPE.MUSIC, "No voice channel found, creating a new one."); this.voiceChannels.put(guild, guild.createVoiceChannel(CHANNEL_NAME)); } else { this.voiceChannels.put(guild, channels.get(0)); @@ -144,7 +147,7 @@ public class MusicPlayer { this.playerManager.loadItemOrdered(getMusicManager(guild), trackURL, new AudioLoadResultHandler() { @Override public void trackLoaded(AudioTrack audioTrack) { - System.out.println("Track successfully loaded: "+audioTrack.getInfo().title); + log.log(BotLog.TYPE.MUSIC, "Track successfully loaded: "+audioTrack.getInfo().title); addToQueue(guild, audioTrack); } @@ -185,9 +188,11 @@ public class MusicPlayer { getMusicManager(guild).scheduler.queue(track); //Build message. StringBuilder sb = new StringBuilder(); - sb.append("Added **").append(track.getInfo().title).append("** to the queue."); + if (timeUntilPlay > 0) { + sb.append("Added **").append(track.getInfo().title).append("** to the queue."); + } //If there's some tracks in the queue, get the time until this one plays. - if (timeUntilPlay != 0){ + if (timeUntilPlay > 0){ sb.append(String.format("\nTime until play: %d min, %d sec", TimeUnit.MILLISECONDS.toMinutes(timeUntilPlay), TimeUnit.MILLISECONDS.toSeconds(timeUntilPlay) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(timeUntilPlay)) @@ -215,6 +220,7 @@ public class MusicPlayer { */ public void skipTrack(IGuild guild){ getMusicManager(guild).scheduler.nextTrack(); + log.log(BotLog.TYPE.MUSIC, "Skipping the current track. "); new DisappearingMessage(getChatChannel(guild), "Skipping the current track.", 3000); } @@ -230,4 +236,18 @@ public class MusicPlayer { } } + /** + * Performs the same functions as quit, but with every guild. + */ + public void quitAll(){ + this.musicManagers.forEach((guild, musicManager) -> { + musicManager.scheduler.quit(); + IVoiceChannel vc = this.getVoiceChannel(guild); + if (vc.isConnected()){ + vc.leave(); + } + }); + this.playerManager.shutdown(); + } + } diff --git a/src/main/java/handiebot/lavaplayer/Playlist.java b/src/main/java/handiebot/lavaplayer/Playlist.java index abb6c08..16c7fb1 100644 --- a/src/main/java/handiebot/lavaplayer/Playlist.java +++ b/src/main/java/handiebot/lavaplayer/Playlist.java @@ -79,6 +79,9 @@ public class Playlist { * @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)); } @@ -87,6 +90,9 @@ public class Playlist { * @return */ 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; diff --git a/src/main/java/handiebot/lavaplayer/TrackScheduler.java b/src/main/java/handiebot/lavaplayer/TrackScheduler.java index 1fb86e0..5fbddcc 100644 --- a/src/main/java/handiebot/lavaplayer/TrackScheduler.java +++ b/src/main/java/handiebot/lavaplayer/TrackScheduler.java @@ -5,11 +5,16 @@ import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; import com.sedmelluq.discord.lavaplayer.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason; +import handiebot.view.BotLog; 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.util.RequestBuffer; import java.util.List; +import static handiebot.HandieBot.log; + /** * @author Andrew Lalis */ @@ -100,7 +105,6 @@ public class TrackScheduler extends AudioEventAdapter { player.startTrack(track, false); } else { this.activePlaylist.addTrack(track); - this.activePlaylist.save(); } } @@ -109,8 +113,11 @@ public class TrackScheduler extends AudioEventAdapter { */ public void nextTrack(){ AudioTrack track = (this.repeat ? this.activePlaylist.getNextTrackAndRequeue(this.shuffle) : this.activePlaylist.getNextTrackAndRemove(this.shuffle)); - this.activePlaylist.save(); - player.startTrack(track, false); + if (track != null) { + player.startTrack(track, false); + } else { + player.stopTrack(); + } } /** @@ -118,14 +125,17 @@ public class TrackScheduler extends AudioEventAdapter { */ public void quit(){ this.player.stopTrack(); + this.player.destroy(); } @Override public void onTrackStart(AudioPlayer player, AudioTrack track) { - System.out.println("Started audio track: "+track.getInfo().title); + log.log(BotLog.TYPE.MUSIC, "Started audio track: "+track.getInfo().title); List channels = this.guild.getChannelsByName(MusicPlayer.CHANNEL_NAME.toLowerCase()); if (channels.size() > 0){ - channels.get(0).sendMessage("Now playing: **"+track.getInfo().title+"**."); + IMessage message = channels.get(0).sendMessage("Now playing: **"+track.getInfo().title+"**."); + RequestBuffer.request(() -> {message.addReaction(":thumbsup:");}).get(); + RequestBuffer.request(() -> {message.addReaction(":thumbsdown:");}); } } @@ -136,6 +146,7 @@ public class TrackScheduler extends AudioEventAdapter { System.out.println("Moving to next track."); nextTrack(); } else { + log.log(BotLog.TYPE.ERROR, "Unable to go to the next track. Reason: "+endReason.name()); System.out.println(endReason.toString()); } } diff --git a/src/main/java/handiebot/view/BotLog.java b/src/main/java/handiebot/view/BotLog.java new file mode 100644 index 0000000..c473b96 --- /dev/null +++ b/src/main/java/handiebot/view/BotLog.java @@ -0,0 +1,73 @@ +package handiebot.view; + +import javax.swing.*; +import javax.swing.text.BadLocationException; +import javax.swing.text.Style; +import javax.swing.text.StyleConstants; +import java.awt.*; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static handiebot.view.BotLog.TYPE.*; + +/** + * @author Andrew Lalis + */ +public class BotLog { + + public enum TYPE { + INFO, + MUSIC, + ERROR + } + + private Map logStyles; + private Style defaultStyle; + + private JTextPane outputArea; + + public BotLog(JTextPane outputArea){ + this.outputArea = outputArea; + initStyles(); + } + + /** + * Initialize the styles for the various log data. + */ + private void initStyles(){ + this.logStyles = new HashMap<>(); + //Define default style. + this.defaultStyle = this.outputArea.addStyle("LogStyle", null); + this.defaultStyle.addAttribute(StyleConstants.FontFamily, "Lucida Console"); + this.defaultStyle.addAttribute(StyleConstants.FontSize, 12); + //Define each type's color. + for (TYPE type : TYPE.values()) { + this.logStyles.put(type, outputArea.addStyle(type.name(), this.defaultStyle)); + } + this.logStyles.get(INFO).addAttribute(StyleConstants.Foreground, Color.blue); + this.logStyles.get(MUSIC).addAttribute(StyleConstants.Foreground, new Color(51, 175, 66)); + this.logStyles.get(ERROR).addAttribute(StyleConstants.Foreground, Color.red); + } + + /** + * Writes a string to the output window with the given tag and text. + * @param type The type of message to write. + * @param message The content of the message. + */ + public void log(TYPE type, String message){ + Date date = new Date(System.currentTimeMillis()); + DateFormat formatter = new SimpleDateFormat("HH:mm:ss:SSS"); + String dateFormatted = formatter.format(date); + try { + this.outputArea.getStyledDocument().insertString(this.outputArea.getStyledDocument().getLength(), dateFormatted, this.defaultStyle); + this.outputArea.getStyledDocument().insertString(this.outputArea.getStyledDocument().getLength(), '['+type.name()+"] ", this.logStyles.get(type)); + this.outputArea.getStyledDocument().insertString(this.outputArea.getStyledDocument().getLength(), message+'\n', this.defaultStyle); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/handiebot/view/BotWindow.java b/src/main/java/handiebot/view/BotWindow.java new file mode 100644 index 0000000..6aa1340 --- /dev/null +++ b/src/main/java/handiebot/view/BotWindow.java @@ -0,0 +1,37 @@ +package handiebot.view; + +import handiebot.HandieBot; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +/** + * @author Andrew Lalis + * This class inherits JFrame and simplifies the creation of a window. + */ +public class BotWindow extends JFrame { + + public BotWindow(View view){ + super(HandieBot.APPLICATION_NAME); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + addWindowListener(new WindowAdapter() { + @Override + public void windowClosing(WindowEvent e) { + if (JOptionPane.showConfirmDialog((JFrame) e.getSource(), "Are you sure you want to exit and shutdown the bot?", + "Confirm shutdown", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE) == JOptionPane.YES_OPTION){ + HandieBot.quit(); + } + } + }); + setContentPane(view.mainPanel); + setJMenuBar(new MenuBar()); + setPreferredSize(new Dimension(800, 600)); + pack(); + setVisible(true); + } + +} diff --git a/src/main/java/handiebot/view/CommandLineListener.java b/src/main/java/handiebot/view/CommandLineListener.java new file mode 100644 index 0000000..56fcc4c --- /dev/null +++ b/src/main/java/handiebot/view/CommandLineListener.java @@ -0,0 +1,47 @@ +package handiebot.view; + +import handiebot.view.actions.QuitAction; + +import javax.swing.*; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; + +/** + * Created by Andrew's Computer on 21-Jun-17. + */ +public class CommandLineListener implements KeyListener { + + @Override + public void keyTyped(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER){ + //user wishes to submit command. + JTextField commandLine = (JTextField) e.getSource(); + String[] words = commandLine.getText().trim().split(" "); + String command = words[0]; + String[] args = new String[words.length-1]; + for (int i = 1; i < words.length; i++) { + args[i-1] = words[i]; + } + executeCommand(command, args); + } + } + + @Override + public void keyReleased(KeyEvent e) { + } + + /** + * Executes a given command on the command line. + * @param command The first word typed, or the command itself. + * @param args The list of arguments for the command. + */ + private void executeCommand(String command, String[] args){ + if (command.equals("quit")){ + new QuitAction().actionPerformed(null); + } + } +} diff --git a/src/main/java/handiebot/view/MenuBar.java b/src/main/java/handiebot/view/MenuBar.java new file mode 100644 index 0000000..b212c59 --- /dev/null +++ b/src/main/java/handiebot/view/MenuBar.java @@ -0,0 +1,20 @@ +package handiebot.view; + +import handiebot.view.actions.ActionItem; +import handiebot.view.actions.QuitAction; + +import javax.swing.*; + +/** + * @author Andrew Lalis + * Custom menu bar to be added to the console control panel. + */ +public class MenuBar extends JMenuBar { + + public MenuBar(){ + JMenu fileMenu = new JMenu("File"); + fileMenu.add(new ActionItem("Quit", new QuitAction())); + this.add(fileMenu); + } + +} diff --git a/src/main/java/handiebot/view/View.form b/src/main/java/handiebot/view/View.form index 3fc93a6..2a4288f 100644 --- a/src/main/java/handiebot/view/View.form +++ b/src/main/java/handiebot/view/View.form @@ -1,6 +1,6 @@
- + @@ -12,16 +12,19 @@ - + + + - + - - - - + + + + + diff --git a/src/main/java/handiebot/view/View.java b/src/main/java/handiebot/view/View.java index 1644382..67580fe 100644 --- a/src/main/java/handiebot/view/View.java +++ b/src/main/java/handiebot/view/View.java @@ -6,7 +6,16 @@ import javax.swing.*; * @author Andrew Lalis */ public class View { - private JPanel panel1; - private JTextArea outputArea; + public JPanel mainPanel; + private JTextPane outputArea; private JTextField commandField; + + public View(){ + this.commandField.addKeyListener(new CommandLineListener()); + } + + public JTextPane getOutputArea(){ + return this.outputArea; + } + } diff --git a/src/main/java/handiebot/view/actions/ActionItem.java b/src/main/java/handiebot/view/actions/ActionItem.java new file mode 100644 index 0000000..1b82c84 --- /dev/null +++ b/src/main/java/handiebot/view/actions/ActionItem.java @@ -0,0 +1,16 @@ +package handiebot.view.actions; + +import javax.swing.*; +import java.awt.event.ActionListener; + +/** + * @author Andrew Lalis + */ +public class ActionItem extends JMenuItem { + + public ActionItem(String name, ActionListener listener){ + super(name); + this.addActionListener(listener); + } + +} diff --git a/src/main/java/handiebot/view/actions/QuitAction.java b/src/main/java/handiebot/view/actions/QuitAction.java new file mode 100644 index 0000000..267d522 --- /dev/null +++ b/src/main/java/handiebot/view/actions/QuitAction.java @@ -0,0 +1,17 @@ +package handiebot.view.actions; + +import handiebot.HandieBot; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +/** + * @author Andrew Lalis + */ +public class QuitAction implements ActionListener { + + @Override + public void actionPerformed(ActionEvent e) { + HandieBot.quit(); + } +} From c77540934e557844589db2ee45570a99c2b6fb8c Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Wed, 21 Jun 2017 10:36:10 +0200 Subject: [PATCH 08/10] Fixed loading a playlist from a file. --- src/main/java/handiebot/HandieBot.java | 7 +-- .../handiebot/command/CommandHandler.java | 12 ++--- .../lavaplayer/GuildMusicManager.java | 2 +- .../handiebot/lavaplayer/MusicPlayer.java | 30 ++++++++----- .../java/handiebot/lavaplayer/Playlist.java | 45 +++++++++++++------ .../handiebot/lavaplayer/TrackScheduler.java | 13 +++--- src/main/java/handiebot/view/BotLog.java | 37 +++++++++++++-- src/main/java/handiebot/view/View.form | 6 ++- 8 files changed, 107 insertions(+), 45 deletions(-) diff --git a/src/main/java/handiebot/HandieBot.java b/src/main/java/handiebot/HandieBot.java index 9c06591..ec09d16 100644 --- a/src/main/java/handiebot/HandieBot.java +++ b/src/main/java/handiebot/HandieBot.java @@ -21,9 +21,9 @@ public class HandieBot { public static final String APPLICATION_NAME = "HandieBot"; private static final String TOKEN = "MjgzNjUyOTg5MjEyNjg4Mzg0.C45A_Q.506b0G6my1FEFa7_YY39lxLBHUY"; - public static IDiscordClient client; + private static IDiscordClient client; public static View view; - public static BotWindow window; + private static BotWindow window; public static BotLog log; private CommandHandler commandHandler; @@ -58,10 +58,11 @@ public class HandieBot { * Safely shuts down the bot on all guilds. */ public static void quit(){ + log.log(BotLog.TYPE.INFO, "Shutting down the bot."); musicPlayer.quitAll(); client.logout(); window.dispose(); - + System.exit(0); } } diff --git a/src/main/java/handiebot/command/CommandHandler.java b/src/main/java/handiebot/command/CommandHandler.java index da1392e..1b2b0d2 100644 --- a/src/main/java/handiebot/command/CommandHandler.java +++ b/src/main/java/handiebot/command/CommandHandler.java @@ -39,28 +39,28 @@ public class CommandHandler { if (command.equals("play")){ //Play or queue a song. if (args.length == 1) { - this.bot.musicPlayer.loadToQueue(guild, args[0]); + HandieBot.musicPlayer.loadToQueue(guild, args[0]); } else if (args.length == 0){ - this.bot.musicPlayer.playQueue(guild); + HandieBot.musicPlayer.playQueue(guild); } } else if (command.equals("skip") && args.length == 0){ //Skip the current song. - this.bot.musicPlayer.skipTrack(guild); + HandieBot.musicPlayer.skipTrack(guild); } else if (command.equals("help")){ //Send a PM to the user with help info. this.sendHelpInfo(user);//TODO finish the help command and fill in with new descriptions each time. } else if (command.equals("queue") && args.length == 0){ //Display the first few items of the queue. - this.bot.musicPlayer.showQueueList(guild); + HandieBot.musicPlayer.showQueueList(guild); } else if (command.equals("repeat")){ //Toggle repeat. - //TODO implement repeat command. + HandieBot.musicPlayer.toggleRepeat(guild); } else if (command.equals("clear")){ //TODO clear command. } else if (command.equals("quit")){ //Quit the application. channel.sendMessage("Quitting HandieBot functions."); - this.bot.musicPlayer.quit(guild); + HandieBot.musicPlayer.quit(guild); } else if (command.equals("playlist")){ //Do playlist actions. //TODO perform actions! diff --git a/src/main/java/handiebot/lavaplayer/GuildMusicManager.java b/src/main/java/handiebot/lavaplayer/GuildMusicManager.java index 54f8268..bd5320f 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); + this.scheduler = new TrackScheduler(this.player, guild, manager); this.player.addListener(this.scheduler); } diff --git a/src/main/java/handiebot/lavaplayer/MusicPlayer.java b/src/main/java/handiebot/lavaplayer/MusicPlayer.java index 364ba99..e4ffb5d 100644 --- a/src/main/java/handiebot/lavaplayer/MusicPlayer.java +++ b/src/main/java/handiebot/lavaplayer/MusicPlayer.java @@ -61,7 +61,7 @@ public class MusicPlayer { */ private GuildMusicManager getMusicManager(IGuild guild){ if (!this.musicManagers.containsKey(guild)){ - log.log(BotLog.TYPE.MUSIC, "Creating new music manager and audio provider for guild: "+guild.getName()); + 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)); guild.getAudioManager().setAudioProvider(this.musicManagers.get(guild).getAudioProvider()); } @@ -78,7 +78,7 @@ public class MusicPlayer { if (!this.chatChannels.containsKey(guild)){ List channels = guild.getChannelsByName(CHANNEL_NAME.toLowerCase()); if (channels.isEmpty()){ - log.log(BotLog.TYPE.MUSIC, "No chat channel found, creating a new one."); + log.log(BotLog.TYPE.MUSIC, guild, "No chat channel found, creating a new one."); this.chatChannels.put(guild, guild.createChannel(CHANNEL_NAME.toLowerCase())); } else { this.chatChannels.put(guild, channels.get(0)); @@ -97,7 +97,7 @@ public class MusicPlayer { if (!this.voiceChannels.containsKey(guild)){ List channels = guild.getVoiceChannelsByName(CHANNEL_NAME); if (channels.isEmpty()){ - log.log(BotLog.TYPE.MUSIC, "No voice channel found, creating a new one."); + log.log(BotLog.TYPE.MUSIC, guild, "No voice channel found, creating a new one."); this.voiceChannels.put(guild, guild.createVoiceChannel(CHANNEL_NAME)); } else { this.voiceChannels.put(guild, channels.get(0)); @@ -106,6 +106,16 @@ public class MusicPlayer { return this.voiceChannels.get(guild); } + /** + * Toggles the repeating of songs for a particular guild. + * @param guild The guild to repeat for. + */ + public void toggleRepeat(IGuild guild){ + GuildMusicManager musicManager = this.getMusicManager(guild); + + musicManager.scheduler.setRepeat(!musicManager.scheduler.isRepeating()); + } + /** * Sends a formatted message to the guild about the first few items in a queue. */ @@ -124,13 +134,12 @@ public class MusicPlayer { sb.append(tracks.get(i).getInfo().title); sb.append("]("); sb.append(tracks.get(i).getInfo().uri); - sb.append(") ["); + sb.append(")"); int seconds = (int) (tracks.get(i).getInfo().length/1000); int minutes = seconds / 60; - sb.append(minutes); - sb.append(":"); - sb.append(seconds % 60); - sb.append("]\n"); + seconds = seconds % 60; + String time = String.format(" [%d:%02d]\n", minutes, seconds); + sb.append(time); } builder.withTimestamp(System.currentTimeMillis()); builder.appendField("Showing " + (tracks.size() <= 10 ? tracks.size() : "the first 10") + " track"+(tracks.size() > 1 ? "s" : "")+".", sb.toString(), false); @@ -147,7 +156,6 @@ public class MusicPlayer { this.playerManager.loadItemOrdered(getMusicManager(guild), trackURL, new AudioLoadResultHandler() { @Override public void trackLoaded(AudioTrack audioTrack) { - log.log(BotLog.TYPE.MUSIC, "Track successfully loaded: "+audioTrack.getInfo().title); addToQueue(guild, audioTrack); } @@ -164,11 +172,13 @@ public class MusicPlayer { @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); } }); @@ -220,7 +230,7 @@ public class MusicPlayer { */ public void skipTrack(IGuild guild){ getMusicManager(guild).scheduler.nextTrack(); - log.log(BotLog.TYPE.MUSIC, "Skipping the current track. "); + log.log(BotLog.TYPE.MUSIC, guild, "Skipping the current track. "); new DisappearingMessage(getChatChannel(guild), "Skipping the current track.", 3000); } diff --git a/src/main/java/handiebot/lavaplayer/Playlist.java b/src/main/java/handiebot/lavaplayer/Playlist.java index 16c7fb1..3d2a8f8 100644 --- a/src/main/java/handiebot/lavaplayer/Playlist.java +++ b/src/main/java/handiebot/lavaplayer/Playlist.java @@ -2,10 +2,10 @@ 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.tools.FriendlyException; import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist; import com.sedmelluq.discord.lavaplayer.track.AudioTrack; +import handiebot.view.BotLog; import java.io.*; import java.nio.file.Files; @@ -13,6 +13,9 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Random; +import java.util.concurrent.ExecutionException; + +import static handiebot.HandieBot.log; /** * @author Andrew Lalis @@ -24,7 +27,7 @@ public class Playlist { private String name; private long creatorUID; - List tracks; + private List tracks; /** * Creates an empty playlist template. @@ -41,9 +44,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){ + public Playlist(String name, AudioPlayerManager playerManager){ this.name = name; - this.load(); + this.load(playerManager); } public String getName(){ @@ -74,6 +77,15 @@ public class Playlist { 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. @@ -87,7 +99,7 @@ public class Playlist { /** * 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 + * @return The next track to be played. */ public AudioTrack getNextTrackAndRequeue(boolean shouldShuffle){ if (this.tracks.isEmpty()){ @@ -122,12 +134,12 @@ public class Playlist { File playlistDir = new File(homeDir+"/.handiebot/playlist"); if (!playlistDir.exists()){ if (!playlistDir.mkdirs()){ - System.out.println("Unable to make directory: "+playlistDir.getPath()); + log.log(BotLog.TYPE.ERROR, "Unable to make directory: "+playlistDir.getPath()); return; } } File playlistFile = new File(playlistDir.getPath()+"/"+this.name.replace(" ", "_")+".txt"); - System.out.println("Saving playlist to: "+playlistFile.getAbsolutePath()); + 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'); @@ -137,6 +149,7 @@ public class Playlist { writer.write('\n'); } } catch (FileNotFoundException e) { + log.log(BotLog.TYPE.ERROR, "Unable to find file to write playlist: "+this.name); e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); @@ -146,8 +159,9 @@ public class Playlist { /** * Loads the playlist from a file with the playlist's name. */ - public void load(){//TODO Make load work!!! + public void load(AudioPlayerManager playerManager){ 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); if (playlistFile.exists()){ try { @@ -156,20 +170,16 @@ public class Playlist { this.creatorUID = Long.parseLong(lines.remove(0)); int trackCount = Integer.parseInt(lines.remove(0)); this.tracks = new ArrayList<>(trackCount); - AudioPlayerManager pm = new DefaultAudioPlayerManager(); for (int i = 0; i < trackCount; i++){ - System.out.println("Loading item "+i); String url = lines.remove(0); - pm.loadItem(url, new AudioLoadResultHandler() { + playerManager.loadItem(url, new AudioLoadResultHandler() { @Override public void trackLoaded(AudioTrack audioTrack) { - System.out.println("Added track"); tracks.add(audioTrack); } @Override public void playlistLoaded(AudioPlaylist audioPlaylist) { - System.out.println("Playlist loaded."); //Do nothing. This should not happen. } @@ -184,9 +194,16 @@ public class Playlist { System.out.println("Load failed: "+e.getMessage()); //Do nothing. This should not happen. } - }); + }).get(); } } 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(); } } diff --git a/src/main/java/handiebot/lavaplayer/TrackScheduler.java b/src/main/java/handiebot/lavaplayer/TrackScheduler.java index 5fbddcc..6e42fdd 100644 --- a/src/main/java/handiebot/lavaplayer/TrackScheduler.java +++ b/src/main/java/handiebot/lavaplayer/TrackScheduler.java @@ -1,6 +1,7 @@ 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; @@ -24,7 +25,7 @@ public class TrackScheduler extends AudioEventAdapter { private Playlist activePlaylist; - private boolean repeat = false; + private boolean repeat = true; private boolean shuffle = false; private IGuild guild; @@ -33,11 +34,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){ + public TrackScheduler(AudioPlayer player, IGuild guild, AudioPlayerManager playerManager){ 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", 283652989212688384L); + this.activePlaylist = new Playlist("HandieBot Active Playlist", playerManager); } /** @@ -130,7 +131,7 @@ public class TrackScheduler extends AudioEventAdapter { @Override public void onTrackStart(AudioPlayer player, AudioTrack track) { - log.log(BotLog.TYPE.MUSIC, "Started audio track: "+track.getInfo().title); + log.log(BotLog.TYPE.MUSIC, this.guild, "Started audio track: "+track.getInfo().title); List channels = this.guild.getChannelsByName(MusicPlayer.CHANNEL_NAME.toLowerCase()); if (channels.size() > 0){ IMessage message = channels.get(0).sendMessage("Now playing: **"+track.getInfo().title+"**."); @@ -146,7 +147,7 @@ public class TrackScheduler extends AudioEventAdapter { System.out.println("Moving to next track."); nextTrack(); } else { - log.log(BotLog.TYPE.ERROR, "Unable to go to the next track. Reason: "+endReason.name()); + log.log(BotLog.TYPE.ERROR, this.guild, "Unable to go to the next track. Reason: "+endReason.name()); System.out.println(endReason.toString()); } } diff --git a/src/main/java/handiebot/view/BotLog.java b/src/main/java/handiebot/view/BotLog.java index c473b96..2e43db4 100644 --- a/src/main/java/handiebot/view/BotLog.java +++ b/src/main/java/handiebot/view/BotLog.java @@ -1,5 +1,7 @@ package handiebot.view; +import sx.blah.discord.handle.obj.IGuild; + import javax.swing.*; import javax.swing.text.BadLocationException; import javax.swing.text.Style; @@ -21,10 +23,19 @@ public class BotLog { public enum TYPE { INFO, MUSIC, - ERROR + ERROR, + COMMAND } + //Styles for output to the console. private Map logStyles; + private static Map logStyleColors = new HashMap(){{ + put(INFO, new Color(22, 63, 160)); + put(MUSIC, new Color(51, 175, 66)); + put(ERROR, new Color(255, 0, 0)); + put(COMMAND, new Color(255, 123, 0)); + }}; + private Style defaultStyle; private JTextPane outputArea; @@ -46,10 +57,8 @@ public class BotLog { //Define each type's color. for (TYPE type : TYPE.values()) { this.logStyles.put(type, outputArea.addStyle(type.name(), this.defaultStyle)); + this.logStyles.get(type).addAttribute(StyleConstants.Foreground, logStyleColors.get(type)); } - this.logStyles.get(INFO).addAttribute(StyleConstants.Foreground, Color.blue); - this.logStyles.get(MUSIC).addAttribute(StyleConstants.Foreground, new Color(51, 175, 66)); - this.logStyles.get(ERROR).addAttribute(StyleConstants.Foreground, Color.red); } /** @@ -70,4 +79,24 @@ public class BotLog { } } + /** + * Writes a string to the output window with the given tag, guild name, and text. + * @param type The type of message to write. + * @param guild The guild to get the name of. + * @param message The content of the message. + */ + public void log(TYPE type, IGuild guild, String message){ + Date date = new Date(System.currentTimeMillis()); + DateFormat formatter = new SimpleDateFormat("HH:mm:ss:SSS"); + String dateFormatted = formatter.format(date); + try { + this.outputArea.getStyledDocument().insertString(this.outputArea.getStyledDocument().getLength(), dateFormatted, this.defaultStyle); + this.outputArea.getStyledDocument().insertString(this.outputArea.getStyledDocument().getLength(), '['+type.name()+']', this.logStyles.get(type)); + this.outputArea.getStyledDocument().insertString(this.outputArea.getStyledDocument().getLength(), '['+guild.getName()+"] ", this.defaultStyle); + this.outputArea.getStyledDocument().insertString(this.outputArea.getStyledDocument().getLength(), message+'\n', this.defaultStyle); + } catch (BadLocationException e) { + e.printStackTrace(); + } + } + } diff --git a/src/main/java/handiebot/view/View.form b/src/main/java/handiebot/view/View.form index 2a4288f..41da417 100644 --- a/src/main/java/handiebot/view/View.form +++ b/src/main/java/handiebot/view/View.form @@ -43,7 +43,11 @@ - + + + + + From b9b6c6277f2c478f66b189f82a4c58f1ae142299 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Wed, 21 Jun 2017 12:48:10 +0200 Subject: [PATCH 09/10] Created actions for manipulation of music and other things. --- pom.xml | 9 ++ src/main/java/handiebot/HandieBot.java | 17 ++-- .../handiebot/command/CommandHandler.java | 47 +++++------ .../handiebot/lavaplayer/MusicPlayer.java | 83 +++++++++++-------- .../handiebot/lavaplayer/TrackScheduler.java | 24 ++++-- .../handiebot/view/CommandLineListener.java | 1 + .../handiebot/view/actions/QuitAction.java | 17 +++- .../view/actions/music/MusicAction.java | 18 ++++ .../view/actions/music/PlayAction.java | 32 +++++++ .../view/actions/music/QueueListAction.java | 24 ++++++ .../view/actions/music/SkipAction.java | 21 +++++ .../actions/music/ToggleRepeatAction.java | 21 +++++ 12 files changed, 241 insertions(+), 73 deletions(-) create mode 100644 src/main/java/handiebot/view/actions/music/MusicAction.java create mode 100644 src/main/java/handiebot/view/actions/music/PlayAction.java create mode 100644 src/main/java/handiebot/view/actions/music/QueueListAction.java create mode 100644 src/main/java/handiebot/view/actions/music/SkipAction.java create mode 100644 src/main/java/handiebot/view/actions/music/ToggleRepeatAction.java diff --git a/pom.xml b/pom.xml index 5ceabfe..21b6797 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,10 @@ jar + + 1.1.0 + + jcenter @@ -43,6 +47,11 @@ 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 ec09d16..b285951 100644 --- a/src/main/java/handiebot/HandieBot.java +++ b/src/main/java/handiebot/HandieBot.java @@ -8,6 +8,7 @@ import handiebot.view.View; import sx.blah.discord.api.ClientBuilder; import sx.blah.discord.api.IDiscordClient; import sx.blah.discord.api.events.EventSubscriber; +import sx.blah.discord.handle.impl.events.ReadyEvent; import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; import sx.blah.discord.util.DiscordException; import sx.blah.discord.util.RateLimitException; @@ -26,17 +27,17 @@ public class HandieBot { private static BotWindow window; public static BotLog log; - private CommandHandler commandHandler; + private static CommandHandler commandHandler; public static MusicPlayer musicPlayer; - private HandieBot() { - this.commandHandler = new CommandHandler(this); - - } - @EventSubscriber public void onMessageReceived(MessageReceivedEvent event) { - this.commandHandler.handleCommand(event); + commandHandler.handleCommand(event); + } + + @EventSubscriber + public void onReady(ReadyEvent event){ + log.log(BotLog.TYPE.INFO, "HandieBot initialized."); } public static void main(String[] args) throws DiscordException, RateLimitException { @@ -47,7 +48,7 @@ public class HandieBot { log = new BotLog(view.getOutputArea()); window = new BotWindow(view); - log.log(BotLog.TYPE.INFO, "Logging client in."); + log.log(BotLog.TYPE.INFO, "Logging client in..."); client = new ClientBuilder().withToken(TOKEN).build(); client.getDispatcher().registerListener(new HandieBot()); client.login(); diff --git a/src/main/java/handiebot/command/CommandHandler.java b/src/main/java/handiebot/command/CommandHandler.java index 1b2b0d2..642682a 100644 --- a/src/main/java/handiebot/command/CommandHandler.java +++ b/src/main/java/handiebot/command/CommandHandler.java @@ -1,14 +1,21 @@ package handiebot.command; import com.sun.istack.internal.NotNull; -import handiebot.HandieBot; 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; import java.awt.*; +import static handiebot.HandieBot.log; + /** * @author Andrew Lalis * Class to process commands. @@ -16,18 +23,11 @@ import java.awt.*; public class CommandHandler { public static String PREFIX = "!"; - - private final HandieBot bot; - - public CommandHandler(HandieBot bot){ - this.bot = bot; - } - /** * Main method to handle user messages. * @param event The event generated by the message. */ - public void handleCommand(MessageReceivedEvent event){ + public static void handleCommand(MessageReceivedEvent event){ IMessage message = event.getMessage(); IUser user = event.getAuthor(); IChannel channel = event.getChannel(); @@ -38,29 +38,24 @@ public class CommandHandler { DisappearingMessage.deleteMessageAfter(1000, message); if (command.equals("play")){ //Play or queue a song. - if (args.length == 1) { - HandieBot.musicPlayer.loadToQueue(guild, args[0]); - } else if (args.length == 0){ - HandieBot.musicPlayer.playQueue(guild); - } + new PlayAction(guild, args).actionPerformed(null); } else if (command.equals("skip") && args.length == 0){ //Skip the current song. - HandieBot.musicPlayer.skipTrack(guild); + new SkipAction(guild).actionPerformed(null); } else if (command.equals("help")){ //Send a PM to the user with help info. - this.sendHelpInfo(user);//TODO finish the help command and fill in with new descriptions each time. - } else if (command.equals("queue") && args.length == 0){ + sendHelpInfo(user);//TODO finish the help command and fill in with new descriptions each time. + } else if (command.equals("queue")){ //Display the first few items of the queue. - HandieBot.musicPlayer.showQueueList(guild); + new QueueListAction(guild, (args.length == 1) && args[0].equals("all")).actionPerformed(null); } else if (command.equals("repeat")){ //Toggle repeat. - HandieBot.musicPlayer.toggleRepeat(guild); + new ToggleRepeatAction(guild).actionPerformed(null); } else if (command.equals("clear")){ //TODO clear command. } else if (command.equals("quit")){ //Quit the application. - channel.sendMessage("Quitting HandieBot functions."); - HandieBot.musicPlayer.quit(guild); + new QuitAction(guild).actionPerformed(null); } else if (command.equals("playlist")){ //Do playlist actions. //TODO perform actions! @@ -69,8 +64,12 @@ public class CommandHandler { if (args[0].length() != 1){ new DisappearingMessage(channel, "You may only set the prefix to 1 character. To do otherwise is simply foolish.", 3000); } else { + new DisappearingMessage(channel, "Command prefix set to "+PREFIX, 10000); + log.log(BotLog.TYPE.INFO, guild, "Prefix set to "+PREFIX); setPrefix(args[0]); } + } else { + log.log(BotLog.TYPE.ERROR, guild, "Invalid command: "+command+" issued by "+user.getName()); } } } @@ -80,7 +79,7 @@ public class CommandHandler { * @param message The message to get a command from. * @return The command word, minus the prefix, or null. */ - private String extractCommand(IMessage message){ + private static String extractCommand(IMessage message){ String[] words = message.getContent().split(" "); if (words[0].startsWith(PREFIX)){ return words[0].replaceFirst(PREFIX, "").toLowerCase(); @@ -94,7 +93,7 @@ public class CommandHandler { * @return A list of strings representing args. */ @NotNull - private String[] extractArgs(IMessage message){ + private static String[] extractArgs(IMessage message){ String[] words = message.getContent().split(" "); if (words[0].startsWith(PREFIX)){ String[] args = new String[words.length-1]; @@ -110,7 +109,7 @@ public class CommandHandler { * Method to send a useful list of commands to any user if they desire. * @param user The user to send the message to. */ - private void sendHelpInfo(IUser user){ + private static void sendHelpInfo(IUser user){ IPrivateChannel pm = user.getOrCreatePMChannel(); EmbedBuilder builder = new EmbedBuilder(); diff --git a/src/main/java/handiebot/lavaplayer/MusicPlayer.java b/src/main/java/handiebot/lavaplayer/MusicPlayer.java index e4ffb5d..79296f6 100644 --- a/src/main/java/handiebot/lavaplayer/MusicPlayer.java +++ b/src/main/java/handiebot/lavaplayer/MusicPlayer.java @@ -31,7 +31,8 @@ import static handiebot.HandieBot.log; public class MusicPlayer { //Name for the message and voice channels dedicated to this bot. - public static String CHANNEL_NAME = "HandieBotMusic"; + static String CHANNEL_NAME = "HandieBotMusic"; + private static String PASTEBIN_KEY = "769adc01154922ece448cabd7a33b57c"; private final AudioPlayerManager playerManager; @@ -74,7 +75,7 @@ public class MusicPlayer { * @param guild The guild to get the channel from. * @return A message channel on a particular guild that is specifically for music. */ - private IChannel getChatChannel(IGuild guild){ + public IChannel getChatChannel(IGuild guild){ if (!this.chatChannels.containsKey(guild)){ List channels = guild.getChannelsByName(CHANNEL_NAME.toLowerCase()); if (channels.isEmpty()){ @@ -93,7 +94,7 @@ public class MusicPlayer { * @param guild The guild to get the channel from. * @return The voice channel on a guild that is for this bot. */ - private IVoiceChannel getVoiceChannel(IGuild guild){ + public IVoiceChannel getVoiceChannel(IGuild guild){ if (!this.voiceChannels.containsKey(guild)){ List channels = guild.getVoiceChannelsByName(CHANNEL_NAME); if (channels.isEmpty()){ @@ -119,32 +120,56 @@ public class MusicPlayer { /** * Sends a formatted message to the guild about the first few items in a queue. */ - public void showQueueList(IGuild guild){ + 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); } 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); + if (!showAll) { + 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); + } + 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); + } + //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); + */ } - 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); } } @@ -188,7 +213,7 @@ public class MusicPlayer { * Adds a track to the queue and sends a message to the appropriate channel notifying users. * @param track The track to queue. */ - public void addToQueue(IGuild guild, AudioTrack track){ + private void addToQueue(IGuild guild, AudioTrack track){ IVoiceChannel voiceChannel = getVoiceChannel(guild); if (voiceChannel != null){ if (!voiceChannel.isConnected()) { @@ -240,10 +265,6 @@ public class MusicPlayer { */ public void quit(IGuild guild){ getMusicManager(guild).scheduler.quit(); - IVoiceChannel vc = this.getVoiceChannel(guild); - if (vc.isConnected()){ - vc.leave(); - } } /** @@ -252,10 +273,6 @@ public class MusicPlayer { public void quitAll(){ this.musicManagers.forEach((guild, musicManager) -> { musicManager.scheduler.quit(); - IVoiceChannel vc = this.getVoiceChannel(guild); - if (vc.isConnected()){ - vc.leave(); - } }); this.playerManager.shutdown(); } diff --git a/src/main/java/handiebot/lavaplayer/TrackScheduler.java b/src/main/java/handiebot/lavaplayer/TrackScheduler.java index 6e42fdd..30d6d6b 100644 --- a/src/main/java/handiebot/lavaplayer/TrackScheduler.java +++ b/src/main/java/handiebot/lavaplayer/TrackScheduler.java @@ -6,10 +6,12 @@ import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter; 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.view.BotLog; 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.RequestBuffer; import java.util.List; @@ -113,11 +115,19 @@ public class TrackScheduler extends AudioEventAdapter { * Starts the next track, stopping the current one if it's playing. */ public void nextTrack(){ + AudioTrack currentTrack = this.player.getPlayingTrack(); + if (currentTrack != null){ + this.player.stopTrack(); + } AudioTrack track = (this.repeat ? this.activePlaylist.getNextTrackAndRequeue(this.shuffle) : this.activePlaylist.getNextTrackAndRemove(this.shuffle)); if (track != null) { + IVoiceChannel voiceChannel = HandieBot.musicPlayer.getVoiceChannel(this.guild); + if (!voiceChannel.isConnected()){ + voiceChannel.join(); + } player.startTrack(track, false); } else { - player.stopTrack(); + this.quit(); } } @@ -125,8 +135,11 @@ public class TrackScheduler extends AudioEventAdapter { * If the user wishes to quit, stop the currently played track. */ public void quit(){ + IVoiceChannel voiceChannel = HandieBot.musicPlayer.getVoiceChannel(this.guild); + if (voiceChannel.isConnected()){ + voiceChannel.leave(); + } this.player.stopTrack(); - this.player.destroy(); } @Override @@ -134,7 +147,7 @@ public class TrackScheduler extends AudioEventAdapter { log.log(BotLog.TYPE.MUSIC, this.guild, "Started audio track: "+track.getInfo().title); List channels = this.guild.getChannelsByName(MusicPlayer.CHANNEL_NAME.toLowerCase()); if (channels.size() > 0){ - IMessage message = channels.get(0).sendMessage("Now playing: **"+track.getInfo().title+"**."); + IMessage message = channels.get(0).sendMessage("Now playing: **"+track.getInfo().title+"**\n"+track.getInfo().uri); RequestBuffer.request(() -> {message.addReaction(":thumbsup:");}).get(); RequestBuffer.request(() -> {message.addReaction(":thumbsdown:");}); } @@ -142,13 +155,10 @@ public class TrackScheduler extends AudioEventAdapter { @Override public void onTrackEnd(AudioPlayer player, AudioTrack track, AudioTrackEndReason endReason) { - System.out.println("Track ended."); if (endReason.mayStartNext){ - System.out.println("Moving to next track."); nextTrack(); } else { - log.log(BotLog.TYPE.ERROR, this.guild, "Unable to go to the next track. Reason: "+endReason.name()); - System.out.println(endReason.toString()); + log.log(BotLog.TYPE.MUSIC, this.guild, "Unable to go to the next track. Reason: "+endReason.name()); } } diff --git a/src/main/java/handiebot/view/CommandLineListener.java b/src/main/java/handiebot/view/CommandLineListener.java index 56fcc4c..0179497 100644 --- a/src/main/java/handiebot/view/CommandLineListener.java +++ b/src/main/java/handiebot/view/CommandLineListener.java @@ -21,6 +21,7 @@ public class CommandLineListener implements KeyListener { //user wishes to submit command. JTextField commandLine = (JTextField) e.getSource(); String[] words = commandLine.getText().trim().split(" "); + commandLine.setText(null); String command = words[0]; String[] args = new String[words.length-1]; for (int i = 1; i < words.length; i++) { diff --git a/src/main/java/handiebot/view/actions/QuitAction.java b/src/main/java/handiebot/view/actions/QuitAction.java index 267d522..23d9089 100644 --- a/src/main/java/handiebot/view/actions/QuitAction.java +++ b/src/main/java/handiebot/view/actions/QuitAction.java @@ -1,6 +1,7 @@ package handiebot.view.actions; import handiebot.HandieBot; +import sx.blah.discord.handle.obj.IGuild; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -10,8 +11,22 @@ import java.awt.event.ActionListener; */ public class QuitAction implements ActionListener { + private IGuild guild; + + public QuitAction(){ + } + + public QuitAction(IGuild guild){ + this.guild = guild; + } + @Override public void actionPerformed(ActionEvent e) { - HandieBot.quit(); + if (guild != null){ + HandieBot.musicPlayer.getChatChannel(this.guild).sendMessage("Quiting HandieBot"); + HandieBot.musicPlayer.quit(this.guild); + } else { + HandieBot.quit(); + } } } diff --git a/src/main/java/handiebot/view/actions/music/MusicAction.java b/src/main/java/handiebot/view/actions/music/MusicAction.java new file mode 100644 index 0000000..14b2e32 --- /dev/null +++ b/src/main/java/handiebot/view/actions/music/MusicAction.java @@ -0,0 +1,18 @@ +package handiebot.view.actions.music; + +import sx.blah.discord.handle.obj.IGuild; + +import java.awt.event.ActionListener; + +/** + * @author Andrew Lalis + */ +public abstract class MusicAction implements ActionListener { + + protected IGuild guild; + + public MusicAction(IGuild guild) { + this.guild = guild; + } + +} diff --git a/src/main/java/handiebot/view/actions/music/PlayAction.java b/src/main/java/handiebot/view/actions/music/PlayAction.java new file mode 100644 index 0000000..a0cdc9b --- /dev/null +++ b/src/main/java/handiebot/view/actions/music/PlayAction.java @@ -0,0 +1,32 @@ +package handiebot.view.actions.music; + +import handiebot.HandieBot; +import sx.blah.discord.handle.obj.IGuild; + +import java.awt.event.ActionEvent; + +/** + * @author Andrew Lalis + */ +public class PlayAction extends MusicAction { + + private String[] args = null; + + public PlayAction(IGuild guild) { + super(guild); + } + + public PlayAction(IGuild guild, String[] args){ + super(guild); + this.args = args; + } + + @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]); + } + } +} diff --git a/src/main/java/handiebot/view/actions/music/QueueListAction.java b/src/main/java/handiebot/view/actions/music/QueueListAction.java new file mode 100644 index 0000000..7e53e87 --- /dev/null +++ b/src/main/java/handiebot/view/actions/music/QueueListAction.java @@ -0,0 +1,24 @@ +package handiebot.view.actions.music; + +import handiebot.HandieBot; +import sx.blah.discord.handle.obj.IGuild; + +import java.awt.event.ActionEvent; + +/** + * @author Andrew Lalis + */ +public class QueueListAction extends MusicAction { + + private boolean showAll = false; + + public QueueListAction(IGuild guild, boolean showAll){ + super(guild); + this.showAll = showAll; + } + + @Override + public void actionPerformed(ActionEvent e) { + HandieBot.musicPlayer.showQueueList(this.guild, this.showAll); + } +} diff --git a/src/main/java/handiebot/view/actions/music/SkipAction.java b/src/main/java/handiebot/view/actions/music/SkipAction.java new file mode 100644 index 0000000..f7b3b57 --- /dev/null +++ b/src/main/java/handiebot/view/actions/music/SkipAction.java @@ -0,0 +1,21 @@ +package handiebot.view.actions.music; + +import handiebot.HandieBot; +import sx.blah.discord.handle.obj.IGuild; + +import java.awt.event.ActionEvent; + +/** + * @author Andrew Lalis + */ +public class SkipAction extends MusicAction { + + public SkipAction(IGuild guild) { + super(guild); + } + + @Override + public void actionPerformed(ActionEvent e) { + HandieBot.musicPlayer.skipTrack(this.guild); + } +} diff --git a/src/main/java/handiebot/view/actions/music/ToggleRepeatAction.java b/src/main/java/handiebot/view/actions/music/ToggleRepeatAction.java new file mode 100644 index 0000000..64ded13 --- /dev/null +++ b/src/main/java/handiebot/view/actions/music/ToggleRepeatAction.java @@ -0,0 +1,21 @@ +package handiebot.view.actions.music; + +import handiebot.HandieBot; +import sx.blah.discord.handle.obj.IGuild; + +import java.awt.event.ActionEvent; + +/** + * @author Andrew Lalis + */ +public class ToggleRepeatAction extends MusicAction { + + public ToggleRepeatAction(IGuild guild) { + super(guild); + } + + @Override + public void actionPerformed(ActionEvent e) { + HandieBot.musicPlayer.toggleRepeat(this.guild); + } +} From cafdfd6988243a55f40e084d22d2d1c651bd9a05 Mon Sep 17 00:00:00 2001 From: Andrew Lalis Date: Wed, 21 Jun 2017 16:46:25 +0200 Subject: [PATCH 10/10] Updated icon. --- src/main/java/handiebot/HandieBot.java | 1 - src/main/resources/icon.png | Bin 80153 -> 71593 bytes src/main/resources/icon.svg | 29 +++++++------------------ 3 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/main/java/handiebot/HandieBot.java b/src/main/java/handiebot/HandieBot.java index b285951..e3b05f8 100644 --- a/src/main/java/handiebot/HandieBot.java +++ b/src/main/java/handiebot/HandieBot.java @@ -52,7 +52,6 @@ public class HandieBot { client = new ClientBuilder().withToken(TOKEN).build(); client.getDispatcher().registerListener(new HandieBot()); client.login(); - } /** diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png index f11fbb914531dbef93bc0a99b956bff785179dd7..cb2d22082939d6a483eeeea0e70e8cfa954c5608 100644 GIT binary patch literal 71593 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 literal 80153 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%0DS0keP)?gJe)Bw?N7>X&)5HyTo{8n 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

#A}#cD%IAIDZ4q*Ju8D3mc8x@P}Cr;7*sJ z6wzD(%D%Cr*NYB4`E%sg=!ty(i{C7eTy6$`r)YY0`X1`%U8wEcaWz_fkIS@x4pm5# zvP{7H@1Yzap}m{Gf_=#k(f*hq)Z+)Z=R|@m)J^!^6?l@Wm?(RHB=(b*P&_xsMSr7{ zGSUg9>9L!R?_wQ8jgk?*N#H;Bp$|uA4Q5BZz|_4BHf@m)oR@jTuUs^wl}O(HqPh;T zWOWh;{n&W}XFs$YYI)_Ad1Wjq@>%SWk64Tm7#{P2imh39 zc-?$}7YoCqPzA#yKN3iCJ?G^Fyyp)GCHQgbLGj_9Fr7_ju(MNNlyKEzLC;mUjM2t$ zL>oAlihrS!q4WHpc)3OA#_iO4F0EvGN>t=Vn~^{E;KX6#$K*#xBd4h`T@PlY00Mo~ z&HQRBzPt#TH#u4oP(wpU;W@)R84pvJ=(x=m1y=KFd%%l zhlVd&^*=uqbKc%a5wY}!S5jZ80-^rPbMPO12|GBH%!u>gUjI#Byv$g{Ax?qZgM=CC z@#@z*srvlB>b=S35{qinsdI+pfEb3+#^WUSx&5-kO<9HLdBiF1eVhJ5s$Z7JQsY(mdnDDdt}2VWaKF-` zWh@Z6kPWGQHZv;~JvD0o8~?ZEJ@ykOA1izIWFz~g1fTR-(!aX<+#315T}!=sZ`z%$ z)g@u?BPSmmAUN3+uts@ z$#mGqy&QM|Q1-c3wPz!@E>{J$5>D;a-I@PiBm4uSK?*SDA81HeFETH_&6gH;``{Vr zU`^I%+vLu?xfmrpYH@k?ZsW`= z4}Ih+Ere%P86~FpyIq}L&35qMy8yKRl3dBd*a!-i8t?sR^Gp6suax^ z_V14Qf-uGfcYq;e&|-S^d-|Uf_k1L7PW~onJM+So$sd)gF5S8@{o1j8J+c>PgQ&b0 z-r3Gi%h&SON*XUR2Q~m&`3>K^S?mAa!S|b7-id$reu1VKfRs$dgXgyG(pvjpul#@2 zY5(JbWUF1$n^*5v1opFApRqSA1oqk(RKF((UH}etO>5g1zgKimage/svg+xml - + @@ -73,13 +73,13 @@ id="layer1" transform="translate(0,-796.36219)"> + height="100%" + style="stroke-width:12;stroke-miterlimit:4;stroke-dasharray:none" /> - - + style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ff6900;stroke-width:12;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />