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.
This commit is contained in:
Alexander Söderberg 2020-10-19 16:27:40 +02:00 committed by GitHub
parent 583bf88207
commit a4f580e319
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 146 additions and 38 deletions

View file

@ -47,7 +47,9 @@ import net.ess3.api.ISettings;
import net.ess3.nms.refl.providers.ReflServerStateProvider; import net.ess3.nms.refl.providers.ReflServerStateProvider;
import net.ess3.nms.refl.providers.ReflSpawnEggProvider; import net.ess3.nms.refl.providers.ReflSpawnEggProvider;
import net.ess3.nms.refl.providers.ReflSpawnerBlockProvider; import net.ess3.nms.refl.providers.ReflSpawnerBlockProvider;
import net.ess3.nms.refl.providers.ReflKnownCommandsProvider;
import net.ess3.provider.ContainerProvider; import net.ess3.provider.ContainerProvider;
import net.ess3.provider.KnownCommandsProvider;
import net.ess3.provider.PotionMetaProvider; import net.ess3.provider.PotionMetaProvider;
import net.ess3.provider.ProviderListener; import net.ess3.provider.ProviderListener;
import net.ess3.provider.ServerStateProvider; 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.LegacyPotionMetaProvider;
import net.ess3.provider.providers.LegacySpawnEggProvider; import net.ess3.provider.providers.LegacySpawnEggProvider;
import net.ess3.provider.providers.PaperContainerProvider; import net.ess3.provider.providers.PaperContainerProvider;
import net.ess3.provider.providers.PaperKnownCommandsProvider;
import net.ess3.provider.providers.PaperRecipeBookListener; import net.ess3.provider.providers.PaperRecipeBookListener;
import net.ess3.provider.providers.PaperServerStateProvider; import net.ess3.provider.providers.PaperServerStateProvider;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -134,6 +137,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
private transient PotionMetaProvider potionMetaProvider; private transient PotionMetaProvider potionMetaProvider;
private transient ServerStateProvider serverStateProvider; private transient ServerStateProvider serverStateProvider;
private transient ContainerProvider containerProvider; private transient ContainerProvider containerProvider;
private transient KnownCommandsProvider knownCommandsProvider;
private transient ProviderListener recipeBookEventProvider; private transient ProviderListener recipeBookEventProvider;
private transient Kits kits; private transient Kits kits;
private transient RandomTeleport randomTeleport; 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)"); execTimer.mark("Init(Providers)");
reload(); reload();
@ -1013,6 +1024,11 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
return containerProvider; return containerProvider;
} }
@Override
public KnownCommandsProvider getKnownCommandsProvider() {
return knownCommandsProvider;
}
private AbstractItemDb getItemDbFromConfig() { private AbstractItemDb getItemDbFromConfig() {
final String setting = settings.getItemDbType(); final String setting = settings.getItemDbType();

View file

@ -5,6 +5,7 @@ import com.earth2me.essentials.api.IJails;
import com.earth2me.essentials.api.IWarps; import com.earth2me.essentials.api.IWarps;
import com.earth2me.essentials.perm.PermissionsHandler; import com.earth2me.essentials.perm.PermissionsHandler;
import net.ess3.provider.ContainerProvider; import net.ess3.provider.ContainerProvider;
import net.ess3.provider.KnownCommandsProvider;
import net.ess3.provider.ServerStateProvider; import net.ess3.provider.ServerStateProvider;
import net.ess3.provider.SpawnerBlockProvider; import net.ess3.provider.SpawnerBlockProvider;
import net.ess3.provider.SpawnerItemProvider; import net.ess3.provider.SpawnerItemProvider;
@ -115,4 +116,6 @@ public interface IEssentials extends Plugin {
ServerStateProvider getServerStateProvider(); ServerStateProvider getServerStateProvider();
ContainerProvider getContainerProvider(); ContainerProvider getContainerProvider();
KnownCommandsProvider getKnownCommandsProvider();
} }

View file

@ -7,13 +7,14 @@ import com.earth2me.essentials.User;
import com.earth2me.essentials.utils.FormatUtil; import com.earth2me.essentials.utils.FormatUtil;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import net.ess3.api.IEssentials; import net.ess3.api.IEssentials;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.util.StringUtil; import org.bukkit.util.StringUtil;
import java.util.ArrayList; import java.util.ArrayList;
@ -309,17 +310,15 @@ public abstract class EssentialsCommand implements IEssentialsCommand {
/** /**
* Lists all commands. * Lists all commands.
* <p>
* TODO: Use the real commandmap to do this automatically.
*/ */
protected final List<String> getCommands(final Server server) { protected final List<String> getCommands(Server server) {
final List<String> commands = Lists.newArrayList(); final Map<String, Command> commandMap = Maps.newHashMap(this.ess.getKnownCommandsProvider().getKnownCommands());
for (final Plugin p : server.getPluginManager().getPlugins()) { final List<String> commands = Lists.newArrayListWithCapacity(commandMap.size());
final PluginDescriptionFile desc = p.getDescription(); for (final Command command : commandMap.values()) {
final Map<String, Map<String, Object>> cmds = desc.getCommands(); if (!(command instanceof PluginIdentifiableCommand)) {
if (cmds != null) { continue;
commands.addAll(cmds.keySet());
} }
commands.add(command.getName());
} }
return commands; return commands;
} }

View file

@ -1,11 +1,14 @@
package com.earth2me.essentials.textreader; package com.earth2me.essentials.textreader;
import com.earth2me.essentials.User; import com.earth2me.essentials.User;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import net.ess3.api.IEssentials; import net.ess3.api.IEssentials;
import org.bukkit.command.Command;
import org.bukkit.command.PluginIdentifiableCommand;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginDescriptionFile;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -17,15 +20,12 @@ import java.util.logging.Logger;
import static com.earth2me.essentials.I18n.tl; import static com.earth2me.essentials.I18n.tl;
public class HelpInput implements IText { 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 static final Logger logger = Logger.getLogger("Essentials");
private final transient List<String> lines = new ArrayList<>(); private final transient List<String> lines = new ArrayList<>();
private final transient List<String> chapters = new ArrayList<>(); private final transient List<String> chapters = new ArrayList<>();
private final transient Map<String, Integer> bookmarks = new HashMap<>(); private final transient Map<String, Integer> 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; boolean reported = false;
final List<String> newLines = new ArrayList<>(); final List<String> newLines = new ArrayList<>();
String pluginName = ""; String pluginName = "";
@ -34,7 +34,16 @@ public class HelpInput implements IText {
lines.add(tl("helpMatching", match)); lines.add(tl("helpMatching", match));
} }
for (final Plugin p : ess.getServer().getPluginManager().getPlugins()) { final Multimap<Plugin, Command> 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 { try {
final List<String> pluginLines = new ArrayList<>(); final List<String> pluginLines = new ArrayList<>();
final PluginDescriptionFile desc = p.getDescription(); final PluginDescriptionFile desc = p.getDescription();
@ -48,46 +57,51 @@ public class HelpInput implements IText {
} }
final boolean isOnWhitelist = user.isAuthorized("essentials.help." + pluginNameLow); final boolean isOnWhitelist = user.isAuthorized("essentials.help." + pluginNameLow);
for (final Map.Entry<String, Map<String, Object>> k : cmds.entrySet()) { for (final Command command : pluginCommands.get(p)) {
try { 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; continue;
} }
if (pluginNameLow.contains("essentials")) { if (pluginNameLow.contains("essentials")) {
final String node = "essentials." + k.getKey(); final String node = "essentials." + commandName;
if (!ess.getSettings().isCommandDisabled(k.getKey()) && user.isAuthorized(node)) { if (!ess.getSettings().isCommandDisabled(commandName) && user.isAuthorized(node)) {
pluginLines.add(tl("helpLine", k.getKey(), k.getValue().get(DESCRIPTION))); pluginLines.add(tl("helpLine", commandName, commandDescription));
} }
} else { } else {
if (ess.getSettings().showNonEssCommandsInHelp()) { if (ess.getSettings().showNonEssCommandsInHelp()) {
final Map<String, Object> value = k.getValue(); final String permissionRaw = command.getPermission();
Object permissions = null; final String[] permissions;
if (value.containsKey(PERMISSION)) { if (permissionRaw == null) {
permissions = value.get(PERMISSION); permissions = new String[0];
} else if (value.containsKey(PERMISSIONS)) { } else {
permissions = value.get(PERMISSIONS); permissions = permissionRaw.split(";");
} }
if (isOnWhitelist || user.isAuthorized("essentials.help." + pluginNameLow + "." + k.getKey())) {
pluginLines.add(tl("helpLine", k.getKey(), value.get(DESCRIPTION))); if (isOnWhitelist || user.isAuthorized("essentials.help." + pluginNameLow + "." + commandName)) {
} else if (permissions instanceof List && !((List<Object>) permissions).isEmpty()) { pluginLines.add(tl("helpLine", commandName, commandDescription));
} else if (permissions.length != 0) {
boolean enabled = false; boolean enabled = false;
for (final Object o : (List<Object>) permissions) {
if (o instanceof String && user.isAuthorized(o.toString())) { for (final String permission : permissions) {
if (user.isAuthorized(permission)) {
enabled = true; enabled = true;
break; break;
} }
} }
if (enabled) { if (enabled) {
pluginLines.add(tl("helpLine", k.getKey(), value.get(DESCRIPTION))); pluginLines.add(tl("helpLine", commandName, commandDescription));
}
} else if (permissions instanceof String && !"".equals(permissions)) {
if (user.isAuthorized(permissions.toString())) {
pluginLines.add(tl("helpLine", k.getKey(), value.get(DESCRIPTION)));
} }
} else { } else {
if (!ess.getSettings().hidePermissionlessHelp()) { if (!ess.getSettings().hidePermissionlessHelp()) {
pluginLines.add(tl("helpLine", k.getKey(), value.get(DESCRIPTION))); pluginLines.add(tl("helpLine", commandName, commandDescription));
} }
} }
} }

View file

@ -0,0 +1,9 @@
package net.ess3.provider;
import org.bukkit.command.Command;
import java.util.Map;
public interface KnownCommandsProvider extends Provider {
Map<String, Command> getKnownCommands();
}

View file

@ -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<String, Command> knownCommands;
public ReflKnownCommandsProvider() {
Map<String, Command> knownCommands = new HashMap<>();
try {
@SuppressWarnings("unchecked")
final Class<? extends Server> craftServerClass = (Class<? extends Server>) 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<String, Command>) knownCommandsField.get(simpleCommandMap);
}
}
}
} catch (final Exception exception) {
exception.printStackTrace();
} finally {
this.knownCommands = knownCommands;
}
}
@Override
public Map<String, Command> getKnownCommands() {
return this.knownCommands;
}
@Override
public String getDescription() {
return "NMS Reflection Known Commands Provider";
}
}

View file

@ -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<String, Command> getKnownCommands() {
return Bukkit.getCommandMap().getKnownCommands();
}
@Override
public String getDescription() {
return "Paper Known Commands Provider";
}
}