Skip to content

Commit 965db12

Browse files
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 <[email protected]> * Update api/src/main/java/com/velocitypowered/api/event/command/CommandExecuteEvent.java Co-authored-by: powercas_gamer <[email protected]> * Fix rename --------- Co-authored-by: powercas_gamer <[email protected]>
1 parent be5f0ac commit 965db12

File tree

8 files changed

+120
-8
lines changed

8 files changed

+120
-8
lines changed

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

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public final class CommandExecuteEvent implements ResultedEvent<CommandResult> {
2626
private final CommandSource commandSource;
2727
private final String command;
2828
private CommandResult result;
29+
private InvocationInfo invocationInfo;
2930

3031
/**
3132
* Constructs a CommandExecuteEvent.
@@ -34,9 +35,21 @@ public final class CommandExecuteEvent implements ResultedEvent<CommandResult> {
3435
* @param command the command being executed without first slash
3536
*/
3637
public CommandExecuteEvent(CommandSource commandSource, String command) {
38+
this(commandSource, command, new InvocationInfo(SignedState.UNSUPPORTED, Source.API));
39+
}
40+
41+
/**
42+
* Constructs a CommandExecuteEvent.
43+
*
44+
* @param commandSource the source executing the command
45+
* @param command the command being executed without first slash
46+
* @param invocationInfo the invocation info of this command
47+
*/
48+
public CommandExecuteEvent(CommandSource commandSource, String command, InvocationInfo invocationInfo) {
3749
this.commandSource = Preconditions.checkNotNull(commandSource, "commandSource");
3850
this.command = Preconditions.checkNotNull(command, "command");
3951
this.result = CommandResult.allowed();
52+
this.invocationInfo = invocationInfo;
4053
}
4154

4255
/**
@@ -61,6 +74,16 @@ public String getCommand() {
6174
return command;
6275
}
6376

77+
/**
78+
* Returns the info of the command invocation.
79+
*
80+
* @since 3.4.0
81+
* @return invocation info
82+
*/
83+
public InvocationInfo getInvocationInfo() {
84+
return this.invocationInfo;
85+
}
86+
6487
@Override
6588
public CommandResult getResult() {
6689
return result;
@@ -80,6 +103,75 @@ public String toString() {
80103
+ '}';
81104
}
82105

106+
/**
107+
* Represents information about a command invocation, including its signed state and source.
108+
*
109+
* @since 3.4.0
110+
*/
111+
public record InvocationInfo(SignedState signedState, Source source) {
112+
}
113+
114+
/**
115+
* Represents the signed state of a command invocation.
116+
*
117+
* @since 3.4.0
118+
*/
119+
public enum SignedState {
120+
/**
121+
* Indicates that the command was executed from a signed source with signed message arguments,
122+
* This is currently only possible by typing a command in chat with signed arguments.
123+
*
124+
* <p><b>Note:</b> Cancelling the {@link CommandExecuteEvent} in this state will result in the player being kicked.</p>
125+
*
126+
* @since 3.4.0
127+
*/
128+
SIGNED_WITH_ARGS,
129+
/**
130+
* Indicates that the command was executed from an signed source with no signed message arguments,
131+
* This is currently only possible by typing a command in chat.
132+
*
133+
* @since 3.4.0
134+
*/
135+
SIGNED_WITHOUT_ARGS,
136+
/**
137+
* Indicates that the command was executed from an unsigned source,
138+
* such as clicking on a component with a {@link net.kyori.adventure.text.event.ClickEvent.Action#RUN_COMMAND}.
139+
*
140+
* <p>Clients running version 1.20.5 or later will send this state.</p>
141+
*
142+
* @since 3.4.0
143+
*/
144+
UNSIGNED,
145+
/**
146+
* Indicates that the command invocation does not support signing.
147+
*
148+
* <p>This state is sent by clients running versions prior to 1.19.3.</p>
149+
*
150+
* @since 3.4.0
151+
*/
152+
UNSUPPORTED
153+
}
154+
155+
/**
156+
* Represents the source of a command invocation.
157+
*
158+
* @since 3.4.0
159+
*/
160+
public enum Source {
161+
/**
162+
* Indicates that the command was invoked by a player.
163+
*
164+
* @since 3.4.0
165+
*/
166+
PLAYER,
167+
/**
168+
* Indicates that the command was invoked programmatically through an API call.
169+
*
170+
* @since 3.4.0
171+
*/
172+
API
173+
}
174+
83175
/**
84176
* Represents the result of the {@link CommandExecuteEvent}.
85177
*/

proxy/src/main/java/com/velocitypowered/proxy/command/VelocityCommandManager.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,13 +218,14 @@ public void unregister(CommandMeta meta) {
218218
*
219219
* @param source the source to execute the command for
220220
* @param cmdLine the command to execute
221+
* @param invocationInfo the invocation info
221222
* @return the {@link CompletableFuture} of the event
222223
*/
223224
public CompletableFuture<CommandExecuteEvent> callCommandEvent(final CommandSource source,
224-
final String cmdLine) {
225+
final String cmdLine, final CommandExecuteEvent.InvocationInfo invocationInfo) {
225226
Preconditions.checkNotNull(source, "source");
226227
Preconditions.checkNotNull(cmdLine, "cmdLine");
227-
return eventManager.fire(new CommandExecuteEvent(source, cmdLine));
228+
return eventManager.fire(new CommandExecuteEvent(source, cmdLine, invocationInfo));
228229
}
229230

230231
private boolean executeImmediately0(final CommandSource source, final ParseResults<CommandSource> parsed) {
@@ -266,7 +267,12 @@ public CompletableFuture<Boolean> executeAsync(final CommandSource source, final
266267
Preconditions.checkNotNull(source, "source");
267268
Preconditions.checkNotNull(cmdLine, "cmdLine");
268269

269-
return callCommandEvent(source, cmdLine).thenComposeAsync(event -> {
270+
CommandExecuteEvent.InvocationInfo invocationInfo = new CommandExecuteEvent.InvocationInfo(
271+
CommandExecuteEvent.SignedState.UNSUPPORTED,
272+
CommandExecuteEvent.Source.API
273+
);
274+
275+
return callCommandEvent(source, cmdLine, invocationInfo).thenComposeAsync(event -> {
270276
CommandExecuteEvent.CommandResult commandResult = event.getResult();
271277
if (commandResult.isForwardToServer() || !commandResult.isAllowed()) {
272278
return CompletableFuture.completedFuture(false);

proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/CommandHandler.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ default CompletableFuture<MinecraftPacket> runCommand(VelocityServer server,
5656

5757
default void queueCommandResult(VelocityServer server, ConnectedPlayer player,
5858
BiFunction<CommandExecuteEvent, LastSeenMessages, CompletableFuture<MinecraftPacket>> futurePacketCreator,
59-
String message, Instant timestamp, @Nullable LastSeenMessages lastSeenMessages) {
60-
CompletableFuture<CommandExecuteEvent> eventFuture = server.getCommandManager().callCommandEvent(player, message);
59+
String message, Instant timestamp, @Nullable LastSeenMessages lastSeenMessages,
60+
CommandExecuteEvent.InvocationInfo invocationInfo) {
61+
CompletableFuture<CommandExecuteEvent> eventFuture = server.getCommandManager().callCommandEvent(player, message,
62+
invocationInfo);
6163
player.getChatQueue().queuePacket(
6264
newLastSeenMessages -> eventFuture
6365
.thenComposeAsync(event -> futurePacketCreator.apply(event, newLastSeenMessages))

proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/keyed/KeyedCommandHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,6 @@ public void handlePlayerCommandInternal(KeyedPlayerCommandPacket packet) {
111111
}
112112
return null;
113113
});
114-
}, packet.getCommand(), packet.getTimestamp(), null);
114+
}, packet.getCommand(), packet.getTimestamp(), null, new CommandExecuteEvent.InvocationInfo(CommandExecuteEvent.SignedState.UNSUPPORTED, CommandExecuteEvent.Source.PLAYER));
115115
}
116116
}

proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/legacy/LegacyCommandHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,6 @@ public void handlePlayerCommandInternal(LegacyChatPacket packet) {
6262
}
6363
return null;
6464
});
65-
}, command, Instant.now(), null);
65+
}, command, Instant.now(), null, new CommandExecuteEvent.InvocationInfo(CommandExecuteEvent.SignedState.UNSUPPORTED, CommandExecuteEvent.Source.PLAYER));
6666
}
6767
}

proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionCommandHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ public void handlePlayerCommandInternal(SessionPlayerCommandPacket packet) {
117117
}
118118
return forwardCommand(fixedPacket, commandToRun);
119119
});
120-
}, packet.command, packet.timeStamp, packet.lastSeenMessages);
120+
}, packet.command, packet.timeStamp, packet.lastSeenMessages,
121+
new CommandExecuteEvent.InvocationInfo(packet.getEventSignedState(), CommandExecuteEvent.Source.PLAYER));
121122
}
122123
}

proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/SessionPlayerCommandPacket.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package com.velocitypowered.proxy.protocol.packet.chat.session;
1919

2020
import com.google.common.collect.Lists;
21+
import com.velocitypowered.api.event.command.CommandExecuteEvent;
2122
import com.velocitypowered.api.network.ProtocolVersion;
2223
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
2324
import com.velocitypowered.proxy.protocol.MinecraftPacket;
@@ -68,6 +69,10 @@ public boolean isSigned() {
6869
return !argumentSignatures.isEmpty();
6970
}
7071

72+
public CommandExecuteEvent.SignedState getEventSignedState() {
73+
return !this.argumentSignatures.isEmpty() ? CommandExecuteEvent.SignedState.SIGNED_WITH_ARGS : CommandExecuteEvent.SignedState.SIGNED_WITHOUT_ARGS;
74+
}
75+
7176
@Override
7277
public boolean handle(MinecraftSessionHandler handler) {
7378
return handler.handle(this);

proxy/src/main/java/com/velocitypowered/proxy/protocol/packet/chat/session/UnsignedPlayerCommandPacket.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package com.velocitypowered.proxy.protocol.packet.chat.session;
1919

20+
import com.velocitypowered.api.event.command.CommandExecuteEvent;
2021
import com.velocitypowered.api.network.ProtocolVersion;
2122
import com.velocitypowered.proxy.protocol.ProtocolUtils;
2223
import com.velocitypowered.proxy.protocol.packet.chat.LastSeenMessages;
@@ -44,6 +45,11 @@ public boolean isSigned() {
4445
return false;
4546
}
4647

48+
@Override
49+
public CommandExecuteEvent.SignedState getEventSignedState() {
50+
return CommandExecuteEvent.SignedState.UNSIGNED;
51+
}
52+
4753
@Override
4854
public String toString() {
4955
return "UnsignedPlayerCommandPacket{" +

0 commit comments

Comments
 (0)