diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..2ac9e9a
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,30 @@
+---
+name: Bug report
+about: Report a bug here to receive help and to help me improve the framework
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. …
+2. ….
+3. …..
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots/Code**
+If applicable, add screenshots to help explain your problem.
+Also, add the part of the code that doesn't work.
+
+**System information**
+- JDK/JVM version
+- CommandAPI version
+- JDA version
+- (Other dependencies)
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..066b2d9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,17 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/README.md b/README.md
index 1801165..2ad76eb 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
# discord-api-command
**A simple Command API for the JDA**
+(An API that is not an API but a framework)
-CURRENT VERSION: **3.2**
-Other versions: **3.1**, **3.0_3**, **3.0_2**, **3.0_1**, **3.0**
+CURRENT VERSION: **3.2_01**
+Other versions: **3.2**, **3.1**, **3.0_3**, **3.0_2**, **3.0_1**, **3.0**
**[Changelog](https://github.com/JohnnyJayJay/discord-api-command/blob/master/changelog.md)**
**[Documentation](http://docs.johnnyjayjay.me)**
@@ -17,6 +18,10 @@ Other versions: **3.1**, **3.0_3**, **3.0_2**, **3.0_1**, **3.0**
- prevention of exceptions and errors by substantial validation and exception handling
- error transparency through `CommandSetException`
+## Important
+This library **requires** an SLF4J implementation and does not provide an own implementation like JDA. Without it, you will not get any logging messages, including
+stack traces, information about CommandSettings, warnings and much more.
+
## On how to add this to your project
### Adding as a library
You can download the .jar-file in [this directory](https://github.com/JohnnyJayJay/discord-api-command/tree/master/builds) and add it to the project
@@ -26,16 +31,24 @@ You can download the .jar-file in [this directory](https://github.com/JohnnyJayJ
3. Eclipse: Right-click the jar: `Build Path -> Add To Build Path`
IntelliJ: Right-click the jar: `Add As Library`
4. Done. You can now use it.
-### Adding Dependency in pom
-If you use Maven, you can add this library even less complicated: Just add the following dependency in your `pom.xml`:
+### Adding Dependency (Maven & Gradle)
+For Maven, add this to your `pom.xml`:
```xml
-
- com.github.johnnyjayjay
- CommandAPI
- VERSION
-
+
+
+ com.github.johnnyjayjay
+ CommandAPI
+ VERSION
+
+
+```
+
+For Gradle, add this to your `build.gradle`:
+```gradle
+dependencies {
+ compile "com.github.johnnyjayjay:CommandAPI:VERSION"
+}
```
-And that's it!
## Getting started
### Wrting Commands
@@ -108,17 +121,17 @@ public class ReportCommand extends AbstractCommand {
// Error message
}
- @SubCommand(args = {this.MEMBER_MENTION}, moreArgs = true) // moreArgs: means that there has to be at least one more argument than specified. Useful for the report reason.
+ @SubCommand(args = {Regex.MEMBER_MENTION}, moreArgs = true) // moreArgs: means that there can be more arguments than specified. Useful for the report reason.
public void onReport(CommandEvent event, Member member, TextChannel channel, String[] args) {
// report the mentioned member
}
- @SubCommand(args = {"get", this.MEMBER_MENTION})
+ @SubCommand(args = {"get", Regex.MEMBER_MENTION})
public void onReportsGet(CommandEvent event, Member member, TextChannel channel, String[] args) {
// get the reports of the mentioned member
}
- @SubCommand(args = {"remove", this.MEMBER_MENTION})
+ @SubCommand(args = {"remove", Regex.MEMBER_MENTION})
public void onReportsRemove(CommandEvent event, Member member, TextChannel channel, String[] args) {
// remove the reports from the mentioned member
}
@@ -189,6 +202,7 @@ There are a few more features to `CommandSettings`, such as:
- Setting a message that is displayed in case of an unknown command
- Setting a command cooldown (and specifying whether the cooldown should be reset on each execution attempt)
- Setting a Color for `DefaultHelpCommand`
+- Adding listeners for Exceptions and unknown commands
### Exceptions
This API should only throw one kind of exception, `CommandSetException` (except for explicitly thrown `IllegalArgumentException`s).
@@ -197,24 +211,24 @@ If this is **NOT** the case and you get another exception thrown by anything ins
`CommandSetException` is a sub class of `RuntimeException`, meaning that they don't have to be caught and they don't terminate the program.
A `CommandSetException` is thrown if:
-- a label or a prefix does not match the requirements, i.e. the regex defined in `CommandSettings.VALID_PREFIX` and `CommandSettings.VALID_LABEL`. This includes:
- - prefixes that contain one or more of the characters `\+*^|$?` or are just an empty String
+- a label or a prefix does not match the requirements. This includes:
+ - prefixes that are empty
- labels that contain any kind of blank spaces or are just an empty String
- an instance of `CommandSettings` is activated or deactivated twice (which is not possible)
- any other settings input for `CommandSettings` is invalid
-If you don't want to have any exceptions concerning prefixes and labels, it is recommended to check whether they match `CommandSettings.VALID_PREFIX`
-or `CommandSettings.VALID_LABEL`.
+If you don't want to have any exceptions concerning prefixes and labels, you should check for the prefix not to be empty and for the label to match `Util.VALID_LABEL`.
+`com.github.johnnyjayjay.discord.commandapi.Util` is a class that contains some regular expressions as `public static final String`s.
```java
-String prefix = // user input or something else you can't verify directly
-if (prefix.matches(CommandSettings.VALID_PREFIX)) {
+String label = // user input or something else you can't verify directly
+if (label.matches(Util.VALID_LABEL)) {
// ...
} else {
// Tell the user
}
```
-With sub commands, you can even set `CommandSettings.VALID_PREFIX`/`CommandSettings.VALID_LABEL` as an argument regular expression.
+With sub commands, you can even set `CommandSettings.VALID_LABEL` as an argument regular expression (if it ever comes to that).
## Contributing
If you think this framework is missing a feature and you think you're able to write it yourself, fork this repository and go for it.
diff --git a/changelog.md b/changelog.md
index a2f4a96..9207982 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,22 @@
# Changelog
+### 3.2_01
+- Increased performance of CommandListener slightly
+- Added configurable Predicate to `CommandSettings` which tests an event before execution. This may be useful for own checks that are not provided by this framework
+- Added "listeners" for unknown commands (`CommandSettings#onUnknownCommand(Consumer)`) and Throwables (`CommandSettings#onException(BiConsumer)`)
+- Added possibility to configure a custom thread pool (`CommandSettings#useMultiThreading(ExecutorService)`)
+- Added configurable messages that are sent in case someone is on cooldown or a command is unknown
+- Added possibility to deactivate Exception logging (`CommandSettings#setLogExceptions(boolean)`)
+- Added Regex util class (mainly for `AbstractCommand`)
+- Added `CommandEvent.Command#getPrefix()` to get the prefix used in a command
+- Finally fixed invalid prefixes; any String that is not empty can be a prefix now
+- Changed Command parsing
+- Adjusted `AbstractCommand`'s code - made it more stream-like
+- Fixed bug in `DefaultHelpCommand` that prevented the general help message from working
+- Corrected mistake in documentation of `SubCommand#moreArgs()`
+- Updated JDA version to 3.8.0_433
+- Updated readme
+
### 3.2
- Updated to JDA version 3.7.1_387
- Adjusted cooldown system: it is now configurable whether the cooldown will reset for each command execution attempt
diff --git a/pom.xml b/pom.xml
index 1be0d35..e9d6b20 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
com.github.johnnyjayjay
CommandAPI
jar
- 3.2
+ 3.2_01-3-SNAPSHOT
@@ -56,6 +56,7 @@
attach-javadocs
+ deploy
jar
@@ -132,13 +133,14 @@
junit
junit
- RELEASE
+ 4.12
test
net.dv8tion
JDA
- 3.7.1_387
+ 3.8.0_433
+ compile
\ No newline at end of file
diff --git a/src/examples/java/CustomPrefixCommand.java b/src/examples/java/CustomPrefixCommand.java
index 683de05..2c4131a 100644
--- a/src/examples/java/CustomPrefixCommand.java
+++ b/src/examples/java/CustomPrefixCommand.java
@@ -1,7 +1,4 @@
-import com.github.johnnyjayjay.discord.commandapi.AbstractCommand;
-import com.github.johnnyjayjay.discord.commandapi.CommandEvent;
-import com.github.johnnyjayjay.discord.commandapi.CommandSettings;
-import com.github.johnnyjayjay.discord.commandapi.SubCommand;
+import com.github.johnnyjayjay.discord.commandapi.*;
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.entities.Member;
import net.dv8tion.jda.core.entities.TextChannel;
@@ -14,8 +11,7 @@ public class CustomPrefixCommand extends AbstractCommand {
@SubCommand(isDefault = true, botPerms = {Permission.MESSAGE_WRITE})
public void everythingElse(CommandEvent event, Member member, TextChannel channel, String[] args) {
- event.respond("Correct usage: `" + event.getCommandSettings().getPrefix(event.getGuild().getIdLong()) + "prefix [get|set] `\n" +
- "If you set a new prefix, it has to be valid, i.e. match this regex: " + CommandSettings.VALID_PREFIX);
+ event.respond("Correct usage: `" + event.getCommandSettings().getPrefix(event.getGuild().getIdLong()) + "prefix [get|set] `");
}
@SubCommand(args = {"get"}, botPerms = {Permission.MESSAGE_WRITE})
@@ -24,16 +20,13 @@ public void getPrefix(CommandEvent event, Member member, TextChannel channel, St
event.respond("The prefix for this guild is: `" + settings.getPrefix(event.getGuild().getIdLong()) + "`\nThe default prefix is: `" + settings.getPrefix() + "`");
}
- @SubCommand(args = {"set", "custom", CommandSettings.VALID_PREFIX}, botPerms = {Permission.MESSAGE_WRITE})
+ @SubCommand(args = {"set", "custom", ".*"}, moreArgs = true, botPerms = {Permission.MESSAGE_WRITE})
public void setCustomPrefix(CommandEvent event, Member member, TextChannel channel, String[] args) {
- if (args[2].matches(CommandSettings.VALID_PREFIX)) {
- event.getCommandSettings().setCustomPrefix(event.getGuild().getIdLong(), args[2]);
- event.respond("Successfully set prefix for this guild to `" + args[2] + "`!");
- } else
- event.respond("You need to specify a valid prefix as the third argument!");
+ event.getCommandSettings().setCustomPrefix(event.getGuild().getIdLong(), args[2]);
+ event.respond("Successfully set prefix for this guild to `" + args[2] + "`!");
}
- @SubCommand(args = {"set", "default", CommandSettings.VALID_PREFIX}, botPerms = {Permission.MESSAGE_WRITE})
+ @SubCommand(args = {"set", "default", ".*"}, moreArgs = true, botPerms = {Permission.MESSAGE_WRITE})
public void setDefaultPrefix(CommandEvent event, Member member, TextChannel channel, String[] args) {
event.getCommandSettings().setDefaultPrefix(args[2]);
event.respond("Successfully set default prefix to `" + args[2] + "`!");
diff --git a/src/examples/java/Main.java b/src/examples/java/Main.java
index 955c246..7e325e3 100644
--- a/src/examples/java/Main.java
+++ b/src/examples/java/Main.java
@@ -14,9 +14,10 @@ public class Main {
public static void main(String[] args) throws LoginException, InterruptedException {
JDA jda = new JDABuilder(AccountType.BOT).setToken(Secrets.TOKEN).buildBlocking();
- // default prefix shall be "!" and we want the labels to be case insensitive.
+ // default prefix shall be "!!" and we want the labels to be case insensitive.
new CommandSettings("!!", jda, true).setCooldown(3000) // commands can only be executed every 3 seconds now
.put(new PingCommand(), "ping", "p")
+ .put(new DefaultHelpCommand(), "help") // registering help command
.put(new CustomPrefixCommand(), "prefix")
.activate(); // Activating! Very important!
}
diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java
index 16459d5..21acae8 100644
--- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java
+++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractCommand.java
@@ -24,19 +24,6 @@
*/
public abstract class AbstractCommand implements ICommand {
- /**
- * A regex to match member mentions.
- */
- protected final String MEMBER_MENTION = "<@!?\\d+>";
- /**
- * A regex to match role mentions.
- */
- protected final String ROLE_MENTION = "<&\\d+>";
- /**
- * A regex to match text channel mentions.
- */
- protected final String CHANNEL_MENTION = "<#\\d+>";
-
private final Map subCommands;
/**
@@ -68,7 +55,7 @@ protected AbstractCommand() {
@Override
public final void onCommand(CommandEvent event, Member member, TextChannel channel, String[] args) {
CommandSettings settings = event.getCommandSettings();
- Optional matchesArgs = subCommands.keySet().stream()
+ subCommands.keySet().stream()
.filter((sub) -> !sub.isDefault())
.filter((sub) -> sub.args().length == args.length || (sub.moreArgs() && args.length > sub.args().length))
.filter((sub) -> {
@@ -80,23 +67,30 @@ public final void onCommand(CommandEvent event, Member member, TextChannel chann
}
return true;
})
- .filter((sub) -> event.checkBotPermissions(sub.botPerms())).findFirst();
- if (matchesArgs.isPresent()) {
+ .filter((sub) -> event.checkBotPermissions(sub.botPerms())).findFirst().map(Optional::of)
+ .orElseGet(() -> subCommands.keySet().stream().filter(SubCommand::isDefault).filter((sub) -> event.checkBotPermissions(sub.botPerms())).findFirst())
+ .map(subCommands::get).ifPresent((method) -> this.invokeMethod(method, event, member, channel, args));
+ /*if (matchesArgs.isPresent()) {
this.invokeMethod(subCommands.get(matchesArgs.get()), event, member, channel, args);
} else {
- subCommands.keySet().stream().filter((sub) -> event.checkBotPermissions(sub.botPerms()))
- .filter(SubCommand::isDefault).findFirst().map(subCommands::get)
+ subCommands.keySet().stream().filter(SubCommand::isDefault).filter((sub) -> event.checkBotPermissions(sub.botPerms()))
+ .findFirst().map(subCommands::get)
.ifPresent((method) -> this.invokeMethod(method, event, member, channel, args));
- }
+ }*/
}
private void invokeMethod(Method method, CommandEvent event, Member member, TextChannel channel, String[] args) {
try {
method.invoke(this, event, member, channel, args);
} catch (IllegalAccessException e) {
- CommandSettings.LOGGER.error("An Exception occurred while trying to invoke sub command method; Please report this in a github issue. https://github.com/JohnnyJayJay/discord-api-command/issues", e);
+ CommandSettings.LOGGER.error("An unexpected Exception occurred while trying to invoke sub command method; Please report this in a github issue. https://github.com/JohnnyJayJay/discord-api-command/issues", e);
} catch (InvocationTargetException e) {
- CommandSettings.LOGGER.warn("One of the commands had an uncaught exception:", e.getCause());
+ CommandSettings settings = event.getCommandSettings();
+ Throwable cause = e.getCause();
+ if (settings.isLogExceptions()) {
+ CommandSettings.LOGGER.warn("Command " + event.getCommand().getExecutor().getClass().getName() + " had an uncaught Exception in SubCommand " + method.getName() + ":", cause);
+ }
+ settings.onException(event, cause);
}
}
diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractHelpCommand.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractHelpCommand.java
index 6a806e7..1ff56b3 100644
--- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractHelpCommand.java
+++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/AbstractHelpCommand.java
@@ -12,7 +12,7 @@
* This class implements ICommand, therefore each sub class can be added as a normal command with CommandSettings#put.
* The framework provides a default implementation of this class, DefaultHelpCommand.
* @author JohnnyJayJay
- * @version 3.2
+ * @version 3.2_01
* @since 3.2
* @see DefaultHelpCommand
*/
diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java
index 0c788ec..35e007c 100644
--- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java
+++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandEvent.java
@@ -15,12 +15,14 @@
import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.regex.Pattern;
+// TODO: 31.08.2018 GenericCommandEvent, CommandFailureEvent (mit reason), CommandOnCooldownEvent, CommandUnknownEvent
/**
* Represents a command event. This is not much different from a GuildMessageReceivedEvent, though it gives access to the called command
* and provides several utilities to work with, such as the getFirstMention-methods
* @author Johnny_JayJay
- * @version 3.2
+ * @version 3.2_01
* @see GuildMessageReceivedEvent
*/
public class CommandEvent extends GuildMessageReceivedEvent {
@@ -39,7 +41,7 @@ public CommandEvent(JDA api, long responseNumber, Message message, Command comma
* @param msg The message to respond with as a String.
*/
public void respond(String msg) {
- if (checkBotPermissions(Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS))
+ if (checkBotPermissions(Permission.MESSAGE_WRITE))
this.getChannel().sendMessage(msg).queue();
}
@@ -107,6 +109,7 @@ public Optional extends IMentionable> getFirstMention(Message.MentionType... m
* @return An Optional of the first mentioned Member in the event message.
* @see Optional
*/
+ @SuppressWarnings("unchecked")
public Optional getFirstUserMention() {
return (Optional) getFirstMention(Message.MentionType.USER);
}
@@ -116,6 +119,7 @@ public Optional getFirstUserMention() {
* @return An Optional of the first mentioned Role in the event message.
* @see Optional
*/
+ @SuppressWarnings("unchecked")
public Optional getFirstRoleMention() {
return (Optional) getFirstMention(Message.MentionType.ROLE);
}
@@ -125,6 +129,7 @@ public Optional getFirstRoleMention() {
* @return An Optional of the first mentioned TextChannel in the event message.
* @see Optional
*/
+ @SuppressWarnings("unchecked")
public Optional getFirstChannelMention() {
return (Optional) getFirstMention(Message.MentionType.CHANNEL);
}
@@ -155,7 +160,7 @@ protected static Command parseCommand(String raw, String prefix, CommandSettings
* Describes an executed Command.
* Is used to parse a message which seems to be a command.
* @author Johnny_JayJay
- * @version 3.1_1
+ * @version 3.2_01
*/
public static class Command {
@@ -163,25 +168,27 @@ public static class Command {
private final String joinedArgs;
private final String rawArgs;
private final String rawMessage;
+ private final String prefix;
private final String label;
private final String[] args;
private Command(String raw, String prefix, CommandSettings settings) {
- String[] argsWithoutPrefix = raw.replaceFirst(prefix, "").split("\\s+");
+ String[] argsWithoutPrefix = raw.replaceFirst(Pattern.quote(prefix), "").split("\\s+");
this.label = settings.isLabelIgnoreCase() ? argsWithoutPrefix[0].toLowerCase() : argsWithoutPrefix[0];;
- if (!settings.getCommands().containsKey(this.label)) {
- this.command = null;
- this.joinedArgs = null;
- this.rawMessage = null;
- this.rawArgs = null;
- this.args = null;
- } else {
- this.command = settings.getCommands().get(this.label);
- this.rawMessage = raw;
- this.args = Arrays.copyOfRange(argsWithoutPrefix, 1, argsWithoutPrefix.length);
- this.joinedArgs = String.join(" ", this.args);
- this.rawArgs = raw.replaceFirst(prefix + this.label + "\\s+", "");
- }
+ this.command = settings.getCommands().getOrDefault(this.label, null);
+ this.rawMessage = raw;
+ this.prefix = prefix;
+ this.args = Arrays.copyOfRange(argsWithoutPrefix, 1, argsWithoutPrefix.length);
+ this.joinedArgs = String.join(" ", this.args);
+ this.rawArgs = raw.replaceFirst(Pattern.quote(prefix + this.label) + "\\s+", "");
+ }
+
+ /**
+ * Returns the prefix used to call this command.
+ * @return The prefix.
+ */
+ public String getPrefix() {
+ return prefix;
}
/**
diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java
index ee73528..623accb 100644
--- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java
+++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandListener.java
@@ -3,14 +3,15 @@
import net.dv8tion.jda.core.Permission;
import net.dv8tion.jda.core.entities.Message;
import net.dv8tion.jda.core.entities.TextChannel;
+import net.dv8tion.jda.core.events.Event;
import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent;
-import net.dv8tion.jda.core.hooks.ListenerAdapter;
+import net.dv8tion.jda.core.hooks.EventListener;
import java.util.HashMap;
import java.util.Map;
-class CommandListener extends ListenerAdapter {
+class CommandListener implements EventListener {
private CommandSettings settings;
private Map cooldowns; // Long: User id, Long: last timestamp
@@ -21,34 +22,54 @@ public CommandListener(CommandSettings settings) {
}
@Override
- public void onGuildMessageReceived(GuildMessageReceivedEvent event) {
+ public void onEvent(Event e) {
+ if (!(e instanceof GuildMessageReceivedEvent))
+ return;
+
+ GuildMessageReceivedEvent event = (GuildMessageReceivedEvent) e;
TextChannel channel = event.getChannel();
+ // if channel is not blacklisted and author is human (or bot execution is enabled)
if (!settings.getBlacklistedChannels().contains(channel.getIdLong()) && (!event.getAuthor().isBot() || settings.botsMayExecute())) {
- String raw = event.getMessage().getContentRaw();
- String prefix = settings.getPrefix(event.getGuild().getIdLong());
- if (raw.startsWith(prefix)) {
- long timestamp = System.currentTimeMillis();
- long userId = event.getAuthor().getIdLong();
- if (cooldowns.containsKey(userId) && (timestamp - cooldowns.get(userId)) < settings.getCooldown()) {
- if (settings.isResetCooldown())
+ // if custom thread pool is configured: run async
+ settings.execute(() -> {
+ String raw = event.getMessage().getContentRaw();
+ String prefix = settings.getPrefix(event.getGuild().getIdLong());
+ if (raw.startsWith(prefix)) {
+ CommandEvent.Command cmd = CommandEvent.parseCommand(raw, prefix, settings); // parse command
+ CommandEvent commandEvent = new CommandEvent(event.getJDA(), event.getResponseNumber(), event.getMessage(), cmd, settings); // create event
+ if (cmd.getExecutor() != null) { // if command exists
+ // care about cooldowns
+ long timestamp = System.currentTimeMillis();
+ long userId = event.getAuthor().getIdLong();
+ if (cooldowns.containsKey(userId) && (timestamp - cooldowns.get(userId)) < settings.getCooldown()) {
+ if (settings.isResetCooldown())
+ cooldowns.put(userId, timestamp);
+ Message cooldownMessage = settings.getCooldownMessage();
+ if (cooldownMessage != null)
+ commandEvent.respond(cooldownMessage);
+ return;
+ }
cooldowns.put(userId, timestamp);
- return;
- }
- cooldowns.put(userId, timestamp);
- CommandEvent.Command cmd = CommandEvent.parseCommand(raw, prefix, settings);
- if (cmd.getExecutor() != null) {
- try {
- cmd.getExecutor().onCommand(new CommandEvent(event.getJDA(), event.getResponseNumber(), event.getMessage(), cmd, settings),
- event.getMember(), channel, cmd.getArgs());
- } catch (Throwable t) {
- CommandSettings.LOGGER.warn("One of the commands had an uncaught exception:", t);
+ // execute command
+ try {
+ if (settings.mayCall(commandEvent)) {
+ cmd.getExecutor().onCommand(commandEvent, event.getMember(), channel, cmd.getArgs());
+ }
+ } catch (Throwable t) {
+ settings.onException(commandEvent, t);
+ if (settings.isLogExceptions()) {
+ CommandSettings.LOGGER.warn("Command " + cmd.getExecutor().getClass().getName() + " had an uncaught exception:", t);
+ }
+ }
+ } else { // command is unknown
+ settings.onUnknownCommand(commandEvent);
+ // TODO: 03.10.2018 das entfernen
+ Message unknownCommand = settings.getUnknownCommandMessage();
+ if (unknownCommand != null)
+ commandEvent.respond(unknownCommand);
}
- } else {
- Message unknownCommand = settings.getUnknownCommandMessage();
- if (unknownCommand != null && event.getGuild().getSelfMember().hasPermission(channel, Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS))
- channel.sendMessage(unknownCommand).queue();
}
- }
+ });
}
}
}
diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSetException.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSetException.java
index eee8a7b..08e8d40 100644
--- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSetException.java
+++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSetException.java
@@ -4,7 +4,7 @@
* Exception that is thrown in case of any problems concerning the CommandSettings.
* CommandSetExceptions are RuntimeExceptions.
* @author Johnny_JayJay
- * @version 3.2
+ * @version 3.2_01
* @since 1.6
* @see RuntimeException
*/
diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java
index a13620d..8a02abe 100644
--- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java
+++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/CommandSettings.java
@@ -16,24 +16,33 @@
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
+
/**
* To use this framework, create a new object of this class and add your command classes by using add(...)
* When you want your commands to become active, use activate()
* @author Johnny_JayJay
- * @version 3.2
+ * @version 3.2_01
* @since 1.1
*/
public class CommandSettings {
/**
* A regex that only matches valid prefixes. Can be used to check user input.
+ * @deprecated Not needed anymore. Anything is a valid prefix now.
*/
+ @Deprecated
public static final String VALID_PREFIX = "[^\\\\+*^|$?]+";
/**
* A regex that only matches valid labels. Can be used to check user input.
+ * @deprecated Use {@link Util#VALID_LABEL} instead
*/
+ @Deprecated
public static final String VALID_LABEL = "[^\\s]+";
// TODO: 05.08.2018 illegal prefix characters
@@ -42,14 +51,22 @@ public class CommandSettings {
* The logger of this framework. This is protected.
*/
protected static final Logger LOGGER = LoggerFactory.getLogger("CommandAPI");
-
- private final String INVALID_PREFIX_MESSAGE = "Prefix cannot be empty or contain the characters +*^|$\\?";
- private final String INVALID_LABEL_MESSAGE = "Label cannot be empty, consist of multiple words or contain new lines!";
- private Message unknownCommandMessage;
- private String defaultPrefix;
- private long cooldown;
+ private final String INVALID_PREFIX_MESSAGE = "Prefix must not be empty!";
+ private final String INVALID_LABEL_MESSAGE = "Label must not be empty, consist of multiple words or contain new lines!";
+
+ private final boolean labelIgnoreCase; // case doesnt matter
+ private boolean useShardManager; // effectively final
+
+ private Message unknownCommandMessage; // message sent if on cooldown
+ private Message cooldownMessage; // message sent if command unknown
+ private String defaultPrefix; // default prefix
+ private long cooldown; // cooldown
private Color helpColor;
+ private Predicate check; // check made before every command execution
+ private Consumer unknownCommandHandler; // called if command unknown
+ private BiConsumer exceptionHandler; // called if uncaught exception
+ private ExecutorService executorService; // thread pool
private final Set blacklistedChannels; // ids of those channels where no command will trigger this api to execute anything.
@Deprecated
@@ -63,11 +80,10 @@ public class CommandSettings {
private final CommandListener listener;
private boolean activated; // ...is this instance activated?
- private boolean useShardManager;
- private boolean labelIgnoreCase;
- private boolean resetCooldown;
- private boolean botExecution;
+ private boolean resetCooldown; // reset cooldown each execution
+ private boolean botExecution; // bots may execute commands
+ private boolean logExceptions; // uncaught exceptions are logged
/**
@@ -111,6 +127,13 @@ private CommandSettings(@Nonnull String defaultPrefix, boolean labelIgnoreCase)
this.helpLabels = new HashSet<>();
this.blacklistedChannels = new HashSet<>();
this.prefixMap = new HashMap<>();
+ this.check = (e) -> true;
+ this.unknownCommandHandler = (e) -> {};
+ this.exceptionHandler = (e, t) -> {};
+ this.executorService = null;
+ this.unknownCommandMessage = null;
+ this.cooldownMessage = null;
+ this.logExceptions = true;
}
/**
@@ -216,6 +239,25 @@ public CommandSettings clearHelpLabels() {
return this;
}
+ /**
+ * Sets an ExecutorService that is used to execute commands asynchronously. This may, of course, increase performance.
+ * By default, this framework uses the JDA event threads.
+ * @param executorService The ExecutorService that should provide threads
+ * @return The current object. This is to use fluent interface.
+ * @throws CommandSetException If the provided ExecutorService is shut down
+ */
+ public CommandSettings useMultiThreading(@Nullable ExecutorService executorService) {
+ if (executorService == null) {
+ this.executorService = null;
+ } else {
+ if (executorService.isShutdown())
+ throw new CommandSetException("Provided ExecutorService is invalid", new IllegalArgumentException("ExecutorService must not be shut down", new IllegalStateException("Illegal thread pool state")));
+
+ this.executorService = executorService;
+ }
+ return this;
+ }
+
/**
* Adds a given channel to the blacklist (meaning commands can not be executed in there).
* @param channelId the id of the channel to be blacklisted.
@@ -242,7 +284,7 @@ public CommandSettings addChannelsToBlacklist(long... channelIds) {
* @param channelIds A Collection of channel ids to be added.
* @return The current object. This is to use fluent interface.
*/
- public CommandSettings addChannelsToBlacklist(Collection channelIds) {
+ public CommandSettings addChannelsToBlacklist(@Nonnull Collection channelIds) {
this.blacklistedChannels.addAll(channelIds);
return this;
}
@@ -276,7 +318,7 @@ public boolean removeChannelsFromBlacklist(long... channelIds) {
* @param channelIds the Collection to remove.
* @return true, if this was successful, false, if not.
*/
- public boolean removeChannelsFromBlackList(Collection channelIds) {
+ public boolean removeChannelsFromBlackList(@Nonnull Collection channelIds) {
return this.blacklistedChannels.removeAll(channelIds);
}
@@ -298,8 +340,8 @@ public CommandSettings clearBlacklist() {
* @return The current object. This is to use fluent interface.
* @throws CommandSetException If the label is empty or consists of multiple words.
*/
- public CommandSettings put(@Nonnull ICommand executor, String label) {
- if (label.matches(VALID_LABEL))
+ public CommandSettings put(@Nonnull ICommand executor, @Nonnull String label) {
+ if (label.matches(Util.VALID_LABEL))
this.commands.put(labelIgnoreCase ? label.toLowerCase() : label, executor);
else
throw new CommandSetException(INVALID_LABEL_MESSAGE, new IllegalArgumentException("Label " + label + " is not valid"));
@@ -338,7 +380,7 @@ public CommandSettings put(@Nonnull ICommand executor, @Nonnull Collection true;
+ this.exceptionHandler = (e, t) -> {};
+ this.unknownCommandHandler = (e) -> {};
if (this.activated)
this.deactivate();
return this;
}
+ /**
+ * Sets a Predicate that tests every CommandEvent before executing it. This may be useful für own cooldown implementations and such.
+ * By default, this Predicate always returns true.
+ * @param check The Predicate to use as a check.
+ * @return The current object. This is to use fluent interface.
+ */
+ public CommandSettings setCheck(@Nonnull Predicate check) {
+ this.check = check;
+ return this;
+ }
+
/**
* Use this method to set the default prefix.
* @param prefix The prefix to set. In case the given String is empty, this will throw a CommandSetException.
* @return The current object. This is to use fluent interface.
- * @throws CommandSetException if a non-null prefix does not match the requirements for a valid prefix.
+ * @throws CommandSetException if a non-null prefix is empty.
*/
public CommandSettings setDefaultPrefix(@Nonnull String prefix) {
- if (prefix.matches(VALID_PREFIX))
+ if (!prefix.isEmpty())
this.defaultPrefix = prefix;
- else
- throw new CommandSetException(INVALID_PREFIX_MESSAGE, new IllegalArgumentException("Prefix " + prefix + " is not valid"));
+ else {
+ throw new CommandSetException("Prefix is not valid", new IllegalArgumentException(INVALID_PREFIX_MESSAGE));
+ }
return this;
}
@@ -412,24 +472,21 @@ public CommandSettings setDefaultPrefix(@Nonnull String prefix) {
* @see MessageBuilder
*/
public CommandSettings setUnknownCommandMessage(@Nullable Message message) {
- if (message == null)
- this.unknownCommandMessage = null;
- else
- this.unknownCommandMessage = new MessageBuilder(message).build();
+ this.unknownCommandMessage = message == null ? null : new MessageBuilder(message).build();
return this;
}
/**
- * Use this method to add a custom command prefix to a guild. This will only work if you instantiated this class with useCustomPrefixes set to true.
+ * Use this method to add a custom command prefix to a guild.
* You can remove the custom prefix from a guild by setting its prefix to null.
* @param guildId The guild id as a long.
* @param prefix The nullable prefix to be set.
* @return The current object. This is to use fluent interface.
- * @throws CommandSetException if a non-null prefix does not match the requirements for a valid prefix.
+ * @throws CommandSetException if a non-null prefix is empty.
*/
public CommandSettings setCustomPrefix(long guildId, @Nullable String prefix) {
- if (prefix != null && !prefix.matches(VALID_PREFIX))
- throw new CommandSetException(INVALID_PREFIX_MESSAGE, new IllegalArgumentException("Prefix " + prefix + " is not valid"));
+ if (prefix != null && prefix.isEmpty())
+ throw new CommandSetException("Prefix is not valid", new IllegalArgumentException(INVALID_PREFIX_MESSAGE));
this.prefixMap.put(guildId, prefix);
return this;
}
@@ -439,13 +496,13 @@ public CommandSettings setCustomPrefix(long guildId, @Nullable String prefix) {
* prefixes for, because this bulk adds the Map parameter.
* @param guildIdPrefixMap A Map which contains the prefix for each guild to add. Key: guild ID (Long), Value: prefix (String)
* @return The current object. This is to use fluent interface.
- * @throws CommandSetException if one of the prefixes is not valid.
+ * @throws CommandSetException if one of the prefixes is empty.
*/
public CommandSettings setCustomPrefixes(@Nonnull Map guildIdPrefixMap) {
- if (guildIdPrefixMap.values().stream().allMatch((prefix) -> prefix.matches(VALID_PREFIX)))
+ if (guildIdPrefixMap.values().stream().noneMatch(String::isEmpty))
prefixMap.putAll(guildIdPrefixMap);
else
- throw new CommandSetException("One or more of the prefixes is not valid: " + INVALID_PREFIX_MESSAGE, new IllegalArgumentException("Invalid prefix"));
+ throw new CommandSetException("Prefix is not valid", new IllegalArgumentException(INVALID_PREFIX_MESSAGE));
return this;
}
@@ -482,6 +539,55 @@ public CommandSettings setResetCooldown(boolean resetCooldown) {
return this;
}
+ /**
+ * Specifies whether uncaught Exceptions that occur in command execution should be logged. You might want to disable this
+ * if you handle Exceptions manually or in the onException-listener. By default, this is true.
+ * @param logExceptions true, if uncaught Exceptions should be logged. False, if not.
+ * @return The current object. This is to use fluent interface.
+ * @see this#onException(BiConsumer)
+ */
+ public CommandSettings setLogExceptions(boolean logExceptions) {
+ this.logExceptions = logExceptions;
+ return this;
+ }
+
+
+ /**
+ * Sets a Consumer that will accept any command execution attempt in which the command label is unknown.
+ * @param action a Consumer that takes the event.
+ * @return The current object. This is to use fluent interface.
+ */
+ public CommandSettings onUnknownCommand(@Nonnull Consumer action) {
+ this.unknownCommandHandler = action;
+ return this;
+ }
+
+ /**
+ * Sets a BiConsumer that will accept any command execution attempt in which an uncaught Exception occurs.
+ * @param action a BiConsumer that takes the event and the corresponding Throwable that had been thrown.
+ * @return The current object. This is to use fluent interface.
+ */
+ public CommandSettings onException(@Nonnull BiConsumer action) {
+ this.exceptionHandler = action;
+ return this;
+ }
+
+ /**
+ * Sets a message that will be sent in case someone is on cooldown. Generally speaking, it is rather recommended to use your own cooldown
+ * implementation for specific cases like that. Still, it is possible.
+ * Setting this to null removes the message. Nothing will be sent then.
+ * @param message Nullable Message object that will be wrapped in a new MessageBuilder to prevent the usage of already sent Messages. If this is null, the message is deactivated.
+ * @return The current object. This is to use fluent interface.
+ * @see MessageBuilder
+ */
+ public CommandSettings setCooldownMessage(@Nullable Message message) {
+ if (message == null)
+ this.cooldownMessage = null;
+ else
+ this.cooldownMessage = new MessageBuilder(message).build();
+ return this;
+ }
+
/**
* Setter for the field botExecution. Decides whether bots may execute commands. By default, this is NOT the case.
* @param botExecution true, if you want to allow bots to execute commands. false, if not.
@@ -589,7 +695,7 @@ public Set getLabelSet() {
* @param command The {@link com.github.johnnyjayjay.discord.commandapi.ICommand ICommand} instance to get the labels from
* @return an unmodifiable Set of labels.
*/
- public Set getLabels(ICommand command) {
+ public Set getLabels(@Nonnull ICommand command) {
return Collections.unmodifiableSet(this.commands.keySet().stream().filter((label) -> this.commands.get(label).equals(command)).collect(Collectors.toSet()));
}
@@ -652,6 +758,14 @@ public boolean botsMayExecute() {
return this.botExecution;
}
+ /**
+ * Returns whether uncaught command exceptions are being logged.
+ * @return True, if this is enabled. False, if not.
+ */
+ public boolean isLogExceptions() {
+ return logExceptions;
+ }
+
/**
* Transforms this instance to a String, showing the current set options.
* @return This instance as a String.
@@ -659,24 +773,57 @@ public boolean botsMayExecute() {
@Override
public String toString() {
return "CommandSettings{" +
- "unknownCommandMessage=" + unknownCommandMessage +
+ "labelIgnoreCase=" + labelIgnoreCase +
+ ", useShardManager=" + useShardManager +
+ ", unknownCommandMessage=" + unknownCommandMessage +
+ ", cooldownMessage=" + cooldownMessage +
", defaultPrefix='" + defaultPrefix + '\'' +
", cooldown=" + cooldown +
", helpColor=" + helpColor +
+ ", check=" + check +
+ ", unknownCommandHandler=" + unknownCommandHandler +
+ ", exceptionHandler=" + exceptionHandler +
+ ", executorService=" + executorService +
", blacklistedChannels=" + blacklistedChannels +
", prefixMap=" + prefixMap +
", commands=" + commands +
+ ", jda=" + jda +
+ ", listener=" + listener +
", activated=" + activated +
- ", labelIgnoreCase=" + labelIgnoreCase +
", resetCooldown=" + resetCooldown +
", botExecution=" + botExecution +
+ ", logExceptions=" + logExceptions +
'}';
}
+
+ protected void onUnknownCommand(CommandEvent event) {
+ this.unknownCommandHandler.accept(event);
+ }
+
+ protected void onException(CommandEvent event, Throwable throwable) {
+ this.exceptionHandler.accept(event, throwable);
+ }
+
protected Message getUnknownCommandMessage() {
return unknownCommandMessage;
}
+ protected Message getCooldownMessage() {
+ return cooldownMessage;
+ }
+
+ protected boolean mayCall(CommandEvent event) {
+ return check.test(event);
+ }
+
+ protected void execute(Runnable command) {
+ if (executorService != null)
+ executorService.execute(command);
+ else
+ command.run(); // is das gut? keine ahnung
+ }
+
protected Map getCommands() {
return this.commands;
}
diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/DefaultHelpCommand.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/DefaultHelpCommand.java
index a5a1e89..c00750a 100644
--- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/DefaultHelpCommand.java
+++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/DefaultHelpCommand.java
@@ -15,7 +15,7 @@
* If you want to use this, add a new instance of this class as a command in your CommandSettings with the put-method.
* This class is final. To create your own help command implementation, please refer to AbstractHelpCommand.
* @author JohnnyJayJay
- * @version 3.2
+ * @version 3.2_01
* @since 3.2
* @see AbstractHelpCommand
*/
@@ -29,7 +29,7 @@ public final class DefaultHelpCommand extends AbstractHelpCommand {
@Override
public void provideGeneralHelp(CommandEvent event, String prefix, Map commands) {
Member selfMember = event.getGuild().getSelfMember();
- if (event.checkBotPermissions(Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS))
+ if (!event.checkBotPermissions(Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS))
return;
CommandSettings settings = event.getCommandSettings();
diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/ICommand.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/ICommand.java
index 75745b1..09e60a5 100644
--- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/ICommand.java
+++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/ICommand.java
@@ -11,7 +11,7 @@
* An interface used to describe a command class.
* In order to use this API, every class which is supposed to execute commands must implement this interface.
* @author Johnny_JayJay
- * @version 3.2
+ * @version 3.2_01
* @since 1.1
* @see AbstractCommand
* @see AbstractHelpCommand
diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/SubCommand.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/SubCommand.java
index b2d7bd5..8d59133 100644
--- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/SubCommand.java
+++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/SubCommand.java
@@ -13,7 +13,7 @@
* Methods annotated with this annotation must have the signature {@code void (com.github.johnnyjayjay.commandapi.CommandEvent, net.dv8tion.jda.core.entities.Member, net.dv8tion.jda.core.entities.TextChannel, java.lang.String[])}
* in order to be registered (you will get a warning if a signature violates that).
* @author JohnnyJayJay
- * @version 3.2
+ * @version 3.2_01
* @since 3.2
* @see AbstractCommand
*/
@@ -39,8 +39,8 @@
String[] args() default {};
/**
- * A boolean that indicates that there must be more arguments than specified in args in order to be triggered.
- * This might be useful for commands with an open argument length. If this is set to true, the command argument length must be greater than SubCommand#args().length.
+ * A boolean that indicates that there may be more arguments than specified in args in order to be triggered.
+ * This might be useful for commands with an open argument length. If this is set to true, the command argument length must be equal to or greater than SubCommand#args().length.
* This is ignored if specified in an isDefault-SubCommand.
* @return false by default.
*/
diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/Util.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/Util.java
new file mode 100644
index 0000000..55ad610
--- /dev/null
+++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/Util.java
@@ -0,0 +1,74 @@
+package com.github.johnnyjayjay.discord.commandapi;
+
+import net.dv8tion.jda.core.entities.Message;
+
+import java.util.function.BiConsumer;
+
+/**
+ * A util class with Strings that may be used as regular expressions (e.g. for {@link SubCommand SubCommands}).
+ * @author Johnny_JayJay
+ * @version 3.2_01
+ * @since 3.2_01
+ */
+public class Util {
+
+ /**
+ * A regex that matches a raw Discord member (or user) mention
+ */
+ public static final String MEMBER_MENTION = "<@!?\\d+>";
+ /**
+ * A regex that matches a raw Discord text channel mention
+ */
+ public static final String CHANNEL_MENTION = "<#\\d+>";
+ /**
+ * A regex that matches a raw Discord role mention
+ */
+ public static final String ROLE_MENTION = "<&\\d+>";
+ /**
+ * A regex that matches one digit (0-9)
+ */
+ public static final String DIGIT = "\\d";
+ /**
+ * A regex that matches everything.
+ */
+ public static final String ANYTHING = ".*";
+
+ /**
+ * A regex that only matches valid labels. Can be used to check user input.
+ */
+ public static final String VALID_LABEL = "[^\\s]+";
+
+ /**
+ * Gets the stack trace of a Throwable as if it was displayed via {@code Throwable#printStackTrace()}.
+ * This might be useful in {@link CommandSettings#onException(BiConsumer)}.
+ * @param throwable The throwable to get the stack trace of
+ * @return the stack trace as a String or at least a part of the stack trace if it is too long.
+ */
+ public static String getStackTraceAsString(Throwable throwable) {
+ StringBuilder builder = new StringBuilder().append("Exception in thread ").append(Thread.currentThread().getName()).append(" - ")
+ .append(throwable.getClass().getName()).append(": ").append(throwable.getMessage());
+ StackTraceElement[] elements = throwable.getStackTrace();
+ for (int i = 0; i < elements.length; i++) {
+ if (builder.length() > Message.MAX_CONTENT_LENGTH - 200) {
+ builder.append("\n\t...").append(elements.length - i).append(" more");
+ return builder.toString();
+ }
+ builder.append("\n\tat: ").append(elements[i]);
+ }
+
+ causes:
+ while ((throwable = throwable.getCause()) != null) {
+ builder.append("\nCaused by: ").append(throwable.getClass().getName()).append(": ").append(throwable.getMessage());
+ elements = throwable.getStackTrace();
+ for (int i = 0; i < elements.length; i++) {
+ if (builder.length() > Message.MAX_CONTENT_LENGTH - 200) {
+ builder.append("\n\t...").append(elements.length - i).append(" more");
+ break causes;
+ }
+ builder.append("\n\tat: ").append(elements[i]);
+ }
+ }
+ return builder.toString();
+ }
+
+}
diff --git a/src/main/java/com/github/johnnyjayjay/discord/commandapi/package-info.java b/src/main/java/com/github/johnnyjayjay/discord/commandapi/package-info.java
index 047a848..73efc5d 100644
--- a/src/main/java/com/github/johnnyjayjay/discord/commandapi/package-info.java
+++ b/src/main/java/com/github/johnnyjayjay/discord/commandapi/package-info.java
@@ -34,5 +34,7 @@
* {@link com.github.johnnyjayjay.discord.commandapi.CommandSetException CommandSetException} is a {@code RuntimeException} that is thrown in case of any problems with {@link com.github.johnnyjayjay.discord.commandapi.CommandSettings CommandSettings}.
*
* Please read the readme on github and take a look at the examples to see how it works.
I will give this documentation a revision in the future to provide more examples and helpful code.
+ *
+ * https://www.github.com/JohnnyJayJay
*/
package com.github.johnnyjayjay.discord.commandapi;
\ No newline at end of file
diff --git a/src/test/java/com/github/johnnyjayjay/discord/commandapi/CommandSettingsTest.java b/src/test/java/com/github/johnnyjayjay/discord/commandapi/CommandSettingsTest.java
index f09118e..864fcb3 100644
--- a/src/test/java/com/github/johnnyjayjay/discord/commandapi/CommandSettingsTest.java
+++ b/src/test/java/com/github/johnnyjayjay/discord/commandapi/CommandSettingsTest.java
@@ -13,7 +13,7 @@
/**
* @author Johnny_JayJay
- * @version 0.1-SNAPSHOT
+ * @version 3.2_01
*/
public class CommandSettingsTest {
@@ -38,14 +38,14 @@ public void before() {
@Test
public void setCustomPrefixTest() {
for (int i = 0; i < 10000; i++) {
- String prefix = randomString(true);
+ String prefix = randomString();
long id = random.nextLong();
settings.setCustomPrefix(id, prefix);
assertEquals("One prefix was not set correctly (manual adding)", prefix, settings.getPrefix(id));
}
Map prefixes = new HashMap<>();
for (int i = 0; i < 10000; i++)
- prefixes.put(random.nextLong(), randomString(true));
+ prefixes.put(random.nextLong(), randomString());
settings.setCustomPrefixes(prefixes);
prefixes.forEach((id, prefix) -> assertEquals("One prefix was not set correctly (bulk adding)", prefix, settings.getPrefix(id)));
}
@@ -72,7 +72,7 @@ public void commandsTest() {
ICommand command = (event, member, channel, args) -> {};
Set randomLabels = new HashSet<>();
for (int i = 0; i < 10000; i++)
- randomLabels.add(randomString(false).toLowerCase());
+ randomLabels.add(randomString().toLowerCase());
settings.put(command, randomLabels);
Set actualLabels = settings.getLabels(command);
assertEquals("Labels for command were not set correctly", actualLabels, randomLabels);
@@ -107,11 +107,10 @@ public void activationTest() {
}
- private String randomString(boolean replaceIllegalPrefixChars) {
+ private String randomString() {
int endIndex = random.nextInt(1, 36);
int beginIndex = random.nextInt(endIndex);
- String ret = UUID.randomUUID().toString().substring(beginIndex, endIndex);
- return replaceIllegalPrefixChars ? ret.replaceAll(CommandSettings.VALID_PREFIX.replace("^", ""), "") : ret;
+ return UUID.randomUUID().toString().substring(beginIndex, endIndex);
}
}
diff --git a/src/test/java/com/github/johnnyjayjay/discord/commandapi/JDAMock.java b/src/test/java/com/github/johnnyjayjay/discord/commandapi/JDAMock.java
index c582978..512b410 100644
--- a/src/test/java/com/github/johnnyjayjay/discord/commandapi/JDAMock.java
+++ b/src/test/java/com/github/johnnyjayjay/discord/commandapi/JDAMock.java
@@ -22,6 +22,21 @@
*/
public class JDAMock implements JDA {
+ @Override
+ public JDA awaitStatus(Status status) throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public IEventManager getEventManager() {
+ return null;
+ }
+
+ @Override
+ public RestAction getWebhookById(String s) {
+ return null;
+ }
+
@Override
public Status getStatus() {
return null;