From 0b8b8027d3d409840e98033b319b06453aca6dfe Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 24 Aug 2018 22:34:13 +0200 Subject: [PATCH 01/12] added some utility, increased performance --- README.md | 4 ++ changelog.md | 6 ++ .../discord/commandapi/AbstractCommand.java | 8 +-- .../discord/commandapi/CommandEvent.java | 2 +- .../discord/commandapi/CommandListener.java | 40 ++++++++---- .../discord/commandapi/CommandSettings.java | 64 +++++++++++++++++++ 6 files changed, 106 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 1801165..65bacdf 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,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 diff --git a/changelog.md b/changelog.md index a2f4a96..c01d970 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # Changelog +### next patch +- 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 configurable Message that is sent in case someone is on cooldown +- Added possibility to configure a custom thread pool + ### 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/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java index 16459d5..8c2ae34 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java @@ -84,8 +84,8 @@ public final void onCommand(CommandEvent event, Member member, TextChannel chann 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)); } } @@ -94,9 +94,9 @@ private void invokeMethod(Method method, CommandEvent event, Member member, Text 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.LOGGER.warn("Command " + event.getCommand().getExecutor().getClass().getName() + " had an uncaught Exception in SubCommand " + method.getName() + ":", e.getCause()); } } 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..e40f422 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java @@ -39,7 +39,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(); } 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..dc0023c 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,16 @@ 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.EventListener; import net.dv8tion.jda.core.hooks.ListenerAdapter; 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,7 +23,11 @@ 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 (!settings.getBlacklistedChannels().contains(channel.getIdLong()) && (!event.getAuthor().isBot() || settings.botsMayExecute())) { String raw = event.getMessage().getContentRaw(); @@ -32,22 +38,30 @@ public void onGuildMessageReceived(GuildMessageReceivedEvent event) { if (cooldowns.containsKey(userId) && (timestamp - cooldowns.get(userId)) < settings.getCooldown()) { if (settings.isResetCooldown()) cooldowns.put(userId, timestamp); + Message cooldownMessage = settings.getCooldownMessage(); + if (cooldownMessage != null && event.getGuild().getSelfMember().hasPermission(channel, Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS)) + channel.sendMessage(cooldownMessage).queue(); 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); + // TODO: 24.08.2018 Optimize Exception handling + settings.execute(() -> { + if (cmd.getExecutor() != null) { + try { + CommandEvent commandEvent = new CommandEvent(event.getJDA(), event.getResponseNumber(), event.getMessage(), cmd, settings); + if (settings.mayCall(commandEvent)) { + cmd.getExecutor().onCommand(commandEvent, event.getMember(), channel, cmd.getArgs()); + } + } catch (Throwable t) { + CommandSettings.LOGGER.warn("Command " + cmd.getExecutor().getClass().getName() + " had an uncaught exception:", t); + } + } else { + Message unknownCommand = settings.getUnknownCommandMessage(); + if (unknownCommand != null && event.getGuild().getSelfMember().hasPermission(channel, Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS)) + channel.sendMessage(unknownCommand).queue(); } - } 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/CommandSettings.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java index a13620d..93c552e 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java @@ -16,6 +16,10 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @@ -47,9 +51,12 @@ public class CommandSettings { private final String INVALID_LABEL_MESSAGE = "Label cannot be empty, consist of multiple words or contain new lines!"; private Message unknownCommandMessage; + private Message cooldownMessage; private String defaultPrefix; private long cooldown; private Color helpColor; + private Predicate check; + private ExecutorService executorService; private final Set blacklistedChannels; // ids of those channels where no command will trigger this api to execute anything. @Deprecated @@ -111,6 +118,8 @@ private CommandSettings(@Nonnull String defaultPrefix, boolean labelIgnoreCase) this.helpLabels = new HashSet<>(); this.blacklistedChannels = new HashSet<>(); this.prefixMap = new HashMap<>(); + this.check = (e) -> true; + this.executorService = Executors.newSingleThreadExecutor(); } /** @@ -216,6 +225,21 @@ public CommandSettings clearHelpLabels() { return this; } + /** + * Creates the ExecutorService for this framework with the provided pool size. This may, of course, increase performance. + * By default, this framework uses only one thread to execute commands. + * @param threadPoolSize The size of the thread pool to use. + * @return The current object. This is to use fluent interface. + * @throws CommandSetException If the provided size is <= 0. + */ + public CommandSettings useFixedThreadPoolSize(int threadPoolSize) { + if (threadPoolSize <= 0) + throw new CommandSetException("Provided thread pool size is invalid", new IllegalArgumentException("Thread pool size must not be <= 0")); + + this.executorService = Executors.newFixedThreadPool(threadPoolSize); + 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. @@ -386,11 +410,23 @@ public CommandSettings clear() { this.cooldown = 0; this.helpColor = null; this.resetCooldown = false; + this.check = (e) -> true; 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. @@ -482,6 +518,22 @@ public CommandSettings setResetCooldown(boolean resetCooldown) { 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. @@ -677,6 +729,18 @@ protected Message getUnknownCommandMessage() { return unknownCommandMessage; } + protected Message getCooldownMessage() { + return cooldownMessage; + } + + protected boolean mayCall(CommandEvent event) { + return check.test(event); + } + + protected void execute(Runnable command) { + executorService.execute(command); + } + protected Map getCommands() { return this.commands; } From be82a32410f1babdfbd59afc6d4cebd39cfaf59f Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 31 Aug 2018 16:33:34 +0200 Subject: [PATCH 02/12] A few more steps towards customization and accessibility Now working on Special events for Command execution failures to allow reactions to cases like being on cooldown or executing an unknown command. --- changelog.md | 3 ++ .../discord/commandapi/CommandEvent.java | 31 ++++++------ .../discord/commandapi/CommandListener.java | 47 ++++++++++--------- .../discord/commandapi/CommandSettings.java | 26 +++++++++- .../discord/commandapi/util/Regex.java | 15 ++++++ 5 files changed, 84 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/github/johnnyjayjay/discord/commandapi/util/Regex.java diff --git a/changelog.md b/changelog.md index c01d970..75069c1 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,9 @@ - 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 configurable Message that is sent in case someone is on cooldown - Added possibility to configure a custom thread pool +- Added Regex util class (mainly for `AbstractCommand`) +- Added `CommandEvent.Command#getPrefix()` +- Changed Command parsing ### 3.2 - Updated to JDA version 3.7.1_387 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 e40f422..a75e9cc 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java @@ -155,7 +155,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 */ public static class Command { @@ -163,25 +163,28 @@ 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; + // TODO: 31.08.2018 performanteste und best accessible methode finden private Command(String raw, String prefix, CommandSettings settings) { String[] argsWithoutPrefix = raw.replaceFirst(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(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 dc0023c..d1e0422 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java @@ -27,26 +27,27 @@ public void onEvent(Event e) { if (!(e instanceof GuildMessageReceivedEvent)) return; - GuildMessageReceivedEvent event = (GuildMessageReceivedEvent) e; - TextChannel channel = event.getChannel(); - 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()) - cooldowns.put(userId, timestamp); - Message cooldownMessage = settings.getCooldownMessage(); - if (cooldownMessage != null && event.getGuild().getSelfMember().hasPermission(channel, Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS)) - channel.sendMessage(cooldownMessage).queue(); - return; - } - cooldowns.put(userId, timestamp); - CommandEvent.Command cmd = CommandEvent.parseCommand(raw, prefix, settings); - // TODO: 24.08.2018 Optimize Exception handling - settings.execute(() -> { + settings.execute(() -> { + GuildMessageReceivedEvent event = (GuildMessageReceivedEvent) e; + TextChannel channel = event.getChannel(); + if (!settings.getBlacklistedChannels().contains(channel.getIdLong()) && (!event.getAuthor().isBot() || settings.botsMayExecute())) { + String raw = event.getMessage().getContentRaw(); + String prefix = settings.getPrefix(event.getGuild().getIdLong()); + // TODO: 31.08.2018 cooldown und unknown command consumer callen; lösung für invalid commands finden (evtl. extra event oder methode, die checkt, ob es ein label wirklich gibt) + if (raw.startsWith(prefix)) { + CommandEvent.Command cmd = CommandEvent.parseCommand(raw, prefix, settings); + 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 && event.getGuild().getSelfMember().hasPermission(channel, Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS)) + channel.sendMessage(cooldownMessage).queue(); + return; + } + cooldowns.put(userId, timestamp); + // TODO: 24.08.2018 Optimize Exception handling if (cmd.getExecutor() != null) { try { CommandEvent commandEvent = new CommandEvent(event.getJDA(), event.getResponseNumber(), event.getMessage(), cmd, settings); @@ -57,12 +58,14 @@ public void onEvent(Event e) { CommandSettings.LOGGER.warn("Command " + cmd.getExecutor().getClass().getName() + " had an uncaught exception:", t); } } 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/CommandSettings.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java index 93c552e..942dfd7 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java @@ -18,10 +18,12 @@ import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ThreadFactory; +import java.util.function.BiConsumer; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; +// TODO: 31.08.2018 Exceptions besser regeln /** * 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() @@ -56,6 +58,7 @@ public class CommandSettings { private long cooldown; private Color helpColor; private Predicate check; + private Consumer unknownCommand; private ExecutorService executorService; private final Set blacklistedChannels; // ids of those channels where no command will trigger this api to execute anything. @@ -119,6 +122,7 @@ private CommandSettings(@Nonnull String defaultPrefix, boolean labelIgnoreCase) this.blacklistedChannels = new HashSet<>(); this.prefixMap = new HashMap<>(); this.check = (e) -> true; + this.unknownCommand = (e) -> {}; this.executorService = Executors.newSingleThreadExecutor(); } @@ -232,7 +236,7 @@ public CommandSettings clearHelpLabels() { * @return The current object. This is to use fluent interface. * @throws CommandSetException If the provided size is <= 0. */ - public CommandSettings useFixedThreadPoolSize(int threadPoolSize) { + public CommandSettings useMultiThreading(int threadPoolSize) { if (threadPoolSize <= 0) throw new CommandSetException("Provided thread pool size is invalid", new IllegalArgumentException("Thread pool size must not be <= 0")); @@ -518,6 +522,15 @@ public CommandSettings setResetCooldown(boolean resetCooldown) { return this; } + // TODO: 31.08.2018 + public CommandSettings onUnknownCommand(Consumer consumer) { + return this; + } + + public CommandSettings onCooldownInvokeAttempt(BiConsumer consumer) { + 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. @@ -725,6 +738,15 @@ public String toString() { '}'; } + // TODO: 31.08.2018 + protected void onUnknownCommand(CommandEvent event) { + this.unknownCommand.accept(event); + } + + protected void onCooldownExecutionAttempt(CommandEvent event, long cooldown) { + + } + protected Message getUnknownCommandMessage() { return unknownCommandMessage; } diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/util/Regex.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/util/Regex.java new file mode 100644 index 0000000..9f38d16 --- /dev/null +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/util/Regex.java @@ -0,0 +1,15 @@ +package com.github.johnnyjayjay.discord.commandapi.util; + +/** + * https://www.github.com/JohnnyJayJay + * @author Johnny_JayJay + */ +public class Regex { + + public static final String MEMBER_MENTION = "<@!?\\d+>"; + public static final String CHANNEL_MENTION = "<#\\d+>"; + public static final String ROLE_MENTION = "<&\\d+>"; + public static final String DIGIT = "\\d"; + public static final String ANYTHING = ".*"; + +} From 24505c3bbccb680a15b23a2a7c3d6d606613c595 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 31 Aug 2018 16:36:46 +0200 Subject: [PATCH 03/12] added todos for the next steps --- .../com/github/johnnyjayjay/discord/commandapi/CommandEvent.java | 1 + 1 file changed, 1 insertion(+) 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 a75e9cc..78ea587 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Optional; +// 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 From 3e17ef729b40fb58c97a4fae2dbc1d13c51fbdef Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 3 Oct 2018 23:20:20 +0200 Subject: [PATCH 04/12] Few more steps towards 3.2_01 Fully implemented unknown command and exception listeners, multi threading Adjusted a few things --- changelog.md | 6 +- pom.xml | 2 +- .../discord/commandapi/AbstractCommand.java | 3 +- .../discord/commandapi/CommandEvent.java | 3 + .../discord/commandapi/CommandListener.java | 43 +++++++------- .../discord/commandapi/CommandSettings.java | 57 ++++++++++++------- .../commandapi/DefaultHelpCommand.java | 1 + .../discord/commandapi/SubCommand.java | 4 +- .../discord/commandapi/util/Regex.java | 2 + 9 files changed, 73 insertions(+), 48 deletions(-) diff --git a/changelog.md b/changelog.md index 75069c1..4566870 100644 --- a/changelog.md +++ b/changelog.md @@ -3,11 +3,13 @@ ### next patch - 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 configurable Message that is sent in case someone is on cooldown -- Added possibility to configure a custom thread pool +- 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 Regex util class (mainly for `AbstractCommand`) - Added `CommandEvent.Command#getPrefix()` - Changed Command parsing +- Corrected mistake in documentation of `SubCommand#moreArgs()` ### 3.2 - Updated to JDA version 3.7.1_387 diff --git a/pom.xml b/pom.xml index 1be0d35..19f8d8b 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.github.johnnyjayjay CommandAPI jar - 3.2 + 3.2_01 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 8c2ae34..ba7b19a 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java @@ -24,6 +24,7 @@ */ public abstract class AbstractCommand implements ICommand { + // TODO: 03.10.2018 Regexes? /** * A regex to match member mentions. */ @@ -70,7 +71,7 @@ public final void onCommand(CommandEvent event, Member member, TextChannel chann CommandSettings settings = event.getCommandSettings(); Optional matchesArgs = subCommands.keySet().stream() .filter((sub) -> !sub.isDefault()) - .filter((sub) -> sub.args().length == args.length || (sub.moreArgs() && args.length > sub.args().length)) + .filter((sub) -> sub.args().length == args.length || (sub.moreArgs() && args.length > sub.args().length)) // FIXME: 03.10.2018 .filter((sub) -> { String regex; for (int i = 0; i < sub.args().length; i++) { 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 78ea587..961e5d2 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java @@ -108,6 +108,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); } @@ -117,6 +118,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); } @@ -126,6 +128,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); } 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 d1e0422..27bd20f 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java @@ -27,45 +27,44 @@ public void onEvent(Event e) { if (!(e instanceof GuildMessageReceivedEvent)) return; - settings.execute(() -> { - GuildMessageReceivedEvent event = (GuildMessageReceivedEvent) e; - TextChannel channel = event.getChannel(); - if (!settings.getBlacklistedChannels().contains(channel.getIdLong()) && (!event.getAuthor().isBot() || settings.botsMayExecute())) { + GuildMessageReceivedEvent event = (GuildMessageReceivedEvent) e; + TextChannel channel = event.getChannel(); + if (!settings.getBlacklistedChannels().contains(channel.getIdLong()) && (!event.getAuthor().isBot() || settings.botsMayExecute())) { + settings.execute(() -> { String raw = event.getMessage().getContentRaw(); String prefix = settings.getPrefix(event.getGuild().getIdLong()); - // TODO: 31.08.2018 cooldown und unknown command consumer callen; lösung für invalid commands finden (evtl. extra event oder methode, die checkt, ob es ein label wirklich gibt) if (raw.startsWith(prefix)) { CommandEvent.Command cmd = CommandEvent.parseCommand(raw, prefix, settings); - 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 && event.getGuild().getSelfMember().hasPermission(channel, Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS)) - channel.sendMessage(cooldownMessage).queue(); - return; - } - cooldowns.put(userId, timestamp); - // TODO: 24.08.2018 Optimize Exception handling + CommandEvent commandEvent = new CommandEvent(event.getJDA(), event.getResponseNumber(), event.getMessage(), cmd, settings); if (cmd.getExecutor() != null) { + 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 && event.getGuild().getSelfMember().hasPermission(channel, Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS)) + channel.sendMessage(cooldownMessage).queue(); + return; + } + cooldowns.put(userId, timestamp); try { - CommandEvent commandEvent = new CommandEvent(event.getJDA(), event.getResponseNumber(), event.getMessage(), cmd, settings); if (settings.mayCall(commandEvent)) { cmd.getExecutor().onCommand(commandEvent, event.getMember(), channel, cmd.getArgs()); } } catch (Throwable t) { + settings.onException(commandEvent, t); CommandSettings.LOGGER.warn("Command " + cmd.getExecutor().getClass().getName() + " had an uncaught exception:", t); } } else { - + settings.onUnknownCommand(commandEvent); + // TODO: 03.10.2018 das entfernen 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/CommandSettings.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java index 942dfd7..cc02dc6 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java @@ -58,7 +58,8 @@ public class CommandSettings { private long cooldown; private Color helpColor; private Predicate check; - private Consumer unknownCommand; + private Consumer unknownCommandHandler; + private BiConsumer exceptionHandler; private ExecutorService executorService; private final Set blacklistedChannels; // ids of those channels where no command will trigger this api to execute anything. @@ -122,8 +123,8 @@ private CommandSettings(@Nonnull String defaultPrefix, boolean labelIgnoreCase) this.blacklistedChannels = new HashSet<>(); this.prefixMap = new HashMap<>(); this.check = (e) -> true; - this.unknownCommand = (e) -> {}; - this.executorService = Executors.newSingleThreadExecutor(); + this.unknownCommandHandler = (e) -> {}; + this.executorService = null; } /** @@ -232,15 +233,19 @@ public CommandSettings clearHelpLabels() { /** * Creates the ExecutorService for this framework with the provided pool size. This may, of course, increase performance. * By default, this framework uses only one thread to execute commands. - * @param threadPoolSize The size of the thread pool to use. + * @param executorService The ExecutorService that should provide threads * @return The current object. This is to use fluent interface. * @throws CommandSetException If the provided size is <= 0. */ - public CommandSettings useMultiThreading(int threadPoolSize) { - if (threadPoolSize <= 0) - throw new CommandSetException("Provided thread pool size is invalid", new IllegalArgumentException("Thread pool size must not be <= 0")); + 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")); - this.executorService = Executors.newFixedThreadPool(threadPoolSize); + this.executorService = executorService; + } return this; } @@ -452,10 +457,7 @@ 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; } @@ -522,12 +524,24 @@ public CommandSettings setResetCooldown(boolean resetCooldown) { return this; } - // TODO: 31.08.2018 - public CommandSettings onUnknownCommand(Consumer consumer) { + + /** + * 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(Consumer action) { + this.unknownCommandHandler = action; return this; } - public CommandSettings onCooldownInvokeAttempt(BiConsumer consumer) { + /** + * 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(BiConsumer action) { + this.exceptionHandler = action; return this; } @@ -738,13 +752,13 @@ public String toString() { '}'; } - // TODO: 31.08.2018 + protected void onUnknownCommand(CommandEvent event) { - this.unknownCommand.accept(event); + this.unknownCommandHandler.accept(event); } - protected void onCooldownExecutionAttempt(CommandEvent event, long cooldown) { - + protected void onException(CommandEvent event, Throwable throwable) { + this.exceptionHandler.accept(event, throwable); } protected Message getUnknownCommandMessage() { @@ -760,7 +774,10 @@ protected boolean mayCall(CommandEvent event) { } protected void execute(Runnable command) { - executorService.execute(command); + if (executorService != null) + executorService.execute(command); + else + command.run(); // is das gut? keine ahnung } protected Map getCommands() { 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..ea6f5c0 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/DefaultHelpCommand.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/DefaultHelpCommand.java @@ -26,6 +26,7 @@ public final class DefaultHelpCommand extends AbstractHelpCommand { /** * Lists all commands along with the information that more help can be received by adding the optional label parameter. */ + // FIXME: 03.10.2018 geht aktuell nicht @Override public void provideGeneralHelp(CommandEvent event, String prefix, Map commands) { Member selfMember = event.getGuild().getSelfMember(); 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..f933365 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/SubCommand.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/SubCommand.java @@ -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/Regex.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/util/Regex.java index 9f38d16..6d6fade 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/util/Regex.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/util/Regex.java @@ -6,6 +6,8 @@ */ public class Regex { + // TODO: 03.10.2018 was passiert hiermit? + public static final String MEMBER_MENTION = "<@!?\\d+>"; public static final String CHANNEL_MENTION = "<#\\d+>"; public static final String ROLE_MENTION = "<&\\d+>"; From f6efa599e20af8ad38897316795afacfd9c122d6 Mon Sep 17 00:00:00 2001 From: Unknown Date: Thu, 4 Oct 2018 19:25:54 +0200 Subject: [PATCH 05/12] Last commits before release updated readme, cleaned code, added documentation for new features, fixed DefaultHelpCommand --- README.md | 43 +++++++----- changelog.md | 6 +- pom.xml | 2 +- src/examples/java/CustomPrefixCommand.java | 13 ++-- src/examples/java/Main.java | 3 +- .../discord/commandapi/AbstractCommand.java | 26 ++------ .../commandapi/AbstractHelpCommand.java | 2 +- .../discord/commandapi/CommandEvent.java | 5 +- .../discord/commandapi/CommandListener.java | 21 +++--- .../commandapi/CommandSetException.java | 2 +- .../discord/commandapi/CommandSettings.java | 65 +++++++++++++------ .../commandapi/DefaultHelpCommand.java | 5 +- .../discord/commandapi/ICommand.java | 2 +- .../discord/commandapi/Regex.java | 41 ++++++++++++ .../discord/commandapi/SubCommand.java | 2 +- .../discord/commandapi/package-info.java | 2 + .../discord/commandapi/util/Regex.java | 17 ----- .../commandapi/CommandSettingsTest.java | 4 +- 18 files changed, 157 insertions(+), 104 deletions(-) create mode 100644 src/main/java/com/github/johnnyjayjay/discord/commandapi/Regex.java delete mode 100644 src/main/java/com/github/johnnyjayjay/discord/commandapi/util/Regex.java diff --git a/README.md b/README.md index 65bacdf..0e5f4fd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # discord-api-command **A simple Command API for the JDA** -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)** @@ -30,16 +30,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 @@ -112,17 +120,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 } @@ -193,6 +201,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). @@ -201,18 +210,18 @@ 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: +- a label or a prefix does not match the requirements, i.e. the regex defined in `Regex.VALID_PREFIX` and `Regex.VALID_LABEL`. This includes: - prefixes that contain one or more of the characters `\+*^|$?` or are just an empty String - 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, it is recommended to check whether they match `Regex.VALID_PREFIX` +or `Regex.VALID_LABEL`. `com.github.johnnyjayjay.discord.commandapi.Regex` 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)) { +if (prefix.matches(Regex.VALID_PREFIX)) { // ... } else { // Tell the user diff --git a/changelog.md b/changelog.md index 4566870..9413877 100644 --- a/changelog.md +++ b/changelog.md @@ -1,15 +1,19 @@ # Changelog -### next patch +### 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()` - 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 readme ### 3.2 - Updated to JDA version 3.7.1_387 diff --git a/pom.xml b/pom.xml index 19f8d8b..cf3d408 100644 --- a/pom.xml +++ b/pom.xml @@ -132,7 +132,7 @@ junit junit - RELEASE + 4.12 test diff --git a/src/examples/java/CustomPrefixCommand.java b/src/examples/java/CustomPrefixCommand.java index 683de05..c7bd2a0 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; @@ -15,7 +12,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); + "If you set a new prefix, it has to be valid, i.e. match this regex: " + Regex.VALID_PREFIX); } @SubCommand(args = {"get"}, botPerms = {Permission.MESSAGE_WRITE}) @@ -24,16 +21,16 @@ 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", Regex.VALID_PREFIX}, botPerms = {Permission.MESSAGE_WRITE}) public void setCustomPrefix(CommandEvent event, Member member, TextChannel channel, String[] args) { - if (args[2].matches(CommandSettings.VALID_PREFIX)) { + if (args[2].matches(Regex.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!"); } - @SubCommand(args = {"set", "default", CommandSettings.VALID_PREFIX}, botPerms = {Permission.MESSAGE_WRITE}) + @SubCommand(args = {"set", "default", Regex.VALID_PREFIX}, 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 ba7b19a..5b6c749 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java @@ -24,20 +24,6 @@ */ public abstract class AbstractCommand implements ICommand { - // TODO: 03.10.2018 Regexes? - /** - * 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; /** @@ -69,9 +55,9 @@ 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)) // FIXME: 03.10.2018 + .filter((sub) -> sub.args().length == args.length || (sub.moreArgs() && args.length > sub.args().length)) .filter((sub) -> { String regex; for (int i = 0; i < sub.args().length; i++) { @@ -81,14 +67,16 @@ 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(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) { 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 961e5d2..0306330 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java @@ -21,7 +21,7 @@ * 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 { @@ -159,7 +159,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.2 + * @version 3.2_01 */ public static class Command { @@ -171,7 +171,6 @@ public static class Command { private final String label; private final String[] args; - // TODO: 31.08.2018 performanteste und best accessible methode finden private Command(String raw, String prefix, CommandSettings settings) { String[] argsWithoutPrefix = raw.replaceFirst(prefix, "").split("\\s+"); this.label = settings.isLabelIgnoreCase() ? argsWithoutPrefix[0].toLowerCase() : argsWithoutPrefix[0];; 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 27bd20f..68846d1 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java @@ -6,7 +6,6 @@ import net.dv8tion.jda.core.events.Event; import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent; import net.dv8tion.jda.core.hooks.EventListener; -import net.dv8tion.jda.core.hooks.ListenerAdapter; import java.util.HashMap; import java.util.Map; @@ -29,25 +28,29 @@ public void onEvent(Event e) { 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())) { + // 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); - CommandEvent commandEvent = new CommandEvent(event.getJDA(), event.getResponseNumber(), event.getMessage(), cmd, settings); - if (cmd.getExecutor() != null) { + 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 && event.getGuild().getSelfMember().hasPermission(channel, Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS)) - channel.sendMessage(cooldownMessage).queue(); + if (cooldownMessage != null) + commandEvent.respond(cooldownMessage); return; } cooldowns.put(userId, timestamp); + // execute command try { if (settings.mayCall(commandEvent)) { cmd.getExecutor().onCommand(commandEvent, event.getMember(), channel, cmd.getArgs()); @@ -56,12 +59,12 @@ public void onEvent(Event e) { settings.onException(commandEvent, t); CommandSettings.LOGGER.warn("Command " + cmd.getExecutor().getClass().getName() + " had an uncaught exception:", t); } - } else { + } else { // command is unknown settings.onUnknownCommand(commandEvent); // TODO: 03.10.2018 das entfernen Message unknownCommand = settings.getUnknownCommandMessage(); - if (unknownCommand != null && event.getGuild().getSelfMember().hasPermission(channel, Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS)) - channel.sendMessage(unknownCommand).queue(); + if (unknownCommand != null) + commandEvent.respond(unknownCommand); } } }); 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 cc02dc6..81fe5f8 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java @@ -17,29 +17,32 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; -// TODO: 31.08.2018 Exceptions besser regeln + /** * 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 Use {@link com.github.johnnyjayjay.discord.commandapi.Regex#VALID_PREFIX} instead */ + @Deprecated public static final String VALID_PREFIX = "[^\\\\+*^|$?]+"; /** * A regex that only matches valid labels. Can be used to check user input. + * @deprecated Use {@link com.github.johnnyjayjay.discord.commandapi.Regex#VALID_LABEL} instead */ + @Deprecated public static final String VALID_LABEL = "[^\\s]+"; // TODO: 05.08.2018 illegal prefix characters @@ -52,15 +55,18 @@ public class CommandSettings { 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 Message cooldownMessage; - private String defaultPrefix; - private long cooldown; + 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; - private Consumer unknownCommandHandler; - private BiConsumer exceptionHandler; - private ExecutorService executorService; + 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 @@ -74,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 /** @@ -124,7 +129,9 @@ private CommandSettings(@Nonnull String defaultPrefix, boolean labelIgnoreCase) this.prefixMap = new HashMap<>(); this.check = (e) -> true; this.unknownCommandHandler = (e) -> {}; + this.exceptionHandler = (e, t) -> {}; this.executorService = null; + this.logExceptions = true; } /** @@ -332,7 +339,7 @@ public CommandSettings clearBlacklist() { * @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)) + if (label.matches(Regex.VALID_LABEL)) this.commands.put(labelIgnoreCase ? label.toLowerCase() : label, executor); else throw new CommandSetException(INVALID_LABEL_MESSAGE, new IllegalArgumentException("Label " + label + " is not valid")); @@ -443,7 +450,7 @@ public CommandSettings setCheck(@Nonnull Predicate check) { * @throws CommandSetException if a non-null prefix does not match the requirements for a valid prefix. */ public CommandSettings setDefaultPrefix(@Nonnull String prefix) { - if (prefix.matches(VALID_PREFIX)) + if (prefix.matches(Regex.VALID_PREFIX)) this.defaultPrefix = prefix; else throw new CommandSetException(INVALID_PREFIX_MESSAGE, new IllegalArgumentException("Prefix " + prefix + " is not valid")); @@ -470,7 +477,7 @@ public CommandSettings setUnknownCommandMessage(@Nullable Message message) { * @throws CommandSetException if a non-null prefix does not match the requirements for a valid prefix. */ public CommandSettings setCustomPrefix(long guildId, @Nullable String prefix) { - if (prefix != null && !prefix.matches(VALID_PREFIX)) + if (prefix != null && !prefix.matches(Regex.VALID_PREFIX)) throw new CommandSetException(INVALID_PREFIX_MESSAGE, new IllegalArgumentException("Prefix " + prefix + " is not valid")); this.prefixMap.put(guildId, prefix); return this; @@ -484,7 +491,7 @@ public CommandSettings setCustomPrefix(long guildId, @Nullable String prefix) { * @throws CommandSetException if one of the prefixes is not valid. */ public CommandSettings setCustomPrefixes(@Nonnull Map guildIdPrefixMap) { - if (guildIdPrefixMap.values().stream().allMatch((prefix) -> prefix.matches(VALID_PREFIX))) + if (guildIdPrefixMap.values().stream().allMatch((prefix) -> prefix.matches(Regex.VALID_PREFIX))) prefixMap.putAll(guildIdPrefixMap); else throw new CommandSetException("One or more of the prefixes is not valid: " + INVALID_PREFIX_MESSAGE, new IllegalArgumentException("Invalid prefix")); @@ -524,6 +531,18 @@ 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. @@ -731,6 +750,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. 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 ea6f5c0..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 */ @@ -26,11 +26,10 @@ public final class DefaultHelpCommand extends AbstractHelpCommand { /** * Lists all commands along with the information that more help can be received by adding the optional label parameter. */ - // FIXME: 03.10.2018 geht aktuell nicht @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/Regex.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/Regex.java new file mode 100644 index 0000000..fc3d5b6 --- /dev/null +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/Regex.java @@ -0,0 +1,41 @@ +package com.github.johnnyjayjay.discord.commandapi; + +/** + * 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 Regex { + + /** + * 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 prefixes. Can be used to check user input. + */ + public static final String VALID_PREFIX = "[^\\\\+*^|$?]+"; + /** + * A regex that only matches valid labels. Can be used to check user input. + */ + public static final String VALID_LABEL = "[^\\s]+"; + +} 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 f933365..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 */ 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/main/java/com/github/johnnyjayjay/discord/commandapi/util/Regex.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/util/Regex.java deleted file mode 100644 index 6d6fade..0000000 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/util/Regex.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.johnnyjayjay.discord.commandapi.util; - -/** - * https://www.github.com/JohnnyJayJay - * @author Johnny_JayJay - */ -public class Regex { - - // TODO: 03.10.2018 was passiert hiermit? - - public static final String MEMBER_MENTION = "<@!?\\d+>"; - public static final String CHANNEL_MENTION = "<#\\d+>"; - public static final String ROLE_MENTION = "<&\\d+>"; - public static final String DIGIT = "\\d"; - public static final String ANYTHING = ".*"; - -} 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..71fa5ed 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 { @@ -111,7 +111,7 @@ private String randomString(boolean replaceIllegalPrefixChars) { 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 replaceIllegalPrefixChars ? ret.replaceAll(Regex.VALID_PREFIX.replace("^", ""), "") : ret; } } From dd54b6024c09aa85c40b4ea1e8e3bd5c36f96930 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sun, 7 Oct 2018 00:40:57 +0200 Subject: [PATCH 06/12] Nonnull annotations, small fixes in CommandSettings --- changelog.md | 3 ++- pom.xml | 5 ++-- .../discord/commandapi/CommandSettings.java | 23 ++++++++++++------- .../discord/commandapi/JDAMock.java | 15 ++++++++++++ 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/changelog.md b/changelog.md index 9413877..dab1998 100644 --- a/changelog.md +++ b/changelog.md @@ -8,11 +8,12 @@ - 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()` +- Added `CommandEvent.Command#getPrefix()` to get the prefix used in a command - 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 diff --git a/pom.xml b/pom.xml index cf3d408..6d87581 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.github.johnnyjayjay CommandAPI jar - 3.2_01 + 3.2_01-1-SNAPSHOT @@ -56,6 +56,7 @@ attach-javadocs + package jar @@ -138,7 +139,7 @@ net.dv8tion JDA - 3.7.1_387 + 3.8.0_433 \ No newline at end of file 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 81fe5f8..66339bd 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java @@ -131,6 +131,8 @@ private CommandSettings(@Nonnull String defaultPrefix, boolean labelIgnoreCase) this.unknownCommandHandler = (e) -> {}; this.exceptionHandler = (e, t) -> {}; this.executorService = null; + this.unknownCommandMessage = null; + this.cooldownMessage = null; this.logExceptions = true; } @@ -249,7 +251,7 @@ public CommandSettings useMultiThreading(@Nullable ExecutorService executorServi this.executorService = null; } else { if (executorService.isShutdown()) - throw new CommandSetException("Provided ExecutorService is invalid", new IllegalArgumentException("ExecutorService must not be shut down")); + throw new CommandSetException("Provided ExecutorService is invalid", new IllegalArgumentException("ExecutorService must not be shut down", new IllegalStateException("Illegal thread pool state"))); this.executorService = executorService; } @@ -282,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; } @@ -316,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); } @@ -338,7 +340,7 @@ 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) { + public CommandSettings put(@Nonnull ICommand executor, @Nonnull String label) { if (label.matches(Regex.VALID_LABEL)) this.commands.put(labelIgnoreCase ? label.toLowerCase() : label, executor); else @@ -378,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; @@ -549,7 +556,7 @@ public CommandSettings setLogExceptions(boolean logExceptions) { * @param action a Consumer that takes the event. * @return The current object. This is to use fluent interface. */ - public CommandSettings onUnknownCommand(Consumer action) { + public CommandSettings onUnknownCommand(@Nonnull Consumer action) { this.unknownCommandHandler = action; return this; } @@ -559,7 +566,7 @@ public CommandSettings onUnknownCommand(Consumer action) { * @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(BiConsumer action) { + public CommandSettings onException(@Nonnull BiConsumer action) { this.exceptionHandler = action; return this; } @@ -687,7 +694,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())); } 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; From 2d77da4a808444145ed7e1b77382a66164769603 Mon Sep 17 00:00:00 2001 From: Unknown Date: Mon, 8 Oct 2018 19:26:54 +0200 Subject: [PATCH 07/12] Fixed invalid prefixes, adjusted JavaDoc --- changelog.md | 1 + .../discord/commandapi/CommandEvent.java | 3 +- .../discord/commandapi/CommandSettings.java | 39 ++++++++++--------- .../discord/commandapi/Regex.java | 4 -- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/changelog.md b/changelog.md index dab1998..9207982 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,7 @@ - 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 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 0306330..0adab4a 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java @@ -15,6 +15,7 @@ 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 /** @@ -172,7 +173,7 @@ public static class Command { 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];; this.command = settings.getCommands().getOrDefault(this.label, null); this.rawMessage = raw; 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 66339bd..154de2e 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java @@ -34,7 +34,7 @@ public class CommandSettings { /** * A regex that only matches valid prefixes. Can be used to check user input. - * @deprecated Use {@link com.github.johnnyjayjay.discord.commandapi.Regex#VALID_PREFIX} instead + * @deprecated Not needed anymore. Anything is a valid prefix now. */ @Deprecated public static final String VALID_PREFIX = "[^\\\\+*^|$?]+"; @@ -51,9 +51,9 @@ 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 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 @@ -84,6 +84,7 @@ public class CommandSettings { private boolean resetCooldown; // reset cooldown each execution private boolean botExecution; // bots may execute commands private boolean logExceptions; // uncaught exceptions are logged + private CommandSetException prefixIsNotValid; /** @@ -115,6 +116,7 @@ public CommandSettings(@Nonnull String defaultPrefix, @Nonnull JDA jda, boolean } private CommandSettings(@Nonnull String defaultPrefix, boolean labelIgnoreCase) { + this.prefixIsNotValid = new CommandSetException("Prefix is not valid", new IllegalArgumentException(INVALID_PREFIX_MESSAGE)); this.commands = new HashMap<>(); this.listener = new CommandListener(this); this.activated = false; @@ -240,11 +242,11 @@ public CommandSettings clearHelpLabels() { } /** - * Creates the ExecutorService for this framework with the provided pool size. This may, of course, increase performance. - * By default, this framework uses only one thread to execute commands. + * 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 size is <= 0. + * @throws CommandSetException If the provided ExecutorService is shut down */ public CommandSettings useMultiThreading(@Nullable ExecutorService executorService) { if (executorService == null) { @@ -454,13 +456,14 @@ public CommandSettings setCheck(@Nonnull Predicate check) { * 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(Regex.VALID_PREFIX)) + if (!prefix.isEmpty()) this.defaultPrefix = prefix; - else - throw new CommandSetException(INVALID_PREFIX_MESSAGE, new IllegalArgumentException("Prefix " + prefix + " is not valid")); + else { + throw prefixIsNotValid; + } return this; } @@ -476,16 +479,16 @@ public CommandSettings setUnknownCommandMessage(@Nullable Message message) { } /** - * 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(Regex.VALID_PREFIX)) - throw new CommandSetException(INVALID_PREFIX_MESSAGE, new IllegalArgumentException("Prefix " + prefix + " is not valid")); + if (prefix != null && prefix.isEmpty()) + throw prefixIsNotValid; this.prefixMap.put(guildId, prefix); return this; } @@ -495,13 +498,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(Regex.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 prefixIsNotValid; return this; } diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/Regex.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/Regex.java index fc3d5b6..8aa6b88 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/Regex.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/Regex.java @@ -29,10 +29,6 @@ public class Regex { */ public static final String ANYTHING = ".*"; - /** - * A regex that only matches valid prefixes. Can be used to check user input. - */ - public static final String VALID_PREFIX = "[^\\\\+*^|$?]+"; /** * A regex that only matches valid labels. Can be used to check user input. */ From ae272b036272ac5f2dbab088e29f3229f6cde6f2 Mon Sep 17 00:00:00 2001 From: Unknown Date: Tue, 9 Oct 2018 20:02:08 +0200 Subject: [PATCH 08/12] Adjusted examples, added util method --- README.md | 15 ++-- src/examples/java/CustomPrefixCommand.java | 14 ++-- .../discord/commandapi/CommandSettings.java | 25 ++++--- .../discord/commandapi/Regex.java | 37 ---------- .../johnnyjayjay/discord/commandapi/Util.java | 74 +++++++++++++++++++ .../commandapi/CommandSettingsTest.java | 11 ++- 6 files changed, 108 insertions(+), 68 deletions(-) delete mode 100644 src/main/java/com/github/johnnyjayjay/discord/commandapi/Regex.java create mode 100644 src/main/java/com/github/johnnyjayjay/discord/commandapi/Util.java diff --git a/README.md b/README.md index 0e5f4fd..2ad76eb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # discord-api-command **A simple Command API for the JDA** +(An API that is not an API but a framework) CURRENT VERSION: **3.2_01**

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

@@ -210,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 `Regex.VALID_PREFIX` and `Regex.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 `Regex.VALID_PREFIX` -or `Regex.VALID_LABEL`. `com.github.johnnyjayjay.discord.commandapi.Regex` is a class that contains some regular expressions as `public static final String`s. +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(Regex.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/src/examples/java/CustomPrefixCommand.java b/src/examples/java/CustomPrefixCommand.java index c7bd2a0..2c4131a 100644 --- a/src/examples/java/CustomPrefixCommand.java +++ b/src/examples/java/CustomPrefixCommand.java @@ -11,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: " + Regex.VALID_PREFIX); + event.respond("Correct usage: `" + event.getCommandSettings().getPrefix(event.getGuild().getIdLong()) + "prefix [get|set] `"); } @SubCommand(args = {"get"}, botPerms = {Permission.MESSAGE_WRITE}) @@ -21,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", Regex.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(Regex.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", Regex.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/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java index 154de2e..8a02abe 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java @@ -40,7 +40,7 @@ public class CommandSettings { public static final String VALID_PREFIX = "[^\\\\+*^|$?]+"; /** * A regex that only matches valid labels. Can be used to check user input. - * @deprecated Use {@link com.github.johnnyjayjay.discord.commandapi.Regex#VALID_LABEL} instead + * @deprecated Use {@link Util#VALID_LABEL} instead */ @Deprecated public static final String VALID_LABEL = "[^\\s]+"; @@ -84,7 +84,6 @@ public class CommandSettings { private boolean resetCooldown; // reset cooldown each execution private boolean botExecution; // bots may execute commands private boolean logExceptions; // uncaught exceptions are logged - private CommandSetException prefixIsNotValid; /** @@ -116,7 +115,6 @@ public CommandSettings(@Nonnull String defaultPrefix, @Nonnull JDA jda, boolean } private CommandSettings(@Nonnull String defaultPrefix, boolean labelIgnoreCase) { - this.prefixIsNotValid = new CommandSetException("Prefix is not valid", new IllegalArgumentException(INVALID_PREFIX_MESSAGE)); this.commands = new HashMap<>(); this.listener = new CommandListener(this); this.activated = false; @@ -343,7 +341,7 @@ public CommandSettings clearBlacklist() { * @throws CommandSetException If the label is empty or consists of multiple words. */ public CommandSettings put(@Nonnull ICommand executor, @Nonnull String label) { - if (label.matches(Regex.VALID_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")); @@ -462,7 +460,7 @@ public CommandSettings setDefaultPrefix(@Nonnull String prefix) { if (!prefix.isEmpty()) this.defaultPrefix = prefix; else { - throw prefixIsNotValid; + throw new CommandSetException("Prefix is not valid", new IllegalArgumentException(INVALID_PREFIX_MESSAGE)); } return this; } @@ -488,7 +486,7 @@ public CommandSettings setUnknownCommandMessage(@Nullable Message message) { */ public CommandSettings setCustomPrefix(long guildId, @Nullable String prefix) { if (prefix != null && prefix.isEmpty()) - throw prefixIsNotValid; + throw new CommandSetException("Prefix is not valid", new IllegalArgumentException(INVALID_PREFIX_MESSAGE)); this.prefixMap.put(guildId, prefix); return this; } @@ -504,7 +502,7 @@ public CommandSettings setCustomPrefixes(@Nonnull Map guildIdPrefi if (guildIdPrefixMap.values().stream().noneMatch(String::isEmpty)) prefixMap.putAll(guildIdPrefixMap); else - throw prefixIsNotValid; + throw new CommandSetException("Prefix is not valid", new IllegalArgumentException(INVALID_PREFIX_MESSAGE)); return this; } @@ -775,17 +773,26 @@ public boolean isLogExceptions() { @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 + '}'; } diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/Regex.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/Regex.java deleted file mode 100644 index 8aa6b88..0000000 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/Regex.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.github.johnnyjayjay.discord.commandapi; - -/** - * 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 Regex { - - /** - * 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]+"; - -} 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/test/java/com/github/johnnyjayjay/discord/commandapi/CommandSettingsTest.java b/src/test/java/com/github/johnnyjayjay/discord/commandapi/CommandSettingsTest.java index 71fa5ed..864fcb3 100644 --- a/src/test/java/com/github/johnnyjayjay/discord/commandapi/CommandSettingsTest.java +++ b/src/test/java/com/github/johnnyjayjay/discord/commandapi/CommandSettingsTest.java @@ -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(Regex.VALID_PREFIX.replace("^", ""), "") : ret; + return UUID.randomUUID().toString().substring(beginIndex, endIndex); } } From 254c066aae41ec0ebc9d0e207a4fee47c229fa67 Mon Sep 17 00:00:00 2001 From: Unknown Date: Fri, 12 Oct 2018 23:22:48 +0200 Subject: [PATCH 09/12] Added onException support for AbstractCommand --- pom.xml | 1 + .../johnnyjayjay/discord/commandapi/AbstractCommand.java | 7 ++++++- .../johnnyjayjay/discord/commandapi/CommandListener.java | 4 +++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 6d87581..60b371a 100644 --- a/pom.xml +++ b/pom.xml @@ -140,6 +140,7 @@ net.dv8tion JDA 3.8.0_433 + compile \ No newline at end of file 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 5b6c749..21acae8 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java @@ -85,7 +85,12 @@ private void invokeMethod(Method method, CommandEvent event, Member member, Text } catch (IllegalAccessException 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("Command " + event.getCommand().getExecutor().getClass().getName() + " had an uncaught Exception in SubCommand " + method.getName() + ":", 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/CommandListener.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java index 68846d1..623accb 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java @@ -57,7 +57,9 @@ public void onEvent(Event e) { } } catch (Throwable t) { settings.onException(commandEvent, t); - CommandSettings.LOGGER.warn("Command " + cmd.getExecutor().getClass().getName() + " had an uncaught exception:", t); + if (settings.isLogExceptions()) { + CommandSettings.LOGGER.warn("Command " + cmd.getExecutor().getClass().getName() + " had an uncaught exception:", t); + } } } else { // command is unknown settings.onUnknownCommand(commandEvent); From 54255bc61fe1fd742176b5881c9452eb7c093af0 Mon Sep 17 00:00:00 2001 From: Unknown Date: Wed, 17 Oct 2018 10:21:54 +0200 Subject: [PATCH 10/12] Fixed Regex issues completely --- pom.xml | 4 ++-- .../github/johnnyjayjay/discord/commandapi/CommandEvent.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 60b371a..e9d6b20 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.github.johnnyjayjay CommandAPI jar - 3.2_01-1-SNAPSHOT + 3.2_01-3-SNAPSHOT @@ -56,7 +56,7 @@ attach-javadocs - package + deploy jar 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 0adab4a..35e007c 100644 --- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java +++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java @@ -180,7 +180,7 @@ private Command(String raw, String prefix, CommandSettings settings) { this.prefix = prefix; this.args = Arrays.copyOfRange(argsWithoutPrefix, 1, argsWithoutPrefix.length); this.joinedArgs = String.join(" ", this.args); - this.rawArgs = raw.replaceFirst(prefix + this.label + "\\s+", ""); + this.rawArgs = raw.replaceFirst(Pattern.quote(prefix + this.label) + "\\s+", ""); } /** From efc968380a744ed59022b2eaea6cbd954e89a628 Mon Sep 17 00:00:00 2001 From: JohnnyJayJay <39348311+JohnnyJayJay@users.noreply.github.com> Date: Wed, 17 Oct 2018 19:58:47 +0200 Subject: [PATCH 11/12] Update issue templates From 9bff7e49237372bc485d233b15b1c977eda2ba93 Mon Sep 17 00:00:00 2001 From: JohnnyJayJay <39348311+JohnnyJayJay@users.noreply.github.com> Date: Wed, 17 Oct 2018 20:01:05 +0200 Subject: [PATCH 12/12] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 30 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 17 +++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md 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.