From 860dc12ec30adbcc6dc3d834283562a33339202f Mon Sep 17 00:00:00 2001 From: Duncan Sterken Date: Tue, 20 Feb 2018 10:30:48 +0100 Subject: [PATCH 1/4] Rewrite command system and write the tests --- .../io/hypesquad/watsonbot/WatsonBot.java | 23 ------ .../commands/AbstractWatsonCommand.java | 82 ------------------- .../watsonbot/commands/ExampleCommand.java | 27 ++++++ .../commands/WatsonExampleCommand.java | 51 ------------ .../watsonbot/commands/WatsonHelpCommand.java | 60 -------------- .../watsonbot/event/WatsonEventListener.java | 26 +----- .../managers/WatsonCommandManager.java | 80 ++++++++++++++++++ .../watsonbot/objects/command/ICommand.java | 38 +++++++++ .../watsonbot/commands/WatsonCommandTest.java | 14 ++-- 9 files changed, 156 insertions(+), 245 deletions(-) delete mode 100644 src/main/java/io/hypesquad/watsonbot/commands/AbstractWatsonCommand.java create mode 100644 src/main/java/io/hypesquad/watsonbot/commands/ExampleCommand.java delete mode 100644 src/main/java/io/hypesquad/watsonbot/commands/WatsonExampleCommand.java delete mode 100644 src/main/java/io/hypesquad/watsonbot/commands/WatsonHelpCommand.java create mode 100644 src/main/java/io/hypesquad/watsonbot/managers/WatsonCommandManager.java create mode 100644 src/main/java/io/hypesquad/watsonbot/objects/command/ICommand.java diff --git a/src/main/java/io/hypesquad/watsonbot/WatsonBot.java b/src/main/java/io/hypesquad/watsonbot/WatsonBot.java index 3e3b5a5..181c665 100644 --- a/src/main/java/io/hypesquad/watsonbot/WatsonBot.java +++ b/src/main/java/io/hypesquad/watsonbot/WatsonBot.java @@ -17,16 +17,10 @@ package io.hypesquad.watsonbot; -import io.hypesquad.watsonbot.commands.AbstractWatsonCommand; -import io.hypesquad.watsonbot.commands.WatsonExampleCommand; -import io.hypesquad.watsonbot.commands.WatsonHelpCommand; import io.hypesquad.watsonbot.event.WatsonEventListener; import sx.blah.discord.api.ClientBuilder; import sx.blah.discord.api.IDiscordClient; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - /** * Represents the main class * @@ -34,11 +28,6 @@ */ public class WatsonBot { - /** - * This stores all the commands. - */ - public static Map commands = new ConcurrentHashMap<>(); - public static void main(final String... args) { if (args.length < 1) throw new IllegalArgumentException("WatsonBot requires a token!"); @@ -46,17 +35,5 @@ public static void main(final String... args) { IDiscordClient client = new ClientBuilder().withToken(args[0]).login(); client.getDispatcher().registerListener(new WatsonEventListener()); - - //Register the commands - registerCommands(); - } - - /** - * This method will register our commands. - */ - public static void registerCommands() { - // Add the commands - commands.put("help", new WatsonHelpCommand()); - commands.put("example", new WatsonExampleCommand()); } } \ No newline at end of file diff --git a/src/main/java/io/hypesquad/watsonbot/commands/AbstractWatsonCommand.java b/src/main/java/io/hypesquad/watsonbot/commands/AbstractWatsonCommand.java deleted file mode 100644 index 98b1026..0000000 --- a/src/main/java/io/hypesquad/watsonbot/commands/AbstractWatsonCommand.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file is part of WatsonBot. - * - * WatsonBot is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * WatsonBot is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with WatsonBot. If not, see . - */ - -package io.hypesquad.watsonbot.commands; - -import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; - -import java.util.Objects; - -/** - * Represents a Command - * - * @author Duncan - */ - -public abstract class AbstractWatsonCommand { - - /** - * This method is ran before the command's action is executed - * - * @param args the arguments from the command - * @param event the MessageReceivedEvent from D4J - * @return true if checks pass, false otherwise - */ - public abstract boolean checkCommand(final String[] args, final MessageReceivedEvent event); - - /** - * This is the action of the command - * - * @param args the arguments from the command - * @param event the MessageReceivedEvent from D4J - */ - public abstract void executeCommand(final String[] args, final MessageReceivedEvent event); - - /** - * The instructions of the command - * - * @return help message for the command - */ - public abstract String commandHelp(); - - /** - * An equals method to compare two command objects - * - * @param object The command object - * @return true if the given object is equal to this object - */ - @Override - public boolean equals(final Object object) { - - if (object == null) { - return false; - } - - if (object.getClass() != this.getClass()) { - return false; - } - - AbstractWatsonCommand command = (AbstractWatsonCommand) object; - - return this.commandHelp().equals(command.commandHelp()); - } - - @Override - public int hashCode() { - return Objects.hashCode(this); - } -} diff --git a/src/main/java/io/hypesquad/watsonbot/commands/ExampleCommand.java b/src/main/java/io/hypesquad/watsonbot/commands/ExampleCommand.java new file mode 100644 index 0000000..edce5e8 --- /dev/null +++ b/src/main/java/io/hypesquad/watsonbot/commands/ExampleCommand.java @@ -0,0 +1,27 @@ +package io.hypesquad.watsonbot.commands; + +import io.hypesquad.watsonbot.objects.command.ICommand; +import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; + +/** + * This is a command to display how commands should be made + * + * @author Duncan + */ +public class ExampleCommand implements ICommand { + @Override + public void execute(String invoke, String[] args, MessageReceivedEvent event) { + event.getChannel().sendMessage("This is an example"); + } + + @Override + public String getName() { + return "example"; + } + + @Override + public String getHelp() { + return "This is an example command\n" + + "Usage: `!" + getName() + "`"; + } +} diff --git a/src/main/java/io/hypesquad/watsonbot/commands/WatsonExampleCommand.java b/src/main/java/io/hypesquad/watsonbot/commands/WatsonExampleCommand.java deleted file mode 100644 index a5f109a..0000000 --- a/src/main/java/io/hypesquad/watsonbot/commands/WatsonExampleCommand.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * This file is part of WatsonBot. - * - * WatsonBot is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * WatsonBot is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with WatsonBot. If not, see . - */ - -package io.hypesquad.watsonbot.commands; - -import org.apache.commons.lang3.StringUtils; -import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; - -/** - * Represents the example command - * - * @author Duncan - */ - -public final class WatsonExampleCommand extends AbstractWatsonCommand { - - // Do any checks here because this get's ran first - @Override - public boolean checkCommand(final String[] args, final MessageReceivedEvent event) { - // Bots are not allowed to run this command - return !event.getAuthor().isBot(); - } - - // The action of the command. - @Override - public void executeCommand(final String[] args, final MessageReceivedEvent event) { - // Send a message with the arguments that the user has put in - event.getChannel().sendMessage("This is a example command." + StringUtils.join(args, " ")); - } - - // How to use the command - @Override - public String commandHelp() { - return "A command that shows how commands are structured."; - } - -} diff --git a/src/main/java/io/hypesquad/watsonbot/commands/WatsonHelpCommand.java b/src/main/java/io/hypesquad/watsonbot/commands/WatsonHelpCommand.java deleted file mode 100644 index 5d1eca3..0000000 --- a/src/main/java/io/hypesquad/watsonbot/commands/WatsonHelpCommand.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of WatsonBot. - * - * WatsonBot is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * WatsonBot is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with WatsonBot. If not, see . - */ - -package io.hypesquad.watsonbot.commands; - -import io.hypesquad.watsonbot.WatsonBot; -import io.hypesquad.watsonbot.util.WatsonUtil; -import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; - -/** - * Represents the help command - * - * @author Duncan - */ - -public final class WatsonHelpCommand extends AbstractWatsonCommand { - - private final transient int MAX_HELPMESSAGE_LENGTH = 20; - - @Override - public boolean checkCommand(final String[] args, final MessageReceivedEvent event) { - return true; - } - - @Override - public void executeCommand(final String[] args, final MessageReceivedEvent event) { - - final StringBuilder stringBuilder = new StringBuilder(MAX_HELPMESSAGE_LENGTH).append("```\n"); - - for (final String cmd : WatsonBot.commands.keySet()) { - stringBuilder.append(WatsonUtil.getProperty("prefix")) - .append(cmd) - .append(" > ") - .append(WatsonBot.commands.get(cmd).commandHelp()) - .append('\n'); - } - stringBuilder.append("```"); - event.getChannel().sendMessage(stringBuilder.toString()); - - } - - @Override - public String commandHelp() { - return "Shows a list of all the commands"; - } -} diff --git a/src/main/java/io/hypesquad/watsonbot/event/WatsonEventListener.java b/src/main/java/io/hypesquad/watsonbot/event/WatsonEventListener.java index c9e92c9..2179010 100644 --- a/src/main/java/io/hypesquad/watsonbot/event/WatsonEventListener.java +++ b/src/main/java/io/hypesquad/watsonbot/event/WatsonEventListener.java @@ -17,15 +17,11 @@ package io.hypesquad.watsonbot.event; -import io.hypesquad.watsonbot.WatsonBot; -import io.hypesquad.watsonbot.commands.AbstractWatsonCommand; +import io.hypesquad.watsonbot.managers.WatsonCommandManager; import io.hypesquad.watsonbot.util.WatsonUtil; import sx.blah.discord.api.events.EventSubscriber; import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; -import java.util.Arrays; -import java.util.Map; - /** * Represents the global EventListener * @@ -33,10 +29,7 @@ */ public class WatsonEventListener { - /** - * This is for easy access to your commands. - */ - private final transient Map commands = WatsonBot.commands; + private static final WatsonCommandManager COMMAND_MANAGER = new WatsonCommandManager(); @EventSubscriber public void onMessageReceivedEvent(MessageReceivedEvent event) { @@ -47,18 +40,7 @@ public void onMessageReceivedEvent(MessageReceivedEvent event) { if (!message.startsWith(prefix)) { return; } - - //Handle command - final String[] split = message.substring(message.indexOf(prefix) + 1, message.length()).split(" "); - final String calledCommand = split[0]; - final String[] args = Arrays.copyOfRange(split, 1, split.length); - - // Check if the command exist and if it does, run it - if (commands.containsKey(calledCommand)) { - final boolean safe = commands.get(calledCommand).checkCommand(args, event); - if (safe) { - commands.get(calledCommand).executeCommand(args, event); - } - } + //Handle the command + COMMAND_MANAGER.dispatchCommand(prefix, event); } } \ No newline at end of file diff --git a/src/main/java/io/hypesquad/watsonbot/managers/WatsonCommandManager.java b/src/main/java/io/hypesquad/watsonbot/managers/WatsonCommandManager.java new file mode 100644 index 0000000..55fbe24 --- /dev/null +++ b/src/main/java/io/hypesquad/watsonbot/managers/WatsonCommandManager.java @@ -0,0 +1,80 @@ +package io.hypesquad.watsonbot.managers; + +import io.hypesquad.watsonbot.commands.ExampleCommand; +import io.hypesquad.watsonbot.objects.command.ICommand; +import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This is a manager that handles adding commands and running commands + */ +public class WatsonCommandManager { + + private final Set commands = ConcurrentHashMap.newKeySet(); + + /** + * This is the constructor where all the commands are registered + */ + public WatsonCommandManager() { + this.addCommand(new ExampleCommand()); + } + + /** + * Returns an unmodifiableSet that contains the commands + * @return an unmodifiableSet that contains the commands + */ + public Set getCommands() { + return Collections.unmodifiableSet(commands); + } + + /** + * This attempts to add a command to the commands + * @param command The command to add + * @return {@code true} if the command was added + */ + private boolean addCommand(ICommand command) { + + if(command.getName().contains(" ")) + throw new IllegalArgumentException("Names can't have spaces"); + if(getCommand(command.getName()) != null) + return false; + commands.add(command); + return true; + } + + /** + * @param invoke The command that we are looking for + * @return A possible null command that matches the invoke or alias + */ + public ICommand getCommand(String invoke) { + + Optional foundInvoke = commands.stream().filter(it -> it.getName().equals(invoke)).findFirst(); + + if(foundInvoke.isPresent()) + return foundInvoke.get(); + else { + Optional foundAlias = commands.stream().filter(it -> Arrays.asList(it.getAliases()).contains(invoke)).findFirst(); + return foundAlias.isPresent() ? foundAlias.get() : null; + } + } + + /** + * This handles a command when a message is received + * @param prefix The prefix to replace in the message + * @param event the event that was called with this message + */ + public void dispatchCommand(String prefix, MessageReceivedEvent event) { + String[] messageContent = event.getMessage().getContent().replaceFirst(prefix, "").split("\\s+"); + String invoke = messageContent[0]; + + ICommand cmd = getCommand(invoke); + if(cmd != null) + cmd.execute(invoke, Arrays.copyOfRange(messageContent, 1, messageContent.length), event); + } + +} diff --git a/src/main/java/io/hypesquad/watsonbot/objects/command/ICommand.java b/src/main/java/io/hypesquad/watsonbot/objects/command/ICommand.java new file mode 100644 index 0000000..edfd059 --- /dev/null +++ b/src/main/java/io/hypesquad/watsonbot/objects/command/ICommand.java @@ -0,0 +1,38 @@ +package io.hypesquad.watsonbot.objects.command; + +import sx.blah.discord.handle.impl.events.guild.channel.message.MessageReceivedEvent; + +/** + * This is the base class for all the commands on the bot + * + * @author Duncan + */ +public interface ICommand { + + /** + * This is the part of the command that gets run when the command is executed + * @param invoke The thing that the user typed after the prefix, example: {@code !hello world}. In this case hello is the invoke + * @param args Everything that comes after the invoke + * @param event The {@link MessageReceivedEvent Event} that got fired when the command for the message + */ + void execute(String invoke, String[] args, MessageReceivedEvent event); + + /** + * Returns the name of the command + * @return the name of the command + */ + String getName(); + + /** + * Returns any optional aliases of the command + * @return any optional aliases of the command + */ + default String[] getAliases() { return new String[0]; } + + /** + * Returns A helpful description of the command with any usage instructions + * @return A helpful description of the command with any usage instructions + */ + String getHelp(); + +} diff --git a/src/test/java/io/hypesquad/watsonbot/commands/WatsonCommandTest.java b/src/test/java/io/hypesquad/watsonbot/commands/WatsonCommandTest.java index 8e94c18..00620af 100644 --- a/src/test/java/io/hypesquad/watsonbot/commands/WatsonCommandTest.java +++ b/src/test/java/io/hypesquad/watsonbot/commands/WatsonCommandTest.java @@ -16,20 +16,20 @@ */ package io.hypesquad.watsonbot.commands; -import io.hypesquad.watsonbot.WatsonBot; +import io.hypesquad.watsonbot.managers.WatsonCommandManager; +import io.hypesquad.watsonbot.objects.command.ICommand; import org.junit.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; public class WatsonCommandTest { @Test public void testCommandExistence() { - WatsonBot.registerCommands(); - String key = "help"; - WatsonHelpCommand value = new WatsonHelpCommand(); + WatsonCommandManager manager = new WatsonCommandManager(); - assertEquals("Retrieved command does not match stored command for same key", - value, WatsonBot.commands.get(key)); + ICommand cmd = manager.getCommand("example"); + + assertNotNull("Command is null or not registered", cmd); } } From 266e83de996bad53cbc74d57c25dd8d49a2620d2 Mon Sep 17 00:00:00 2001 From: Duncan Sterken Date: Tue, 20 Feb 2018 10:43:35 +0100 Subject: [PATCH 2/4] Fix codacy issues --- .../io/hypesquad/watsonbot/WatsonBot.java | 5 +++- .../managers/WatsonCommandManager.java | 26 +++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/main/java/io/hypesquad/watsonbot/WatsonBot.java b/src/main/java/io/hypesquad/watsonbot/WatsonBot.java index 181c665..ebf84f1 100644 --- a/src/main/java/io/hypesquad/watsonbot/WatsonBot.java +++ b/src/main/java/io/hypesquad/watsonbot/WatsonBot.java @@ -28,9 +28,12 @@ */ public class WatsonBot { + private WatsonBot() {} + public static void main(final String... args) { - if (args.length < 1) + if (args.length < 1) { throw new IllegalArgumentException("WatsonBot requires a token!"); + } IDiscordClient client = new ClientBuilder().withToken(args[0]).login(); diff --git a/src/main/java/io/hypesquad/watsonbot/managers/WatsonCommandManager.java b/src/main/java/io/hypesquad/watsonbot/managers/WatsonCommandManager.java index 55fbe24..e9df4ac 100644 --- a/src/main/java/io/hypesquad/watsonbot/managers/WatsonCommandManager.java +++ b/src/main/java/io/hypesquad/watsonbot/managers/WatsonCommandManager.java @@ -37,12 +37,14 @@ public Set getCommands() { * @param command The command to add * @return {@code true} if the command was added */ - private boolean addCommand(ICommand command) { + private boolean addCommand(final ICommand command) { - if(command.getName().contains(" ")) + if(command.getName().contains(" ")) { throw new IllegalArgumentException("Names can't have spaces"); - if(getCommand(command.getName()) != null) + } + if(getCommand(command.getName()) != null) { return false; + } commands.add(command); return true; } @@ -51,15 +53,16 @@ private boolean addCommand(ICommand command) { * @param invoke The command that we are looking for * @return A possible null command that matches the invoke or alias */ - public ICommand getCommand(String invoke) { + public ICommand getCommand(final String invoke) { - Optional foundInvoke = commands.stream().filter(it -> it.getName().equals(invoke)).findFirst(); + Optional foundInvoke = commands.stream().filter(command -> command.getName().equals(invoke)).findFirst(); - if(foundInvoke.isPresent()) + if(foundInvoke.isPresent()) { return foundInvoke.get(); - else { - Optional foundAlias = commands.stream().filter(it -> Arrays.asList(it.getAliases()).contains(invoke)).findFirst(); - return foundAlias.isPresent() ? foundAlias.get() : null; + } else { + Optional foundAlias = commands.stream().filter(command -> Arrays.asList(command.getAliases()).contains(invoke)).findFirst(); + //noinspection ConstantConditions + return foundAlias.get(); } } @@ -68,13 +71,14 @@ public ICommand getCommand(String invoke) { * @param prefix The prefix to replace in the message * @param event the event that was called with this message */ - public void dispatchCommand(String prefix, MessageReceivedEvent event) { + public void dispatchCommand(final String prefix, final MessageReceivedEvent event) { String[] messageContent = event.getMessage().getContent().replaceFirst(prefix, "").split("\\s+"); String invoke = messageContent[0]; ICommand cmd = getCommand(invoke); - if(cmd != null) + if(cmd != null) { cmd.execute(invoke, Arrays.copyOfRange(messageContent, 1, messageContent.length), event); + } } } From a62def9a43927eda6dd9b42a6d60f4e01a63fb74 Mon Sep 17 00:00:00 2001 From: Duncan Sterken Date: Tue, 20 Feb 2018 11:09:02 +0100 Subject: [PATCH 3/4] Fix final codacy issues --- .../java/io/hypesquad/watsonbot/commands/ExampleCommand.java | 2 +- .../java/io/hypesquad/watsonbot/objects/command/ICommand.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/hypesquad/watsonbot/commands/ExampleCommand.java b/src/main/java/io/hypesquad/watsonbot/commands/ExampleCommand.java index edce5e8..185565a 100644 --- a/src/main/java/io/hypesquad/watsonbot/commands/ExampleCommand.java +++ b/src/main/java/io/hypesquad/watsonbot/commands/ExampleCommand.java @@ -10,7 +10,7 @@ */ public class ExampleCommand implements ICommand { @Override - public void execute(String invoke, String[] args, MessageReceivedEvent event) { + public void execute(final String invoke, final String[] args, final MessageReceivedEvent event) { event.getChannel().sendMessage("This is an example"); } diff --git a/src/main/java/io/hypesquad/watsonbot/objects/command/ICommand.java b/src/main/java/io/hypesquad/watsonbot/objects/command/ICommand.java index edfd059..f870992 100644 --- a/src/main/java/io/hypesquad/watsonbot/objects/command/ICommand.java +++ b/src/main/java/io/hypesquad/watsonbot/objects/command/ICommand.java @@ -15,7 +15,7 @@ public interface ICommand { * @param args Everything that comes after the invoke * @param event The {@link MessageReceivedEvent Event} that got fired when the command for the message */ - void execute(String invoke, String[] args, MessageReceivedEvent event); + void execute(final String invoke, final String[] args, final MessageReceivedEvent event); /** * Returns the name of the command From 18169dd51bd94bd54269339497d0fc5ce23fd0a6 Mon Sep 17 00:00:00 2001 From: Duncan Sterken Date: Tue, 20 Feb 2018 11:14:20 +0100 Subject: [PATCH 4/4] Return null if there are no commands registered --- .../hypesquad/watsonbot/managers/WatsonCommandManager.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/io/hypesquad/watsonbot/managers/WatsonCommandManager.java b/src/main/java/io/hypesquad/watsonbot/managers/WatsonCommandManager.java index e9df4ac..85be129 100644 --- a/src/main/java/io/hypesquad/watsonbot/managers/WatsonCommandManager.java +++ b/src/main/java/io/hypesquad/watsonbot/managers/WatsonCommandManager.java @@ -55,6 +55,11 @@ private boolean addCommand(final ICommand command) { */ public ICommand getCommand(final String invoke) { + //Return nothing if there are no commands + if(commands.size() == 0) { + return null; + } + Optional foundInvoke = commands.stream().filter(command -> command.getName().equals(invoke)).findFirst(); if(foundInvoke.isPresent()) {