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