diff --git a/build.gradle b/build.gradle index 4c0a3d5..a5c3b7d 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ processResources { } dependencies { - compileOnly "io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT" + compileOnly("io.papermc.paper:paper-api:1.21-R0.1-SNAPSHOT") } jar { @@ -36,7 +36,7 @@ jar { } -def targetJavaVersion = 17 +def targetJavaVersion = 21 java { def javaVersion = JavaVersion.toVersion(targetJavaVersion) sourceCompatibility = javaVersion diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b82aa23..94113f2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/src/main/java/de/shiewk/smoderation/paper/SModerationPaper.java b/src/main/java/de/shiewk/smoderation/paper/SModerationPaper.java index 37715ef..3d783ff 100644 --- a/src/main/java/de/shiewk/smoderation/paper/SModerationPaper.java +++ b/src/main/java/de/shiewk/smoderation/paper/SModerationPaper.java @@ -5,13 +5,13 @@ import de.shiewk.smoderation.paper.config.SModerationConfig; import de.shiewk.smoderation.paper.input.ChatInputListener; import de.shiewk.smoderation.paper.listener.*; import de.shiewk.smoderation.paper.storage.PunishmentContainer; +import io.papermc.paper.command.brigadier.Commands; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.logger.slf4j.ComponentLogger; import org.bukkit.Bukkit; -import org.bukkit.command.PluginCommand; -import org.bukkit.command.TabExecutor; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; @@ -34,7 +34,6 @@ public final class SModerationPaper extends JavaPlugin { public static final TextColor PRIMARY_COLOR = TextColor.color(212, 0, 255); public static final TextColor SECONDARY_COLOR = TextColor.color(52, 143, 255); public static final TextColor INACTIVE_COLOR = NamedTextColor.GRAY; - public static final TextColor FAIL_COLOR = NamedTextColor.RED; public static final TextComponent CHAT_PREFIX = text("SM \u00BB ").color(PRIMARY_COLOR); @Override @@ -55,29 +54,31 @@ public final class SModerationPaper extends JavaPlugin { getPluginManager().registerEvents(new ChatInputListener(), this); getPluginManager().registerEvents(new SocialSpyListener(), this); - registerCommand("mute", new MuteCommand()); - registerCommand("ban", new BanCommand()); - registerCommand("kick", new KickCommand()); - registerCommand("smod", new SModCommand()); - registerCommand("modlogs", new ModLogsCommand()); - registerCommand("unmute", new UnmuteCommand()); - registerCommand("unban", new UnbanCommand()); - registerCommand("invsee", new InvseeCommand()); - registerCommand("enderchestsee", new EnderchestSeeCommand()); - registerCommand("vanish", new VanishCommand()); - registerCommand("socialspy", new SocialSpyCommand()); + getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> { + Commands commands = event.registrar(); + + registerCommand(commands, new KickCommand()); + registerCommand(commands, new ModLogsCommand()); + registerCommand(commands, new SModCommand()); + registerCommand(commands, new InvseeCommand()); + registerCommand(commands, new EnderchestSeeCommand()); + registerCommand(commands, new SocialSpyCommand()); + registerCommand(commands, new VanishCommand()); + registerCommand(commands, new UnmuteCommand()); + registerCommand(commands, new UnbanCommand()); + registerCommand(commands, new MuteCommand()); + registerCommand(commands, new BanCommand()); + }); container.load(SAVE_FILE); } - private void registerCommand(String label, TabExecutor executor){ - final PluginCommand command = getCommand(label); - if (command != null) { - command.setExecutor(executor); - command.setTabCompleter(executor); - } else { - LOGGER.warn("Command {} failed to register: This command does not exist", label); - } + private void registerCommand(Commands commands, CommandProvider provider){ + commands.register( + provider.getCommandNode(), + provider.getCommandDescription(), + provider.getAliases() + ); } @Override diff --git a/src/main/java/de/shiewk/smoderation/paper/command/BanCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/BanCommand.java index 88fbb00..97ccd13 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/BanCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/BanCommand.java @@ -1,124 +1,95 @@ package de.shiewk.smoderation.paper.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.SModerationPaper; +import de.shiewk.smoderation.paper.command.argument.DurationArgument; +import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument; import de.shiewk.smoderation.paper.punishments.Punishment; -import de.shiewk.smoderation.paper.util.PlayerUtil; -import de.shiewk.smoderation.paper.util.TimeUtil; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; +import de.shiewk.smoderation.paper.util.CommandUtil; +import io.papermc.paper.command.brigadier.CommandSourceStack; import org.bukkit.Bukkit; -import org.bukkit.command.*; import org.bukkit.entity.Player; -import org.bukkit.util.StringUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.UUID; -import static net.kyori.adventure.text.Component.text; +import static io.papermc.paper.command.brigadier.Commands.argument; +import static io.papermc.paper.command.brigadier.Commands.literal; + +public final class BanCommand implements CommandProvider { -public class BanCommand implements TabExecutor { @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 2){ - return false; - } else { - UUID senderUUID; - if (sender instanceof ConsoleCommandSender){ - senderUUID = PlayerUtil.UUID_CONSOLE; - } else if (sender instanceof Player pl){ - senderUUID = pl.getUniqueId(); - } else if (sender instanceof BlockCommandSender){ - sender.sendMessage(Component.text("Blocks can't execute this command.").color(NamedTextColor.RED)); - return true; + public LiteralCommandNode getCommandNode() { + return literal("ban") + .requires(CommandUtil.requirePermission("smod.ban")) + .then(argument("player", new PlayerUUIDArgument()) + .then(argument("duration", new DurationArgument()) + .executes(this::banWithoutReason) + .then(argument("reason", StringArgumentType.greedyString()) + .executes(this::banWithReason) + ) + ) + ) + .build(); + } + + private int banWithoutReason(CommandContext context) throws CommandSyntaxException { + UUID sender = CommandUtil.getSenderUUID(context.getSource()); + UUID target = context.getArgument("player", UUID.class); + long duration = context.getArgument("duration", Long.class); + executeBan(sender, target, duration, Punishment.DEFAULT_REASON); + return Command.SINGLE_SUCCESS; + } + + private int banWithReason(CommandContext context) throws CommandSyntaxException { + UUID sender = CommandUtil.getSenderUUID(context.getSource()); + UUID target = context.getArgument("player", UUID.class); + long duration = context.getArgument("duration", Long.class); + String reason = StringArgumentType.getString(context, "reason"); + executeBan(sender, target, duration, reason); + return Command.SINGLE_SUCCESS; + } + + public static void executeBan(UUID sender, UUID target, long duration, String reason) throws CommandSyntaxException { + Player targetPlayer = Bukkit.getPlayer(target); + if (duration == 0){ + if (targetPlayer == null){ + CommandUtil.error("You can't ban an offline player for less than 1ms."); } else { - sender.sendMessage(Component.text("Your command sender type is unknown (%s).".formatted(sender.getClass().getName())).color(NamedTextColor.RED)); - return true; + KickCommand.executeKick(sender, targetPlayer, reason); } - String playerName = args[0]; - UUID uuid = PlayerUtil.offlinePlayerUUIDByName(playerName); - if (senderUUID.equals(uuid)) { - sender.sendMessage(Component.text("You can't ban yourself.").color(NamedTextColor.RED)); - return true; + return; + } + if (sender.equals(target)) { + CommandUtil.error("You can't ban yourself."); + } else { + if (targetPlayer != null && targetPlayer.hasPermission("smod.preventban")){ + CommandUtil.error("This player can't be banned."); + } else { + final Punishment punishment = Punishment.ban( + System.currentTimeMillis(), + System.currentTimeMillis() + duration, + sender, + target, + reason + ); + Punishment.issue(punishment, SModerationPaper.container); } - if (uuid == null) { - sender.sendMessage(Component.text("This player is either offline or was never on this server.").color(NamedTextColor.RED)); - return true; - } - final Player toPlayer = Bukkit.getPlayer(uuid); - if (toPlayer != null && toPlayer.hasPermission("smod.preventban")){ - sender.sendMessage(text().content("This player can't be banned.").color(NamedTextColor.RED)); - return true; - } - long duration = 0; - int p = 1; - for (int i = 1 /* start with index 1 to avoid player name */; i < args.length; i++) { - String arg = args[i]; - long parsedDuration = TimeUtil.parseDurationMillisSafely(arg); - if (parsedDuration == -1){ - p = i; - break; - } else { - duration += parsedDuration; - } - if (i == args.length - 1){ p = args.length; } - } - if (duration == 0){ - sender.sendMessage(Component.text("Please provide a valid duration.").color(NamedTextColor.RED)); - return false; - } - if (duration < 0){ - sender.sendMessage(Component.text("Please provide a duration that's longer than 0ms.").color(NamedTextColor.RED)); - return false; - } - StringBuilder reason = new StringBuilder(); - for (int i = p; i < args.length; i++) { - if (!reason.isEmpty()){ - reason.append(" "); - } - reason.append(args[i]); - } - final Punishment punishment = Punishment.ban(System.currentTimeMillis(), System.currentTimeMillis() + duration, senderUUID, uuid, reason.isEmpty() ? Punishment.DEFAULT_REASON : reason.toString()); - Punishment.issue(punishment, SModerationPaper.container); - return true; } } @Override - public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 2){ - String toComplete = args.length > 0 ? args[0] : ""; - ArrayList names = new ArrayList<>(); - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - names.add(onlinePlayer.getName()); - } - ArrayList completions = new ArrayList<>(); - StringUtil.copyPartialMatches(toComplete, names, completions); - return completions; - } else { - for (int i = 1; i < args.length; i++) { - if (TimeUtil.parseDurationMillisSafely(args[i]) == -1){ - try { - Long.parseLong(args[i]); - } catch (NumberFormatException ignored){ - if (i != 1){ - return List.of(); - } - } - } - } - return List.of( - "100ms", - "15s", // some sample completions for duration - "30min", // you can input your own ones as well - "6h", - "1d", - "2w", - "3mo", - "1y" - ); - } + public String getCommandDescription() { + return "Bans a player for a customizable duration."; + } + + @Override + public Collection getAliases() { + return List.of("smodban"); } } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/CommandProvider.java b/src/main/java/de/shiewk/smoderation/paper/command/CommandProvider.java new file mode 100644 index 0000000..8ee01f8 --- /dev/null +++ b/src/main/java/de/shiewk/smoderation/paper/command/CommandProvider.java @@ -0,0 +1,19 @@ +package de.shiewk.smoderation.paper.command; + +import com.mojang.brigadier.tree.LiteralCommandNode; +import io.papermc.paper.command.brigadier.CommandSourceStack; + +import java.util.Collection; +import java.util.List; + +public interface CommandProvider { + + LiteralCommandNode getCommandNode(); + + String getCommandDescription(); + + default Collection getAliases(){ + return List.of(); + } + +} diff --git a/src/main/java/de/shiewk/smoderation/paper/command/EnderchestSeeCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/EnderchestSeeCommand.java index 2a0224e..3276ab2 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/EnderchestSeeCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/EnderchestSeeCommand.java @@ -1,59 +1,53 @@ package de.shiewk.smoderation.paper.command; -import de.shiewk.smoderation.paper.util.PlayerUtil; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.LiteralCommandNode; +import de.shiewk.smoderation.paper.util.CommandUtil; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import net.kyori.adventure.text.Component; -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabExecutor; -import org.bukkit.entity.HumanEntity; import org.bukkit.entity.Player; -import org.bukkit.util.StringUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; import static de.shiewk.smoderation.paper.SModerationPaper.*; +import static io.papermc.paper.command.brigadier.Commands.argument; +import static io.papermc.paper.command.brigadier.Commands.literal; -public class EnderchestSeeCommand implements TabExecutor { - +public final class EnderchestSeeCommand implements CommandProvider { @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 1) { - return false; - } - if (sender instanceof HumanEntity human){ - final Player player = PlayerUtil.findOnlinePlayer(args[0]); - if (player != null) { - human.sendMessage(CHAT_PREFIX.append( - Component.text("Opening ender chest of ").color(PRIMARY_COLOR) - .append(Component.text(player.getName()).color(SECONDARY_COLOR)) - .append(Component.text(".")) - )); - human.openInventory(player.getEnderChest()); - } else { - human.sendMessage(Component.text("This player is not online.").color(FAIL_COLOR)); - } - } else { - sender.sendMessage(Component.text("Only an entity that can open inventories can execute this command!").color(FAIL_COLOR)); - } - return true; + public LiteralCommandNode getCommandNode() { + return literal("enderchestsee") + .requires(CommandUtil.requirePermission("smod.enderchestsee")) + .then(argument("player", ArgumentTypes.player()) + .executes(this::openEnderChest) + ) + .build(); + } + + private int openEnderChest(CommandContext context) throws CommandSyntaxException { + Player sender = CommandUtil.getExecutingPlayer(context.getSource()); + Player target = CommandUtil.getPlayerSingle(context, "player"); + sender.sendMessage(CHAT_PREFIX.append( + Component.text("Opening ender chest of ").color(PRIMARY_COLOR) + .append(target.teamDisplayName().colorIfAbsent(SECONDARY_COLOR)) + .append(Component.text(".")) + )); + sender.openInventory(target.getEnderChest()); + return Command.SINGLE_SUCCESS; } @Override - public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length > 1){ - return List.of(); - } - List available = new ArrayList<>(); - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - available.add(onlinePlayer.getName()); - } - List completions = new ArrayList<>(); - StringUtil.copyPartialMatches(args.length > 0 ? args[0] : "", available, completions); - return completions; + public String getCommandDescription() { + return "Views the ender chest of a player."; + } + + @Override + public Collection getAliases() { + return List.of("ecsee"); } } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/InvseeCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/InvseeCommand.java index 2070419..4a09fb7 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/InvseeCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/InvseeCommand.java @@ -1,83 +1,81 @@ package de.shiewk.smoderation.paper.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.inventory.InvSeeEquipmentInventory; -import de.shiewk.smoderation.paper.util.PlayerUtil; -import net.kyori.adventure.text.Component; -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabExecutor; -import org.bukkit.entity.HumanEntity; +import de.shiewk.smoderation.paper.util.CommandUtil; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import org.bukkit.entity.Player; -import org.bukkit.util.StringUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; import static de.shiewk.smoderation.paper.SModerationPaper.*; +import static io.papermc.paper.command.brigadier.Commands.argument; +import static io.papermc.paper.command.brigadier.Commands.literal; +import static net.kyori.adventure.text.Component.text; -public class InvseeCommand implements TabExecutor { - - private enum InvseeType { - INVENTORY, - EQUIPMENT - } - +public final class InvseeCommand implements CommandProvider { @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 1) { - return false; + public LiteralCommandNode getCommandNode() { + return literal("invsee") + .requires(CommandUtil.requirePermission("smod.invsee")) + .then(argument("player", ArgumentTypes.player()) + .executes(this::invseeInventory) + .then(literal("inventory") + .executes(this::invseeInventory) + ) + .then(literal("armor") + .executes(this::invseeEquipment) + ) + .then(literal("equipment") + .executes(this::invseeEquipment) + ) + ) + .build(); + } + + private int invseeInventory(CommandContext context) throws CommandSyntaxException { + Player sender = CommandUtil.getExecutingPlayer(context.getSource()); + Player target = CommandUtil.getPlayerSingle(context, "player"); + if (sender.equals(target)){ + CommandUtil.error("You can't open your own inventory."); } - final InvseeType type; - if (args.length > 1){ - switch (args[1].toLowerCase()){ - case "armor", "equipment" -> type = InvseeType.EQUIPMENT; - default -> type = InvseeType.INVENTORY; - } - } else { - type = InvseeType.INVENTORY; + sender.sendMessage(CHAT_PREFIX.append( + text("Opening inventory of ").color(PRIMARY_COLOR) + .append(target.teamDisplayName().colorIfAbsent(SECONDARY_COLOR)) + .append(text(".")) + )); + sender.openInventory(target.getInventory()); + return Command.SINGLE_SUCCESS; + } + + private int invseeEquipment(CommandContext context) throws CommandSyntaxException { + Player sender = CommandUtil.getExecutingPlayer(context.getSource()); + Player target = CommandUtil.getPlayerSingle(context, "player"); + if (sender.equals(target)){ + CommandUtil.error("You can't open your own inventory."); } - if (sender instanceof HumanEntity human){ - final Player player = PlayerUtil.findOnlinePlayer(args[0]); - if (player != null) { - if (human.getUniqueId().equals(player.getUniqueId()) && type != InvseeType.EQUIPMENT){ - human.sendMessage(Component.text("You can't open your own inventory.").color(FAIL_COLOR)); - } else { - human.sendMessage(CHAT_PREFIX.append( - Component.text("Opening inventory of ").color(PRIMARY_COLOR) - .append(Component.text(player.getName()).color(SECONDARY_COLOR)) - .append(Component.text(".")) - )); - switch (type){ - case INVENTORY -> human.openInventory(player.getInventory()); - case EQUIPMENT -> new InvSeeEquipmentInventory(human, player).open(); - } - } - } else { - human.sendMessage(Component.text("This player is not online.").color(FAIL_COLOR)); - } - } else { - sender.sendMessage(Component.text("Only an entity that can open inventories can execute this command!").color(FAIL_COLOR)); - } - return true; + sender.sendMessage(CHAT_PREFIX.append( + text("Opening inventory of ").color(PRIMARY_COLOR) + .append(target.teamDisplayName().colorIfAbsent(SECONDARY_COLOR)) + .append(text(".")) + )); + new InvSeeEquipmentInventory(sender, target).open(); + return Command.SINGLE_SUCCESS; } @Override - public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length > 2){ - return List.of(); - } else if (args.length > 1){ - return List.of("armor", "equipment", "inventory"); - } - List available = new ArrayList<>(); - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - available.add(onlinePlayer.getName()); - } - List completions = new ArrayList<>(); - StringUtil.copyPartialMatches(args.length > 0 ? args[0] : "", available, completions); - return completions; + public String getCommandDescription() { + return "Views the inventory of another player."; + } + + @Override + public Collection getAliases() { + return List.of("sinvsee", "smodinvsee", "invs"); } } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/KickCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/KickCommand.java index 67d70f9..e9c6306 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/KickCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/KickCommand.java @@ -1,81 +1,77 @@ package de.shiewk.smoderation.paper.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.SModerationPaper; import de.shiewk.smoderation.paper.punishments.Punishment; -import de.shiewk.smoderation.paper.util.PlayerUtil; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.Bukkit; -import org.bukkit.command.*; +import de.shiewk.smoderation.paper.util.CommandUtil; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; import org.bukkit.entity.Player; -import org.bukkit.util.StringUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.UUID; -import static net.kyori.adventure.text.Component.text; +import static io.papermc.paper.command.brigadier.Commands.argument; +import static io.papermc.paper.command.brigadier.Commands.literal; + +public final class KickCommand implements CommandProvider { -public class KickCommand implements TabExecutor { @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 1){ - return false; - } else { - UUID senderUUID; - if (sender instanceof ConsoleCommandSender){ - senderUUID = PlayerUtil.UUID_CONSOLE; - } else if (sender instanceof Player pl){ - senderUUID = pl.getUniqueId(); - } else if (sender instanceof BlockCommandSender){ - sender.sendMessage(Component.text("Blocks can't execute this command.").color(NamedTextColor.RED)); - return true; - } else { - sender.sendMessage(Component.text("Your command sender type is unknown (%s).".formatted(sender.getClass().getName())).color(NamedTextColor.RED)); - return true; - } - String playerName = args[0]; - Player player = Bukkit.getPlayer(playerName); - if (player == null) { - sender.sendMessage(Component.text("This player is not online.").color(NamedTextColor.RED)); - return true; - } - UUID uuid = player.getUniqueId(); - if (senderUUID.equals(uuid)) { - sender.sendMessage(Component.text("You can't kick yourself.").color(NamedTextColor.RED)); - return true; - } - if (player.hasPermission("smod.preventkick")){ - sender.sendMessage(text().content("This player can't be kicked.").color(NamedTextColor.RED)); - return true; - } - StringBuilder reason = new StringBuilder(); - for (int i = 1; i < args.length; i++) { - if (!reason.isEmpty()){ - reason.append(" "); - } - reason.append(args[i]); - } - final Punishment punishment = Punishment.kick(System.currentTimeMillis(), senderUUID, uuid, reason.isEmpty() ? Punishment.DEFAULT_REASON : reason.toString()); - Punishment.issue(punishment, SModerationPaper.container); - return true; + public LiteralCommandNode getCommandNode() { + return literal("kick") + .requires(CommandUtil.requirePermission("smod.kick")) + .then(argument("player", ArgumentTypes.player()) + .executes(this::kickWithoutReason) + .then(argument("reason", StringArgumentType.greedyString()) + .executes(this::kickWithReason) + ) + ) + .build(); + } + + private int kickWithReason(CommandContext context) throws CommandSyntaxException { + UUID sender = CommandUtil.getSenderUUID(context.getSource()); + Player target = CommandUtil.getPlayerSingle(context, "player"); + String reason = StringArgumentType.getString(context, "reason"); + executeKick(sender, target, reason); + return Command.SINGLE_SUCCESS; + } + + private int kickWithoutReason(CommandContext context) throws CommandSyntaxException { + UUID sender = CommandUtil.getSenderUUID(context.getSource()); + Player target = CommandUtil.getPlayerSingle(context, "player"); + executeKick(sender, target, Punishment.DEFAULT_REASON); + return Command.SINGLE_SUCCESS; + } + + public static void executeKick(UUID sender, Player target, String reason) throws CommandSyntaxException { + UUID targetId = target.getUniqueId(); + if (sender.equals(targetId)) { + CommandUtil.error("You can't kick yourself."); + } else if (target.hasPermission("smod.preventkick")){ + CommandUtil.error("This player can't be kicked."); } + final Punishment punishment = Punishment.kick( + System.currentTimeMillis(), + sender, + targetId, + reason + ); + Punishment.issue(punishment, SModerationPaper.container); } @Override - public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 2){ - String toComplete = args.length > 0 ? args[0] : ""; - ArrayList names = new ArrayList<>(); - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - names.add(onlinePlayer.getName()); - } - ArrayList completions = new ArrayList<>(); - StringUtil.copyPartialMatches(toComplete, names, completions); - return completions; - } - return List.of(); + public String getCommandDescription() { + return "Kicks a player"; + } + + @Override + public Collection getAliases() { + return List.of("smodkick"); } } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/ModLogsCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/ModLogsCommand.java index 720644a..f89595d 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/ModLogsCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/ModLogsCommand.java @@ -1,79 +1,69 @@ package de.shiewk.smoderation.paper.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.SModerationPaper; +import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument; import de.shiewk.smoderation.paper.punishments.Punishment; import de.shiewk.smoderation.paper.punishments.PunishmentType; +import de.shiewk.smoderation.paper.util.CommandUtil; import de.shiewk.smoderation.paper.util.PlayerUtil; import de.shiewk.smoderation.paper.util.TimeUtil; +import io.papermc.paper.command.brigadier.CommandSourceStack; 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.CommandSender; -import org.bukkit.command.TabExecutor; -import org.bukkit.entity.Player; -import org.bukkit.util.StringUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.UUID; import static de.shiewk.smoderation.paper.SModerationPaper.*; +import static io.papermc.paper.command.brigadier.Commands.argument; +import static io.papermc.paper.command.brigadier.Commands.literal; + +public final class ModLogsCommand implements CommandProvider { -public class ModLogsCommand implements TabExecutor { @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 1){ - return false; - } else { - String playername = args[0]; - String name; - UUID uuid; - try { - uuid = UUID.fromString(playername); - } catch (IllegalArgumentException ignored){ - uuid = PlayerUtil.offlinePlayerUUIDByName(playername); - } - if (uuid == null){ - sender.sendMessage(Component.text("This player was not found. Try running /%s with an UUID instead.".formatted(label)).color(NamedTextColor.RED)); - return true; - } - name = PlayerUtil.offlinePlayerName(uuid); - sender.sendMessage(CHAT_PREFIX.append(Component.text("Player ").color(PRIMARY_COLOR) - .append(Component.text(name).color(SECONDARY_COLOR)) - .append(Component.text(" (%s)".formatted(uuid)).color(INACTIVE_COLOR)))); - UUID finalUuid = uuid; - final List punishments = SModerationPaper.container.findAll(p -> p.to.equals(finalUuid) && p.isActive()); - for (Punishment punishment : punishments) { - sender.sendMessage(Component.text("- is currently ").color(PRIMARY_COLOR) - .append(Component.text(punishment.type == PunishmentType.BAN ? "banned" : "muted").color(SECONDARY_COLOR)) - .append(Component.text(" until ").color(PRIMARY_COLOR)) - .append(Component.text(TimeUtil.calendarTimestamp(punishment.until)).color(SECONDARY_COLOR)) - .append(Component.text(" (in %s)".formatted(TimeUtil.formatTimeLong(punishment.until - System.currentTimeMillis()))).color(INACTIVE_COLOR)) - .append(Component.text(". Reason: ").color(PRIMARY_COLOR)) - .append(Component.text(punishment.reason).color(SECONDARY_COLOR))); - } - if (punishments.isEmpty()){ - sender.sendMessage(Component.text("- has no punishments.").color(PRIMARY_COLOR)); - } - return true; + public LiteralCommandNode getCommandNode() { + return literal("modlogs") + .requires(CommandUtil.requirePermission("smod.logs")) + .then(argument("player", new PlayerUUIDArgument()) + .executes(this::showModLogs) + ) + .build(); + } + + private int showModLogs(CommandContext context) { + CommandSender sender = context.getSource().getSender(); + UUID uuid = context.getArgument("player", UUID.class); + String name = PlayerUtil.offlinePlayerName(uuid); + sender.sendMessage(CHAT_PREFIX.append(Component.text("Player ").color(PRIMARY_COLOR) + .append(Component.text(name).color(SECONDARY_COLOR)) + .append(Component.text(" (%s)".formatted(uuid)).color(INACTIVE_COLOR)))); + final List punishments = SModerationPaper.container.findAll(p -> p.to.equals(uuid) && p.isActive()); + for (Punishment punishment : punishments) { + sender.sendMessage(Component.text("- is currently ").color(PRIMARY_COLOR) + .append(Component.text(punishment.type == PunishmentType.BAN ? "banned" : "muted").color(SECONDARY_COLOR)) + .append(Component.text(" until ").color(PRIMARY_COLOR)) + .append(Component.text(TimeUtil.calendarTimestamp(punishment.until)).color(SECONDARY_COLOR)) + .append(Component.text(" (in %s)".formatted(TimeUtil.formatTimeLong(punishment.until - System.currentTimeMillis()))).color(INACTIVE_COLOR)) + .append(Component.text(". Reason: ").color(PRIMARY_COLOR)) + .append(Component.text(punishment.reason).color(SECONDARY_COLOR))); } + if (punishments.isEmpty()){ + sender.sendMessage(Component.text("- has no punishments.").color(PRIMARY_COLOR)); + } + return Command.SINGLE_SUCCESS; } @Override - public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length > 1){ - return List.of(); - } - String search = args.length > 0 ? args[0] : ""; - List playernames = new ArrayList<>(); - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - playernames.add(onlinePlayer.getName()); - } - List completions = new ArrayList<>(); - StringUtil.copyPartialMatches(search, playernames, completions); - return completions; + public String getCommandDescription() { + return "Views all current punishments of a player."; + } + + @Override + public Collection getAliases() { + return List.of("logs", "seen", "smodlogs"); } } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/MuteCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/MuteCommand.java index 31ac311..55e2fec 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/MuteCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/MuteCommand.java @@ -1,123 +1,87 @@ package de.shiewk.smoderation.paper.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.SModerationPaper; +import de.shiewk.smoderation.paper.command.argument.DurationArgument; +import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument; import de.shiewk.smoderation.paper.punishments.Punishment; -import de.shiewk.smoderation.paper.util.PlayerUtil; -import de.shiewk.smoderation.paper.util.TimeUtil; -import net.kyori.adventure.text.format.NamedTextColor; +import de.shiewk.smoderation.paper.util.CommandUtil; +import io.papermc.paper.command.brigadier.CommandSourceStack; import org.bukkit.Bukkit; -import org.bukkit.command.*; import org.bukkit.entity.Player; -import org.bukkit.util.StringUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.UUID; -import static net.kyori.adventure.text.Component.text; +import static io.papermc.paper.command.brigadier.Commands.argument; +import static io.papermc.paper.command.brigadier.Commands.literal; + +public final class MuteCommand implements CommandProvider { -public class MuteCommand implements TabExecutor { @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 2){ - return false; + public LiteralCommandNode getCommandNode() { + return literal("mute") + .requires(CommandUtil.requirePermission("smod.mute")) + .then(argument("player", new PlayerUUIDArgument()) + .then(argument("duration", new DurationArgument()) + .executes(this::muteWithoutReason) + .then(argument("reason", StringArgumentType.greedyString()) + .executes(this::muteWithReason) + ) + ) + ) + .build(); + } + + private int muteWithoutReason(CommandContext context) throws CommandSyntaxException { + UUID sender = CommandUtil.getSenderUUID(context.getSource()); + UUID target = context.getArgument("player", UUID.class); + long duration = context.getArgument("duration", Long.class); + executeMute(sender, target, duration, Punishment.DEFAULT_REASON); + return Command.SINGLE_SUCCESS; + } + + private int muteWithReason(CommandContext context) throws CommandSyntaxException { + UUID sender = CommandUtil.getSenderUUID(context.getSource()); + UUID target = context.getArgument("player", UUID.class); + long duration = context.getArgument("duration", Long.class); + String reason = StringArgumentType.getString(context, "reason"); + executeMute(sender, target, duration, reason); + return Command.SINGLE_SUCCESS; + } + + public static void executeMute(UUID sender, UUID target, long duration, String reason) throws CommandSyntaxException { + if (sender.equals(target)) { + CommandUtil.error("You can't mute yourself."); } else { - UUID senderUUID; - if (sender instanceof ConsoleCommandSender){ - senderUUID = PlayerUtil.UUID_CONSOLE; - } else if (sender instanceof Player pl){ - senderUUID = pl.getUniqueId(); - } else if (sender instanceof BlockCommandSender){ - sender.sendMessage(text("Blocks can't execute this command.").color(NamedTextColor.RED)); - return true; + Player targetPlayer = Bukkit.getPlayer(target); + if (targetPlayer != null && targetPlayer.hasPermission("smod.preventmute")){ + CommandUtil.error("This player can't be muted."); } else { - sender.sendMessage(text("Your command sender type is unknown (%s).".formatted(sender.getClass().getName())).color(NamedTextColor.RED)); - return true; + final Punishment punishment = Punishment.mute( + System.currentTimeMillis(), + System.currentTimeMillis() + duration, + sender, + target, + reason + ); + Punishment.issue(punishment, SModerationPaper.container); } - String playerName = args[0]; - UUID uuid = PlayerUtil.offlinePlayerUUIDByName(playerName); - if (senderUUID.equals(uuid)) { - sender.sendMessage(text("You can't mute yourself.").color(NamedTextColor.RED)); - return true; - } - if (uuid == null) { - sender.sendMessage(text("This player is either offline or was never on this server.").color(NamedTextColor.RED)); - return true; - } - final Player toPlayer = Bukkit.getPlayer(uuid); - if (toPlayer != null && toPlayer.hasPermission("smod.preventmute")){ - sender.sendMessage(text().content("This player can't be muted.").color(NamedTextColor.RED)); - return true; - } - long duration = 0; - int p = 1; - for (int i = 1 /* start with index 1 to avoid player name */; i < args.length; i++) { - String arg = args[i]; - long parsedDuration = TimeUtil.parseDurationMillisSafely(arg); - if (parsedDuration == -1){ - p = i; - break; - } else { - duration += parsedDuration; - } - if (i == args.length - 1){ p = args.length; } - } - if (duration == 0){ - sender.sendMessage(text("Please provide a valid duration.").color(NamedTextColor.RED)); - return false; - } - if (duration < 0){ - sender.sendMessage(text("Please provide a duration that's longer than 0ms.").color(NamedTextColor.RED)); - return false; - } - StringBuilder reason = new StringBuilder(); - for (int i = p; i < args.length; i++) { - if (!reason.isEmpty()){ - reason.append(" "); - } - reason.append(args[i]); - } - final Punishment punishment = Punishment.mute(System.currentTimeMillis(), System.currentTimeMillis() + duration, senderUUID, uuid, reason.isEmpty() ? Punishment.DEFAULT_REASON : reason.toString()); - Punishment.issue(punishment, SModerationPaper.container); - return true; } } @Override - public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 2){ - String toComplete = args.length > 0 ? args[0] : ""; - ArrayList names = new ArrayList<>(); - for (Player onlinePlayer : Bukkit.getOnlinePlayers()) { - names.add(onlinePlayer.getName()); - } - ArrayList completions = new ArrayList<>(); - StringUtil.copyPartialMatches(toComplete, names, completions); - return completions; - } else { - for (int i = 1; i < args.length; i++) { - if (TimeUtil.parseDurationMillisSafely(args[i]) == -1){ - try { - Long.parseLong(args[i]); - } catch (NumberFormatException ignored){ - if (i != 1){ - return List.of(); - } - } - } - } - return List.of( - "100ms", - "15s", // some sample completions for duration - "30min", // you can input your own ones as well - "6h", - "1d", - "2w", - "3mo", - "1y" - ); - } + public String getCommandDescription() { + return "Mutes a player for a customizable duration."; + } + + @Override + public Collection getAliases() { + return List.of("smodmute"); } } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/SModCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/SModCommand.java index cc2aede..de12488 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/SModCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/SModCommand.java @@ -1,27 +1,43 @@ package de.shiewk.smoderation.paper.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.SModerationPaper; import de.shiewk.smoderation.paper.inventory.SModMenu; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabExecutor; +import de.shiewk.smoderation.paper.util.CommandUtil; +import io.papermc.paper.command.brigadier.CommandSourceStack; import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.List; -public class SModCommand implements TabExecutor { +import static io.papermc.paper.command.brigadier.Commands.literal; + +public final class SModCommand implements CommandProvider { + @Override - public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) { - if (commandSender instanceof Player player){ - new SModMenu(player, SModerationPaper.container).open(); - } - return true; + public LiteralCommandNode getCommandNode() { + return literal("smod") + .requires(CommandUtil.requirePermission("smod.menu")) + .executes(this::openMenu) + .build(); + } + + private int openMenu(CommandContext context) throws CommandSyntaxException { + Player player = CommandUtil.getExecutingPlayer(context.getSource()); + new SModMenu(player, SModerationPaper.container).open(); + return Command.SINGLE_SUCCESS; } @Override - public @Nullable List onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] strings) { - return List.of(); + public String getCommandDescription() { + return "Shows the SModeration menu."; + } + + @Override + public Collection getAliases() { + return List.of("smodmenu", "smoderation"); } } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/SocialSpyCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/SocialSpyCommand.java index ed241a6..c5d53a3 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/SocialSpyCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/SocialSpyCommand.java @@ -1,32 +1,49 @@ package de.shiewk.smoderation.paper.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.listener.SocialSpyListener; +import de.shiewk.smoderation.paper.util.CommandUtil; +import io.papermc.paper.command.brigadier.CommandSourceStack; import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; -import org.bukkit.command.TabExecutor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.List; import static de.shiewk.smoderation.paper.SModerationPaper.CHAT_PREFIX; +import static io.papermc.paper.command.brigadier.Commands.literal; import static net.kyori.adventure.text.Component.text; -public class SocialSpyCommand implements TabExecutor { +public final class SocialSpyCommand implements CommandProvider { + @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + public LiteralCommandNode getCommandNode() { + return literal("socialspy") + .requires(CommandUtil.requirePermission("smod.socialspy")) + .executes(this::toggleSocialSpy) + .build(); + } + + private int toggleSocialSpy(CommandContext context) { + CommandSender sender = context.getSource().getSender(); final boolean enabled = SocialSpyListener.toggle(sender); sender.sendMessage(CHAT_PREFIX.append(text("SocialSpy ").append( - enabled ? - text("enabled").color(NamedTextColor.GREEN) : - text("disabled").color(NamedTextColor.RED) + enabled ? + text("enabled").color(NamedTextColor.GREEN) : + text("disabled").color(NamedTextColor.RED) ).append(text(".")))); - return true; + return Command.SINGLE_SUCCESS; } @Override - public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - return List.of(); + public String getCommandDescription() { + return "Enables socialspy mode (you can see private messages of other players)"; + } + + @Override + public Collection getAliases() { + return List.of("smodsocialspy"); } } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/UnbanCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/UnbanCommand.java index 9ba1a8f..46ffdbf 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/UnbanCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/UnbanCommand.java @@ -1,64 +1,56 @@ package de.shiewk.smoderation.paper.command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.SModerationPaper; +import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument; import de.shiewk.smoderation.paper.punishments.Punishment; import de.shiewk.smoderation.paper.punishments.PunishmentType; -import de.shiewk.smoderation.paper.util.PlayerUtil; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.command.*; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import de.shiewk.smoderation.paper.util.CommandUtil; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import java.util.Collection; import java.util.List; import java.util.UUID; -public class UnbanCommand implements TabExecutor { +import static io.papermc.paper.command.brigadier.Commands.argument; +import static io.papermc.paper.command.brigadier.Commands.literal; + +public final class UnbanCommand implements CommandProvider { + @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 1){ - return false; + public LiteralCommandNode getCommandNode() { + return literal("unban") + .requires(CommandUtil.requirePermission("smod.unban")) + .then(argument("player", new PlayerUUIDArgument()) + .executes(this::unbanPlayer) + ) + .build(); + } + + private int unbanPlayer(CommandContext context) throws CommandSyntaxException { + UUID senderUUID = CommandUtil.getSenderUUID(context.getSource()); + UUID target = context.getArgument("player", UUID.class); + final Punishment punishment = SModerationPaper.container.find( + p -> p.to.equals(target) && p.isActive() && p.type == PunishmentType.BAN + ); + if (punishment != null) { + punishment.undo(senderUUID); + punishment.broadcastUndo(SModerationPaper.container); } else { - - UUID senderUUID; - if (sender instanceof ConsoleCommandSender){ - senderUUID = PlayerUtil.UUID_CONSOLE; - } else if (sender instanceof Player pl){ - senderUUID = pl.getUniqueId(); - } else if (sender instanceof BlockCommandSender){ - sender.sendMessage(Component.text("Blocks can't execute this command.").color(NamedTextColor.RED)); - return true; - } else { - sender.sendMessage(Component.text("Your command sender type is unknown (%s).".formatted(sender.getClass().getName())).color(NamedTextColor.RED)); - return true; - } - - String nameArg = args[0]; - UUID uuid; - try { - uuid = UUID.fromString(nameArg); - } catch (IllegalArgumentException ignored){ - uuid = PlayerUtil.offlinePlayerUUIDByName(nameArg); - } - if (uuid == null){ - sender.sendMessage(Component.text("This player was not found. Try running /%s with an UUID instead.".formatted(label)).color(NamedTextColor.RED)); - return true; - } - UUID finalUuid = uuid; - final Punishment punishment = SModerationPaper.container.find(p -> p.to.equals(finalUuid) && p.isActive() && p.type == PunishmentType.BAN); - if (punishment != null) { - punishment.undo(senderUUID); - punishment.broadcastUndo(SModerationPaper.container); - } else { - sender.sendMessage(Component.text("This player is not banned.").color(NamedTextColor.RED)); - } - return true; + CommandUtil.error("This player is not banned."); } + return com.mojang.brigadier.Command.SINGLE_SUCCESS; } @Override - public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - return List.of(); + public String getCommandDescription() { + return "Unbans a banned player."; + } + + @Override + public Collection getAliases() { + return List.of("smodunban"); } } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/UnmuteCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/UnmuteCommand.java index 4541a99..bd9605d 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/UnmuteCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/UnmuteCommand.java @@ -1,64 +1,57 @@ package de.shiewk.smoderation.paper.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.SModerationPaper; +import de.shiewk.smoderation.paper.command.argument.PlayerUUIDArgument; import de.shiewk.smoderation.paper.punishments.Punishment; import de.shiewk.smoderation.paper.punishments.PunishmentType; -import de.shiewk.smoderation.paper.util.PlayerUtil; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.bukkit.command.*; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import de.shiewk.smoderation.paper.util.CommandUtil; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import java.util.Collection; import java.util.List; import java.util.UUID; -public class UnmuteCommand implements TabExecutor { +import static io.papermc.paper.command.brigadier.Commands.argument; +import static io.papermc.paper.command.brigadier.Commands.literal; + +public final class UnmuteCommand implements CommandProvider { + @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 1){ - return false; + public LiteralCommandNode getCommandNode() { + return literal("unmute") + .requires(CommandUtil.requirePermission("smod.unmute")) + .then(argument("player", new PlayerUUIDArgument()) + .executes(this::unmutePlayer) + ) + .build(); + } + + private int unmutePlayer(CommandContext context) throws CommandSyntaxException { + UUID senderUUID = CommandUtil.getSenderUUID(context.getSource()); + UUID target = context.getArgument("player", UUID.class); + final Punishment punishment = SModerationPaper.container.find( + p -> p.to.equals(target) && p.isActive() && p.type == PunishmentType.MUTE + ); + if (punishment != null) { + punishment.undo(senderUUID); + punishment.broadcastUndo(SModerationPaper.container); } else { - - UUID senderUUID; - if (sender instanceof ConsoleCommandSender){ - senderUUID = PlayerUtil.UUID_CONSOLE; - } else if (sender instanceof Player pl){ - senderUUID = pl.getUniqueId(); - } else if (sender instanceof BlockCommandSender){ - sender.sendMessage(Component.text("Blocks can't execute this command.").color(NamedTextColor.RED)); - return true; - } else { - sender.sendMessage(Component.text("Your command sender type is unknown (%s).".formatted(sender.getClass().getName())).color(NamedTextColor.RED)); - return true; - } - - String nameArg = args[0]; - UUID uuid; - try { - uuid = UUID.fromString(nameArg); - } catch (IllegalArgumentException ignored){ - uuid = PlayerUtil.offlinePlayerUUIDByName(nameArg); - } - if (uuid == null){ - sender.sendMessage(Component.text("This player was not found. Try running /%s with an UUID instead.".formatted(label)).color(NamedTextColor.RED)); - return true; - } - UUID finalUuid = uuid; - final Punishment punishment = SModerationPaper.container.find(p -> p.to.equals(finalUuid) && p.isActive() && p.type == PunishmentType.MUTE); - if (punishment != null) { - punishment.undo(senderUUID); - punishment.broadcastUndo(SModerationPaper.container); - } else { - sender.sendMessage(Component.text("This player is not muted.").color(NamedTextColor.RED)); - } - return true; + CommandUtil.error("This player is not muted."); } + return Command.SINGLE_SUCCESS; } @Override - public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - return List.of(); + public String getCommandDescription() { + return "Unmutes a muted player."; + } + + @Override + public Collection getAliases() { + return List.of("smodunmute"); } } diff --git a/src/main/java/de/shiewk/smoderation/paper/command/VanishCommand.java b/src/main/java/de/shiewk/smoderation/paper/command/VanishCommand.java index 291430c..44b1d0f 100644 --- a/src/main/java/de/shiewk/smoderation/paper/command/VanishCommand.java +++ b/src/main/java/de/shiewk/smoderation/paper/command/VanishCommand.java @@ -1,66 +1,83 @@ package de.shiewk.smoderation.paper.command; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.LiteralCommandNode; import de.shiewk.smoderation.paper.SModerationPaper; import de.shiewk.smoderation.paper.event.VanishToggleEvent; -import de.shiewk.smoderation.paper.util.PlayerUtil; +import de.shiewk.smoderation.paper.util.CommandUtil; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import it.unimi.dsi.fastutil.objects.ObjectListIterator; 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.CommandSender; -import org.bukkit.command.TabExecutor; import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.List; import static de.shiewk.smoderation.paper.SModerationPaper.*; +import static io.papermc.paper.command.brigadier.Commands.argument; +import static io.papermc.paper.command.brigadier.Commands.literal; import static net.kyori.adventure.text.Component.text; -public class VanishCommand implements TabExecutor { +public final class VanishCommand implements CommandProvider { @Override - public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length == 0 || args[0].equalsIgnoreCase("toggle")){ - Player player = null; - if (args.length > 1){ - player = PlayerUtil.findOnlinePlayer(args[1]); - } else if (sender instanceof Player){ - player = (Player) sender; - } - if (player != null){ - toggleVanish(player); - return true; - } else { - return false; - } - } else if (args[0].equalsIgnoreCase("list")) { - if (sender.hasPermission("smod.vanish.see")){ - listVanishedPlayersTo(sender); - } else { - sender.sendMessage(text().color(NamedTextColor.RED).content("You do not have permission to list all vanished players.")); - } - return true; + public LiteralCommandNode getCommandNode() { + return literal("vanish") + .requires(CommandUtil.requirePermission("smod.vanish")) + .executes(this::toggleVanishSelf) + .then(literal("toggle") + .executes(this::toggleVanishSelf) + .then(argument("targets", ArgumentTypes.players()) + .executes(this::toggleVanishForTargets) + ) + ) + .then(literal("list") + .requires(CommandUtil.requirePermission("smod.vanish.see")) + .executes(this::listVanishedPlayers) + ) + .build(); + } + + private int toggleVanishForTargets(CommandContext context) throws CommandSyntaxException { + List targets = context.getArgument("targets", PlayerSelectorArgumentResolver.class).resolve(context.getSource()); + if (targets.isEmpty()){ + CommandUtil.error("No player was found."); } else { - return false; + for (Player target : targets) { + toggleVanish(target); + } } + return Command.SINGLE_SUCCESS; + } + + private int toggleVanishSelf(CommandContext context) throws CommandSyntaxException { + toggleVanish(CommandUtil.getExecutingPlayer(context.getSource())); + return Command.SINGLE_SUCCESS; + } + + private int listVanishedPlayers(CommandContext context) { + listVanishedPlayersTo(context.getSource().getSender()); + return Command.SINGLE_SUCCESS; } @Override - public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { - if (args.length < 2){ - return List.of("list", "toggle"); - } - if (args.length < 3 && args[0].equalsIgnoreCase("toggle")){ - return PlayerUtil.listPlayerNames(args[1]); - } - return List.of(); + public String getCommandDescription() { + return "Toggles vanish mode which prevents other players from seeing you're online"; } - private static final ObjectArrayList vanishedPlayers = new ObjectArrayList<>(); + @Override + public Collection getAliases() { + return List.of("smvanish", "smodvanish", "v", "smv"); + } + + private static final ObjectArrayList vanishedPlayers = new ObjectArrayList<>(1); public static void toggleVanish(Player player){ final boolean newStatus = !isVanished(player); @@ -73,12 +90,10 @@ public class VanishCommand implements TabExecutor { vanishedPlayers.add(player); for (CommandSender sender : SModerationPaper.container.collectBroadcastTargets()) { sender.sendMessage(CHAT_PREFIX.append( - player.displayName() - .colorIfAbsent(SECONDARY_COLOR) - ).append( - text() - .content(" vanished.") - .color(PRIMARY_COLOR) + player.displayName().colorIfAbsent(SECONDARY_COLOR) + ).append(text() + .content(" vanished.") + .color(PRIMARY_COLOR) )); } player.sendMessage(CHAT_PREFIX.append(text("You are now vanished.").color(PRIMARY_COLOR))); @@ -92,12 +107,10 @@ public class VanishCommand implements TabExecutor { vanishedPlayers.remove(player); for (CommandSender sender : container.collectBroadcastTargets()) { sender.sendMessage(CHAT_PREFIX.append( - player.displayName() - .colorIfAbsent(SECONDARY_COLOR) - ).append( - text() - .content(" re-appeared.") - .color(PRIMARY_COLOR) + player.displayName().colorIfAbsent(SECONDARY_COLOR) + ).append(text() + .content(" re-appeared.") + .color(PRIMARY_COLOR) )); } player.sendMessage(CHAT_PREFIX.append(text("You are no longer vanished.").color(PRIMARY_COLOR))); diff --git a/src/main/java/de/shiewk/smoderation/paper/command/argument/DurationArgument.java b/src/main/java/de/shiewk/smoderation/paper/command/argument/DurationArgument.java new file mode 100644 index 0000000..71ffea7 --- /dev/null +++ b/src/main/java/de/shiewk/smoderation/paper/command/argument/DurationArgument.java @@ -0,0 +1,74 @@ +package de.shiewk.smoderation.paper.command.argument; + +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.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import de.shiewk.smoderation.paper.util.CommandUtil; +import io.papermc.paper.command.brigadier.argument.CustomArgumentType; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicLong; +import java.util.regex.MatchResult; +import java.util.regex.Pattern; + +public final class DurationArgument implements CustomArgumentType.Converted { + + public static final Pattern DURATION_PATTERN = Pattern.compile("([0-9]{1,9})(ms|s|min|h|d|w|mo|y)"); + public static final Pattern VALIDATION_PATTERN = Pattern.compile("(([0-9]{1,9})(ms|s|min|h|d|w|mo|y))+"); + + @Override + public @NotNull Long convert(@NotNull String nativeType) throws CommandSyntaxException { + if (!VALIDATION_PATTERN.matcher(nativeType).matches()){ + CommandUtil.error("Please provide a valid duration, e.g. '1d6h30min'"); + } + AtomicLong totalDuration = new AtomicLong(); + for (MatchResult result : DURATION_PATTERN.matcher(nativeType).results().toList()) { + long amount = Long.parseLong(result.group(1)); + long timeSpan = switch (result.group(2)) { + case "ms" -> 1; + case "s" -> 1000; + case "min" -> 60_000; + case "h" -> 3600_000; + case "d" -> 86400_000; + case "w" -> 604800_000; + case "mo" -> 2_592_000_000L; + case "y" -> 31_536_000_000L; + default -> { + CommandUtil.error("Invalid time span '%s'".formatted(result.group(2))); + throw new UnknownError(); // can't happen + } + }; + totalDuration.addAndGet(amount*timeSpan); + } + return totalDuration.get(); + } + + @Override + public @NotNull ArgumentType getNativeType() { + return StringArgumentType.word(); + } + + @Override + public @NotNull CompletableFuture listSuggestions(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) { + if (builder.getRemaining().isBlank()){ + List.of( + "100ms", + "15s", + "2min", + "3h", + "7d", + "1w", + "3mo", + "1y", + "1mo15d", + "2h30min" + ).forEach(builder::suggest); + } + return builder.buildFuture(); + } +} diff --git a/src/main/java/de/shiewk/smoderation/paper/command/argument/PlayerUUIDArgument.java b/src/main/java/de/shiewk/smoderation/paper/command/argument/PlayerUUIDArgument.java new file mode 100644 index 0000000..47d55ea --- /dev/null +++ b/src/main/java/de/shiewk/smoderation/paper/command/argument/PlayerUUIDArgument.java @@ -0,0 +1,51 @@ +package de.shiewk.smoderation.paper.command.argument; + +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.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import de.shiewk.smoderation.paper.util.CommandUtil; +import io.papermc.paper.command.brigadier.argument.CustomArgumentType; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +public final class PlayerUUIDArgument implements CustomArgumentType.Converted { + + @Override + public @NotNull UUID convert(@NotNull String nativeType) throws CommandSyntaxException { + try { + return UUID.fromString(nativeType); + } catch (IllegalArgumentException e) { + OfflinePlayer player = Bukkit.getOfflinePlayerIfCached(nativeType); + if (player != null){ + return player.getUniqueId(); + } else { + CommandUtil.error("This player is not cached. Try providing an UUID instead."); + throw new UnknownError(); // can't happen + } + } + } + + @Override + public @NotNull ArgumentType getNativeType() { + return StringArgumentType.word(); + } + + @Override + public @NotNull CompletableFuture listSuggestions(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) { + Bukkit.getOnlinePlayers() + .stream() + .map(Player::getName) + .filter(name -> name.toLowerCase().startsWith(builder.getRemainingLowerCase())) + .forEach(builder::suggest); + return builder.buildFuture(); + } + +} diff --git a/src/main/java/de/shiewk/smoderation/paper/util/CommandUtil.java b/src/main/java/de/shiewk/smoderation/paper/util/CommandUtil.java new file mode 100644 index 0000000..e199949 --- /dev/null +++ b/src/main/java/de/shiewk/smoderation/paper/util/CommandUtil.java @@ -0,0 +1,66 @@ +package de.shiewk.smoderation.paper.util; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.MessageComponentSerializer; +import io.papermc.paper.command.brigadier.argument.resolvers.selector.PlayerSelectorArgumentResolver; +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.UUID; +import java.util.function.Predicate; + +import static de.shiewk.smoderation.paper.util.PlayerUtil.UUID_CONSOLE; + +public abstract class CommandUtil { + private CommandUtil(){} + + public static Predicate requirePermission(String permission) { + return stack -> stack.getSender().hasPermission(permission); + } + + public static Player getExecutingPlayer(CommandSourceStack stack) throws CommandSyntaxException { + CommandSender sender = stack.getSender(); + if (sender instanceof Player player) { + return player; + } else { + error("Only players can execute this command."); + throw new UnknownError(); // can't happen + } + } + + public static UUID getSenderUUID(CommandSourceStack stack) throws CommandSyntaxException { + CommandSender sender = stack.getSender(); + if (sender instanceof Player player) { + return player.getUniqueId(); + } else if (sender instanceof ConsoleCommandSender){ + return UUID_CONSOLE; + } else { + error("Only players and the console can execute this command."); + throw new UnknownError(); // can't happen + } + } + + public static Player getPlayerSingle(CommandContext context, String name) throws CommandSyntaxException { + @NotNull List players = context.getArgument(name, PlayerSelectorArgumentResolver.class).resolve(context.getSource()); + if (players.isEmpty()){ + CommandUtil.error("Please provide a valid player."); + } + return players.getFirst(); + } + + public static void error(String message) throws CommandSyntaxException { + throw new CommandSyntaxException( + new SimpleCommandExceptionType(null), + MessageComponentSerializer.message().serialize( + Component.text(message) + ) + ); + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 2e46037..268ac8c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,94 +1,11 @@ name: SModeration version: '${version}' main: de.shiewk.smoderation.paper.SModerationPaper -api-version: '1.20' +api-version: '1.21' load: STARTUP authors: - Shiewk description: "SModeration is an easy-to-use minecraft plugin for moderating your server." -commands: - modlogs: - usage: "§cUsage: /modlogs " - aliases: - - logs - - seen - - smodlogs - permission: smod.logs - mute: - usage: "§cUsage: /mute " - aliases: - - smodmute - permission: smod.mute - description: Mutes a player, either temporarily or permanently. - ban: - usage: "§cUsage: /ban " - aliases: - - smodban - - tempban - permission: smod.ban - description: Bans a player, either temporarily or permanently. - kick: - usage: "§cUsage: /kick " - aliases: - - smodkick - permission: smod.kick - description: Kicks a player - smod: - usage: "§cUsage: /smod" - aliases: - - smodmenu - - smoderation - permission: smod.menu - description: Shows the SModeration menu. - unmute: - usage: "§cUsage: /unmute " - aliases: - - sunmute - permission: smod.unmute - description: Unmutes a muted player. - unban: - usage: "§cUsage: /unban " - aliases: - - sunban - - pardon - - spardon - permission: smod.unban - description: Unbans a banned player. - invsee: - usage: "§cUsage: /invsee " - aliases: - - sinvsee - - smodinvsee - - invs - permission: smod.invsee - description: Views the inventory of another player. - enderchestsee: - usage: "§cUsage: /enderchestsee " - aliases: - - secsee - - senderchestsee - - ecsee - - ecs - permission: smod.enderchestsee - description: Views the ender chest of another player. - vanish: - usage: "§cUsage: /vanish list or /vanish toggle " - aliases: - - smvanish - - smodvanish - - v - - smv - permission: smod.vanish - description: Toggles vanish mode which prevents other players from seeing you're online - socialspy: - usage: "§cUsage: /socialspy" - description: Enables socialspy mode (you can see private messages of other players) - permission: smod.socialspy - aliases: - - smodsocialspy - - smsocialspy - - smss - - ss permissions: smod.mute: default: op @@ -140,10 +57,10 @@ permissions: description: Prevents the player from being muted (if online) smod.preventkick: default: op - description: Prevents the player from being muted (if online) + description: Prevents the player from being kicked (if online) smod.preventban: default: op - description: Prevents the player from being muted (if online) + description: Prevents the player from being banned (if online) smod.vanish: default: op description: Allows the player to use /vanish