Progress on running commands from Discord

This commit is contained in:
Vankka 2023-07-01 19:04:52 +03:00
parent c1edd44751
commit 383a7ad97a
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
8 changed files with 186 additions and 51 deletions

View File

@ -1,12 +0,0 @@
package com.discordsrv.bukkit;
import org.bukkit.Server;
import java.util.Set;
public class PaperCmdMap {
public static Set<String> getMap(Server server) {
return server.getCommandMap().getKnownCommands().keySet();
}
}

View File

@ -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<String> getKnownCommands(Server server) {
return server.getCommandMap().getKnownCommands().keySet();
}
}

View File

@ -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<DiscordSRVBukkitBootstrap
private final BukkitPluginManager pluginManager;
private AbstractBukkitCommandHandler commandHandler;
private final BukkitRequiredLinkingListener requiredLinkingListener;
private final BukkitAutoCompleteHelper autoCompleteHelper;
private final BukkitConnectionConfigManager connectionConfigManager;
private final BukkitConfigManager configManager;
@ -98,6 +97,7 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
load();
this.requiredLinkingListener = new BukkitRequiredLinkingListener(this);
this.autoCompleteHelper = new BukkitAutoCompleteHelper(this);
}
public JavaPlugin plugin() {
@ -231,38 +231,6 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<DiscordSRVBukkitBootstrap
}
public ExecuteCommand.AutoCompleteHelper autoCompleteHelper() {
return parts -> {
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<String> 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<String> 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;
}
}

View File

@ -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<List<String>> suggestCommands(List<String> 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<String> suggestions = new ArrayList<>();
if (PaperCommandMap.IS_AVAILABLE) {
// If Paper's CommandMap is available we can list out 'root' commands
CompletableFuture<List<String>> 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<List<String>> future = new CompletableFuture<>();
String finalPrefix = prefix;
discordSRV.scheduler().runOnMainThread(() -> {
try {
CommandSender commandSender = discordSRV.server().getConsoleSender();
List<String> completions = command.tabComplete(commandSender, commandName, parts.toArray(new String[0]));
List<String> suggestions = new ArrayList<>();
for (String suggestion : completions) {
suggestions.add(commandName + " " + finalPrefix + suggestion);
}
future.complete(suggestions);
} catch (Throwable t) {
future.completeExceptionally(t);
}
});
return future;
}
}

View File

@ -153,6 +153,7 @@ public interface DiscordSRV extends DiscordSRVApi {
List<ReloadResult> runReload(Set<ReloadFlag> flags, boolean silent);
CompletableFuture<Void> invokeDisable();
@Nullable
default ExecuteCommand.AutoCompleteHelper autoCompleteHelper() {
return null;
}

View File

@ -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<DiscordChatInputInteractionEvent>, DiscordCommand.AutoCompleteHandler {
@ -36,9 +42,11 @@ public class ExecuteCommand implements Consumer<DiscordChatInputInteractionEvent
}
private final DiscordSRV discordSRV;
private final Logger logger;
public ExecuteCommand(DiscordSRV discordSRV) {
this.discordSRV = discordSRV;
this.logger = new NamedLogger(discordSRV, "EXECUTE_COMMAND");
}
@Override
@ -63,12 +71,25 @@ public class ExecuteCommand implements Consumer<DiscordChatInputInteractionEvent
List<String> parts = new ArrayList<>(Arrays.asList(command.split(" ")));
AutoCompleteHelper helper = discordSRV.autoCompleteHelper();
if (helper == null) {
// No suggestions available.
return;
}
List<String> suggestions = getSuggestions(helper, parts);
if (suggestions == null) {
return;
}
List<String> suggestions = helper.suggestCommands(new ArrayList<>(parts));
if (suggestions.isEmpty() || suggestions.contains(command)) {
parts.add("");
suggestions = new ArrayList<>(helper.suggestCommands(parts));
List<String> 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<DiscordChatInputInteractionEvent
}
}
private List<String> getSuggestions(AutoCompleteHelper helper, List<String> 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<String> suggestCommands(List<String> parts);
CompletableFuture<List<String>> suggestCommands(List<String> parts);
}
}

View File

@ -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 {
}
}

View File

@ -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<Long> 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<String> commands = new ArrayList<>();
}