Replace old version command with brigadier equivalent (#12502)

---------

Co-authored-by: Owen1212055 <23108066+Owen1212055@users.noreply.github.com>
This commit is contained in:
David 2025-05-18 22:24:02 +02:00 committed by GitHub
parent 6f73e62ecd
commit ce0fa4c438
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 216 additions and 17 deletions

View File

@ -21,12 +21,24 @@ public interface VersionFetcher {
/** /**
* Gets the version message to cache and show to command senders. * Gets the version message to cache and show to command senders.
* *
* <p>NOTE: This is run in a new thread separate from that of the command processing thread</p> * @return the message to show when requesting a version
* @apiNote This method may involve a web request which will block the executing thread
*/
Component getVersionMessage();
/**
* Gets the version message to cache and show to command senders.
* *
* @param serverVersion the current version of the server (will match {@link Bukkit#getVersion()}) * @param serverVersion the current version of the server (will match {@link Bukkit#getVersion()})
* @return the message to show when requesting a version * @return the message to show when requesting a version
* @apiNote This method may involve a web request which will block the current thread
* @see #getVersionMessage()
* @deprecated {@code serverVersion} is not required
*/ */
Component getVersionMessage(String serverVersion); @Deprecated
default Component getVersionMessage(String serverVersion) {
return getVersionMessage();
}
@ApiStatus.Internal @ApiStatus.Internal
class DummyVersionFetcher implements VersionFetcher { class DummyVersionFetcher implements VersionFetcher {
@ -37,7 +49,7 @@ public interface VersionFetcher {
} }
@Override @Override
public Component getVersionMessage(final String serverVersion) { public Component getVersionMessage() {
Bukkit.getLogger().warning("Version provider has not been set, cannot check for updates!"); Bukkit.getLogger().warning("Version provider has not been set, cannot check for updates!");
Bukkit.getLogger().info("Override the default implementation of org.bukkit.UnsafeValues#getVersionFetcher()"); Bukkit.getLogger().info("Override the default implementation of org.bukkit.UnsafeValues#getVersionFetcher()");
new Throwable().printStackTrace(); new Throwable().printStackTrace();

View File

@ -5,7 +5,6 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -14,32 +13,26 @@ import org.bukkit.Location;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.command.defaults.BukkitCommand; import org.bukkit.command.defaults.BukkitCommand;
import org.bukkit.command.defaults.HelpCommand; import org.bukkit.command.defaults.HelpCommand;
import org.bukkit.command.defaults.PluginsCommand;
import org.bukkit.command.defaults.ReloadCommand; import org.bukkit.command.defaults.ReloadCommand;
import org.bukkit.command.defaults.VersionCommand;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.util.StringUtil; import org.bukkit.util.StringUtil;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public class SimpleCommandMap implements CommandMap { public class SimpleCommandMap implements CommandMap {
protected final Map<String, Command> knownCommands; // Paper protected final Map<String, Command> knownCommands;
private final Server server; private final Server server;
// Paper start
@org.jetbrains.annotations.ApiStatus.Internal @org.jetbrains.annotations.ApiStatus.Internal
public SimpleCommandMap(@NotNull final Server server, Map<String, Command> backing) { public SimpleCommandMap(@NotNull final Server server, Map<String, Command> backing) {
this.knownCommands = backing; this.knownCommands = backing;
// Paper end
this.server = server; this.server = server;
setDefaultCommands(); setDefaultCommands();
} }
private void setDefaultCommands() { private void setDefaultCommands() {
register("bukkit", new VersionCommand("version"));
register("bukkit", new ReloadCommand("reload")); register("bukkit", new ReloadCommand("reload"));
//register("bukkit", new PluginsCommand("plugins")); // Paper register("bukkit", new co.aikar.timings.TimingsCommand("timings"));
register("bukkit", new co.aikar.timings.TimingsCommand("timings")); // Paper
} }
public void setFallbackCommands() { public void setFallbackCommands() {

View File

@ -32,6 +32,7 @@ import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
@Deprecated(forRemoval = true)
public class VersionCommand extends BukkitCommand { public class VersionCommand extends BukkitCommand {
private VersionFetcher versionFetcher; // Paper - version command 2.0 private VersionFetcher versionFetcher; // Paper - version command 2.0
private VersionFetcher getVersionFetcher() { // lazy load because unsafe isn't available at command registration private VersionFetcher getVersionFetcher() { // lazy load because unsafe isn't available at command registration

View File

@ -43,7 +43,7 @@ public class PaperVersionFetcher implements VersionFetcher {
} }
@Override @Override
public Component getVersionMessage(final String serverVersion) { public Component getVersionMessage() {
final Component updateMessage; final Component updateMessage;
final ServerBuildInfo build = ServerBuildInfo.buildInfo(); final ServerBuildInfo build = ServerBuildInfo.buildInfo();
if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) { if (build.buildNumber().isEmpty() && build.gitCommit().isEmpty()) {

View File

@ -1,7 +1,15 @@
package io.papermc.paper.command; package io.papermc.paper.command;
import io.papermc.paper.FeatureHooks; import io.papermc.paper.FeatureHooks;
import io.papermc.paper.command.subcommands.*; import io.papermc.paper.command.subcommands.DumpItemCommand;
import io.papermc.paper.command.subcommands.DumpListenersCommand;
import io.papermc.paper.command.subcommands.DumpPluginsCommand;
import io.papermc.paper.command.subcommands.EntityCommand;
import io.papermc.paper.command.subcommands.HeapDumpCommand;
import io.papermc.paper.command.subcommands.MobcapsCommand;
import io.papermc.paper.command.subcommands.ReloadCommand;
import io.papermc.paper.command.subcommands.SyncLoadInfoCommand;
import io.papermc.paper.command.subcommands.VersionCommand;
import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.Pair;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;

View File

@ -34,12 +34,13 @@ public final class PaperCommands {
public static void registerCommands() { public static void registerCommands() {
// Paper commands go here // Paper commands go here
registerInternalCommand(PaperVersionCommand.create(), "bukkit", PaperVersionCommand.DESCRIPTION, List.of("ver", "about"), Set.of());
} }
private static void registerInternalCommand(final LiteralCommandNode<CommandSourceStack> node, final String description, final List<String> aliases, final Set<CommandRegistrationFlag> flags) { private static void registerInternalCommand(final LiteralCommandNode<CommandSourceStack> node, final String namespace, final String description, final List<String> aliases, final Set<CommandRegistrationFlag> flags) {
io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.registerWithFlagsInternal( io.papermc.paper.command.brigadier.PaperCommands.INSTANCE.registerWithFlagsInternal(
null, null,
"paper", namespace,
"Paper", "Paper",
node, node,
description, description,

View File

@ -0,0 +1,184 @@
package io.papermc.paper.command;
import com.destroystokyo.paper.PaperVersionFetcher;
import com.destroystokyo.paper.util.VersionFetcher;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import com.mojang.brigadier.tree.LiteralCommandNode;
import io.papermc.paper.command.brigadier.CommandSourceStack;
import io.papermc.paper.command.brigadier.Commands;
import io.papermc.paper.plugin.configuration.PluginMeta;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.minecraft.server.MinecraftServer;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.StringUtil;
import org.jspecify.annotations.NullMarked;
@NullMarked
public class PaperVersionCommand {
public static final String DESCRIPTION = "Gets the version of this server including any plugins in use";
private static final Component NOT_RUNNING = Component.text()
.append(Component.text("This server is not running any plugin by that name."))
.appendNewline()
.append(Component.text("Use /plugins to get a list of plugins.").clickEvent(ClickEvent.suggestCommand("/plugins")))
.build();
private static final JoinConfiguration PLAYER_JOIN_CONFIGURATION = JoinConfiguration.separators(
Component.text(", ", NamedTextColor.WHITE),
Component.text(", and ", NamedTextColor.WHITE)
);
private static final Component FAILED_TO_FETCH = Component.text("Could not fetch version information!", NamedTextColor.RED);
private static final Component FETCHING = Component.text("Checking version, please wait...", NamedTextColor.WHITE, TextDecoration.ITALIC);
private final VersionFetcher versionFetcher = new PaperVersionFetcher();
private CompletableFuture<ComputedVersion> computedVersion = CompletableFuture.completedFuture(new ComputedVersion(Component.empty(), -1)); // Precompute-- someday move that stuff out of bukkit
public static LiteralCommandNode<CommandSourceStack> create() {
final PaperVersionCommand command = new PaperVersionCommand();
return Commands.literal("version")
.requires(source -> source.getSender().hasPermission("bukkit.command.version"))
.then(Commands.argument("plugin", StringArgumentType.word())
.suggests(command::suggestPlugins)
.executes(command::pluginVersion))
.executes(command::serverVersion)
.build();
}
private int pluginVersion(final CommandContext<CommandSourceStack> context) {
final CommandSender sender = context.getSource().getSender();
final String pluginName = context.getArgument("plugin", String.class).toLowerCase(Locale.ROOT);
Plugin plugin = Bukkit.getPluginManager().getPlugin(pluginName);
if (plugin == null) {
plugin = Arrays.stream(Bukkit.getPluginManager().getPlugins())
.filter(checkPlugin -> checkPlugin.getName().toLowerCase(Locale.ROOT).contains(pluginName))
.findAny()
.orElse(null);
}
if (plugin != null) {
this.sendPluginInfo(plugin, sender);
} else {
sender.sendMessage(NOT_RUNNING);
}
return Command.SINGLE_SUCCESS;
}
private CompletableFuture<Suggestions> suggestPlugins(final CommandContext<CommandSourceStack> context, final SuggestionsBuilder builder) {
for (final Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
final String name = plugin.getName();
if (StringUtil.startsWithIgnoreCase(name, builder.getRemainingLowerCase())) {
builder.suggest(name);
}
}
return CompletableFuture.completedFuture(builder.build());
}
private void sendPluginInfo(final Plugin plugin, final CommandSender sender) {
final PluginMeta meta = plugin.getPluginMeta();
final TextComponent.Builder builder = Component.text()
.append(Component.text(meta.getName()))
.append(Component.text(" version "))
.append(Component.text(meta.getVersion(), NamedTextColor.GREEN)
.hoverEvent(Component.translatable("chat.copy.click"))
.clickEvent(ClickEvent.copyToClipboard(meta.getVersion()))
);
if (meta.getDescription() != null) {
builder
.appendNewline()
.append(Component.text(meta.getDescription()));
}
if (meta.getWebsite() != null) {
Component websiteComponent = Component.text(meta.getWebsite(), NamedTextColor.GREEN).clickEvent(ClickEvent.openUrl(meta.getWebsite()));
builder.appendNewline().append(Component.text("Website: ").append(websiteComponent));
}
if (!meta.getAuthors().isEmpty()) {
String prefix = meta.getAuthors().size() == 1 ? "Author: " : "Authors: ";
builder.appendNewline().append(Component.text(prefix).append(formatNameList(meta.getAuthors())));
}
if (!meta.getContributors().isEmpty()) {
builder.appendNewline().append(Component.text("Contributors: ").append(formatNameList(meta.getContributors())));
}
sender.sendMessage(builder.build());
}
private static Component formatNameList(final List<String> names) {
return Component.join(PLAYER_JOIN_CONFIGURATION, names.stream().map(Component::text).toList()).color(NamedTextColor.GREEN);
}
private int serverVersion(CommandContext<CommandSourceStack> context) {
sendVersion(context.getSource().getSender());
return Command.SINGLE_SUCCESS;
}
private void sendVersion(final CommandSender sender) {
final CompletableFuture<ComputedVersion> version = getVersionOrFetch();
if (!version.isDone()) {
sender.sendMessage(FETCHING);
}
version.whenComplete((computedVersion, throwable) -> {
if (computedVersion != null) {
sender.sendMessage(computedVersion.message);
} else if (throwable != null) {
sender.sendMessage(FAILED_TO_FETCH);
MinecraftServer.LOGGER.warn("Could not fetch version information!", throwable);
}
});
}
private CompletableFuture<ComputedVersion> getVersionOrFetch() {
if (!this.computedVersion.isDone()) {
return this.computedVersion;
}
if (this.computedVersion.isCompletedExceptionally() || System.currentTimeMillis() - this.computedVersion.resultNow().computedTime() > this.versionFetcher.getCacheTime()) {
this.computedVersion = this.fetchVersionMessage();
}
return this.computedVersion;
}
private CompletableFuture<ComputedVersion> fetchVersionMessage() {
return CompletableFuture.supplyAsync(() -> {
final Component message = Component.textOfChildren(
Component.text(Bukkit.getVersionMessage(), NamedTextColor.WHITE),
Component.newline(),
this.versionFetcher.getVersionMessage()
);
return new ComputedVersion(
message.hoverEvent(Component.translatable("chat.copy.click", NamedTextColor.WHITE))
.clickEvent(ClickEvent.copyToClipboard(PlainTextComponentSerializer.plainText().serialize(message))),
System.currentTimeMillis()
);
});
}
record ComputedVersion(Component message, long computedTime) {
}
}