diff --git a/build.gradle b/build.gradle index 941c6fe..564df74 100644 --- a/build.gradle +++ b/build.gradle @@ -60,6 +60,8 @@ repositories.jcenter() dependencies { api 'org.apache.logging.log4j:log4j-api:2.13.0' + + implementation 'me.xdrop:fuzzywuzzy:1.2.0' compileOnly 'org.javacord:javacord-api:3.0.5' compileOnly 'org.javacord:javacord-core:3.0.5' diff --git a/src/main/java/org/comroid/javacord/util/commands/CommandHandler.java b/src/main/java/org/comroid/javacord/util/commands/CommandHandler.java index f05ea69..8b491e0 100644 --- a/src/main/java/org/comroid/javacord/util/commands/CommandHandler.java +++ b/src/main/java/org/comroid/javacord/util/commands/CommandHandler.java @@ -26,6 +26,7 @@ import org.comroid.javacord.util.model.command.SelfBotOwnerIgnorable; import org.comroid.javacord.util.model.command.SelfCommandChannelable; import org.comroid.javacord.util.model.command.SelfCustomPrefixable; +import org.comroid.javacord.util.model.command.SelfFuzzyComparable; import org.comroid.javacord.util.model.command.SelfMultiCommandRegisterable; import org.comroid.javacord.util.model.command.SelfUnknownCommandRespondable; import org.comroid.javacord.util.ui.embed.DefaultEmbedFactory; @@ -36,6 +37,8 @@ import org.comroid.javacord.util.ui.messages.paging.PagedMessage; import org.comroid.javacord.util.ui.reactions.InfoReaction; +import me.xdrop.fuzzywuzzy.FuzzySearch; +import me.xdrop.fuzzywuzzy.model.ExtractedResult; import org.apache.logging.log4j.Logger; import org.javacord.api.DiscordApi; import org.javacord.api.entity.DiscordEntity; @@ -68,6 +71,7 @@ public final class CommandHandler implements SelfCommandChannelable, SelfCustomPrefixable, SelfBotOwnerIgnorable, + SelfFuzzyComparable, SelfUnknownCommandRespondable { private static final Logger logger = LoggerUtil.getLogger(CommandHandler.class); static final String NO_GROUP = "@NoGroup#"; @@ -85,6 +89,7 @@ public final class CommandHandler implements private long[] serverBlacklist; private boolean ignoreBotOwnerPermissions; private boolean respondToUnknownCommand; + private @Nullable Function fuzzyMatchingThresholdFunction; public CommandHandler(DiscordApi api) { this(api, false); @@ -328,6 +333,18 @@ public boolean doesRespondToUnknownCommands() { return respondToUnknownCommand; } + @Override + public CommandHandler withFuzzyMatchingThreshold(@Nullable Function fuzzyMatchingThresholdFunction) { + this.fuzzyMatchingThresholdFunction = fuzzyMatchingThresholdFunction; + + return this; + } + + @Override + public Optional> getFuzzyMatchingThreshold() { + return Optional.ofNullable(fuzzyMatchingThresholdFunction); + } + private void extractCommandRep(@Nullable Object invocationTarget, Method... methods) { for (Method method : methods) { Command cmd = method.getAnnotation(Command.class); @@ -462,27 +479,21 @@ private void handleCommand(final Message message, final TextChannel channel, fin CommandRepresentation cmd; String[] split = splitContent(content); final String[] commandName = new String[1]; + String[] args; + Optional serverOptional = commandParams.getServer(); if (usedPrefix.matches("^(.*\\s.*)+$")) { - cmd = commands.entrySet() - .stream() - .filter(entry -> entry.getKey() - .toLowerCase() - .equals((commandName[0] = split[1]).substring(usedPrefix.length()).toLowerCase())) - .findFirst() - .map(Map.Entry::getValue) - .orElse(null); + final String cmdQuery = (commandName[0] = split[1]).substring(usedPrefix.length()).toLowerCase(); + + cmd = findCommand(serverOptional.orElse(null), cmdQuery); + args = new String[split.length - 2]; arraycopy(split, 2, args, 0, args.length); } else { - cmd = commands.entrySet() - .stream() - .filter(entry -> entry.getKey() - .toLowerCase() - .equals((commandName[0] = split[0]).substring(usedPrefix.length()).toLowerCase())) - .findFirst() - .map(Map.Entry::getValue) - .orElse(null); + final String cmdQuery = (commandName[0] = split[0]).substring(usedPrefix.length()).toLowerCase(); + + cmd = findCommand(serverOptional.orElse(null), cmdQuery); + args = new String[split.length - 1]; arraycopy(split, 1, args, 0, args.length); } @@ -557,6 +568,32 @@ else if (!message.isPrivateMessage() && !cmd.enableServerChat) else doInvoke(cmd, commandParams, channel, message); } + private @Nullable CommandRepresentation findCommand(@Nullable Server server, String cmdQuery) { + CommandRepresentation yield = null; + + if (fuzzyMatchingThresholdFunction != null && server != null) { + final List extractedResults = FuzzySearch.extractTop( + cmdQuery, + commands.keySet(), + fuzzyMatchingThresholdFunction.apply(server.getId()) + ); + + if (extractedResults.size() == 1) + yield = commands.get(extractedResults.get(0).getString()); + } else { + yield = commands.entrySet() + .stream() + .filter(entry -> entry.getKey() + .toLowerCase() + .equals(cmdQuery)) + .findFirst() + .map(Map.Entry::getValue) + .orElse(null); + } + + return yield; + } + private @Nullable String extractUsedPrefix(final Message message) { String content = message.getContent(); int usedPrefix = -1; diff --git a/src/main/java/org/comroid/javacord/util/model/command/SelfFuzzyComparable.java b/src/main/java/org/comroid/javacord/util/model/command/SelfFuzzyComparable.java new file mode 100644 index 0000000..6af1fe3 --- /dev/null +++ b/src/main/java/org/comroid/javacord/util/model/command/SelfFuzzyComparable.java @@ -0,0 +1,16 @@ +package org.comroid.javacord.util.model.command; + +import java.util.Optional; +import java.util.function.Function; + +import org.jetbrains.annotations.Nullable; + +public interface SelfFuzzyComparable> { + Self withFuzzyMatchingThreshold(@Nullable Function fuzzyMatchingThresholdFunction); + + Optional> getFuzzyMatchingThreshold(); + + default Self removeFuzzyMatchingThreshold() { + return withFuzzyMatchingThreshold(null); + } +}