From 99f963aa10b5fee861b0e59f26bb93f4c48a0ade Mon Sep 17 00:00:00 2001 From: Owen1212055 <23108066+Owen1212055@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:32:29 -0500 Subject: [PATCH] Invocation Source API Allows proxies to detect if a command is executed from an unsigned/signed/api source. This is useful because it allows commands executed from the player manually or by clicking on a chat message to be controlled. --- .../event/command/CommandExecuteEvent.java | 51 +++++++++++++++++++ .../proxy/command/VelocityCommandManager.java | 6 +-- .../protocol/packet/chat/CommandHandler.java | 5 +- .../chat/keyed/KeyedCommandHandler.java | 2 +- .../chat/legacy/LegacyCommandHandler.java | 2 +- .../chat/session/SessionCommandHandler.java | 2 +- .../session/SessionPlayerCommandPacket.java | 4 ++ .../session/UnsignedPlayerCommandPacket.java | 5 ++ 8 files changed, 69 insertions(+), 8 deletions(-) diff --git a/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java b/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java index 65c9b9016a..d1a73d826c 100644 --- a/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java @@ -26,6 +26,7 @@ public final class CommandExecuteEvent implements ResultedEvent { private final CommandSource commandSource; private final String command; private CommandResult result; + private InvocationSource invocationSource; /** * Constructs a CommandExecuteEvent. @@ -34,9 +35,21 @@ public final class CommandExecuteEvent implements ResultedEvent { * @param command the command being executed without first slash */ public CommandExecuteEvent(CommandSource commandSource, String command) { + this(commandSource, command, InvocationSource.API); + } + + /** + * Constructs a CommandExecuteEvent. + * + * @param commandSource the source executing the command + * @param command the command being executed without first slash + * @param invocationSource the invocation source of this command + */ + public CommandExecuteEvent(CommandSource commandSource, String command, InvocationSource invocationSource) { this.commandSource = Preconditions.checkNotNull(commandSource, "commandSource"); this.command = Preconditions.checkNotNull(command, "command"); this.result = CommandResult.allowed(); + this.invocationSource = invocationSource; } /** @@ -61,6 +74,16 @@ public String getCommand() { return command; } + /** + * Returns the source of the command invocation, indicating how the command was executed. + * + * @return invocation source + */ + @NonNull + public InvocationSource getInvocationSource() { + return this.invocationSource; + } + @Override public CommandResult getResult() { return result; @@ -80,6 +103,34 @@ public String toString() { + '}'; } + /** + * Represents the source of a command invocation. + */ + public enum InvocationSource { + /** + * Indicates that the command was executed from an signed source, + * such as a player's direct input (e.g., typing in chat). + */ + SIGNED, + /** + * Indicates that the command was executed from an unsigned source, + * such as clicking a component with a {@link net.kyori.adventure.text.event.ClickEvent.Action#RUN_COMMAND}. + * + *

Sent by clients on 1.20.5+ + */ + UNSIGNED, + /** + * Indicates that the command was invoked programmatically via an API call. + */ + API, + /** + * Indicates that the command was executed from an unknown source. + * + *

This is sent on command execution for pre 1.19.3 clients. + */ + UNKNOWN + } + /** * Represents the result of the {@link CommandExecuteEvent}. */ diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java index f8bb39f078..9ef30e0ff7 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java @@ -221,10 +221,10 @@ public void unregister(CommandMeta meta) { * @return the {@link CompletableFuture} of the event */ public CompletableFuture callCommandEvent(final CommandSource source, - final String cmdLine) { + final String cmdLine, final CommandExecuteEvent.InvocationSource unsignedSource) { Preconditions.checkNotNull(source, "source"); Preconditions.checkNotNull(cmdLine, "cmdLine"); - return eventManager.fire(new CommandExecuteEvent(source, cmdLine)); + return eventManager.fire(new CommandExecuteEvent(source, cmdLine, unsignedSource)); } private boolean executeImmediately0(final CommandSource source, final ParseResults parsed) { @@ -266,7 +266,7 @@ public CompletableFuture executeAsync(final CommandSource source, final Preconditions.checkNotNull(source, "source"); Preconditions.checkNotNull(cmdLine, "cmdLine"); - return callCommandEvent(source, cmdLine).thenComposeAsync(event -> { + return callCommandEvent(source, cmdLine, CommandExecuteEvent.InvocationSource.API).thenComposeAsync(event -> { CommandExecuteEvent.CommandResult commandResult = event.getResult(); if (commandResult.isForwardToServer() || !commandResult.isAllowed()) { return CompletableFuture.completedFuture(false); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/CommandHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/CommandHandler.java index 9786fe1453..ce6c6c80c9 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/CommandHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/CommandHandler.java @@ -56,8 +56,9 @@ default CompletableFuture runCommand(VelocityServer server, default void queueCommandResult(VelocityServer server, ConnectedPlayer player, BiFunction> futurePacketCreator, - String message, Instant timestamp, @Nullable LastSeenMessages lastSeenMessages) { - CompletableFuture eventFuture = server.getCommandManager().callCommandEvent(player, message); + String message, Instant timestamp, @Nullable LastSeenMessages lastSeenMessages, CommandExecuteEvent.InvocationSource invocationSource) { + CompletableFuture eventFuture = server.getCommandManager().callCommandEvent(player, message, + invocationSource); player.getChatQueue().queuePacket( newLastSeenMessages -> eventFuture .thenComposeAsync(event -> futurePacketCreator.apply(event, newLastSeenMessages)) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedCommandHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedCommandHandler.java index 1d3751e45b..193bcf020b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedCommandHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedCommandHandler.java @@ -111,6 +111,6 @@ public void handlePlayerCommandInternal(KeyedPlayerCommandPacket packet) { } return null; }); - }, packet.getCommand(), packet.getTimestamp(), null); + }, packet.getCommand(), packet.getTimestamp(), null, CommandExecuteEvent.InvocationSource.UNKNOWN); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyCommandHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyCommandHandler.java index 7c5a2ec3c4..2f7656ae67 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyCommandHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyCommandHandler.java @@ -62,6 +62,6 @@ public void handlePlayerCommandInternal(LegacyChatPacket packet) { } return null; }); - }, command, Instant.now(), null); + }, command, Instant.now(), null, CommandExecuteEvent.InvocationSource.UNKNOWN); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java index 0e47feedee..4f64332975 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java @@ -117,6 +117,6 @@ public void handlePlayerCommandInternal(SessionPlayerCommandPacket packet) { } return forwardCommand(fixedPacket, commandToRun); }); - }, packet.command, packet.timeStamp, packet.lastSeenMessages); + }, packet.command, packet.timeStamp, packet.lastSeenMessages, packet.unsignedSource() ? CommandExecuteEvent.InvocationSource.UNSIGNED : CommandExecuteEvent.InvocationSource.SIGNED); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java index 07374c626e..be49c8e3ba 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java @@ -68,6 +68,10 @@ public boolean isSigned() { return !argumentSignatures.isEmpty(); } + public boolean unsignedSource() { + return false; + } + @Override public boolean handle(MinecraftSessionHandler handler) { return handler.handle(this); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java index b4e26fe075..78d011b372 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java @@ -44,6 +44,11 @@ public boolean isSigned() { return false; } + @Override + public boolean unsignedSource() { + return true; + } + @Override public String toString() { return "UnsignedPlayerCommandPacket{" +