diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..2ac9e9a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,30 @@ +--- +name: Bug report +about: Report a bug here to receive help and to help me improve the framework + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. … +2. …. +3. ….. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots/Code** +If applicable, add screenshots to help explain your problem. +Also, add the part of the code that doesn't work. + +**System information** +- JDK/JVM version +- CommandAPI version +- JDA version +- (Other dependencies) + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..066b2d9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: Feature request +about: Suggest an idea for this project + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/README.md b/README.md index 1801165..2ad76eb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # discord-api-command **A simple Command API for the JDA** +(An API that is not an API but a framework) -CURRENT VERSION: **3.2**

-Other versions: **3.1**, **3.0_3**, **3.0_2**, **3.0_1**, **3.0**

+CURRENT VERSION: **3.2_01**

+Other versions: **3.2**, **3.1**, **3.0_3**, **3.0_2**, **3.0_1**, **3.0**

**[Changelog](https://github.com/JohnnyJayJay/discord-api-command/blob/master/changelog.md)**
**[Documentation](http://docs.johnnyjayjay.me)** @@ -17,6 +18,10 @@ Other versions: **3.1**, **3.0_3**, **3.0_2**, **3.0_1**, **3.0**

- prevention of exceptions and errors by substantial validation and exception handling - error transparency through `CommandSetException` +## Important +This library **requires** an SLF4J implementation and does not provide an own implementation like JDA. Without it, you will not get any logging messages, including +stack traces, information about CommandSettings, warnings and much more. + ## On how to add this to your project ### Adding as a library You can download the .jar-file in [this directory](https://github.com/JohnnyJayJay/discord-api-command/tree/master/builds) and add it to the project @@ -26,16 +31,24 @@ You can download the .jar-file in [this directory](https://github.com/JohnnyJayJ 3. Eclipse: Right-click the jar: `Build Path -> Add To Build Path`

IntelliJ: Right-click the jar: `Add As Library` 4. Done. You can now use it. -### Adding Dependency in pom -If you use Maven, you can add this library even less complicated: Just add the following dependency in your `pom.xml`: +### Adding Dependency (Maven & Gradle) +For Maven, add this to your `pom.xml`: ```xml - - com.github.johnnyjayjay - CommandAPI - VERSION - + + + com.github.johnnyjayjay + CommandAPI + VERSION + + +``` + +For Gradle, add this to your `build.gradle`: +```gradle +dependencies { + compile "com.github.johnnyjayjay:CommandAPI:VERSION" +} ``` -And that's it! ## Getting started ### Wrting Commands @@ -108,17 +121,17 @@ public class ReportCommand extends AbstractCommand { // Error message } - @SubCommand(args = {this.MEMBER_MENTION}, moreArgs = true) // moreArgs: means that there has to be at least one more argument than specified. Useful for the report reason. + @SubCommand(args = {Regex.MEMBER_MENTION}, moreArgs = true) // moreArgs: means that there can be more arguments than specified. Useful for the report reason. public void onReport(CommandEvent event, Member member, TextChannel channel, String[] args) { // report the mentioned member } - @SubCommand(args = {"get", this.MEMBER_MENTION}) + @SubCommand(args = {"get", Regex.MEMBER_MENTION}) public void onReportsGet(CommandEvent event, Member member, TextChannel channel, String[] args) { // get the reports of the mentioned member } - @SubCommand(args = {"remove", this.MEMBER_MENTION}) + @SubCommand(args = {"remove", Regex.MEMBER_MENTION}) public void onReportsRemove(CommandEvent event, Member member, TextChannel channel, String[] args) { // remove the reports from the mentioned member } @@ -189,6 +202,7 @@ There are a few more features to `CommandSettings`, such as: - Setting a message that is displayed in case of an unknown command - Setting a command cooldown (and specifying whether the cooldown should be reset on each execution attempt) - Setting a Color for `DefaultHelpCommand` +- Adding listeners for Exceptions and unknown commands ### Exceptions This API should only throw one kind of exception, `CommandSetException` (except for explicitly thrown `IllegalArgumentException`s). @@ -197,24 +211,24 @@ If this is **NOT** the case and you get another exception thrown by anything ins `CommandSetException` is a sub class of `RuntimeException`, meaning that they don't have to be caught and they don't terminate the program. A `CommandSetException` is thrown if: -- a label or a prefix does not match the requirements, i.e. the regex defined in `CommandSettings.VALID_PREFIX` and `CommandSettings.VALID_LABEL`. This includes: - - prefixes that contain one or more of the characters `\+*^|$?` or are just an empty String +- a label or a prefix does not match the requirements. This includes: + - prefixes that are empty - labels that contain any kind of blank spaces or are just an empty String - an instance of `CommandSettings` is activated or deactivated twice (which is not possible) - any other settings input for `CommandSettings` is invalid -If you don't want to have any exceptions concerning prefixes and labels, it is recommended to check whether they match `CommandSettings.VALID_PREFIX` -or `CommandSettings.VALID_LABEL`. +If you don't want to have any exceptions concerning prefixes and labels, you should check for the prefix not to be empty and for the label to match `Util.VALID_LABEL`. +`com.github.johnnyjayjay.discord.commandapi.Util` is a class that contains some regular expressions as `public static final String`s. ```java -String prefix = // user input or something else you can't verify directly -if (prefix.matches(CommandSettings.VALID_PREFIX)) { +String label = // user input or something else you can't verify directly +if (label.matches(Util.VALID_LABEL)) { // ... } else { // Tell the user } ``` -With sub commands, you can even set `CommandSettings.VALID_PREFIX`/`CommandSettings.VALID_LABEL` as an argument regular expression. +With sub commands, you can even set `CommandSettings.VALID_LABEL` as an argument regular expression (if it ever comes to that). ## Contributing If you think this framework is missing a feature and you think you're able to write it yourself, fork this repository and go for it. diff --git a/changelog.md b/changelog.md index a2f4a96..9207982 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,22 @@ # Changelog +### 3.2_01 +- Increased performance of CommandListener slightly +- Added configurable Predicate to `CommandSettings` which tests an event before execution. This may be useful for own checks that are not provided by this framework +- Added "listeners" for unknown commands (`CommandSettings#onUnknownCommand(Consumer)`) and Throwables (`CommandSettings#onException(BiConsumer)`) +- Added possibility to configure a custom thread pool (`CommandSettings#useMultiThreading(ExecutorService)`) +- Added configurable messages that are sent in case someone is on cooldown or a command is unknown +- Added possibility to deactivate Exception logging (`CommandSettings#setLogExceptions(boolean)`) +- Added Regex util class (mainly for `AbstractCommand`) +- Added `CommandEvent.Command#getPrefix()` to get the prefix used in a command +- Finally fixed invalid prefixes; any String that is not empty can be a prefix now +- Changed Command parsing +- Adjusted `AbstractCommand`'s code - made it more stream-like +- Fixed bug in `DefaultHelpCommand` that prevented the general help message from working +- Corrected mistake in documentation of `SubCommand#moreArgs()` +- Updated JDA version to 3.8.0_433 +- Updated readme + ### 3.2 - Updated to JDA version 3.7.1_387 - Adjusted cooldown system: it is now configurable whether the cooldown will reset for each command execution attempt diff --git a/pom.xml b/pom.xml index 1be0d35..e9d6b20 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.github.johnnyjayjay CommandAPI jar - 3.2 + 3.2_01-3-SNAPSHOT @@ -56,6 +56,7 @@ attach-javadocs + deploy jar @@ -132,13 +133,14 @@ junit junit - RELEASE + 4.12 test net.dv8tion JDA - 3.7.1_387 + 3.8.0_433 + compile \ No newline at end of file diff --git a/src/examples/java/CustomPrefixCommand.java b/src/examples/java/CustomPrefixCommand.java index 683de05..2c4131a 100644 --- a/src/examples/java/CustomPrefixCommand.java +++ b/src/examples/java/CustomPrefixCommand.java @@ -1,7 +1,4 @@ -import com.github.johnnyjayjay.discord.commandapi.AbstractCommand; -import com.github.johnnyjayjay.discord.commandapi.CommandEvent; -import com.github.johnnyjayjay.discord.commandapi.CommandSettings; -import com.github.johnnyjayjay.discord.commandapi.SubCommand; +import com.github.johnnyjayjay.discord.commandapi.*; import net.dv8tion.jda.core.Permission; import net.dv8tion.jda.core.entities.Member; import net.dv8tion.jda.core.entities.TextChannel; @@ -14,8 +11,7 @@ public class CustomPrefixCommand extends AbstractCommand { @SubCommand(isDefault = true, botPerms = {Permission.MESSAGE_WRITE}) public void everythingElse(CommandEvent event, Member member, TextChannel channel, String[] args) { - event.respond("Correct usage: `" + event.getCommandSettings().getPrefix(event.getGuild().getIdLong()) + "prefix [get|set] `\n" + - "If you set a new prefix, it has to be valid, i.e. match this regex: " + CommandSettings.VALID_PREFIX); + event.respond("Correct usage: `" + event.getCommandSettings().getPrefix(event.getGuild().getIdLong()) + "prefix [get|set] `"); } @SubCommand(args = {"get"}, botPerms = {Permission.MESSAGE_WRITE}) @@ -24,16 +20,13 @@ public void getPrefix(CommandEvent event, Member member, TextChannel channel, St event.respond("The prefix for this guild is: `" + settings.getPrefix(event.getGuild().getIdLong()) + "`\nThe default prefix is: `" + settings.getPrefix() + "`"); } - @SubCommand(args = {"set", "custom", CommandSettings.VALID_PREFIX}, botPerms = {Permission.MESSAGE_WRITE}) + @SubCommand(args = {"set", "custom", ".*"}, moreArgs = true, botPerms = {Permission.MESSAGE_WRITE}) public void setCustomPrefix(CommandEvent event, Member member, TextChannel channel, String[] args) { - if (args[2].matches(CommandSettings.VALID_PREFIX)) { - event.getCommandSettings().setCustomPrefix(event.getGuild().getIdLong(), args[2]); - event.respond("Successfully set prefix for this guild to `" + args[2] + "`!"); - } else - event.respond("You need to specify a valid prefix as the third argument!"); + event.getCommandSettings().setCustomPrefix(event.getGuild().getIdLong(), args[2]); + event.respond("Successfully set prefix for this guild to `" + args[2] + "`!"); } - @SubCommand(args = {"set", "default", CommandSettings.VALID_PREFIX}, botPerms = {Permission.MESSAGE_WRITE}) + @SubCommand(args = {"set", "default", ".*"}, moreArgs = true, botPerms = {Permission.MESSAGE_WRITE}) public void setDefaultPrefix(CommandEvent event, Member member, TextChannel channel, String[] args) { event.getCommandSettings().setDefaultPrefix(args[2]); event.respond("Successfully set default prefix to `" + args[2] + "`!"); diff --git a/src/examples/java/Main.java b/src/examples/java/Main.java index 955c246..7e325e3 100644 --- a/src/examples/java/Main.java +++ b/src/examples/java/Main.java @@ -14,9 +14,10 @@ public class Main { public static void main(String[] args) throws LoginException, InterruptedException { JDA jda = new JDABuilder(AccountType.BOT).setToken(Secrets.TOKEN).buildBlocking(); - // default prefix shall be "!" and we want the labels to be case insensitive. + // default prefix shall be "!!" and we want the labels to be case insensitive. new CommandSettings("!!", jda, true).setCooldown(3000) // commands can only be executed every 3 seconds now .put(new PingCommand(), "ping", "p") + .put(new DefaultHelpCommand(), "help") // registering help command .put(new CustomPrefixCommand(), "prefix") .activate(); // Activating! Very important! } diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java index 16459d5..21acae8 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java @@ -24,19 +24,6 @@ */ public abstract class AbstractCommand implements ICommand { - /** - * A regex to match member mentions. - */ - protected final String MEMBER_MENTION = "<@!?\\d+>"; - /** - * A regex to match role mentions. - */ - protected final String ROLE_MENTION = "<&\\d+>"; - /** - * A regex to match text channel mentions. - */ - protected final String CHANNEL_MENTION = "<#\\d+>"; - private final Map subCommands; /** @@ -68,7 +55,7 @@ protected AbstractCommand() { @Override public final void onCommand(CommandEvent event, Member member, TextChannel channel, String[] args) { CommandSettings settings = event.getCommandSettings(); - Optional matchesArgs = subCommands.keySet().stream() + subCommands.keySet().stream() .filter((sub) -> !sub.isDefault()) .filter((sub) -> sub.args().length == args.length || (sub.moreArgs() && args.length > sub.args().length)) .filter((sub) -> { @@ -80,23 +67,30 @@ public final void onCommand(CommandEvent event, Member member, TextChannel chann } return true; }) - .filter((sub) -> event.checkBotPermissions(sub.botPerms())).findFirst(); - if (matchesArgs.isPresent()) { + .filter((sub) -> event.checkBotPermissions(sub.botPerms())).findFirst().map(Optional::of) + .orElseGet(() -> subCommands.keySet().stream().filter(SubCommand::isDefault).filter((sub) -> event.checkBotPermissions(sub.botPerms())).findFirst()) + .map(subCommands::get).ifPresent((method) -> this.invokeMethod(method, event, member, channel, args)); + /*if (matchesArgs.isPresent()) { this.invokeMethod(subCommands.get(matchesArgs.get()), event, member, channel, args); } else { - subCommands.keySet().stream().filter((sub) -> event.checkBotPermissions(sub.botPerms())) - .filter(SubCommand::isDefault).findFirst().map(subCommands::get) + subCommands.keySet().stream().filter(SubCommand::isDefault).filter((sub) -> event.checkBotPermissions(sub.botPerms())) + .findFirst().map(subCommands::get) .ifPresent((method) -> this.invokeMethod(method, event, member, channel, args)); - } + }*/ } private void invokeMethod(Method method, CommandEvent event, Member member, TextChannel channel, String[] args) { try { method.invoke(this, event, member, channel, args); } catch (IllegalAccessException e) { - CommandSettings.LOGGER.error("An Exception occurred while trying to invoke sub command method; Please report this in a github issue. https://github.com/JohnnyJayJay/discord-api-command/issues", e); + CommandSettings.LOGGER.error("An unexpected Exception occurred while trying to invoke sub command method; Please report this in a github issue. https://github.com/JohnnyJayJay/discord-api-command/issues", e); } catch (InvocationTargetException e) { - CommandSettings.LOGGER.warn("One of the commands had an uncaught exception:", e.getCause()); + CommandSettings settings = event.getCommandSettings(); + Throwable cause = e.getCause(); + if (settings.isLogExceptions()) { + CommandSettings.LOGGER.warn("Command " + event.getCommand().getExecutor().getClass().getName() + " had an uncaught Exception in SubCommand " + method.getName() + ":", cause); + } + settings.onException(event, cause); } } diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractHelpCommand.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractHelpCommand.java index 6a806e7..1ff56b3 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractHelpCommand.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractHelpCommand.java @@ -12,7 +12,7 @@ * This class implements ICommand, therefore each sub class can be added as a normal command with CommandSettings#put. * The framework provides a default implementation of this class, DefaultHelpCommand. * @author JohnnyJayJay - * @version 3.2 + * @version 3.2_01 * @since 3.2 * @see DefaultHelpCommand */ diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java index 0c788ec..35e007c 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java @@ -15,12 +15,14 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.regex.Pattern; +// TODO: 31.08.2018 GenericCommandEvent, CommandFailureEvent (mit reason), CommandOnCooldownEvent, CommandUnknownEvent /** * Represents a command event. This is not much different from a GuildMessageReceivedEvent, though it gives access to the called command * and provides several utilities to work with, such as the getFirstMention-methods * @author Johnny_JayJay - * @version 3.2 + * @version 3.2_01 * @see GuildMessageReceivedEvent */ public class CommandEvent extends GuildMessageReceivedEvent { @@ -39,7 +41,7 @@ public CommandEvent(JDA api, long responseNumber, Message message, Command comma * @param msg The message to respond with as a String. */ public void respond(String msg) { - if (checkBotPermissions(Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS)) + if (checkBotPermissions(Permission.MESSAGE_WRITE)) this.getChannel().sendMessage(msg).queue(); } @@ -107,6 +109,7 @@ public Optional getFirstMention(Message.MentionType... m * @return An Optional of the first mentioned Member in the event message. * @see Optional */ + @SuppressWarnings("unchecked") public Optional getFirstUserMention() { return (Optional) getFirstMention(Message.MentionType.USER); } @@ -116,6 +119,7 @@ public Optional getFirstUserMention() { * @return An Optional of the first mentioned Role in the event message. * @see Optional */ + @SuppressWarnings("unchecked") public Optional getFirstRoleMention() { return (Optional) getFirstMention(Message.MentionType.ROLE); } @@ -125,6 +129,7 @@ public Optional getFirstRoleMention() { * @return An Optional of the first mentioned TextChannel in the event message. * @see Optional */ + @SuppressWarnings("unchecked") public Optional getFirstChannelMention() { return (Optional) getFirstMention(Message.MentionType.CHANNEL); } @@ -155,7 +160,7 @@ protected static Command parseCommand(String raw, String prefix, CommandSettings * Describes an executed Command.

* Is used to parse a message which seems to be a command. * @author Johnny_JayJay - * @version 3.1_1 + * @version 3.2_01 */ public static class Command { @@ -163,25 +168,27 @@ public static class Command { private final String joinedArgs; private final String rawArgs; private final String rawMessage; + private final String prefix; private final String label; private final String[] args; private Command(String raw, String prefix, CommandSettings settings) { - String[] argsWithoutPrefix = raw.replaceFirst(prefix, "").split("\\s+"); + String[] argsWithoutPrefix = raw.replaceFirst(Pattern.quote(prefix), "").split("\\s+"); this.label = settings.isLabelIgnoreCase() ? argsWithoutPrefix[0].toLowerCase() : argsWithoutPrefix[0];; - if (!settings.getCommands().containsKey(this.label)) { - this.command = null; - this.joinedArgs = null; - this.rawMessage = null; - this.rawArgs = null; - this.args = null; - } else { - this.command = settings.getCommands().get(this.label); - this.rawMessage = raw; - this.args = Arrays.copyOfRange(argsWithoutPrefix, 1, argsWithoutPrefix.length); - this.joinedArgs = String.join(" ", this.args); - this.rawArgs = raw.replaceFirst(prefix + this.label + "\\s+", ""); - } + this.command = settings.getCommands().getOrDefault(this.label, null); + this.rawMessage = raw; + this.prefix = prefix; + this.args = Arrays.copyOfRange(argsWithoutPrefix, 1, argsWithoutPrefix.length); + this.joinedArgs = String.join(" ", this.args); + this.rawArgs = raw.replaceFirst(Pattern.quote(prefix + this.label) + "\\s+", ""); + } + + /** + * Returns the prefix used to call this command. + * @return The prefix. + */ + public String getPrefix() { + return prefix; } /** diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java index ee73528..623accb 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java @@ -3,14 +3,15 @@ import net.dv8tion.jda.core.Permission; import net.dv8tion.jda.core.entities.Message; import net.dv8tion.jda.core.entities.TextChannel; +import net.dv8tion.jda.core.events.Event; import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent; -import net.dv8tion.jda.core.hooks.ListenerAdapter; +import net.dv8tion.jda.core.hooks.EventListener; import java.util.HashMap; import java.util.Map; -class CommandListener extends ListenerAdapter { +class CommandListener implements EventListener { private CommandSettings settings; private Map cooldowns; // Long: User id, Long: last timestamp @@ -21,34 +22,54 @@ public CommandListener(CommandSettings settings) { } @Override - public void onGuildMessageReceived(GuildMessageReceivedEvent event) { + public void onEvent(Event e) { + if (!(e instanceof GuildMessageReceivedEvent)) + return; + + GuildMessageReceivedEvent event = (GuildMessageReceivedEvent) e; TextChannel channel = event.getChannel(); + // if channel is not blacklisted and author is human (or bot execution is enabled) if (!settings.getBlacklistedChannels().contains(channel.getIdLong()) && (!event.getAuthor().isBot() || settings.botsMayExecute())) { - String raw = event.getMessage().getContentRaw(); - String prefix = settings.getPrefix(event.getGuild().getIdLong()); - if (raw.startsWith(prefix)) { - long timestamp = System.currentTimeMillis(); - long userId = event.getAuthor().getIdLong(); - if (cooldowns.containsKey(userId) && (timestamp - cooldowns.get(userId)) < settings.getCooldown()) { - if (settings.isResetCooldown()) + // if custom thread pool is configured: run async + settings.execute(() -> { + String raw = event.getMessage().getContentRaw(); + String prefix = settings.getPrefix(event.getGuild().getIdLong()); + if (raw.startsWith(prefix)) { + CommandEvent.Command cmd = CommandEvent.parseCommand(raw, prefix, settings); // parse command + CommandEvent commandEvent = new CommandEvent(event.getJDA(), event.getResponseNumber(), event.getMessage(), cmd, settings); // create event + if (cmd.getExecutor() != null) { // if command exists + // care about cooldowns + long timestamp = System.currentTimeMillis(); + long userId = event.getAuthor().getIdLong(); + if (cooldowns.containsKey(userId) && (timestamp - cooldowns.get(userId)) < settings.getCooldown()) { + if (settings.isResetCooldown()) + cooldowns.put(userId, timestamp); + Message cooldownMessage = settings.getCooldownMessage(); + if (cooldownMessage != null) + commandEvent.respond(cooldownMessage); + return; + } cooldowns.put(userId, timestamp); - return; - } - cooldowns.put(userId, timestamp); - CommandEvent.Command cmd = CommandEvent.parseCommand(raw, prefix, settings); - if (cmd.getExecutor() != null) { - try { - cmd.getExecutor().onCommand(new CommandEvent(event.getJDA(), event.getResponseNumber(), event.getMessage(), cmd, settings), - event.getMember(), channel, cmd.getArgs()); - } catch (Throwable t) { - CommandSettings.LOGGER.warn("One of the commands had an uncaught exception:", t); + // execute command + try { + if (settings.mayCall(commandEvent)) { + cmd.getExecutor().onCommand(commandEvent, event.getMember(), channel, cmd.getArgs()); + } + } catch (Throwable t) { + settings.onException(commandEvent, t); + if (settings.isLogExceptions()) { + CommandSettings.LOGGER.warn("Command " + cmd.getExecutor().getClass().getName() + " had an uncaught exception:", t); + } + } + } else { // command is unknown + settings.onUnknownCommand(commandEvent); + // TODO: 03.10.2018 das entfernen + Message unknownCommand = settings.getUnknownCommandMessage(); + if (unknownCommand != null) + commandEvent.respond(unknownCommand); } - } else { - Message unknownCommand = settings.getUnknownCommandMessage(); - if (unknownCommand != null && event.getGuild().getSelfMember().hasPermission(channel, Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS)) - channel.sendMessage(unknownCommand).queue(); } - } + }); } } } diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSetException.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSetException.java index eee8a7b..08e8d40 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSetException.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSetException.java @@ -4,7 +4,7 @@ * Exception that is thrown in case of any problems concerning the CommandSettings.
* CommandSetExceptions are RuntimeExceptions. * @author Johnny_JayJay - * @version 3.2 + * @version 3.2_01 * @since 1.6 * @see RuntimeException */ diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java index a13620d..8a02abe 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java @@ -16,24 +16,33 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.stream.Collectors; + /** * To use this framework, create a new object of this class and add your command classes by using add(...)

* When you want your commands to become active, use activate() * @author Johnny_JayJay - * @version 3.2 + * @version 3.2_01 * @since 1.1 */ public class CommandSettings { /** * A regex that only matches valid prefixes. Can be used to check user input. + * @deprecated Not needed anymore. Anything is a valid prefix now. */ + @Deprecated public static final String VALID_PREFIX = "[^\\\\+*^|$?]+"; /** * A regex that only matches valid labels. Can be used to check user input. + * @deprecated Use {@link Util#VALID_LABEL} instead */ + @Deprecated public static final String VALID_LABEL = "[^\\s]+"; // TODO: 05.08.2018 illegal prefix characters @@ -42,14 +51,22 @@ public class CommandSettings { * The logger of this framework. This is protected. */ protected static final Logger LOGGER = LoggerFactory.getLogger("CommandAPI"); - - private final String INVALID_PREFIX_MESSAGE = "Prefix cannot be empty or contain the characters +*^|$\\?"; - private final String INVALID_LABEL_MESSAGE = "Label cannot be empty, consist of multiple words or contain new lines!"; - private Message unknownCommandMessage; - private String defaultPrefix; - private long cooldown; + private final String INVALID_PREFIX_MESSAGE = "Prefix must not be empty!"; + private final String INVALID_LABEL_MESSAGE = "Label must not be empty, consist of multiple words or contain new lines!"; + + private final boolean labelIgnoreCase; // case doesnt matter + private boolean useShardManager; // effectively final + + private Message unknownCommandMessage; // message sent if on cooldown + private Message cooldownMessage; // message sent if command unknown + private String defaultPrefix; // default prefix + private long cooldown; // cooldown private Color helpColor; + private Predicate check; // check made before every command execution + private Consumer unknownCommandHandler; // called if command unknown + private BiConsumer exceptionHandler; // called if uncaught exception + private ExecutorService executorService; // thread pool private final Set blacklistedChannels; // ids of those channels where no command will trigger this api to execute anything. @Deprecated @@ -63,11 +80,10 @@ public class CommandSettings { private final CommandListener listener; private boolean activated; // ...is this instance activated? - private boolean useShardManager; - private boolean labelIgnoreCase; - private boolean resetCooldown; - private boolean botExecution; + private boolean resetCooldown; // reset cooldown each execution + private boolean botExecution; // bots may execute commands + private boolean logExceptions; // uncaught exceptions are logged /** @@ -111,6 +127,13 @@ private CommandSettings(@Nonnull String defaultPrefix, boolean labelIgnoreCase) this.helpLabels = new HashSet<>(); this.blacklistedChannels = new HashSet<>(); this.prefixMap = new HashMap<>(); + this.check = (e) -> true; + this.unknownCommandHandler = (e) -> {}; + this.exceptionHandler = (e, t) -> {}; + this.executorService = null; + this.unknownCommandMessage = null; + this.cooldownMessage = null; + this.logExceptions = true; } /** @@ -216,6 +239,25 @@ public CommandSettings clearHelpLabels() { return this; } + /** + * Sets an ExecutorService that is used to execute commands asynchronously. This may, of course, increase performance. + * By default, this framework uses the JDA event threads. + * @param executorService The ExecutorService that should provide threads + * @return The current object. This is to use fluent interface. + * @throws CommandSetException If the provided ExecutorService is shut down + */ + public CommandSettings useMultiThreading(@Nullable ExecutorService executorService) { + if (executorService == null) { + this.executorService = null; + } else { + if (executorService.isShutdown()) + throw new CommandSetException("Provided ExecutorService is invalid", new IllegalArgumentException("ExecutorService must not be shut down", new IllegalStateException("Illegal thread pool state"))); + + this.executorService = executorService; + } + return this; + } + /** * Adds a given channel to the blacklist (meaning commands can not be executed in there). * @param channelId the id of the channel to be blacklisted. @@ -242,7 +284,7 @@ public CommandSettings addChannelsToBlacklist(long... channelIds) { * @param channelIds A Collection of channel ids to be added. * @return The current object. This is to use fluent interface. */ - public CommandSettings addChannelsToBlacklist(Collection channelIds) { + public CommandSettings addChannelsToBlacklist(@Nonnull Collection channelIds) { this.blacklistedChannels.addAll(channelIds); return this; } @@ -276,7 +318,7 @@ public boolean removeChannelsFromBlacklist(long... channelIds) { * @param channelIds the Collection to remove. * @return true, if this was successful, false, if not. */ - public boolean removeChannelsFromBlackList(Collection channelIds) { + public boolean removeChannelsFromBlackList(@Nonnull Collection channelIds) { return this.blacklistedChannels.removeAll(channelIds); } @@ -298,8 +340,8 @@ public CommandSettings clearBlacklist() { * @return The current object. This is to use fluent interface. * @throws CommandSetException If the label is empty or consists of multiple words. */ - public CommandSettings put(@Nonnull ICommand executor, String label) { - if (label.matches(VALID_LABEL)) + public CommandSettings put(@Nonnull ICommand executor, @Nonnull String label) { + if (label.matches(Util.VALID_LABEL)) this.commands.put(labelIgnoreCase ? label.toLowerCase() : label, executor); else throw new CommandSetException(INVALID_LABEL_MESSAGE, new IllegalArgumentException("Label " + label + " is not valid")); @@ -338,7 +380,7 @@ public CommandSettings put(@Nonnull ICommand executor, @Nonnull Collection true; + this.exceptionHandler = (e, t) -> {}; + this.unknownCommandHandler = (e) -> {}; if (this.activated) this.deactivate(); return this; } + /** + * Sets a Predicate that tests every CommandEvent before executing it. This may be useful für own cooldown implementations and such. + * By default, this Predicate always returns true. + * @param check The Predicate to use as a check. + * @return The current object. This is to use fluent interface. + */ + public CommandSettings setCheck(@Nonnull Predicate check) { + this.check = check; + return this; + } + /** * Use this method to set the default prefix. * @param prefix The prefix to set. In case the given String is empty, this will throw a CommandSetException. * @return The current object. This is to use fluent interface. - * @throws CommandSetException if a non-null prefix does not match the requirements for a valid prefix. + * @throws CommandSetException if a non-null prefix is empty. */ public CommandSettings setDefaultPrefix(@Nonnull String prefix) { - if (prefix.matches(VALID_PREFIX)) + if (!prefix.isEmpty()) this.defaultPrefix = prefix; - else - throw new CommandSetException(INVALID_PREFIX_MESSAGE, new IllegalArgumentException("Prefix " + prefix + " is not valid")); + else { + throw new CommandSetException("Prefix is not valid", new IllegalArgumentException(INVALID_PREFIX_MESSAGE)); + } return this; } @@ -412,24 +472,21 @@ public CommandSettings setDefaultPrefix(@Nonnull String prefix) { * @see MessageBuilder */ public CommandSettings setUnknownCommandMessage(@Nullable Message message) { - if (message == null) - this.unknownCommandMessage = null; - else - this.unknownCommandMessage = new MessageBuilder(message).build(); + this.unknownCommandMessage = message == null ? null : new MessageBuilder(message).build(); return this; } /** - * Use this method to add a custom command prefix to a guild. This will only work if you instantiated this class with useCustomPrefixes set to true. + * Use this method to add a custom command prefix to a guild. * You can remove the custom prefix from a guild by setting its prefix to null. * @param guildId The guild id as a long. * @param prefix The nullable prefix to be set. * @return The current object. This is to use fluent interface. - * @throws CommandSetException if a non-null prefix does not match the requirements for a valid prefix. + * @throws CommandSetException if a non-null prefix is empty. */ public CommandSettings setCustomPrefix(long guildId, @Nullable String prefix) { - if (prefix != null && !prefix.matches(VALID_PREFIX)) - throw new CommandSetException(INVALID_PREFIX_MESSAGE, new IllegalArgumentException("Prefix " + prefix + " is not valid")); + if (prefix != null && prefix.isEmpty()) + throw new CommandSetException("Prefix is not valid", new IllegalArgumentException(INVALID_PREFIX_MESSAGE)); this.prefixMap.put(guildId, prefix); return this; } @@ -439,13 +496,13 @@ public CommandSettings setCustomPrefix(long guildId, @Nullable String prefix) { * prefixes for, because this bulk adds the Map parameter. * @param guildIdPrefixMap A Map which contains the prefix for each guild to add. Key: guild ID (Long), Value: prefix (String) * @return The current object. This is to use fluent interface. - * @throws CommandSetException if one of the prefixes is not valid. + * @throws CommandSetException if one of the prefixes is empty. */ public CommandSettings setCustomPrefixes(@Nonnull Map guildIdPrefixMap) { - if (guildIdPrefixMap.values().stream().allMatch((prefix) -> prefix.matches(VALID_PREFIX))) + if (guildIdPrefixMap.values().stream().noneMatch(String::isEmpty)) prefixMap.putAll(guildIdPrefixMap); else - throw new CommandSetException("One or more of the prefixes is not valid: " + INVALID_PREFIX_MESSAGE, new IllegalArgumentException("Invalid prefix")); + throw new CommandSetException("Prefix is not valid", new IllegalArgumentException(INVALID_PREFIX_MESSAGE)); return this; } @@ -482,6 +539,55 @@ public CommandSettings setResetCooldown(boolean resetCooldown) { return this; } + /** + * Specifies whether uncaught Exceptions that occur in command execution should be logged. You might want to disable this + * if you handle Exceptions manually or in the onException-listener. By default, this is true. + * @param logExceptions true, if uncaught Exceptions should be logged. False, if not. + * @return The current object. This is to use fluent interface. + * @see this#onException(BiConsumer) + */ + public CommandSettings setLogExceptions(boolean logExceptions) { + this.logExceptions = logExceptions; + return this; + } + + + /** + * Sets a Consumer that will accept any command execution attempt in which the command label is unknown. + * @param action a Consumer that takes the event. + * @return The current object. This is to use fluent interface. + */ + public CommandSettings onUnknownCommand(@Nonnull Consumer action) { + this.unknownCommandHandler = action; + return this; + } + + /** + * Sets a BiConsumer that will accept any command execution attempt in which an uncaught Exception occurs. + * @param action a BiConsumer that takes the event and the corresponding Throwable that had been thrown. + * @return The current object. This is to use fluent interface. + */ + public CommandSettings onException(@Nonnull BiConsumer action) { + this.exceptionHandler = action; + return this; + } + + /** + * Sets a message that will be sent in case someone is on cooldown. Generally speaking, it is rather recommended to use your own cooldown + * implementation for specific cases like that. Still, it is possible. + * Setting this to null removes the message. Nothing will be sent then. + * @param message Nullable Message object that will be wrapped in a new MessageBuilder to prevent the usage of already sent Messages. If this is null, the message is deactivated. + * @return The current object. This is to use fluent interface. + * @see MessageBuilder + */ + public CommandSettings setCooldownMessage(@Nullable Message message) { + if (message == null) + this.cooldownMessage = null; + else + this.cooldownMessage = new MessageBuilder(message).build(); + return this; + } + /** * Setter for the field botExecution. Decides whether bots may execute commands. By default, this is NOT the case. * @param botExecution true, if you want to allow bots to execute commands. false, if not. @@ -589,7 +695,7 @@ public Set getLabelSet() { * @param command The {@link com.github.johnnyjayjay.discord.commandapi.ICommand ICommand} instance to get the labels from * @return an unmodifiable Set of labels. */ - public Set getLabels(ICommand command) { + public Set getLabels(@Nonnull ICommand command) { return Collections.unmodifiableSet(this.commands.keySet().stream().filter((label) -> this.commands.get(label).equals(command)).collect(Collectors.toSet())); } @@ -652,6 +758,14 @@ public boolean botsMayExecute() { return this.botExecution; } + /** + * Returns whether uncaught command exceptions are being logged. + * @return True, if this is enabled. False, if not. + */ + public boolean isLogExceptions() { + return logExceptions; + } + /** * Transforms this instance to a String, showing the current set options. * @return This instance as a String. @@ -659,24 +773,57 @@ public boolean botsMayExecute() { @Override public String toString() { return "CommandSettings{" + - "unknownCommandMessage=" + unknownCommandMessage + + "labelIgnoreCase=" + labelIgnoreCase + + ", useShardManager=" + useShardManager + + ", unknownCommandMessage=" + unknownCommandMessage + + ", cooldownMessage=" + cooldownMessage + ", defaultPrefix='" + defaultPrefix + '\'' + ", cooldown=" + cooldown + ", helpColor=" + helpColor + + ", check=" + check + + ", unknownCommandHandler=" + unknownCommandHandler + + ", exceptionHandler=" + exceptionHandler + + ", executorService=" + executorService + ", blacklistedChannels=" + blacklistedChannels + ", prefixMap=" + prefixMap + ", commands=" + commands + + ", jda=" + jda + + ", listener=" + listener + ", activated=" + activated + - ", labelIgnoreCase=" + labelIgnoreCase + ", resetCooldown=" + resetCooldown + ", botExecution=" + botExecution + + ", logExceptions=" + logExceptions + '}'; } + + protected void onUnknownCommand(CommandEvent event) { + this.unknownCommandHandler.accept(event); + } + + protected void onException(CommandEvent event, Throwable throwable) { + this.exceptionHandler.accept(event, throwable); + } + protected Message getUnknownCommandMessage() { return unknownCommandMessage; } + protected Message getCooldownMessage() { + return cooldownMessage; + } + + protected boolean mayCall(CommandEvent event) { + return check.test(event); + } + + protected void execute(Runnable command) { + if (executorService != null) + executorService.execute(command); + else + command.run(); // is das gut? keine ahnung + } + protected Map getCommands() { return this.commands; } diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/DefaultHelpCommand.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/DefaultHelpCommand.java index a5a1e89..c00750a 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/DefaultHelpCommand.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/DefaultHelpCommand.java @@ -15,7 +15,7 @@ * If you want to use this, add a new instance of this class as a command in your CommandSettings with the put-method. * This class is final. To create your own help command implementation, please refer to AbstractHelpCommand. * @author JohnnyJayJay - * @version 3.2 + * @version 3.2_01 * @since 3.2 * @see AbstractHelpCommand */ @@ -29,7 +29,7 @@ public final class DefaultHelpCommand extends AbstractHelpCommand { @Override public void provideGeneralHelp(CommandEvent event, String prefix, Map commands) { Member selfMember = event.getGuild().getSelfMember(); - if (event.checkBotPermissions(Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS)) + if (!event.checkBotPermissions(Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS)) return; CommandSettings settings = event.getCommandSettings(); diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/ICommand.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/ICommand.java index 75745b1..09e60a5 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/ICommand.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/ICommand.java @@ -11,7 +11,7 @@ * An interface used to describe a command class. * In order to use this API, every class which is supposed to execute commands must implement this interface. * @author Johnny_JayJay - * @version 3.2 + * @version 3.2_01 * @since 1.1 * @see AbstractCommand * @see AbstractHelpCommand diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/SubCommand.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/SubCommand.java index b2d7bd5..8d59133 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/SubCommand.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/SubCommand.java @@ -13,7 +13,7 @@ * Methods annotated with this annotation must have the signature {@code void (com.github.johnnyjayjay.commandapi.CommandEvent, net.dv8tion.jda.core.entities.Member, net.dv8tion.jda.core.entities.TextChannel, java.lang.String[])} * in order to be registered (you will get a warning if a signature violates that). * @author JohnnyJayJay - * @version 3.2 + * @version 3.2_01 * @since 3.2 * @see AbstractCommand */ @@ -39,8 +39,8 @@ String[] args() default {}; /** - * A boolean that indicates that there must be more arguments than specified in args in order to be triggered. - * This might be useful for commands with an open argument length. If this is set to true, the command argument length must be greater than SubCommand#args().length. + * A boolean that indicates that there may be more arguments than specified in args in order to be triggered. + * This might be useful for commands with an open argument length. If this is set to true, the command argument length must be equal to or greater than SubCommand#args().length. * This is ignored if specified in an isDefault-SubCommand. * @return false by default. */ diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/Util.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/Util.java new file mode 100644 index 0000000..55ad610 --- /dev/null +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/Util.java @@ -0,0 +1,74 @@ +package com.github.johnnyjayjay.discord.commandapi; + +import net.dv8tion.jda.core.entities.Message; + +import java.util.function.BiConsumer; + +/** + * A util class with Strings that may be used as regular expressions (e.g. for {@link SubCommand SubCommands}). + * @author Johnny_JayJay + * @version 3.2_01 + * @since 3.2_01 + */ +public class Util { + + /** + * A regex that matches a raw Discord member (or user) mention + */ + public static final String MEMBER_MENTION = "<@!?\\d+>"; + /** + * A regex that matches a raw Discord text channel mention + */ + public static final String CHANNEL_MENTION = "<#\\d+>"; + /** + * A regex that matches a raw Discord role mention + */ + public static final String ROLE_MENTION = "<&\\d+>"; + /** + * A regex that matches one digit (0-9) + */ + public static final String DIGIT = "\\d"; + /** + * A regex that matches everything. + */ + public static final String ANYTHING = ".*"; + + /** + * A regex that only matches valid labels. Can be used to check user input. + */ + public static final String VALID_LABEL = "[^\\s]+"; + + /** + * Gets the stack trace of a Throwable as if it was displayed via {@code Throwable#printStackTrace()}. + * This might be useful in {@link CommandSettings#onException(BiConsumer)}. + * @param throwable The throwable to get the stack trace of + * @return the stack trace as a String or at least a part of the stack trace if it is too long. + */ + public static String getStackTraceAsString(Throwable throwable) { + StringBuilder builder = new StringBuilder().append("Exception in thread ").append(Thread.currentThread().getName()).append(" - ") + .append(throwable.getClass().getName()).append(": ").append(throwable.getMessage()); + StackTraceElement[] elements = throwable.getStackTrace(); + for (int i = 0; i < elements.length; i++) { + if (builder.length() > Message.MAX_CONTENT_LENGTH - 200) { + builder.append("\n\t...").append(elements.length - i).append(" more"); + return builder.toString(); + } + builder.append("\n\tat: ").append(elements[i]); + } + + causes: + while ((throwable = throwable.getCause()) != null) { + builder.append("\nCaused by: ").append(throwable.getClass().getName()).append(": ").append(throwable.getMessage()); + elements = throwable.getStackTrace(); + for (int i = 0; i < elements.length; i++) { + if (builder.length() > Message.MAX_CONTENT_LENGTH - 200) { + builder.append("\n\t...").append(elements.length - i).append(" more"); + break causes; + } + builder.append("\n\tat: ").append(elements[i]); + } + } + return builder.toString(); + } + +} diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/package-info.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/package-info.java index 047a848..73efc5d 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/package-info.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/package-info.java @@ -34,5 +34,7 @@ * {@link com.github.johnnyjayjay.discord.commandapi.CommandSetException CommandSetException} is a {@code RuntimeException} that is thrown in case of any problems with {@link com.github.johnnyjayjay.discord.commandapi.CommandSettings CommandSettings}. *

* Please read the readme on github and take a look at the examples to see how it works.
I will give this documentation a revision in the future to provide more examples and helpful code. + *

+ * https://www.github.com/JohnnyJayJay */ package com.github.johnnyjayjay.discord.commandapi; \ No newline at end of file diff --git a/src/test/java/com/github/johnnyjayjay/discord/commandapi/CommandSettingsTest.java b/src/test/java/com/github/johnnyjayjay/discord/commandapi/CommandSettingsTest.java index f09118e..864fcb3 100644 --- a/src/test/java/com/github/johnnyjayjay/discord/commandapi/CommandSettingsTest.java +++ b/src/test/java/com/github/johnnyjayjay/discord/commandapi/CommandSettingsTest.java @@ -13,7 +13,7 @@ /** * @author Johnny_JayJay - * @version 0.1-SNAPSHOT + * @version 3.2_01 */ public class CommandSettingsTest { @@ -38,14 +38,14 @@ public void before() { @Test public void setCustomPrefixTest() { for (int i = 0; i < 10000; i++) { - String prefix = randomString(true); + String prefix = randomString(); long id = random.nextLong(); settings.setCustomPrefix(id, prefix); assertEquals("One prefix was not set correctly (manual adding)", prefix, settings.getPrefix(id)); } Map prefixes = new HashMap<>(); for (int i = 0; i < 10000; i++) - prefixes.put(random.nextLong(), randomString(true)); + prefixes.put(random.nextLong(), randomString()); settings.setCustomPrefixes(prefixes); prefixes.forEach((id, prefix) -> assertEquals("One prefix was not set correctly (bulk adding)", prefix, settings.getPrefix(id))); } @@ -72,7 +72,7 @@ public void commandsTest() { ICommand command = (event, member, channel, args) -> {}; Set randomLabels = new HashSet<>(); for (int i = 0; i < 10000; i++) - randomLabels.add(randomString(false).toLowerCase()); + randomLabels.add(randomString().toLowerCase()); settings.put(command, randomLabels); Set actualLabels = settings.getLabels(command); assertEquals("Labels for command were not set correctly", actualLabels, randomLabels); @@ -107,11 +107,10 @@ public void activationTest() { } - private String randomString(boolean replaceIllegalPrefixChars) { + private String randomString() { int endIndex = random.nextInt(1, 36); int beginIndex = random.nextInt(endIndex); - String ret = UUID.randomUUID().toString().substring(beginIndex, endIndex); - return replaceIllegalPrefixChars ? ret.replaceAll(CommandSettings.VALID_PREFIX.replace("^", ""), "") : ret; + return UUID.randomUUID().toString().substring(beginIndex, endIndex); } } diff --git a/src/test/java/com/github/johnnyjayjay/discord/commandapi/JDAMock.java b/src/test/java/com/github/johnnyjayjay/discord/commandapi/JDAMock.java index c582978..512b410 100644 --- a/src/test/java/com/github/johnnyjayjay/discord/commandapi/JDAMock.java +++ b/src/test/java/com/github/johnnyjayjay/discord/commandapi/JDAMock.java @@ -22,6 +22,21 @@ */ public class JDAMock implements JDA { + @Override + public JDA awaitStatus(Status status) throws InterruptedException { + return null; + } + + @Override + public IEventManager getEventManager() { + return null; + } + + @Override + public RestAction getWebhookById(String s) { + return null; + } + @Override public Status getStatus() { return null;