From a4f580e3193a12e5456b56123c85dc99500efad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20S=C3=B6derberg?= Date: Mon, 19 Oct 2020 16:27:40 +0200 Subject: [PATCH] Make HelpInput use the known command map (#3675) Co-authored-by: MD <1917406+md678685@users.noreply.github.com> This PR makes HelpInput use knownCommands in the command map, rather than relying on plugin descriptions. This means that commands that have been registered programmatically (or impromptu) will be recognized and displayed in the help menu. The ugly reflection is due to Spigot not exposing the command map getter. This has been tested on Spigot 1.8, 1.16.3 and Paper 1.16.3 with plugins that register commands directly to the command map. --- .../com/earth2me/essentials/Essentials.java | 16 +++++ .../com/earth2me/essentials/IEssentials.java | 3 + .../commands/EssentialsCommand.java | 19 +++-- .../essentials/textreader/HelpInput.java | 70 +++++++++++-------- .../ess3/provider/KnownCommandsProvider.java | 9 +++ .../providers/ReflKnownCommandsProvider.java | 48 +++++++++++++ .../providers/PaperKnownCommandsProvider.java | 19 +++++ 7 files changed, 146 insertions(+), 38 deletions(-) create mode 100644 providers/BaseProviders/src/net/ess3/provider/KnownCommandsProvider.java create mode 100644 providers/NMSReflectionProvider/src/net/ess3/nms/refl/providers/ReflKnownCommandsProvider.java create mode 100644 providers/PaperProvider/src/net/ess3/provider/providers/PaperKnownCommandsProvider.java diff --git a/Essentials/src/com/earth2me/essentials/Essentials.java b/Essentials/src/com/earth2me/essentials/Essentials.java index 933a4b6b7..709c1b263 100644 --- a/Essentials/src/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/com/earth2me/essentials/Essentials.java @@ -47,7 +47,9 @@ import net.ess3.api.ISettings; import net.ess3.nms.refl.providers.ReflServerStateProvider; import net.ess3.nms.refl.providers.ReflSpawnEggProvider; import net.ess3.nms.refl.providers.ReflSpawnerBlockProvider; +import net.ess3.nms.refl.providers.ReflKnownCommandsProvider; import net.ess3.provider.ContainerProvider; +import net.ess3.provider.KnownCommandsProvider; import net.ess3.provider.PotionMetaProvider; import net.ess3.provider.ProviderListener; import net.ess3.provider.ServerStateProvider; @@ -61,6 +63,7 @@ import net.ess3.provider.providers.FlatSpawnEggProvider; import net.ess3.provider.providers.LegacyPotionMetaProvider; import net.ess3.provider.providers.LegacySpawnEggProvider; import net.ess3.provider.providers.PaperContainerProvider; +import net.ess3.provider.providers.PaperKnownCommandsProvider; import net.ess3.provider.providers.PaperRecipeBookListener; import net.ess3.provider.providers.PaperServerStateProvider; import org.bukkit.Bukkit; @@ -134,6 +137,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { private transient PotionMetaProvider potionMetaProvider; private transient ServerStateProvider serverStateProvider; private transient ContainerProvider containerProvider; + private transient KnownCommandsProvider knownCommandsProvider; private transient ProviderListener recipeBookEventProvider; private transient Kits kits; private transient RandomTeleport randomTeleport; @@ -324,6 +328,13 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { } } + //Known Commands Provider + if (PaperLib.isPaper() && VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_11_2_R01)) { + knownCommandsProvider = new PaperKnownCommandsProvider(); + } else { + knownCommandsProvider = new ReflKnownCommandsProvider(); + } + execTimer.mark("Init(Providers)"); reload(); @@ -1013,6 +1024,11 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { return containerProvider; } + @Override + public KnownCommandsProvider getKnownCommandsProvider() { + return knownCommandsProvider; + } + private AbstractItemDb getItemDbFromConfig() { final String setting = settings.getItemDbType(); diff --git a/Essentials/src/com/earth2me/essentials/IEssentials.java b/Essentials/src/com/earth2me/essentials/IEssentials.java index 98294dfee..dda4eba61 100644 --- a/Essentials/src/com/earth2me/essentials/IEssentials.java +++ b/Essentials/src/com/earth2me/essentials/IEssentials.java @@ -5,6 +5,7 @@ import com.earth2me.essentials.api.IJails; import com.earth2me.essentials.api.IWarps; import com.earth2me.essentials.perm.PermissionsHandler; import net.ess3.provider.ContainerProvider; +import net.ess3.provider.KnownCommandsProvider; import net.ess3.provider.ServerStateProvider; import net.ess3.provider.SpawnerBlockProvider; import net.ess3.provider.SpawnerItemProvider; @@ -115,4 +116,6 @@ public interface IEssentials extends Plugin { ServerStateProvider getServerStateProvider(); ContainerProvider getContainerProvider(); + + KnownCommandsProvider getKnownCommandsProvider(); } diff --git a/Essentials/src/com/earth2me/essentials/commands/EssentialsCommand.java b/Essentials/src/com/earth2me/essentials/commands/EssentialsCommand.java index 5af932a6d..af261d804 100644 --- a/Essentials/src/com/earth2me/essentials/commands/EssentialsCommand.java +++ b/Essentials/src/com/earth2me/essentials/commands/EssentialsCommand.java @@ -7,13 +7,14 @@ import com.earth2me.essentials.User; import com.earth2me.essentials.utils.FormatUtil; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import net.ess3.api.IEssentials; import org.bukkit.Server; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; +import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.util.StringUtil; import java.util.ArrayList; @@ -309,17 +310,15 @@ public abstract class EssentialsCommand implements IEssentialsCommand { /** * Lists all commands. - *

- * TODO: Use the real commandmap to do this automatically. */ - protected final List getCommands(final Server server) { - final List commands = Lists.newArrayList(); - for (final Plugin p : server.getPluginManager().getPlugins()) { - final PluginDescriptionFile desc = p.getDescription(); - final Map> cmds = desc.getCommands(); - if (cmds != null) { - commands.addAll(cmds.keySet()); + protected final List getCommands(Server server) { + final Map commandMap = Maps.newHashMap(this.ess.getKnownCommandsProvider().getKnownCommands()); + final List commands = Lists.newArrayListWithCapacity(commandMap.size()); + for (final Command command : commandMap.values()) { + if (!(command instanceof PluginIdentifiableCommand)) { + continue; } + commands.add(command.getName()); } return commands; } diff --git a/Essentials/src/com/earth2me/essentials/textreader/HelpInput.java b/Essentials/src/com/earth2me/essentials/textreader/HelpInput.java index 859a5f30a..dd3442c80 100644 --- a/Essentials/src/com/earth2me/essentials/textreader/HelpInput.java +++ b/Essentials/src/com/earth2me/essentials/textreader/HelpInput.java @@ -1,11 +1,14 @@ package com.earth2me.essentials.textreader; import com.earth2me.essentials.User; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; import net.ess3.api.IEssentials; +import org.bukkit.command.Command; +import org.bukkit.command.PluginIdentifiableCommand; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -17,15 +20,12 @@ import java.util.logging.Logger; import static com.earth2me.essentials.I18n.tl; public class HelpInput implements IText { - private static final String DESCRIPTION = "description"; - private static final String PERMISSION = "permission"; - private static final String PERMISSIONS = "permissions"; private static final Logger logger = Logger.getLogger("Essentials"); private final transient List lines = new ArrayList<>(); private final transient List chapters = new ArrayList<>(); private final transient Map bookmarks = new HashMap<>(); - public HelpInput(final User user, final String match, final IEssentials ess) throws IOException { + public HelpInput(final User user, final String match, final IEssentials ess) { boolean reported = false; final List newLines = new ArrayList<>(); String pluginName = ""; @@ -34,7 +34,16 @@ public class HelpInput implements IText { lines.add(tl("helpMatching", match)); } - for (final Plugin p : ess.getServer().getPluginManager().getPlugins()) { + final Multimap pluginCommands = HashMultimap.create(); + for (final Command command : ess.getKnownCommandsProvider().getKnownCommands().values()) { + if (!(command instanceof PluginIdentifiableCommand)) { + continue; + } + final PluginIdentifiableCommand pluginIdentifiableCommand = (PluginIdentifiableCommand) command; + pluginCommands.put(pluginIdentifiableCommand.getPlugin(), command); + } + + for (Plugin p : ess.getServer().getPluginManager().getPlugins()) { try { final List pluginLines = new ArrayList<>(); final PluginDescriptionFile desc = p.getDescription(); @@ -48,46 +57,51 @@ public class HelpInput implements IText { } final boolean isOnWhitelist = user.isAuthorized("essentials.help." + pluginNameLow); - for (final Map.Entry> k : cmds.entrySet()) { + for (final Command command : pluginCommands.get(p)) { try { - if (!match.equalsIgnoreCase("") && !pluginNameLow.contains(match) && !k.getKey().toLowerCase(Locale.ENGLISH).contains(match) && !(k.getValue().get(DESCRIPTION) instanceof String && ((String) k.getValue().get(DESCRIPTION)).toLowerCase(Locale.ENGLISH).contains(match))) { + final String commandName = command.getName(); + final String commandDescription = command.getDescription(); + + if (!match.equalsIgnoreCase("") + && (!pluginNameLow.contains(match)) + && (!commandName.toLowerCase(Locale.ENGLISH).contains(match)) + && (!commandDescription.toLowerCase(Locale.ENGLISH).contains(match))) { continue; } if (pluginNameLow.contains("essentials")) { - final String node = "essentials." + k.getKey(); - if (!ess.getSettings().isCommandDisabled(k.getKey()) && user.isAuthorized(node)) { - pluginLines.add(tl("helpLine", k.getKey(), k.getValue().get(DESCRIPTION))); + final String node = "essentials." + commandName; + if (!ess.getSettings().isCommandDisabled(commandName) && user.isAuthorized(node)) { + pluginLines.add(tl("helpLine", commandName, commandDescription)); } } else { if (ess.getSettings().showNonEssCommandsInHelp()) { - final Map value = k.getValue(); - Object permissions = null; - if (value.containsKey(PERMISSION)) { - permissions = value.get(PERMISSION); - } else if (value.containsKey(PERMISSIONS)) { - permissions = value.get(PERMISSIONS); + final String permissionRaw = command.getPermission(); + final String[] permissions; + if (permissionRaw == null) { + permissions = new String[0]; + } else { + permissions = permissionRaw.split(";"); } - if (isOnWhitelist || user.isAuthorized("essentials.help." + pluginNameLow + "." + k.getKey())) { - pluginLines.add(tl("helpLine", k.getKey(), value.get(DESCRIPTION))); - } else if (permissions instanceof List && !((List) permissions).isEmpty()) { + + if (isOnWhitelist || user.isAuthorized("essentials.help." + pluginNameLow + "." + commandName)) { + pluginLines.add(tl("helpLine", commandName, commandDescription)); + } else if (permissions.length != 0) { boolean enabled = false; - for (final Object o : (List) permissions) { - if (o instanceof String && user.isAuthorized(o.toString())) { + + for (final String permission : permissions) { + if (user.isAuthorized(permission)) { enabled = true; break; } } + if (enabled) { - pluginLines.add(tl("helpLine", k.getKey(), value.get(DESCRIPTION))); - } - } else if (permissions instanceof String && !"".equals(permissions)) { - if (user.isAuthorized(permissions.toString())) { - pluginLines.add(tl("helpLine", k.getKey(), value.get(DESCRIPTION))); + pluginLines.add(tl("helpLine", commandName, commandDescription)); } } else { if (!ess.getSettings().hidePermissionlessHelp()) { - pluginLines.add(tl("helpLine", k.getKey(), value.get(DESCRIPTION))); + pluginLines.add(tl("helpLine", commandName, commandDescription)); } } } diff --git a/providers/BaseProviders/src/net/ess3/provider/KnownCommandsProvider.java b/providers/BaseProviders/src/net/ess3/provider/KnownCommandsProvider.java new file mode 100644 index 000000000..952db7136 --- /dev/null +++ b/providers/BaseProviders/src/net/ess3/provider/KnownCommandsProvider.java @@ -0,0 +1,9 @@ +package net.ess3.provider; + +import org.bukkit.command.Command; + +import java.util.Map; + +public interface KnownCommandsProvider extends Provider { + Map getKnownCommands(); +} diff --git a/providers/NMSReflectionProvider/src/net/ess3/nms/refl/providers/ReflKnownCommandsProvider.java b/providers/NMSReflectionProvider/src/net/ess3/nms/refl/providers/ReflKnownCommandsProvider.java new file mode 100644 index 000000000..19c558d56 --- /dev/null +++ b/providers/NMSReflectionProvider/src/net/ess3/nms/refl/providers/ReflKnownCommandsProvider.java @@ -0,0 +1,48 @@ +package net.ess3.nms.refl.providers; + +import net.ess3.nms.refl.ReflUtil; +import net.ess3.provider.KnownCommandsProvider; +import org.bukkit.Bukkit; +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.command.SimpleCommandMap; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +public class ReflKnownCommandsProvider implements KnownCommandsProvider { + private final Map knownCommands; + + public ReflKnownCommandsProvider() { + Map knownCommands = new HashMap<>(); + try { + @SuppressWarnings("unchecked") + final Class craftServerClass = (Class) ReflUtil.getOBCClass("CraftServer"); + if (craftServerClass != null) { + final Field commandMapField = ReflUtil.getFieldCached(craftServerClass, "commandMap"); + if (commandMapField != null) { + final SimpleCommandMap simpleCommandMap = (SimpleCommandMap) commandMapField.get(Bukkit.getServer()); + final Field knownCommandsField = ReflUtil.getFieldCached(SimpleCommandMap.class, "knownCommands"); + if (knownCommandsField != null) { + knownCommands = (Map) knownCommandsField.get(simpleCommandMap); + } + } + } + } catch (final Exception exception) { + exception.printStackTrace(); + } finally { + this.knownCommands = knownCommands; + } + } + + @Override + public Map getKnownCommands() { + return this.knownCommands; + } + + @Override + public String getDescription() { + return "NMS Reflection Known Commands Provider"; + } +} diff --git a/providers/PaperProvider/src/net/ess3/provider/providers/PaperKnownCommandsProvider.java b/providers/PaperProvider/src/net/ess3/provider/providers/PaperKnownCommandsProvider.java new file mode 100644 index 000000000..b1666962b --- /dev/null +++ b/providers/PaperProvider/src/net/ess3/provider/providers/PaperKnownCommandsProvider.java @@ -0,0 +1,19 @@ +package net.ess3.provider.providers; + +import net.ess3.provider.KnownCommandsProvider; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; + +import java.util.Map; + +public class PaperKnownCommandsProvider implements KnownCommandsProvider { + @Override + public Map getKnownCommands() { + return Bukkit.getCommandMap().getKnownCommands(); + } + + @Override + public String getDescription() { + return "Paper Known Commands Provider"; + } +}