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.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();

View file

@ -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();
}

View file

@ -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.
* <p>
* TODO: Use the real commandmap to do this automatically.
*/
protected final List<String> getCommands(final Server server) {
final List<String> commands = Lists.newArrayList();
for (final Plugin p : server.getPluginManager().getPlugins()) {
final PluginDescriptionFile desc = p.getDescription();
final Map<String, Map<String, Object>> cmds = desc.getCommands();
if (cmds != null) {
commands.addAll(cmds.keySet());
protected final List<String> getCommands(Server server) {
final Map<String, Command> commandMap = Maps.newHashMap(this.ess.getKnownCommandsProvider().getKnownCommands());
final List<String> commands = Lists.newArrayListWithCapacity(commandMap.size());
for (final Command command : commandMap.values()) {
if (!(command instanceof PluginIdentifiableCommand)) {
continue;
}
commands.add(command.getName());
}
return commands;
}

View file

@ -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<String> lines = new ArrayList<>();
private final transient List<String> chapters = new ArrayList<>();
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;
final List<String> 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<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 {
final List<String> 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<String, Map<String, Object>> 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<String, Object> 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<Object>) 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<Object>) 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));
}
}
}

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";
}
}