3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-16 21:10:30 +01:00

Refactor built-in commands to use Brigadier (#1208)

* Refactor /velocity command to use Brigadier

* Added default usage message and code cleanup

* Reimplemented callback subcommand

* Fixed execution of the callback subcommand when all permissions of the main command subcommands are denied

* Migrated callback subcommand to its own command

* Migrated server command

* GList, Send and Server command cleanup

---------

Co-authored-by: Adrian <adriangonzalesval@gmail.com>
Dieser Commit ist enthalten in:
Riley Park 2024-01-22 15:01:49 -08:00 committet von GitHub
Ursprung c3583e182c
Commit b9b11665b9
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: B5690EEEBB952194
8 geänderte Dateien mit 325 neuen und 392 gelöschten Zeilen

Datei anzeigen

@ -37,6 +37,7 @@ import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.ProxyVersion;
import com.velocitypowered.proxy.command.VelocityCommandManager;
import com.velocitypowered.proxy.command.builtin.CallbackCommand;
import com.velocitypowered.proxy.command.builtin.GlistCommand;
import com.velocitypowered.proxy.command.builtin.SendCommand;
import com.velocitypowered.proxy.command.builtin.ServerCommand;
@ -223,8 +224,9 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
cm.logChannelInformation();
// Initialize commands first
commandManager.register("velocity", new VelocityCommand(this));
commandManager.register("server", new ServerCommand(this));
commandManager.register(VelocityCommand.create(this));
commandManager.register(CallbackCommand.create());
commandManager.register(ServerCommand.create(this));
commandManager.register("shutdown", ShutdownCommand.command(this),
"end", "stop");
new GlistCommand(this).register();

Datei anzeigen

@ -33,7 +33,7 @@ import org.checkerframework.checker.index.qual.NonNegative;
public class ClickCallbackManager {
public static final ClickCallbackManager INSTANCE = new ClickCallbackManager();
static final String COMMAND = "/velocity callback ";
static final String COMMAND = "/velocity:callback ";
private final Cache<UUID, RegisteredCallback> registrations = Caffeine.newBuilder()
.expireAfter(new Expiry<UUID, RegisteredCallback>() {

Datei anzeigen

@ -0,0 +1,58 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.command.builtin;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.proxy.adventure.ClickCallbackManager;
import java.util.UUID;
/**
* Callback Command.
*/
public final class CallbackCommand implements Command<CommandSource> {
@SuppressWarnings("checkstyle:MissingJavadocMethod")
public static BrigadierCommand create() {
final LiteralCommandNode<CommandSource> node = BrigadierCommand
.literalArgumentBuilder("velocity:callback")
.then(BrigadierCommand.requiredArgumentBuilder("id", StringArgumentType.word())
.executes(new CallbackCommand()))
.build();
return new BrigadierCommand(node);
}
@Override
public int run(final CommandContext<CommandSource> context) {
final String providedId = StringArgumentType.getString(context, "id");
final UUID id;
try {
id = UUID.fromString(providedId);
} catch (final IllegalArgumentException ignored) {
return Command.SINGLE_SUCCESS;
}
ClickCallbackManager.INSTANCE.runCallback(context.getSource(), id);
return Command.SINGLE_SUCCESS;
}
}

Datei anzeigen

@ -20,12 +20,11 @@ package com.velocitypowered.proxy.command.builtin;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate;
@ -34,7 +33,6 @@ import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import java.util.List;
import java.util.Optional;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
@ -57,20 +55,19 @@ public class GlistCommand {
* Registers this command.
*/
public void register() {
LiteralCommandNode<CommandSource> totalNode = LiteralArgumentBuilder
.<CommandSource>literal("glist")
final LiteralArgumentBuilder<CommandSource> rootNode = BrigadierCommand
.literalArgumentBuilder("glist")
.requires(source ->
source.getPermissionValue("velocity.command.glist") == Tristate.TRUE)
.executes(this::totalCount)
.build();
ArgumentCommandNode<CommandSource, String> serverNode = RequiredArgumentBuilder
.<CommandSource, String>argument(SERVER_ARG, StringArgumentType.string())
.executes(this::totalCount);
final ArgumentCommandNode<CommandSource, String> serverNode = BrigadierCommand
.requiredArgumentBuilder(SERVER_ARG, StringArgumentType.string())
.suggests((context, builder) -> {
String argument = context.getArguments().containsKey(SERVER_ARG)
final String argument = context.getArguments().containsKey(SERVER_ARG)
? context.getArgument(SERVER_ARG, String.class)
: "";
for (RegisteredServer server : server.getAllServers()) {
String serverName = server.getServerInfo().getName();
final String serverName = server.getServerInfo().getName();
if (serverName.regionMatches(true, 0, argument, 0, argument.length())) {
builder.suggest(serverName);
}
@ -82,14 +79,14 @@ public class GlistCommand {
})
.executes(this::serverCount)
.build();
totalNode.addChild(serverNode);
server.getCommandManager().register(new BrigadierCommand(totalNode));
rootNode.then(serverNode);
server.getCommandManager().register(new BrigadierCommand(rootNode));
}
private int totalCount(final CommandContext<CommandSource> context) {
final CommandSource source = context.getSource();
sendTotalProxyCount(source);
source.sendMessage(Identity.nil(),
source.sendMessage(
Component.translatable("velocity.command.glist-view-all", NamedTextColor.YELLOW));
return 1;
}
@ -98,38 +95,42 @@ public class GlistCommand {
final CommandSource source = context.getSource();
final String serverName = getString(context, SERVER_ARG);
if (serverName.equalsIgnoreCase("all")) {
for (RegisteredServer server : BuiltinCommandUtil.sortedServerList(server)) {
for (final RegisteredServer server : BuiltinCommandUtil.sortedServerList(server)) {
sendServerPlayers(source, server, true);
}
sendTotalProxyCount(source);
} else {
Optional<RegisteredServer> registeredServer = server.getServer(serverName);
if (!registeredServer.isPresent()) {
source.sendMessage(Identity.nil(),
CommandMessages.SERVER_DOES_NOT_EXIST.args(Component.text(serverName)));
final Optional<RegisteredServer> registeredServer = server.getServer(serverName);
if (registeredServer.isEmpty()) {
source.sendMessage(
CommandMessages.SERVER_DOES_NOT_EXIST
.arguments(Component.text(serverName)));
return -1;
}
sendServerPlayers(source, registeredServer.get(), false);
}
return 1;
return Command.SINGLE_SUCCESS;
}
private void sendTotalProxyCount(CommandSource target) {
int online = server.getPlayerCount();
TranslatableComponent msg = online == 1
? Component.translatable("velocity.command.glist-player-singular")
: Component.translatable("velocity.command.glist-player-plural");
target.sendMessage(msg.color(NamedTextColor.YELLOW)
.args(Component.text(Integer.toString(online), NamedTextColor.GREEN)));
final int online = server.getPlayerCount();
final TranslatableComponent.Builder msg = Component.translatable()
.key(online == 1
? "velocity.command.glist-player-singular"
: "velocity.command.glist-player-plural"
).color(NamedTextColor.YELLOW)
.arguments(Component.text(Integer.toString(online), NamedTextColor.GREEN));
target.sendMessage(msg.build());
}
private void sendServerPlayers(CommandSource target, RegisteredServer server, boolean fromAll) {
List<Player> onServer = ImmutableList.copyOf(server.getPlayersConnected());
private void sendServerPlayers(final CommandSource target,
final RegisteredServer server, final boolean fromAll) {
final List<Player> onServer = ImmutableList.copyOf(server.getPlayersConnected());
if (onServer.isEmpty() && fromAll) {
return;
}
TextComponent.Builder builder = Component.text()
final TextComponent.Builder builder = Component.text()
.append(Component.text("[" + server.getServerInfo().getName() + "] ",
NamedTextColor.DARK_AQUA))
.append(Component.text("(" + onServer.size() + ")", NamedTextColor.GRAY))
@ -137,7 +138,7 @@ public class GlistCommand {
.resetStyle();
for (int i = 0; i < onServer.size(); i++) {
Player player = onServer.get(i);
final Player player = onServer.get(i);
builder.append(Component.text(player.getUsername()));
if (i + 1 < onServer.size()) {
@ -145,6 +146,6 @@ public class GlistCommand {
}
}
target.sendMessage(Identity.nil(), builder.build());
target.sendMessage(builder.build());
}
}

Datei anzeigen

@ -17,12 +17,12 @@
package com.velocitypowered.proxy.command.builtin;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate;
@ -51,20 +51,19 @@ public class SendCommand {
* Registers this command.
*/
public void register() {
LiteralCommandNode<CommandSource> totalNode = LiteralArgumentBuilder
.<CommandSource>literal("send")
final LiteralArgumentBuilder<CommandSource> rootNode = BrigadierCommand
.literalArgumentBuilder("send")
.requires(source ->
source.getPermissionValue("velocity.command.send") == Tristate.TRUE)
.executes(this::usage)
.build();
ArgumentCommandNode<CommandSource, String> playerNode = RequiredArgumentBuilder
.<CommandSource, String>argument("player", StringArgumentType.word())
.executes(this::usage);
final RequiredArgumentBuilder<CommandSource, String> playerNode = BrigadierCommand
.requiredArgumentBuilder(PLAYER_ARG, StringArgumentType.word())
.suggests((context, builder) -> {
String argument = context.getArguments().containsKey(PLAYER_ARG)
final String argument = context.getArguments().containsKey(PLAYER_ARG)
? context.getArgument(PLAYER_ARG, String.class)
: "";
for (Player player : server.getAllPlayers()) {
String playerName = player.getUsername();
for (final Player player : server.getAllPlayers()) {
final String playerName = player.getUsername();
if (playerName.regionMatches(true, 0, argument, 0, argument.length())) {
builder.suggest(playerName);
}
@ -78,16 +77,15 @@ public class SendCommand {
}
return builder.buildFuture();
})
.executes(this::usage)
.build();
ArgumentCommandNode<CommandSource, String> serverNode = RequiredArgumentBuilder
.<CommandSource, String>argument("server", StringArgumentType.word())
.executes(this::usage);
final ArgumentCommandNode<CommandSource, String> serverNode = BrigadierCommand
.requiredArgumentBuilder(SERVER_ARG, StringArgumentType.word())
.suggests((context, builder) -> {
String argument = context.getArguments().containsKey(SERVER_ARG)
final String argument = context.getArguments().containsKey(SERVER_ARG)
? context.getArgument(SERVER_ARG, String.class)
: "";
for (RegisteredServer server : server.getAllServers()) {
String serverName = server.getServerInfo().getName();
for (final RegisteredServer server : server.getAllServers()) {
final String serverName = server.getServerInfo().getName();
if (serverName.regionMatches(true, 0, argument, 0, argument.length())) {
builder.suggest(server.getServerInfo().getName());
}
@ -96,66 +94,68 @@ public class SendCommand {
})
.executes(this::send)
.build();
totalNode.addChild(playerNode);
playerNode.addChild(serverNode);
server.getCommandManager().register(new BrigadierCommand(totalNode));
playerNode.then(serverNode);
rootNode.then(playerNode.build());
server.getCommandManager().register(new BrigadierCommand(rootNode.build()));
}
private int usage(CommandContext<CommandSource> context) {
private int usage(final CommandContext<CommandSource> context) {
context.getSource().sendMessage(
Component.translatable("velocity.command.send-usage", NamedTextColor.YELLOW)
);
return 1;
return Command.SINGLE_SUCCESS;
}
private int send(CommandContext<CommandSource> context) {
String serverName = context.getArgument(SERVER_ARG, String.class);
String player = context.getArgument(PLAYER_ARG, String.class);
private int send(final CommandContext<CommandSource> context) {
final String serverName = context.getArgument(SERVER_ARG, String.class);
final String player = context.getArgument(PLAYER_ARG, String.class);
Optional<RegisteredServer> maybeServer = server.getServer(serverName);
final Optional<RegisteredServer> maybeServer = server.getServer(serverName);
if (maybeServer.isEmpty()) {
context.getSource().sendMessage(
CommandMessages.SERVER_DOES_NOT_EXIST.args(Component.text(serverName))
CommandMessages.SERVER_DOES_NOT_EXIST.arguments(Component.text(serverName))
);
return 0;
}
if (server.getPlayer(player).isEmpty()
final RegisteredServer targetServer = maybeServer.get();
final Optional<Player> maybePlayer = server.getPlayer(player);
if (maybePlayer.isEmpty()
&& !Objects.equals(player, "all")
&& !Objects.equals(player, "current")) {
context.getSource().sendMessage(
CommandMessages.PLAYER_NOT_FOUND.args(Component.text(player))
CommandMessages.PLAYER_NOT_FOUND.arguments(Component.text(player))
);
return 0;
}
if (Objects.equals(player, "all")) {
for (Player p : server.getAllPlayers()) {
p.createConnectionRequest(server.getServer(serverName).get()).fireAndForget();
for (final Player p : server.getAllPlayers()) {
p.createConnectionRequest(targetServer).fireAndForget();
}
return 1;
return Command.SINGLE_SUCCESS;
}
if (Objects.equals(player, "current")) {
if (!(context.getSource() instanceof Player)) {
if (!(context.getSource() instanceof Player source)) {
context.getSource().sendMessage(CommandMessages.PLAYERS_ONLY);
return 0;
}
Player source = (Player) context.getSource();
Optional<ServerConnection> connectedServer = source.getCurrentServer();
final Optional<ServerConnection> connectedServer = source.getCurrentServer();
if (connectedServer.isPresent()) {
for (Player p : connectedServer.get().getServer().getPlayersConnected()) {
for (final Player p : connectedServer.get().getServer().getPlayersConnected()) {
p.createConnectionRequest(maybeServer.get()).fireAndForget();
}
return 1;
return Command.SINGLE_SUCCESS;
}
return 0;
}
server.getPlayer(player).get().createConnectionRequest(
maybeServer.get()).fireAndForget();
return 1;
// The player at this point must be present
maybePlayer.orElseThrow().createConnectionRequest(targetServer).fireAndForget();
return Command.SINGLE_SUCCESS;
}
}

Datei anzeigen

@ -19,9 +19,11 @@ package com.velocitypowered.proxy.command.builtin;
import static net.kyori.adventure.text.event.HoverEvent.showText;
import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
@ -30,8 +32,6 @@ import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.proxy.server.ServerInfo;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
@ -41,50 +41,65 @@ import net.kyori.adventure.text.format.NamedTextColor;
/**
* Implements Velocity's {@code /server} command.
*/
public class ServerCommand implements SimpleCommand {
public final class ServerCommand {
private static final String SERVER_ARG = "server";
public static final int MAX_SERVERS_TO_LIST = 50;
private final ProxyServer server;
public ServerCommand(ProxyServer server) {
this.server = server;
@SuppressWarnings("checkstyle:MissingJavadocMethod")
public static BrigadierCommand create(final ProxyServer server) {
final LiteralCommandNode<CommandSource> node = BrigadierCommand
.literalArgumentBuilder("server")
.requires(src -> src instanceof Player
&& src.getPermissionValue("velocity.command.server") != Tristate.FALSE)
.executes(ctx -> {
final Player player = (Player) ctx.getSource();
outputServerInformation(player, server);
return Command.SINGLE_SUCCESS;
})
.then(BrigadierCommand.requiredArgumentBuilder(SERVER_ARG, StringArgumentType.word())
.suggests((ctx, builder) -> {
final String argument = ctx.getArguments().containsKey(SERVER_ARG)
? StringArgumentType.getString(ctx, SERVER_ARG)
: "";
for (final RegisteredServer sv : server.getAllServers()) {
final String serverName = sv.getServerInfo().getName();
if (serverName.regionMatches(true, 0, argument, 0, argument.length())) {
builder.suggest(serverName);
}
@Override
public void execute(final SimpleCommand.Invocation invocation) {
final CommandSource source = invocation.source();
final String[] args = invocation.arguments();
if (!(source instanceof Player)) {
source.sendMessage(CommandMessages.PLAYERS_ONLY);
return;
}
Player player = (Player) source;
if (args.length == 1) {
return builder.buildFuture();
})
.executes(ctx -> {
final Player player = (Player) ctx.getSource();
// Trying to connect to a server.
String serverName = args[0];
Optional<RegisteredServer> toConnect = server.getServer(serverName);
final String serverName = StringArgumentType.getString(ctx, SERVER_ARG);
final Optional<RegisteredServer> toConnect = server.getServer(serverName);
if (toConnect.isEmpty()) {
player.sendMessage(CommandMessages.SERVER_DOES_NOT_EXIST.args(Component.text(serverName)));
return;
player.sendMessage(CommandMessages.SERVER_DOES_NOT_EXIST
.arguments(Component.text(serverName)));
return -1;
}
player.createConnectionRequest(toConnect.get()).fireAndForget();
} else {
outputServerInformation(player);
}
return Command.SINGLE_SUCCESS;
})
).build();
return new BrigadierCommand(node);
}
private void outputServerInformation(Player executor) {
String currentServer = executor.getCurrentServer().map(ServerConnection::getServerInfo)
.map(ServerInfo::getName).orElse("<unknown>");
private static void outputServerInformation(final Player executor,
final ProxyServer server) {
final String currentServer = executor.getCurrentServer()
.map(ServerConnection::getServerInfo)
.map(ServerInfo::getName)
.orElse("<unknown>");
executor.sendMessage(Component.translatable(
"velocity.command.server-current-server",
NamedTextColor.YELLOW,
Component.text(currentServer)));
List<RegisteredServer> servers = BuiltinCommandUtil.sortedServerList(server);
final List<RegisteredServer> servers = BuiltinCommandUtil.sortedServerList(server);
if (servers.size() > MAX_SERVERS_TO_LIST) {
executor.sendMessage(Component.translatable(
"velocity.command.server-too-many", NamedTextColor.RED));
@ -92,12 +107,12 @@ public class ServerCommand implements SimpleCommand {
}
// Assemble the list of servers as components
TextComponent.Builder serverListBuilder = Component.text()
final TextComponent.Builder serverListBuilder = Component.text()
.append(Component.translatable("velocity.command.server-available",
NamedTextColor.YELLOW))
.append(Component.space());
.appendSpace();
for (int i = 0; i < servers.size(); i++) {
RegisteredServer rs = servers.get(i);
final RegisteredServer rs = servers.get(i);
serverListBuilder.append(formatServerComponent(currentServer, rs));
if (i != servers.size() - 1) {
serverListBuilder.append(Component.text(", ", NamedTextColor.GRAY));
@ -107,22 +122,22 @@ public class ServerCommand implements SimpleCommand {
executor.sendMessage(serverListBuilder.build());
}
private TextComponent formatServerComponent(String currentPlayerServer, RegisteredServer server) {
ServerInfo serverInfo = server.getServerInfo();
TextComponent serverTextComponent = Component.text(serverInfo.getName());
private static TextComponent formatServerComponent(final String currentPlayerServer,
final RegisteredServer server) {
final ServerInfo serverInfo = server.getServerInfo();
final TextComponent.Builder serverTextComponent = Component.text()
.content(serverInfo.getName());
int connectedPlayers = server.getPlayersConnected().size();
TranslatableComponent playersTextComponent;
final int connectedPlayers = server.getPlayersConnected().size();
final TranslatableComponent.Builder playersTextComponent = Component.translatable();
if (connectedPlayers == 1) {
playersTextComponent = Component.translatable(
"velocity.command.server-tooltip-player-online");
playersTextComponent.key("velocity.command.server-tooltip-player-online");
} else {
playersTextComponent = Component.translatable(
"velocity.command.server-tooltip-players-online");
playersTextComponent.key("velocity.command.server-tooltip-players-online");
}
playersTextComponent = playersTextComponent.args(Component.text(connectedPlayers));
playersTextComponent.arguments(Component.text(connectedPlayers));
if (serverInfo.getName().equals(currentPlayerServer)) {
serverTextComponent = serverTextComponent.color(NamedTextColor.GREEN)
serverTextComponent.color(NamedTextColor.GREEN)
.hoverEvent(
showText(
Component.translatable("velocity.command.server-tooltip-current-server")
@ -130,7 +145,7 @@ public class ServerCommand implements SimpleCommand {
.append(playersTextComponent))
);
} else {
serverTextComponent = serverTextComponent.color(NamedTextColor.GRAY)
serverTextComponent.color(NamedTextColor.GRAY)
.clickEvent(ClickEvent.runCommand("/server " + serverInfo.getName()))
.hoverEvent(
showText(
@ -139,28 +154,6 @@ public class ServerCommand implements SimpleCommand {
.append(playersTextComponent))
);
}
return serverTextComponent;
}
@Override
public List<String> suggest(final SimpleCommand.Invocation invocation) {
final String[] currentArgs = invocation.arguments();
Stream<String> possibilities = server.getAllServers().stream()
.map(rs -> rs.getServerInfo().getName());
if (currentArgs.length == 0) {
return possibilities.collect(Collectors.toList());
} else if (currentArgs.length == 1) {
return possibilities
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
.collect(Collectors.toList());
} else {
return ImmutableList.of();
}
}
@Override
public boolean hasPermission(final SimpleCommand.Invocation invocation) {
return invocation.source().getPermissionValue("velocity.command.server") != Tristate.FALSE;
return serverTextComponent.build();
}
}

Datei anzeigen

@ -17,13 +17,16 @@
package com.velocitypowered.proxy.command.builtin;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription;
@ -31,7 +34,6 @@ import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.ProxyVersion;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.adventure.ClickCallbackManager;
import com.velocitypowered.proxy.util.InformationUtils;
import java.io.BufferedWriter;
import java.io.IOException;
@ -44,14 +46,12 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
@ -63,129 +63,71 @@ import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Implements the {@code /velocity} command and friends.
*/
public class VelocityCommand implements SimpleCommand {
public final class VelocityCommand {
private static final String USAGE = "/velocity <%s>";
private interface SubCommand {
void execute(final CommandSource source, final String @NonNull [] args);
default List<String> suggest(final CommandSource source, final String @NonNull [] currentArgs) {
return ImmutableList.of();
}
boolean hasPermission(final CommandSource source, final String @NonNull [] args);
}
private final Map<String, SubCommand> commands;
/**
* Initializes the command object for /velocity.
*
* @param server the Velocity server
*/
public VelocityCommand(VelocityServer server) {
this.commands = ImmutableMap.<String, SubCommand>builder()
.put("version", new Info(server))
.put("plugins", new Plugins(server))
.put("reload", new Reload(server))
.put("dump", new Dump(server))
.put("heap", new Heap())
.put("callback", new Callback())
@SuppressWarnings("checkstyle:MissingJavadocMethod")
public static BrigadierCommand create(final VelocityServer server) {
final LiteralCommandNode<CommandSource> dump = BrigadierCommand.literalArgumentBuilder("dump")
.requires(source -> source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE)
.executes(new Dump(server))
.build();
final LiteralCommandNode<CommandSource> heap = BrigadierCommand.literalArgumentBuilder("heap")
.requires(source -> source.getPermissionValue("velocity.command.heap") == Tristate.TRUE)
.executes(new Heap())
.build();
final LiteralCommandNode<CommandSource> info = BrigadierCommand.literalArgumentBuilder("info")
.requires(source -> source.getPermissionValue("velocity.command.info") != Tristate.FALSE)
.executes(new Info(server))
.build();
final LiteralCommandNode<CommandSource> plugins = BrigadierCommand
.literalArgumentBuilder("plugins")
.requires(source -> source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE)
.executes(new Plugins(server))
.build();
final LiteralCommandNode<CommandSource> reload = BrigadierCommand
.literalArgumentBuilder("reload")
.requires(source -> source.getPermissionValue("velocity.command.reload") == Tristate.TRUE)
.executes(new Reload(server))
.build();
}
private void usage(CommandSource source) {
String availableCommands = commands.entrySet().stream()
.filter(e -> e.getValue().hasPermission(source, new String[0]))
.map(Map.Entry::getKey)
final List<LiteralCommandNode<CommandSource>> commands = List
.of(dump, heap, info, plugins, reload);
return new BrigadierCommand(
commands.stream()
.reduce(
BrigadierCommand.literalArgumentBuilder("velocity")
.executes(ctx -> {
final CommandSource source = ctx.getSource();
final String availableCommands = commands.stream()
.filter(e -> e.getRequirement().test(source))
.map(LiteralCommandNode::getName)
.collect(Collectors.joining("|"));
String commandText = "/velocity <" + availableCommands + ">";
final String commandText = USAGE.formatted(availableCommands);
source.sendMessage(Component.text(commandText, NamedTextColor.RED));
return Command.SINGLE_SUCCESS;
})
.requires(commands.stream()
.map(CommandNode::getRequirement)
.reduce(Predicate::or)
.orElseThrow()),
ArgumentBuilder::then,
ArgumentBuilder::then
)
);
}
@Override
public void execute(final SimpleCommand.Invocation invocation) {
final CommandSource source = invocation.source();
final String[] args = invocation.arguments();
if (args.length == 0) {
usage(source);
return;
}
SubCommand command = commands.get(args[0].toLowerCase(Locale.US));
if (command == null) {
usage(source);
return;
}
@SuppressWarnings("nullness")
String[] actualArgs = Arrays.copyOfRange(args, 1, args.length);
command.execute(source, actualArgs);
}
@Override
public List<String> suggest(final SimpleCommand.Invocation invocation) {
final CommandSource source = invocation.source();
final String[] currentArgs = invocation.arguments();
if (currentArgs.length == 0) {
return commands.entrySet().stream()
.filter(e -> e.getValue().hasPermission(source, new String[0]))
.map(Map.Entry::getKey)
.collect(ImmutableList.toImmutableList());
}
if (currentArgs.length == 1) {
return commands.entrySet().stream()
.filter(e -> e.getKey().regionMatches(true, 0, currentArgs[0], 0,
currentArgs[0].length()))
.filter(e -> e.getValue().hasPermission(source, new String[0]))
.map(Map.Entry::getKey)
.collect(ImmutableList.toImmutableList());
}
SubCommand command = commands.get(currentArgs[0].toLowerCase(Locale.US));
if (command == null) {
return ImmutableList.of();
}
@SuppressWarnings("nullness")
String[] actualArgs = Arrays.copyOfRange(currentArgs, 1, currentArgs.length);
return command.suggest(source, actualArgs);
}
@Override
public boolean hasPermission(final SimpleCommand.Invocation invocation) {
final CommandSource source = invocation.source();
final String[] args = invocation.arguments();
if (args.length == 0) {
return commands.values().stream().anyMatch(e -> e.hasPermission(source, args));
}
SubCommand command = commands.get(args[0].toLowerCase(Locale.US));
if (command == null) {
return true;
}
@SuppressWarnings("nullness")
String[] actualArgs = Arrays.copyOfRange(args, 1, args.length);
return command.hasPermission(source, actualArgs);
}
private static class Reload implements SubCommand {
private record Reload(VelocityServer server) implements Command<CommandSource> {
private static final Logger logger = LogManager.getLogger(Reload.class);
private final VelocityServer server;
private Reload(VelocityServer server) {
this.server = server;
}
@Override
public void execute(CommandSource source, String @NonNull [] args) {
public int run(final CommandContext<CommandSource> context) {
final CommandSource source = context.getSource();
try {
if (server.reloadConfiguration()) {
source.sendMessage(Component.translatable("velocity.command.reload-success",
@ -199,38 +141,28 @@ public class VelocityCommand implements SimpleCommand {
source.sendMessage(Component.translatable("velocity.command.reload-failure",
NamedTextColor.RED));
}
return Command.SINGLE_SUCCESS;
}
}
private record Info(ProxyServer server) implements Command<CommandSource> {
private static final TextColor VELOCITY_COLOR = TextColor.color(0x09add3);
@Override
public boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
return source.getPermissionValue("velocity.command.reload") == Tristate.TRUE;
}
}
public int run(final CommandContext<CommandSource> context) {
final CommandSource source = context.getSource();
final ProxyVersion version = server.getVersion();
private static class Info implements SubCommand {
private static final TextColor VELOCITY_COLOR = TextColor.fromHexString("#09add3");
private final ProxyServer server;
private Info(ProxyServer server) {
this.server = server;
}
@Override
public void execute(CommandSource source, String @NonNull [] args) {
if (args.length != 0) {
source.sendMessage(Component.text("/velocity version", NamedTextColor.RED));
return;
}
ProxyVersion version = server.getVersion();
Component velocity = Component.text().content(version.getName() + " ")
final Component velocity = Component.text()
.content(version.getName() + " ")
.decoration(TextDecoration.BOLD, true)
.color(VELOCITY_COLOR)
.append(Component.text(version.getVersion()).decoration(TextDecoration.BOLD, false))
.append(Component.text()
.content(version.getVersion())
.decoration(TextDecoration.BOLD, false))
.build();
Component copyright = Component
final Component copyright = Component
.translatable("velocity.command.version-copyright",
Component.text(version.getVendor()),
Component.text(version.getName()));
@ -238,14 +170,16 @@ public class VelocityCommand implements SimpleCommand {
source.sendMessage(copyright);
if (version.getName().equals("Velocity")) {
TextComponent embellishment = Component.text()
.append(Component.text().content("velocitypowered.com")
final TextComponent embellishment = Component.text()
.append(Component.text()
.content("velocitypowered.com")
.color(NamedTextColor.GREEN)
.clickEvent(
ClickEvent.openUrl("https://velocitypowered.com"))
.build())
.append(Component.text(" - "))
.append(Component.text().content("GitHub")
.append(Component.text()
.content("GitHub")
.color(NamedTextColor.GREEN)
.decoration(TextDecoration.UNDERLINED, true)
.clickEvent(ClickEvent.openUrl(
@ -254,59 +188,48 @@ public class VelocityCommand implements SimpleCommand {
.build();
source.sendMessage(embellishment);
}
return Command.SINGLE_SUCCESS;
}
}
private record Plugins(ProxyServer server) implements Command<CommandSource> {
@Override
public boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
return source.getPermissionValue("velocity.command.info") != Tristate.FALSE;
}
}
public int run(final CommandContext<CommandSource> context) {
final CommandSource source = context.getSource();
private static class Plugins implements SubCommand {
private final ProxyServer server;
private Plugins(ProxyServer server) {
this.server = server;
}
@Override
public void execute(CommandSource source, String @NonNull [] args) {
if (args.length != 0) {
source.sendMessage(Component.text("/velocity plugins", NamedTextColor.RED));
return;
}
List<PluginContainer> plugins = ImmutableList.copyOf(server.getPluginManager().getPlugins());
int pluginCount = plugins.size();
final List<PluginContainer> plugins = List.copyOf(server.getPluginManager().getPlugins());
final int pluginCount = plugins.size();
if (pluginCount == 0) {
source.sendMessage(Component.translatable("velocity.command.no-plugins",
NamedTextColor.YELLOW));
return;
return Command.SINGLE_SUCCESS;
}
TextComponent.Builder listBuilder = Component.text();
final TextComponent.Builder listBuilder = Component.text();
for (int i = 0; i < pluginCount; i++) {
PluginContainer plugin = plugins.get(i);
final PluginContainer plugin = plugins.get(i);
listBuilder.append(componentForPlugin(plugin.getDescription()));
if (i + 1 < pluginCount) {
listBuilder.append(Component.text(", "));
}
}
TranslatableComponent.Builder output = Component.translatable()
final TranslatableComponent output = Component.translatable()
.key("velocity.command.plugins-list")
.color(NamedTextColor.YELLOW)
.args(listBuilder.build());
.arguments(listBuilder.build())
.build();
source.sendMessage(output);
return Command.SINGLE_SUCCESS;
}
private TextComponent componentForPlugin(PluginDescription description) {
String pluginInfo = description.getName().orElse(description.getId())
final String pluginInfo = description.getName().orElse(description.getId())
+ description.getVersion().map(v -> " " + v).orElse("");
TextComponent.Builder hoverText = Component.text().content(pluginInfo);
final TextComponent.Builder hoverText = Component.text().content(pluginInfo);
description.getUrl().ifPresent(url -> {
hoverText.append(Component.newline());
@ -333,61 +256,51 @@ public class VelocityCommand implements SimpleCommand {
hoverText.append(Component.text(pdesc));
});
return Component.text(description.getId(), NamedTextColor.GRAY)
.hoverEvent(HoverEvent.showText(hoverText.build()));
}
@Override
public boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
return source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE;
return Component.text()
.content(description.getId())
.color(NamedTextColor.GRAY)
.hoverEvent(HoverEvent.showText(hoverText.build()))
.build();
}
}
private static class Dump implements SubCommand {
private record Dump(ProxyServer server) implements Command<CommandSource> {
private static final Logger logger = LogManager.getLogger(Dump.class);
private final ProxyServer server;
private Dump(ProxyServer server) {
this.server = server;
}
@Override
public void execute(CommandSource source, String @NonNull [] args) {
if (args.length != 0) {
source.sendMessage(Component.text("/velocity dump", NamedTextColor.RED));
return;
}
public int run(final CommandContext<CommandSource> context) {
final CommandSource source = context.getSource();
Collection<RegisteredServer> allServers = ImmutableSet.copyOf(server.getAllServers());
JsonObject servers = new JsonObject();
for (RegisteredServer iter : allServers) {
final Collection<RegisteredServer> allServers = Set.copyOf(server.getAllServers());
final JsonObject servers = new JsonObject();
for (final RegisteredServer iter : allServers) {
servers.add(iter.getServerInfo().getName(),
InformationUtils.collectServerInfo(iter));
}
JsonArray connectOrder = new JsonArray();
List<String> attemptedConnectionOrder = ImmutableList.copyOf(
final JsonArray connectOrder = new JsonArray();
final List<String> attemptedConnectionOrder = List.copyOf(
server.getConfiguration().getAttemptConnectionOrder());
for (String s : attemptedConnectionOrder) {
for (final String s : attemptedConnectionOrder) {
connectOrder.add(s);
}
JsonObject proxyConfig = InformationUtils.collectProxyConfig(server.getConfiguration());
final JsonObject proxyConfig = InformationUtils.collectProxyConfig(server.getConfiguration());
proxyConfig.add("servers", servers);
proxyConfig.add("connectOrder", connectOrder);
proxyConfig.add("forcedHosts",
InformationUtils.collectForcedHosts(server.getConfiguration()));
JsonObject dump = new JsonObject();
final JsonObject dump = new JsonObject();
dump.add("versionInfo", InformationUtils.collectProxyInfo(server.getVersion()));
dump.add("platform", InformationUtils.collectEnvironmentInfo());
dump.add("config", proxyConfig);
dump.add("plugins", InformationUtils.collectPluginInfo(server));
Path dumpPath = Path.of("velocity-dump-"
final Path dumpPath = Path.of("velocity-dump-"
+ new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date())
+ ".json");
try (BufferedWriter bw = Files.newBufferedWriter(
try (final BufferedWriter bw = Files.newBufferedWriter(
dumpPath, StandardCharsets.UTF_8, StandardOpenOption.CREATE_NEW)) {
bw.write(InformationUtils.toHumanReadableString(dump));
@ -397,32 +310,29 @@ public class VelocityCommand implements SimpleCommand {
NamedTextColor.GREEN));
} catch (IOException e) {
logger.error("Failed to complete dump command, "
+ "the executor was interrupted: " + e.getMessage());
e.printStackTrace();
+ "the executor was interrupted: " + e.getMessage(), e);
source.sendMessage(Component.text(
"We could not save the anonymized dump. Check the console for more details.",
NamedTextColor.RED)
);
}
}
@Override
public boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
return source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE;
return Command.SINGLE_SUCCESS;
}
}
/**
* Heap SubCommand.
*/
public static class Heap implements SubCommand {
public static final class Heap implements Command<CommandSource> {
private static final Logger logger = LogManager.getLogger(Heap.class);
private MethodHandle heapGenerator;
private Consumer<CommandSource> heapConsumer;
private final Path dir = Path.of("./dumps");
@Override
public void execute(CommandSource source, String @NonNull [] args) {
public int run(final CommandContext<CommandSource> context) throws CommandSyntaxException {
final CommandSource source = context.getSource();
try {
if (Files.notExists(dir)) {
Files.createDirectories(dir);
@ -480,39 +390,7 @@ public class VelocityCommand implements SimpleCommand {
NamedTextColor.RED));
logger.error("Could not write heap", t);
}
}
@Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
return source.getPermissionValue("velocity.command.heap") == Tristate.TRUE;
}
}
/**
* Callback SubCommand.
*/
public static class Callback implements SubCommand {
@Override
public void execute(final CommandSource source, final String @NonNull [] args) {
if (args.length != 1) {
return;
}
final UUID id;
try {
id = UUID.fromString(args[0]);
} catch (final IllegalArgumentException ignored) {
return;
}
ClickCallbackManager.INSTANCE.runCallback(source, id);
}
@Override
public boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
return true;
return Command.SINGLE_SUCCESS;
}
}
}

Datei anzeigen

@ -297,6 +297,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
// Inject commands from the proxy.
final CommandGraphInjector<CommandSource> injector = server.getCommandManager().getInjector();
injector.inject(rootNode, serverConn.getPlayer());
rootNode.removeChildByName("velocity:callback");
}
server.getEventManager().fire(