13
0
geforkt von Mirrors/Velocity

InvocationInfo API (#1467)

* 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.

* Update api significantly to improve api coverage

* javadoc

* javadoc

* Update api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java

Co-authored-by: powercas_gamer <cas@mizule.dev>

* Update api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java

Co-authored-by: powercas_gamer <cas@mizule.dev>

* Fix rename

---------

Co-authored-by: powercas_gamer <cas@mizule.dev>
Dieser Commit ist enthalten in:
Owen 2024-12-04 12:36:45 -05:00 committet von GitHub
Ursprung be5f0ace26
Commit 965db127a9
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: B5690EEEBB952194
8 geänderte Dateien mit 120 neuen und 8 gelöschten Zeilen

Datei anzeigen

@ -26,6 +26,7 @@ public final class CommandExecuteEvent implements ResultedEvent<CommandResult> {
private final CommandSource commandSource; private final CommandSource commandSource;
private final String command; private final String command;
private CommandResult result; private CommandResult result;
private InvocationInfo invocationInfo;
/** /**
* Constructs a CommandExecuteEvent. * Constructs a CommandExecuteEvent.
@ -34,9 +35,21 @@ public final class CommandExecuteEvent implements ResultedEvent<CommandResult> {
* @param command the command being executed without first slash * @param command the command being executed without first slash
*/ */
public CommandExecuteEvent(CommandSource commandSource, String command) { public CommandExecuteEvent(CommandSource commandSource, String command) {
this(commandSource, command, new InvocationInfo(SignedState.UNSUPPORTED, Source.API));
}
/**
* Constructs a CommandExecuteEvent.
*
* @param commandSource the source executing the command
* @param command the command being executed without first slash
* @param invocationInfo the invocation info of this command
*/
public CommandExecuteEvent(CommandSource commandSource, String command, InvocationInfo invocationInfo) {
this.commandSource = Preconditions.checkNotNull(commandSource, "commandSource"); this.commandSource = Preconditions.checkNotNull(commandSource, "commandSource");
this.command = Preconditions.checkNotNull(command, "command"); this.command = Preconditions.checkNotNull(command, "command");
this.result = CommandResult.allowed(); this.result = CommandResult.allowed();
this.invocationInfo = invocationInfo;
} }
/** /**
@ -61,6 +74,16 @@ public final class CommandExecuteEvent implements ResultedEvent<CommandResult> {
return command; return command;
} }
/**
* Returns the info of the command invocation.
*
* @since 3.4.0
* @return invocation info
*/
public InvocationInfo getInvocationInfo() {
return this.invocationInfo;
}
@Override @Override
public CommandResult getResult() { public CommandResult getResult() {
return result; return result;
@ -80,6 +103,75 @@ public final class CommandExecuteEvent implements ResultedEvent<CommandResult> {
+ '}'; + '}';
} }
/**
* Represents information about a command invocation, including its signed state and source.
*
* @since 3.4.0
*/
public record InvocationInfo(SignedState signedState, Source source) {
}
/**
* Represents the signed state of a command invocation.
*
* @since 3.4.0
*/
public enum SignedState {
/**
* Indicates that the command was executed from a signed source with signed message arguments,
* This is currently only possible by typing a command in chat with signed arguments.
*
* <p><b>Note:</b> Cancelling the {@link CommandExecuteEvent} in this state will result in the player being kicked.</p>
*
* @since 3.4.0
*/
SIGNED_WITH_ARGS,
/**
* Indicates that the command was executed from an signed source with no signed message arguments,
* This is currently only possible by typing a command in chat.
*
* @since 3.4.0
*/
SIGNED_WITHOUT_ARGS,
/**
* Indicates that the command was executed from an unsigned source,
* such as clicking on a component with a {@link net.kyori.adventure.text.event.ClickEvent.Action#RUN_COMMAND}.
*
* <p>Clients running version 1.20.5 or later will send this state.</p>
*
* @since 3.4.0
*/
UNSIGNED,
/**
* Indicates that the command invocation does not support signing.
*
* <p>This state is sent by clients running versions prior to 1.19.3.</p>
*
* @since 3.4.0
*/
UNSUPPORTED
}
/**
* Represents the source of a command invocation.
*
* @since 3.4.0
*/
public enum Source {
/**
* Indicates that the command was invoked by a player.
*
* @since 3.4.0
*/
PLAYER,
/**
* Indicates that the command was invoked programmatically through an API call.
*
* @since 3.4.0
*/
API
}
/** /**
* Represents the result of the {@link CommandExecuteEvent}. * Represents the result of the {@link CommandExecuteEvent}.
*/ */

Datei anzeigen

@ -218,13 +218,14 @@ public class VelocityCommandManager implements CommandManager {
* *
* @param source the source to execute the command for * @param source the source to execute the command for
* @param cmdLine the command to execute * @param cmdLine the command to execute
* @param invocationInfo the invocation info
* @return the {@link CompletableFuture} of the event * @return the {@link CompletableFuture} of the event
*/ */
public CompletableFuture<CommandExecuteEvent> callCommandEvent(final CommandSource source, public CompletableFuture<CommandExecuteEvent> callCommandEvent(final CommandSource source,
final String cmdLine) { final String cmdLine, final CommandExecuteEvent.InvocationInfo invocationInfo) {
Preconditions.checkNotNull(source, "source"); Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmdLine, "cmdLine"); Preconditions.checkNotNull(cmdLine, "cmdLine");
return eventManager.fire(new CommandExecuteEvent(source, cmdLine)); return eventManager.fire(new CommandExecuteEvent(source, cmdLine, invocationInfo));
} }
private boolean executeImmediately0(final CommandSource source, final ParseResults<CommandSource> parsed) { private boolean executeImmediately0(final CommandSource source, final ParseResults<CommandSource> parsed) {
@ -266,7 +267,12 @@ public class VelocityCommandManager implements CommandManager {
Preconditions.checkNotNull(source, "source"); Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmdLine, "cmdLine"); Preconditions.checkNotNull(cmdLine, "cmdLine");
return callCommandEvent(source, cmdLine).thenComposeAsync(event -> { CommandExecuteEvent.InvocationInfo invocationInfo = new CommandExecuteEvent.InvocationInfo(
CommandExecuteEvent.SignedState.UNSUPPORTED,
CommandExecuteEvent.Source.API
);
return callCommandEvent(source, cmdLine, invocationInfo).thenComposeAsync(event -> {
CommandExecuteEvent.CommandResult commandResult = event.getResult(); CommandExecuteEvent.CommandResult commandResult = event.getResult();
if (commandResult.isForwardToServer() || !commandResult.isAllowed()) { if (commandResult.isForwardToServer() || !commandResult.isAllowed()) {
return CompletableFuture.completedFuture(false); return CompletableFuture.completedFuture(false);

Datei anzeigen

@ -56,8 +56,10 @@ public interface CommandHandler<T extends MinecraftPacket> {
default void queueCommandResult(VelocityServer server, ConnectedPlayer player, default void queueCommandResult(VelocityServer server, ConnectedPlayer player,
BiFunction<CommandExecuteEvent, LastSeenMessages, CompletableFuture<MinecraftPacket>> futurePacketCreator, BiFunction<CommandExecuteEvent, LastSeenMessages, CompletableFuture<MinecraftPacket>> futurePacketCreator,
String message, Instant timestamp, @Nullable LastSeenMessages lastSeenMessages) { String message, Instant timestamp, @Nullable LastSeenMessages lastSeenMessages,
CompletableFuture<CommandExecuteEvent> eventFuture = server.getCommandManager().callCommandEvent(player, message); CommandExecuteEvent.InvocationInfo invocationInfo) {
CompletableFuture<CommandExecuteEvent> eventFuture = server.getCommandManager().callCommandEvent(player, message,
invocationInfo);
player.getChatQueue().queuePacket( player.getChatQueue().queuePacket(
newLastSeenMessages -> eventFuture newLastSeenMessages -> eventFuture
.thenComposeAsync(event -> futurePacketCreator.apply(event, newLastSeenMessages)) .thenComposeAsync(event -> futurePacketCreator.apply(event, newLastSeenMessages))

Datei anzeigen

@ -111,6 +111,6 @@ public class KeyedCommandHandler implements CommandHandler<KeyedPlayerCommandPac
} }
return null; return null;
}); });
}, packet.getCommand(), packet.getTimestamp(), null); }, packet.getCommand(), packet.getTimestamp(), null, new CommandExecuteEvent.InvocationInfo(CommandExecuteEvent.SignedState.UNSUPPORTED, CommandExecuteEvent.Source.PLAYER));
} }
} }

Datei anzeigen

@ -62,6 +62,6 @@ public class LegacyCommandHandler implements CommandHandler<LegacyChatPacket> {
} }
return null; return null;
}); });
}, command, Instant.now(), null); }, command, Instant.now(), null, new CommandExecuteEvent.InvocationInfo(CommandExecuteEvent.SignedState.UNSUPPORTED, CommandExecuteEvent.Source.PLAYER));
} }
} }

Datei anzeigen

@ -117,6 +117,7 @@ public class SessionCommandHandler implements CommandHandler<SessionPlayerComman
} }
return forwardCommand(fixedPacket, commandToRun); return forwardCommand(fixedPacket, commandToRun);
}); });
}, packet.command, packet.timeStamp, packet.lastSeenMessages); }, packet.command, packet.timeStamp, packet.lastSeenMessages,
new CommandExecuteEvent.InvocationInfo(packet.getEventSignedState(), CommandExecuteEvent.Source.PLAYER));
} }
} }

Datei anzeigen

@ -18,6 +18,7 @@
package com.velocitypowered.proxy.protocol.packet.chat.session; package com.velocitypowered.proxy.protocol.packet.chat.session;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
@ -68,6 +69,10 @@ public class SessionPlayerCommandPacket implements MinecraftPacket {
return !argumentSignatures.isEmpty(); return !argumentSignatures.isEmpty();
} }
public CommandExecuteEvent.SignedState getEventSignedState() {
return !this.argumentSignatures.isEmpty() ? CommandExecuteEvent.SignedState.SIGNED_WITH_ARGS : CommandExecuteEvent.SignedState.SIGNED_WITHOUT_ARGS;
}
@Override @Override
public boolean handle(MinecraftSessionHandler handler) { public boolean handle(MinecraftSessionHandler handler) {
return handler.handle(this); return handler.handle(this);

Datei anzeigen

@ -17,6 +17,7 @@
package com.velocitypowered.proxy.protocol.packet.chat.session; package com.velocitypowered.proxy.protocol.packet.chat.session;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages; import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages;
@ -44,6 +45,11 @@ public class UnsignedPlayerCommandPacket extends SessionPlayerCommandPacket {
return false; return false;
} }
@Override
public CommandExecuteEvent.SignedState getEventSignedState() {
return CommandExecuteEvent.SignedState.UNSIGNED;
}
@Override @Override
public String toString() { public String toString() {
return "UnsignedPlayerCommandPacket{" + return "UnsignedPlayerCommandPacket{" +