From 26fc3a660abd8e7b7148b46f779760e49cd62b5a Mon Sep 17 00:00:00 2001 From: amy <144570677+amyavi@users.noreply.github.com> Date: Sun, 21 Sep 2025 00:20:47 -0300 Subject: [PATCH 1/2] refactor: use brigadier for command --- pom.xml | 6 +- src/main/java/pw/kaboom/commandspy/Main.java | 159 +++++++----------- .../command/PlayerOrUUIDArgumentType.java | 56 ++++++ .../commandspy/command/StateArgumentType.java | 67 ++++++++ src/main/resources/plugin.yml | 9 +- 5 files changed, 184 insertions(+), 113 deletions(-) create mode 100644 src/main/java/pw/kaboom/commandspy/command/PlayerOrUUIDArgumentType.java create mode 100644 src/main/java/pw/kaboom/commandspy/command/StateArgumentType.java diff --git a/pom.xml b/pom.xml index 61c531f..fae898a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,8 @@ master - 17 - 17 + 21 + 21 true UTF-8 @@ -15,7 +15,7 @@ io.papermc.paper paper-api - 1.20.4-R0.1-SNAPSHOT + 1.21.8-R0.1-SNAPSHOT provided diff --git a/src/main/java/pw/kaboom/commandspy/Main.java b/src/main/java/pw/kaboom/commandspy/Main.java index a5d4f40..fb19516 100644 --- a/src/main/java/pw/kaboom/commandspy/Main.java +++ b/src/main/java/pw/kaboom/commandspy/Main.java @@ -1,10 +1,16 @@ package pw.kaboom.commandspy; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.tree.LiteralCommandNode; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.command.brigadier.MessageComponentSerializer; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -14,19 +20,30 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; +import pw.kaboom.commandspy.command.PlayerOrUUIDArgumentType; +import pw.kaboom.commandspy.command.StateArgumentType; import java.io.File; -import java.util.UUID; +import java.util.List; + +import static io.papermc.paper.command.brigadier.Commands.*; +import static pw.kaboom.commandspy.command.PlayerOrUUIDArgumentType.getPlayer; +import static pw.kaboom.commandspy.command.StateArgumentType.getState; + +public final class Main extends JavaPlugin implements Listener { + public static final SimpleCommandExceptionType ERROR_NOT_PLAYER = + new SimpleCommandExceptionType(MessageComponentSerializer.message() + .serialize(Component.translatable("permissions.requires.player"))); -public final class Main extends JavaPlugin implements CommandExecutor, Listener { private CommandSpyState config; @Override public void onEnable() { this.config = new CommandSpyState(new File(this.getDataFolder(), "state.bin")); - //noinspection DataFlowIssue - this.getCommand("commandspy").setExecutor(this); + + this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, + event -> this.registerCommands(event.registrar())); this.getServer().getPluginManager().registerEvents(this, this); // Save the state every 30 seconds @@ -38,12 +55,40 @@ public void onDisable() { this.config.trySave(); } - private void updateCommandSpyState(final @NotNull Player target, - final @NotNull CommandSender source, final boolean state) { + private void registerCommands(final Commands registrar) { + final LiteralCommandNode commandSpyCommand = + literal("commandspy") + .requires(Commands.restricted(ctx -> ctx.getSender() + .hasPermission("commandspy.command"))) + .then(argument("state", new StateArgumentType()) + .executes(ctx -> updateState(ctx.getSource().getSender(), + null, getState(ctx, "state")))) + .then(argument("target", new PlayerOrUUIDArgumentType()) + .then(argument("state", new StateArgumentType()) + .executes(ctx -> updateState(ctx.getSource().getSender(), + getPlayer(ctx, "target"), getState(ctx, "state")))) + .executes(ctx -> updateState(ctx.getSource().getSender(), + getPlayer(ctx, "target"), null))) + .executes(ctx -> updateState(ctx.getSource().getSender(), + null, null)) + .build(); + + registrar.register(commandSpyCommand, + "Allows you to spy on players' commands", List.of("c", "cs", "cspy")); + } + + private int updateState(final @NotNull CommandSender source, + Player target, Boolean state) throws CommandSyntaxException { + if (target == null) { + if (!(source instanceof final Player player)) throw ERROR_NOT_PLAYER.create(); + target = player; + } + + if (state == null) state = !this.config.getCommandSpyState(target.getUniqueId()); + this.config.setCommandSpyState(target.getUniqueId(), state); final Component stateString = Component.text(state ? "enabled" : "disabled"); - target.sendMessage(Component.empty() .append(Component.text("Successfully ")) .append(stateString) @@ -54,9 +99,10 @@ private void updateCommandSpyState(final @NotNull Player target, .append(Component.text("Successfully ")) .append(stateString) .append(Component.text(" CommandSpy for ")) - .append(target.name()) - ); + .append(target.name())); } + + return Command.SINGLE_SUCCESS; } private NamedTextColor getTextColor(final Player player) { @@ -67,67 +113,6 @@ private NamedTextColor getTextColor(final Player player) { return NamedTextColor.AQUA; } - @Override - public boolean onCommand(final @NotNull CommandSender sender, final @NotNull Command cmd, - final @NotNull String label, final String[] args) { - - Player target = null; - Boolean state = null; - - switch (args.length) { - case 0 -> { - } - case 1, 2 -> { - // Get the last argument as a state. Fail if we have 2 arguments. - state = getState(args[args.length - 1]); - if (state != null && args.length == 1) { - break; - } else if (state == null && args.length == 2) { - sender.sendMessage(Component.text("Usage: ", NamedTextColor.RED) - .append(Component.text(cmd.getUsage().replace("", label))) - ); - return true; - } - - // Get the first argument as a player. Fail if it can't be found. - target = getPlayer(args[0]); - if (target != null) { - break; - } - - sender.sendMessage(Component.empty() - .append(Component.text("Player \"")) - .append(Component.text(args[0])) - .append(Component.text("\" not found")) - ); - return true; - } - default -> { - sender.sendMessage(Component.text("Usage: ", NamedTextColor.RED) - .append(Component.text(cmd.getUsage().replace("", label))) - ); - return true; - } - } - - if (target == null) { - if (!(sender instanceof final Player player)) { - sender.sendMessage(Component.text("Command has to be run by a player")); - return true; - } - - target = player; - } - - if (state == null) { - state = !this.config.getCommandSpyState(target.getUniqueId()); - } - - this.updateCommandSpyState(target, sender, state); - - return true; - } - @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) void onPlayerCommandPreprocess(final PlayerCommandPreprocessEvent event) { final Player player = event.getPlayer(); @@ -155,34 +140,4 @@ void onSignChange(final SignChangeEvent event) { this.config.broadcastSpyMessage(message); } - - private static Player getPlayer(final String arg) { - final Player player = Bukkit.getPlayer(arg); - if (player != null) { - return player; - } - - final UUID uuid; - try { - uuid = UUID.fromString(arg); - } catch (final IllegalArgumentException ignored) { - return null; - } - - return Bukkit.getPlayer(uuid); - } - - private static Boolean getState(final String arg) { - switch (arg) { - case "on", "enable" -> { - return true; - } - case "off", "disable" -> { - return false; - } - default -> { - return null; - } - } - } } diff --git a/src/main/java/pw/kaboom/commandspy/command/PlayerOrUUIDArgumentType.java b/src/main/java/pw/kaboom/commandspy/command/PlayerOrUUIDArgumentType.java new file mode 100644 index 0000000..46716d7 --- /dev/null +++ b/src/main/java/pw/kaboom/commandspy/command/PlayerOrUUIDArgumentType.java @@ -0,0 +1,56 @@ +package pw.kaboom.commandspy.command; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.command.brigadier.argument.CustomArgumentType; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.EntitySelectorArgumentResolver; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public final class PlayerOrUUIDArgumentType implements + CustomArgumentType<@NotNull Player, @NotNull EntitySelectorArgumentResolver> { + + // We lie to clients and tell them we're an entity selector so that UUIDs don't show up as red. + @Override + public @NotNull ArgumentType getNativeType() { + return ArgumentTypes.entity(); + } + + @Override + public Player parse(final @NotNull StringReader reader) { + throw new IllegalStateException("method should never be called as we implement override"); + } + + @Override + public Player parse(final @NotNull StringReader reader, final S source) + throws CommandSyntaxException { + if (!(source instanceof final CommandSourceStack stack)) + throw new IllegalStateException("source was not a CommandSourceStack"); + + final int cursor = reader.getCursor(); + final String string = reader.readString(); + + try { + final UUID uuid = UUID.fromString(string); + final Player player = Bukkit.getPlayer(uuid); + + if (player != null) return player; + } catch (final IllegalArgumentException ignored) { + } + + reader.setCursor(cursor); + return ArgumentTypes.player().parse(reader).resolve(stack).getFirst(); + } + + public static Player getPlayer(final CommandContext context, + final String name) { + return context.getArgument(name, Player.class); + } +} diff --git a/src/main/java/pw/kaboom/commandspy/command/StateArgumentType.java b/src/main/java/pw/kaboom/commandspy/command/StateArgumentType.java new file mode 100644 index 0000000..5785117 --- /dev/null +++ b/src/main/java/pw/kaboom/commandspy/command/StateArgumentType.java @@ -0,0 +1,67 @@ +package pw.kaboom.commandspy.command; + +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.MessageComponentSerializer; +import io.papermc.paper.command.brigadier.argument.CustomArgumentType; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +public final class StateArgumentType implements + CustomArgumentType.Converted<@NotNull Boolean, @NotNull String> { + + private static final Collection VALUES = Arrays.asList("on", "enable", + "off", "disable"); + + private static final DynamicCommandExceptionType ERROR_INVALID_VALUE = + new DynamicCommandExceptionType(o -> + MessageComponentSerializer.message() + .serialize(Component.translatable("argument.enum.invalid") + .arguments(Component.text(o.toString())))); + + @Override + public @NotNull ArgumentType getNativeType() { + return StringArgumentType.word(); + } + + @Override + public Boolean convert(final String s) throws CommandSyntaxException { + switch (s) { + case "on", "enable" -> { + return true; + } + case "off", "disable" -> { + return false; + } + + default -> throw ERROR_INVALID_VALUE.create(s); + } + } + + @Override + public @NotNull CompletableFuture listSuggestions( + final @NotNull CommandContext context, final @NotNull SuggestionsBuilder builder) { + for (final String value: VALUES) { + if (!value.startsWith(builder.getRemainingLowerCase())) continue; + + builder.suggest(value); + } + + return builder.buildFuture(); + } + + public static boolean getState(final CommandContext context, + final String name) { + return context.getArgument(name, Boolean.class); + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 9ba5483..9678ad5 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,13 +1,6 @@ name: CommandSpy main: pw.kaboom.commandspy.Main description: Plugin that allows you to spy on players' commands. -api-version: '1.20' +api-version: '1.21' version: master folia-supported: true - -commands: - commandspy: - aliases: [ c, cs, cspy ] - description: Allows you to spy on players' commands - permission: commandspy.command - usage: '/ [player] [on|enable|off|disable]' From 9f01759bddb47464286acbaea92d3b7386272fee Mon Sep 17 00:00:00 2001 From: amy <144570677+amyavi@users.noreply.github.com> Date: Sat, 25 Oct 2025 18:55:04 -0300 Subject: [PATCH 2/2] refactor: update indentation; pass CommandContext to updateState --- src/main/java/pw/kaboom/commandspy/Main.java | 39 +++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/src/main/java/pw/kaboom/commandspy/Main.java b/src/main/java/pw/kaboom/commandspy/Main.java index fb19516..81949a2 100644 --- a/src/main/java/pw/kaboom/commandspy/Main.java +++ b/src/main/java/pw/kaboom/commandspy/Main.java @@ -1,6 +1,7 @@ package pw.kaboom.commandspy; import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; import com.mojang.brigadier.tree.LiteralCommandNode; @@ -58,27 +59,37 @@ public void onDisable() { private void registerCommands(final Commands registrar) { final LiteralCommandNode commandSpyCommand = literal("commandspy") - .requires(Commands.restricted(ctx -> ctx.getSender() - .hasPermission("commandspy.command"))) - .then(argument("state", new StateArgumentType()) - .executes(ctx -> updateState(ctx.getSource().getSender(), - null, getState(ctx, "state")))) - .then(argument("target", new PlayerOrUUIDArgumentType()) - .then(argument("state", new StateArgumentType()) - .executes(ctx -> updateState(ctx.getSource().getSender(), - getPlayer(ctx, "target"), getState(ctx, "state")))) - .executes(ctx -> updateState(ctx.getSource().getSender(), - getPlayer(ctx, "target"), null))) - .executes(ctx -> updateState(ctx.getSource().getSender(), - null, null)) + .requires( + Commands.restricted( + ctx -> ctx.getSender().hasPermission("commandspy.command") + ) + ) + .then( + argument("state", new StateArgumentType()) + .executes(ctx -> updateState(ctx, null, getState(ctx, "state"))) + ) + .then( + argument("target", new PlayerOrUUIDArgumentType()) + .then( + argument("state", new StateArgumentType()) + .executes( + ctx -> updateState( + ctx, getPlayer(ctx, "target"), getState(ctx, "state") + ) + ) + ) + .executes(ctx -> updateState(ctx, getPlayer(ctx, "target"), null)) + ) + .executes(ctx -> updateState(ctx, null, null)) .build(); registrar.register(commandSpyCommand, "Allows you to spy on players' commands", List.of("c", "cs", "cspy")); } - private int updateState(final @NotNull CommandSender source, + private int updateState(final @NotNull CommandContext ctx, Player target, Boolean state) throws CommandSyntaxException { + final CommandSender source = ctx.getSource().getSender(); if (target == null) { if (!(source instanceof final Player player)) throw ERROR_NOT_PLAYER.create(); target = player;