diff --git a/avatarIcon.psd b/avatarIcon.psd new file mode 100644 index 0000000..b77b7c5 Binary files /dev/null and b/avatarIcon.psd differ diff --git a/src/main/java/handiebot/HandieBot.java b/src/main/java/handiebot/HandieBot.java index 46390c0..757fe33 100644 --- a/src/main/java/handiebot/HandieBot.java +++ b/src/main/java/handiebot/HandieBot.java @@ -17,10 +17,6 @@ import sx.blah.discord.handle.obj.Permissions; import sx.blah.discord.util.DiscordException; import sx.blah.discord.util.RateLimitException; -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.FileReader; -import java.io.IOException; import java.util.*; /** @@ -35,35 +31,16 @@ public class HandieBot { public static final String APPLICATION_NAME = "HandieBot"; //The token required for logging into Discord. This is secure and must not be in the source code on GitHub. - private static final String TOKEN; - static { - TOKEN = readToken(); - if (TOKEN.isEmpty()){ - System.out.println("You do not have the token required to start the bot. Shutting down."); - System.exit(-1); - } - } - - + private static String TOKEN; //Variable to enable or disable GUI. private static boolean USE_GUI = true; //Settings for the bot. Tries to load the settings, or if that doesn't work, it will load defaults. public static Properties settings; - static{ - try { - Properties defaultSettings = new Properties(); - defaultSettings.load(HandieBot.class.getClassLoader().getResourceAsStream("default_settings")); - settings = new Properties(defaultSettings); - settings.load(new FileInputStream(FileUtil.getDataDirectory()+"settings")); - } catch (IOException e) { - e.printStackTrace(); - } - } //Resource bundle for localized strings. - public static ResourceBundle resourceBundle = ResourceBundle.getBundle("Strings", Locale.forLanguageTag(settings.getProperty("language"))); + public static ResourceBundle resourceBundle; //Discord client object. public static IDiscordClient client; @@ -91,8 +68,25 @@ public class HandieBot { //client.changeAvatar(Image.forStream("png", getClass().getClassLoader().getResourceAsStream("avatarIcon.png"))); } + /** + * Set up the basic functions of the bot. + */ + private static void init(){ + TOKEN = FileUtil.readToken(); + if (TOKEN.isEmpty()){ + System.out.println("You do not have the token required to start the bot. Shutting down."); + System.exit(-1); + } + + settings = FileUtil.loadSettings(); + + resourceBundle = ResourceBundle.getBundle("Strings", Locale.forLanguageTag(settings.getProperty("language"))); + } + public static void main(String[] args) throws DiscordException, RateLimitException { + init(); + musicPlayer = new MusicPlayer(); List argsList = Arrays.asList(args); @@ -124,21 +118,6 @@ public class HandieBot { return channel.getModifiedPermissions(client.getOurUser()).contains(permission); } - /** - * Reads the private discord token necessary to start the bot. If this fails, the bot will shut down. - * @return The string token needed to log in. - */ - private static String readToken(){ - String path = FileUtil.getDataDirectory()+"token.txt"; - String result = ""; - try(BufferedReader reader = new BufferedReader(new FileReader(path))){ - result = reader.readLine(); - } catch (IOException e) { - System.err.println("Unable to find the token file. You are unable to start the bot without this."); - } - return result; - } - /** * Safely shuts down the bot on all guilds. */ @@ -147,7 +126,7 @@ public class HandieBot { musicPlayer.quitAll(); client.logout(); window.dispose(); - FileUtil.saveSettings(); + FileUtil.saveSettings(settings); System.exit(0); } diff --git a/src/main/java/handiebot/command/ReactionHandler.java b/src/main/java/handiebot/command/ReactionHandler.java index 9ef69b7..2735c58 100644 --- a/src/main/java/handiebot/command/ReactionHandler.java +++ b/src/main/java/handiebot/command/ReactionHandler.java @@ -2,6 +2,7 @@ package handiebot.command; import handiebot.command.types.ReactionListener; import sx.blah.discord.handle.impl.events.guild.channel.message.reaction.ReactionEvent; +import sx.blah.discord.util.RequestBuffer; import java.util.ArrayList; import java.util.List; @@ -13,8 +14,12 @@ import java.util.List; public class ReactionHandler { private static List listeners = new ArrayList<>(); + //Flag to tell if the handler is iterating over the listeners. private static boolean iterating = false; + //Queue of listeners to remove after an iteration. private static List listenersToRemove = new ArrayList<>(); + //Flag that individual listeners can set to request the message be deleted after processing. + private static boolean deleteRequested = false; /** * Adds a listener, so that it is notified when reaction events are received. * @param listener The listener to add. @@ -35,6 +40,13 @@ public class ReactionHandler { } } + /** + * Requests that the currently processing message should be deleted after the iteration. + */ + public static void requestMessageDeletion(){ + deleteRequested = true; + } + /** * Notifies all listeners that a ReactionEvent has occurred, and calls each one's function. * @param event The event that occurred. @@ -45,6 +57,10 @@ public class ReactionHandler { listener.onReactionEvent(event); } iterating = false; + if (deleteRequested) { + RequestBuffer.request(event.getMessage()::delete); + } + deleteRequested = false; listeners.removeAll(listenersToRemove); listenersToRemove.clear(); } diff --git a/src/main/java/handiebot/command/commands/music/PlayCommand.java b/src/main/java/handiebot/command/commands/music/PlayCommand.java index 5dd6502..44a9a20 100644 --- a/src/main/java/handiebot/command/commands/music/PlayCommand.java +++ b/src/main/java/handiebot/command/commands/music/PlayCommand.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutionException; +import static handiebot.HandieBot.log; import static handiebot.HandieBot.resourceBundle; import static handiebot.utils.MessageUtils.sendMessage; import static handiebot.utils.YoutubeSearch.WATCH_URL; @@ -99,6 +100,8 @@ public class PlayCommand extends ContextCommand { videos.forEach((video) -> urls.add(WATCH_URL+video.getId())); IMessage message = YoutubeSearch.displayChoicesDialog(videos, context.getChannel()); ReactionHandler.addListener(new YoutubePlayListener(message, context.getUser(), urls)); + } else { + log.log(BotLog.TYPE.ERROR, "YouTube query returned a null list of videos."); } } } diff --git a/src/main/java/handiebot/command/commands/music/RepeatCommand.java b/src/main/java/handiebot/command/commands/music/RepeatCommand.java index bc26181..ba46e2c 100644 --- a/src/main/java/handiebot/command/commands/music/RepeatCommand.java +++ b/src/main/java/handiebot/command/commands/music/RepeatCommand.java @@ -9,6 +9,7 @@ import handiebot.utils.FileUtil; import java.text.MessageFormat; import static handiebot.HandieBot.resourceBundle; +import static handiebot.HandieBot.settings; import static handiebot.utils.MessageUtils.sendMessage; /** @@ -30,7 +31,7 @@ public class RepeatCommand extends ContextCommand { boolean shouldRepeat = (context.getArgs()[0].toLowerCase().equals("true")); HandieBot.musicPlayer.setRepeat(context.getGuild(), shouldRepeat); HandieBot.settings.setProperty(context.getGuild().getName()+"_repeat", Boolean.toString(shouldRepeat)); - FileUtil.saveSettings(); + FileUtil.saveSettings(settings); } else { sendMessage(MessageFormat.format(resourceBundle.getString("player.getRepeat"), HandieBot.musicPlayer.isRepeating(context.getGuild())), context.getChannel()); } diff --git a/src/main/java/handiebot/command/commands/music/ShuffleCommand.java b/src/main/java/handiebot/command/commands/music/ShuffleCommand.java index b663936..7a01087 100644 --- a/src/main/java/handiebot/command/commands/music/ShuffleCommand.java +++ b/src/main/java/handiebot/command/commands/music/ShuffleCommand.java @@ -9,6 +9,7 @@ import handiebot.utils.FileUtil; import java.text.MessageFormat; import static handiebot.HandieBot.resourceBundle; +import static handiebot.HandieBot.settings; import static handiebot.utils.MessageUtils.sendMessage; /** @@ -31,7 +32,7 @@ public class ShuffleCommand extends ContextCommand { HandieBot.musicPlayer.setShuffle(context.getGuild(), shouldShuffle); //Save the settings for this guild to the settings file. HandieBot.settings.setProperty(context.getGuild().getName()+"_shuffle", Boolean.toString(shouldShuffle)); - FileUtil.saveSettings(); + FileUtil.saveSettings(settings); } else { sendMessage(MessageFormat.format(resourceBundle.getString("player.getShuffle"), HandieBot.musicPlayer.isShuffling(context.getGuild())), context.getChannel()); } diff --git a/src/main/java/handiebot/command/reactionListeners/YoutubeChoiceListener.java b/src/main/java/handiebot/command/reactionListeners/YoutubeChoiceListener.java index da95d16..57117bf 100644 --- a/src/main/java/handiebot/command/reactionListeners/YoutubeChoiceListener.java +++ b/src/main/java/handiebot/command/reactionListeners/YoutubeChoiceListener.java @@ -6,7 +6,6 @@ import handiebot.view.BotLog; import sx.blah.discord.handle.impl.events.guild.channel.message.reaction.ReactionEvent; import sx.blah.discord.handle.obj.IMessage; import sx.blah.discord.handle.obj.IUser; -import sx.blah.discord.util.RequestBuffer; import java.util.List; @@ -56,7 +55,7 @@ public abstract class YoutubeChoiceListener implements ReactionListener { if ((event.getMessage().getLongID() == this.message.getLongID()) && (this.user.getLongID() == event.getUser().getLongID())){ for (int i = 0; i < choices.length; i++){ - if (event.getReaction().toString().equals(choices[i])){ + if (event.getReaction().getEmoji().getName().equals(choices[i])){ onChoice(i); break; } @@ -69,7 +68,7 @@ public abstract class YoutubeChoiceListener implements ReactionListener { * Method to delete the large, unwieldy message and remove the listener for this set of videos. */ private void cleanup(){ - RequestBuffer.request(message::delete); + ReactionHandler.requestMessageDeletion(); ReactionHandler.removeListener(this); } diff --git a/src/main/java/handiebot/utils/FileUtil.java b/src/main/java/handiebot/utils/FileUtil.java index 5653aee..e1e5ec6 100644 --- a/src/main/java/handiebot/utils/FileUtil.java +++ b/src/main/java/handiebot/utils/FileUtil.java @@ -1,5 +1,6 @@ package handiebot.utils; +import handiebot.HandieBot; import handiebot.view.BotLog; import java.io.*; @@ -7,8 +8,10 @@ import java.nio.file.Files; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; +import java.util.Properties; -import static handiebot.HandieBot.*; +import static handiebot.HandieBot.log; +import static handiebot.HandieBot.resourceBundle; /** * @author Andrew Lalis @@ -16,10 +19,19 @@ import static handiebot.HandieBot.*; */ public class FileUtil { + /** + * Gets the directory where handiebot's data is stored. + * @return The directory leading to 'user.home'. + */ public static String getDataDirectory(){ return System.getProperty("user.home")+"/.handiebot/"; } + /** + * Reads a file, and returns a list of lines in the file. + * @param file The file to read. + * @return A list of lines from the file. + */ public static List getLinesFromFile(File file){ try { return Files.readAllLines(file.toPath()); @@ -29,6 +41,11 @@ public class FileUtil { } } + /** + * Writes a list of strings to a file, separated by newlines. + * @param lines The list of lines. + * @param file The file to write to. + */ public static void writeLinesToFile(List lines, File file){ if (lines.size() == 0){ return; @@ -56,7 +73,11 @@ public class FileUtil { } } - public static void saveSettings(){ + /** + * Saves the global set of settings. + * @param settings The settings to save. + */ + public static void saveSettings(Properties settings){ try { settings.store(new FileWriter(FileUtil.getDataDirectory()+"settings"), "Settings for HandieBot"); } catch (IOException e) { @@ -64,4 +85,34 @@ public class FileUtil { } } + /** + * Loads a properties file. + * @return The settings saved locally for HandieBot. + */ + public static Properties loadSettings(){ + Properties settings = new Properties(); + try { + settings.load(HandieBot.class.getClassLoader().getResourceAsStream("default_settings")); + settings.load(new FileInputStream(FileUtil.getDataDirectory()+"settings")); + } catch (IOException e){ + e.printStackTrace(); + } + return settings; + } + + /** + * Reads the private discord token necessary to start the bot. If this fails, the bot will shut down. + * @return The string token needed to log in. + */ + public static String readToken(){ + String path = FileUtil.getDataDirectory()+"token.txt"; + String result = ""; + try(BufferedReader reader = new BufferedReader(new FileReader(path))){ + result = reader.readLine(); + } catch (IOException e) { + System.err.println("Unable to find the token file. You are unable to start the bot without this."); + } + return result; + } + } diff --git a/src/main/java/handiebot/utils/YoutubeSearch.java b/src/main/java/handiebot/utils/YoutubeSearch.java index 182e59b..8f2f7c0 100644 --- a/src/main/java/handiebot/utils/YoutubeSearch.java +++ b/src/main/java/handiebot/utils/YoutubeSearch.java @@ -25,8 +25,8 @@ import sx.blah.discord.util.EmbedBuilder; import java.awt.*; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.io.InputStreamReader; import java.security.GeneralSecurityException; import java.text.MessageFormat; @@ -76,14 +76,13 @@ public class YoutubeSearch { * @throws IOException */ public static Credential authorize() throws IOException { + // Load client secrets. - InputStream in = YoutubeSearch.class.getClassLoader().getResourceAsStream("client_secret.json"); - GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); + GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, + new InputStreamReader(new FileInputStream(FileUtil.getDataDirectory()+"googleData/client_secret.json"))); // Build flow and trigger user authorization request. - GoogleAuthorizationCodeFlow flow = - new GoogleAuthorizationCodeFlow.Builder( - HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) + GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) .setDataStoreFactory(DATA_STORE_FACTORY) .setAccessType("offline") .build(); diff --git a/src/main/resources/avatarIcon.png b/src/main/resources/avatarIcon.png index e18afd7..324dc8f 100644 Binary files a/src/main/resources/avatarIcon.png and b/src/main/resources/avatarIcon.png differ