From 383a7ad97ac129172ee33c59e76969a2897f7d40 Mon Sep 17 00:00:00 2001 From: Vankka Date: Sat, 1 Jul 2023 19:04:52 +0300 Subject: [PATCH] Progress on running commands from Discord --- .../com/discordsrv/bukkit/PaperCmdMap.java | 12 --- .../discordsrv/bukkit/PaperCommandMap.java | 24 ++++++ .../discordsrv/bukkit/BukkitDiscordSRV.java | 40 +-------- .../game/BukkitAutoCompleteHelper.java | 81 +++++++++++++++++++ .../com/discordsrv/common/DiscordSRV.java | 1 + .../commands/subcommand/ExecuteCommand.java | 44 +++++++++- .../config/main/DiscordCommandConfig.java | 15 ++++ .../main/generic/GameCommandFilterConfig.java | 20 +++++ 8 files changed, 186 insertions(+), 51 deletions(-) delete mode 100644 bukkit/paper/src/main/java/com/discordsrv/bukkit/PaperCmdMap.java create mode 100644 bukkit/paper/src/main/java/com/discordsrv/bukkit/PaperCommandMap.java create mode 100644 bukkit/src/main/java/com/discordsrv/bukkit/command/game/BukkitAutoCompleteHelper.java create mode 100644 common/src/main/java/com/discordsrv/common/config/main/DiscordCommandConfig.java create mode 100644 common/src/main/java/com/discordsrv/common/config/main/generic/GameCommandFilterConfig.java diff --git a/bukkit/paper/src/main/java/com/discordsrv/bukkit/PaperCmdMap.java b/bukkit/paper/src/main/java/com/discordsrv/bukkit/PaperCmdMap.java deleted file mode 100644 index ddfbb7bd..00000000 --- a/bukkit/paper/src/main/java/com/discordsrv/bukkit/PaperCmdMap.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.discordsrv.bukkit; - -import org.bukkit.Server; - -import java.util.Set; - -public class PaperCmdMap { - - public static Set getMap(Server server) { - return server.getCommandMap().getKnownCommands().keySet(); - } -} diff --git a/bukkit/paper/src/main/java/com/discordsrv/bukkit/PaperCommandMap.java b/bukkit/paper/src/main/java/com/discordsrv/bukkit/PaperCommandMap.java new file mode 100644 index 00000000..6ef3b263 --- /dev/null +++ b/bukkit/paper/src/main/java/com/discordsrv/bukkit/PaperCommandMap.java @@ -0,0 +1,24 @@ +package com.discordsrv.bukkit; + +import org.bukkit.Server; + +import java.util.Set; + +public class PaperCommandMap { + + public static final boolean IS_AVAILABLE; + + static { + boolean is = false; + try { + Class serverClass = Server.class; + serverClass.getDeclaredMethod("getCommandMap"); + is = true; + } catch (Throwable ignored) {} + IS_AVAILABLE = is; + } + + public static Set getKnownCommands(Server server) { + return server.getCommandMap().getKnownCommands().keySet(); + } +} diff --git a/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java b/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java index a62866ec..9d2e0e94 100644 --- a/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java +++ b/bukkit/src/main/java/com/discordsrv/bukkit/BukkitDiscordSRV.java @@ -19,6 +19,7 @@ package com.discordsrv.bukkit; import com.discordsrv.api.DiscordSRVApi; +import com.discordsrv.bukkit.command.game.BukkitAutoCompleteHelper; import com.discordsrv.bukkit.command.game.handler.AbstractBukkitCommandHandler; import com.discordsrv.bukkit.component.translation.BukkitTranslationLoader; import com.discordsrv.bukkit.config.connection.BukkitConnectionConfig; @@ -48,14 +49,11 @@ import com.discordsrv.common.messageforwarding.game.minecrafttodiscord.Minecraft import com.discordsrv.common.plugin.PluginManager; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bukkit.Server; -import org.bukkit.command.Command; import org.bukkit.plugin.ServicePriority; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Set; @@ -70,6 +68,7 @@ public class BukkitDiscordSRV extends ServerDiscordSRV { - String commandName = !parts.isEmpty() ? parts.remove(0) : null; - Command command = commandName != null ? server().getPluginCommand(commandName) : null; - if (command == null) { - if (parts.size() > 1) { - // Command is not known but there are arguments, nothing to auto complete... - return Collections.emptyList(); - } else { - // List out commands - List suggestions = new ArrayList<>(); - for (String cmd : PaperCmdMap.getMap(server())) { - if (commandName == null || cmd.startsWith(commandName)) { - suggestions.add(cmd); - } - } - - return suggestions; - } - } - - // Get the arguments minus the last one (if any) - String prefix = String.join(" ", parts.subList(0, parts.size() - (!parts.isEmpty() ? 1 : 0))); - if (!prefix.isEmpty()) { - prefix = prefix + " "; - } - - List suggestions = new ArrayList<>(); - for (String suggestion : command.tabComplete(server().getConsoleSender(), commandName, parts.toArray(new String[0]))) { - suggestions.add(commandName + " " + prefix + suggestion); - } - - return suggestions; - }; + return autoCompleteHelper; } } diff --git a/bukkit/src/main/java/com/discordsrv/bukkit/command/game/BukkitAutoCompleteHelper.java b/bukkit/src/main/java/com/discordsrv/bukkit/command/game/BukkitAutoCompleteHelper.java new file mode 100644 index 00000000..c54cd439 --- /dev/null +++ b/bukkit/src/main/java/com/discordsrv/bukkit/command/game/BukkitAutoCompleteHelper.java @@ -0,0 +1,81 @@ +package com.discordsrv.bukkit.command.game; + +import com.discordsrv.bukkit.BukkitDiscordSRV; +import com.discordsrv.bukkit.PaperCommandMap; +import com.discordsrv.common.command.discord.commands.subcommand.ExecuteCommand; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class BukkitAutoCompleteHelper implements ExecuteCommand.AutoCompleteHelper { + + private final BukkitDiscordSRV discordSRV; + + public BukkitAutoCompleteHelper(BukkitDiscordSRV discordSRV) { + this.discordSRV = discordSRV; + } + + @Override + public CompletableFuture> suggestCommands(List parts) { + String commandName = !parts.isEmpty() ? parts.remove(0) : null; + Command command = commandName != null ? discordSRV.server().getPluginCommand(commandName) : null; + if (command == null) { + if (parts.size() > 1) { + // Command is not known but there are arguments, nothing to auto complete... + return CompletableFuture.completedFuture(Collections.emptyList()); + } else { + // List out commands + List suggestions = new ArrayList<>(); + + if (PaperCommandMap.IS_AVAILABLE) { + // If Paper's CommandMap is available we can list out 'root' commands + CompletableFuture> future = new CompletableFuture<>(); + discordSRV.scheduler().runOnMainThread(() -> { + try { + for (String cmd : PaperCommandMap.getKnownCommands(discordSRV.server())) { + if (commandName == null || cmd.startsWith(commandName)) { + suggestions.add(cmd); + } + } + future.complete(suggestions); + } catch (Throwable t) { + future.completeExceptionally(t); + } + }); + return future; + } + + return CompletableFuture.completedFuture(suggestions); + } + } + + // Get the arguments minus the last one (if any) + String prefix = String.join(" ", parts.subList(0, parts.size() - (!parts.isEmpty() ? 1 : 0))); + if (!prefix.isEmpty()) { + prefix = prefix + " "; + } + + CompletableFuture> future = new CompletableFuture<>(); + String finalPrefix = prefix; + discordSRV.scheduler().runOnMainThread(() -> { + try { + CommandSender commandSender = discordSRV.server().getConsoleSender(); + List completions = command.tabComplete(commandSender, commandName, parts.toArray(new String[0])); + + List suggestions = new ArrayList<>(); + for (String suggestion : completions) { + suggestions.add(commandName + " " + finalPrefix + suggestion); + } + future.complete(suggestions); + } catch (Throwable t) { + future.completeExceptionally(t); + } + }); + + return future; + } +} diff --git a/common/src/main/java/com/discordsrv/common/DiscordSRV.java b/common/src/main/java/com/discordsrv/common/DiscordSRV.java index 5e2ced71..9c7b47e5 100644 --- a/common/src/main/java/com/discordsrv/common/DiscordSRV.java +++ b/common/src/main/java/com/discordsrv/common/DiscordSRV.java @@ -153,6 +153,7 @@ public interface DiscordSRV extends DiscordSRVApi { List runReload(Set flags, boolean silent); CompletableFuture invokeDisable(); + @Nullable default ExecuteCommand.AutoCompleteHelper autoCompleteHelper() { return null; } diff --git a/common/src/main/java/com/discordsrv/common/command/discord/commands/subcommand/ExecuteCommand.java b/common/src/main/java/com/discordsrv/common/command/discord/commands/subcommand/ExecuteCommand.java index 48e557d8..70fa8b44 100644 --- a/common/src/main/java/com/discordsrv/common/command/discord/commands/subcommand/ExecuteCommand.java +++ b/common/src/main/java/com/discordsrv/common/command/discord/commands/subcommand/ExecuteCommand.java @@ -6,11 +6,17 @@ import com.discordsrv.api.discord.entity.interaction.component.ComponentIdentifi import com.discordsrv.api.discord.events.interaction.command.DiscordChatInputInteractionEvent; import com.discordsrv.api.discord.events.interaction.command.DiscordCommandAutoCompleteInteractionEvent; import com.discordsrv.common.DiscordSRV; +import com.discordsrv.common.logging.Logger; +import com.discordsrv.common.logging.NamedLogger; import net.dv8tion.jda.api.interactions.commands.OptionMapping; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; public class ExecuteCommand implements Consumer, DiscordCommand.AutoCompleteHandler { @@ -36,9 +42,11 @@ public class ExecuteCommand implements Consumer parts = new ArrayList<>(Arrays.asList(command.split(" "))); AutoCompleteHelper helper = discordSRV.autoCompleteHelper(); + if (helper == null) { + // No suggestions available. + return; + } + + List suggestions = getSuggestions(helper, parts); + if (suggestions == null) { + return; + } - List suggestions = helper.suggestCommands(new ArrayList<>(parts)); if (suggestions.isEmpty() || suggestions.contains(command)) { parts.add(""); - suggestions = new ArrayList<>(helper.suggestCommands(parts)); + List newSuggestions = getSuggestions(helper, parts); + if (newSuggestions == null) { + return; + } + + suggestions = new ArrayList<>(newSuggestions); if (suggestions.isEmpty()) { suggestions.add(command); } @@ -81,8 +102,25 @@ public class ExecuteCommand implements Consumer getSuggestions(AutoCompleteHelper helper, List parts) { + try { + return helper.suggestCommands(new ArrayList<>(parts)).get(2, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; + } catch (TimeoutException e) { + return null; + } catch (ExecutionException e) { + logger.error("Failed to suggest commands", e.getCause()); + return null; + } catch (Throwable t) { + logger.error("Failed to suggest commands", t); + return null; + } + } + public interface AutoCompleteHelper { - List suggestCommands(List parts); + CompletableFuture> suggestCommands(List parts); } } diff --git a/common/src/main/java/com/discordsrv/common/config/main/DiscordCommandConfig.java b/common/src/main/java/com/discordsrv/common/config/main/DiscordCommandConfig.java new file mode 100644 index 00000000..e6176461 --- /dev/null +++ b/common/src/main/java/com/discordsrv/common/config/main/DiscordCommandConfig.java @@ -0,0 +1,15 @@ +package com.discordsrv.common.config.main; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; + +@ConfigSerializable +public class DiscordCommandConfig { + + public ExecuteConfig execute = new ExecuteConfig(); + + @ConfigSerializable + public static class ExecuteConfig { + + + } +} diff --git a/common/src/main/java/com/discordsrv/common/config/main/generic/GameCommandFilterConfig.java b/common/src/main/java/com/discordsrv/common/config/main/generic/GameCommandFilterConfig.java new file mode 100644 index 00000000..08d94513 --- /dev/null +++ b/common/src/main/java/com/discordsrv/common/config/main/generic/GameCommandFilterConfig.java @@ -0,0 +1,20 @@ +package com.discordsrv.common.config.main.generic; + +import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.objectmapping.meta.Comment; + +import java.util.ArrayList; +import java.util.List; + +@ConfigSerializable +public class GameCommandFilterConfig { + + @Comment("true for blacklist (blocking commands), false for whitelist (allowing commands)") + public boolean blacklist = true; + + @Comment("The role and user ids which this set of allowed/blocked commands is for") + public List roleAndUserIds = new ArrayList<>(); + + @Comment("The commands that are allowed/blocked. Use / at the beginning and end of a value for a regular expression (regex)") + public List commands = new ArrayList<>(); +}