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.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 @@