diff --git a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/InvocationSource.java b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/InvocationSource.java index 71e765893..9e2f16134 100644 --- a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/InvocationSource.java +++ b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/InvocationSource.java @@ -23,12 +23,15 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; +import java.util.function.Predicate; + @Getter @RequiredArgsConstructor public abstract class InvocationSource { private final String name; private final Audience audience; private final boolean player; + private final Predicate permissionFunction; /** * Sends an empty chat message to the command executor diff --git a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/SonarCommand.java b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/SonarCommand.java index 64a8f4ef5..96d1d069c 100644 --- a/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/SonarCommand.java +++ b/sonar-api/src/main/java/xyz/jonesdev/sonar/api/command/SonarCommand.java @@ -40,6 +40,65 @@ public interface SonarCommand { List CACHED_HELP_MESSAGE = new ArrayList<>(); + default void handle(final @NotNull InvocationSource source, final String[] args) { + if (source.isPlayer()) { + // Check if the player actually has the permission to run the command + if (!source.getPermissionFunction().test("sonar.command")) { + source.sendMessage(Sonar.get().getConfig().getNoPermission()); + return; + } + // Checking if it contains will only break more since it can throw + // a NullPointerException if the cache is being accessed from parallel threads + DELAY.cleanUp(); // Clean up the cache + final long mapTimestamp = DELAY.asMap().getOrDefault(source, -1L); + + // There were some exploits with spamming commands in the past. + // Spamming should be prevented, especially if some heavy operations are done, + // which is not the case here but let's still stay safe! + if (mapTimestamp > 0L) { + source.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDown()); + + // Format delay + final long timestamp = System.currentTimeMillis(); + final double left = 0.5D - (timestamp - mapTimestamp) / 1000D; + + source.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDownLeft() + .replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left))); + return; + } + + DELAY.put(source); + } + + if (args.length > 0) { + // Search subcommand if command arguments are present + final Optional subcommand = Sonar.get().getSubcommandRegistry().getSubcommands().stream() + .filter(sub -> sub.getInfo().name().equalsIgnoreCase(args[0]) + || Arrays.stream(sub.getInfo().aliases()) + .anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) + .findFirst(); + + // Check permissions for subcommands + if (subcommand.isPresent()) { + if (!subcommand.get().getInfo().onlyConsole() + && !source.getPermissionFunction().test(subcommand.get().getPermission())) { + source.sendMessage(Sonar.get().getConfig().getCommands().getSubCommandNoPerm() + .replace("%permission%", subcommand.get().getPermission())); + return; + } + subcommand.get().invoke(source, args); + return; + } + } + + // Re-use the old, cached help message since we don't want to scan + // for each subcommand and it's arguments/attributes every time + // someone runs /sonar since the subcommand don't change + for (final Component component : CACHED_HELP_MESSAGE) { + source.sendMessage(component); + } + } + static void prepareCachedMessages() { // Cache help message CACHED_HELP_MESSAGE.clear(); diff --git a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitInvocationSource.java b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitInvocationSource.java index 73c6faa90..100caba85 100644 --- a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitInvocationSource.java +++ b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitInvocationSource.java @@ -25,6 +25,9 @@ public final class BukkitInvocationSource extends InvocationSource { public BukkitInvocationSource(final @NotNull CommandSender sender) { - super(sender.getName(), SonarBukkit.INSTANCE.getBukkitAudiences().sender(sender), sender instanceof Player); + super(sender.getName(), + SonarBukkit.INSTANCE.getBukkitAudiences().sender(sender), + sender instanceof Player, + sender::hasPermission); } } diff --git a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitSonarCommand.java b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitSonarCommand.java index 86c79d520..5472d1a09 100644 --- a/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitSonarCommand.java +++ b/sonar-bukkit/src/main/java/xyz/jonesdev/sonar/bukkit/command/BukkitSonarCommand.java @@ -17,20 +17,15 @@ package xyz.jonesdev.sonar.bukkit.command; -import net.kyori.adventure.text.Component; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.TabExecutor; import org.jetbrains.annotations.NotNull; -import xyz.jonesdev.sonar.api.Sonar; import xyz.jonesdev.sonar.api.command.InvocationSource; import xyz.jonesdev.sonar.api.command.SonarCommand; -import xyz.jonesdev.sonar.api.command.subcommand.Subcommand; -import java.util.Arrays; import java.util.List; -import java.util.Optional; import static java.util.Collections.emptyList; @@ -43,70 +38,9 @@ public boolean onCommand(final CommandSender sender, final String[] args) { // Create our own invocation source wrapper to handle messages properly final InvocationSource invocationSource = new BukkitInvocationSource(sender); - - if (invocationSource.isPlayer()) { - // Check if the player actually has the permission to run the command - if (!sender.hasPermission("sonar.command")) { - invocationSource.sendMessage(Sonar.get().getConfig().getNoPermission()); - return false; - } - // Checking if it contains will only break more since it can throw - // a NullPointerException if the cache is being accessed from parallel threads - DELAY.cleanUp(); // Clean up the cache - final long mapTimestamp = DELAY.asMap().getOrDefault(sender, -1L); - - // There were some exploits with spamming commands in the past. - // Spamming should be prevented, especially if some heavy operations are done, - // which is not the case here but let's still stay safe! - if (mapTimestamp > 0L) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDown()); - - // Format delay - final long timestamp = System.currentTimeMillis(); - final double left = 0.5D - (timestamp - mapTimestamp) / 1000D; - - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDownLeft() - .replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left))); - return false; - } - - DELAY.put(sender); - } - - Optional subcommand = Optional.empty(); - - if (args.length > 0) { - // Search subcommand if command arguments are present - subcommand = Sonar.get().getSubcommandRegistry().getSubcommands().parallelStream() - .filter(sub -> sub.getInfo().name().equalsIgnoreCase(args[0]) - || Arrays.stream(sub.getInfo().aliases()) - .anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) - .findFirst(); - - // Check permissions for subcommands - if (subcommand.isPresent()) { - if (!subcommand.get().getInfo().onlyConsole() - && !sender.hasPermission(subcommand.get().getPermission()) - ) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getSubCommandNoPerm() - .replace("%permission%", subcommand.get().getPermission())); - return false; - } - } - } - - if (!subcommand.isPresent()) { - - // Re-use the old, cached help message since we don't want to scan - // for each subcommand and it's arguments/attributes every time - // someone runs /sonar since the subcommand don't change - for (final Component component : CACHED_HELP_MESSAGE) { - invocationSource.sendMessage(component); - } - } else { - subcommand.get().invoke(invocationSource, args); - } - return false; + // Pass the invocation source and command arguments to our command handler + handle(invocationSource, args); + return true; // Valid } @Override diff --git a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeInvocationSource.java b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeInvocationSource.java index e40835c12..d3d99a5b1 100644 --- a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeInvocationSource.java +++ b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeInvocationSource.java @@ -25,6 +25,9 @@ public final class BungeeInvocationSource extends InvocationSource { public BungeeInvocationSource(final @NotNull CommandSender sender) { - super(sender.getName(), SonarBungee.INSTANCE.getBungeeAudiences().sender(sender), sender instanceof ProxiedPlayer); + super(sender.getName(), + SonarBungee.INSTANCE.getBungeeAudiences().sender(sender), + sender instanceof ProxiedPlayer, + sender::hasPermission); } } diff --git a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeSonarCommand.java b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeSonarCommand.java index f49b9b31c..214876979 100644 --- a/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeSonarCommand.java +++ b/sonar-bungee/src/main/java/xyz/jonesdev/sonar/bungee/command/BungeeSonarCommand.java @@ -17,18 +17,12 @@ package xyz.jonesdev.sonar.bungee.command; -import net.kyori.adventure.text.Component; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.plugin.TabExecutor; import org.jetbrains.annotations.NotNull; -import xyz.jonesdev.sonar.api.Sonar; import xyz.jonesdev.sonar.api.command.InvocationSource; import xyz.jonesdev.sonar.api.command.SonarCommand; -import xyz.jonesdev.sonar.api.command.subcommand.Subcommand; - -import java.util.Arrays; -import java.util.Optional; import static java.util.Collections.emptyList; @@ -42,69 +36,8 @@ public BungeeSonarCommand() { public void execute(final @NotNull CommandSender sender, final String[] args) { // Create our own invocation source wrapper to handle messages properly final InvocationSource invocationSource = new BungeeInvocationSource(sender); - - if (invocationSource.isPlayer()) { - // Check if the player actually has the permission to run the command - if (!sender.hasPermission("sonar.command")) { - invocationSource.sendMessage(Sonar.get().getConfig().getNoPermission()); - return; - } - // Checking if it contains will only break more since it can throw - // a NullPointerException if the cache is being accessed from parallel threads - DELAY.cleanUp(); // Clean up the cache - final long mapTimestamp = DELAY.asMap().getOrDefault(sender, -1L); - - // There were some exploits with spamming commands in the past. - // Spamming should be prevented, especially if some heavy operations are done, - // which is not the case here but let's still stay safe! - if (mapTimestamp > 0L) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDown()); - - // Format delay - final long timestamp = System.currentTimeMillis(); - final double left = 0.5D - (timestamp - mapTimestamp) / 1000D; - - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDownLeft() - .replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left))); - return; - } - - DELAY.put(sender); - } - - Optional subcommand = Optional.empty(); - - if (args.length > 0) { - // Search subcommand if command arguments are present - subcommand = Sonar.get().getSubcommandRegistry().getSubcommands().parallelStream() - .filter(sub -> sub.getInfo().name().equalsIgnoreCase(args[0]) - || Arrays.stream(sub.getInfo().aliases()) - .anyMatch(alias -> alias.equalsIgnoreCase(args[0]))) - .findFirst(); - - // Check permissions for subcommands - if (subcommand.isPresent()) { - if (!subcommand.get().getInfo().onlyConsole() - && !sender.hasPermission(subcommand.get().getPermission()) - ) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getSubCommandNoPerm() - .replace("%permission%", subcommand.get().getPermission())); - return; - } - } - } - - if (!subcommand.isPresent()) { - - // Re-use the old, cached help message since we don't want to scan - // for each subcommand and it's arguments/attributes every time - // someone runs /sonar since the subcommand don't change - for (final Component component : CACHED_HELP_MESSAGE) { - invocationSource.sendMessage(component); - } - } else { - subcommand.get().invoke(invocationSource, args); - } + // Pass the invocation source and command arguments to our command handler + handle(invocationSource, args); } @Override diff --git a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocityInvocationSource.java b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocityInvocationSource.java index 2779db459..d27bc6e87 100644 --- a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocityInvocationSource.java +++ b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocityInvocationSource.java @@ -24,6 +24,9 @@ public final class VelocityInvocationSource extends InvocationSource { public VelocityInvocationSource(final @NotNull CommandSource sender) { - super(sender instanceof Player ? ((Player) sender).getUsername() : "Console", sender, sender instanceof Player); + super(sender instanceof Player ? ((Player) sender).getUsername() : "Console", + sender, + sender instanceof Player, + sender::hasPermission); } } diff --git a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocitySonarCommand.java b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocitySonarCommand.java index 554cdf362..b75a16987 100644 --- a/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocitySonarCommand.java +++ b/sonar-velocity/src/main/java/xyz/jonesdev/sonar/velocity/command/VelocitySonarCommand.java @@ -18,16 +18,11 @@ package xyz.jonesdev.sonar.velocity.command; import com.velocitypowered.api.command.SimpleCommand; -import net.kyori.adventure.text.Component; import org.jetbrains.annotations.NotNull; -import xyz.jonesdev.sonar.api.Sonar; import xyz.jonesdev.sonar.api.command.InvocationSource; import xyz.jonesdev.sonar.api.command.SonarCommand; -import xyz.jonesdev.sonar.api.command.subcommand.Subcommand; -import java.util.Arrays; import java.util.List; -import java.util.Optional; import static java.util.Collections.emptyList; @@ -37,69 +32,8 @@ public final class VelocitySonarCommand implements SimpleCommand, SonarCommand { public void execute(final @NotNull Invocation invocation) { // Create our own invocation source wrapper to handle messages properly final InvocationSource invocationSource = new VelocityInvocationSource(invocation.source()); - - if (invocationSource.isPlayer()) { - // Check if the player actually has the permission to run the command - if (!invocation.source().hasPermission("sonar.command")) { - invocationSource.sendMessage(Sonar.get().getConfig().getNoPermission()); - return; - } - // Checking if it contains will only break more since it can throw - // a NullPointerException if the cache is being accessed from parallel threads - DELAY.cleanUp(); // Clean up the cache - final long mapTimestamp = DELAY.asMap().getOrDefault(invocation.source(), -1L); - - // There were some exploits with spamming commands in the past. - // Spamming should be prevented, especially if some heavy operations are done, - // which is not the case here but let's still stay safe! - if (mapTimestamp > 0L) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDown()); - - // Format delay - final long timestamp = System.currentTimeMillis(); - final double left = 0.5D - (timestamp - mapTimestamp) / 1000D; - - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDownLeft() - .replace("%time-left%", Sonar.DECIMAL_FORMAT.format(left))); - return; - } - - DELAY.put(invocation.source()); - } - - Optional subcommand = Optional.empty(); - - if (invocation.arguments().length > 0) { - // Search subcommand if command arguments are present - subcommand = Sonar.get().getSubcommandRegistry().getSubcommands().parallelStream() - .filter(sub -> sub.getInfo().name().equalsIgnoreCase(invocation.arguments()[0]) - || Arrays.stream(sub.getInfo().aliases()) - .anyMatch(alias -> alias.equalsIgnoreCase(invocation.arguments()[0]))) - .findFirst(); - - // Check permissions for subcommands - if (subcommand.isPresent()) { - if (!subcommand.get().getInfo().onlyConsole() - && !invocation.source().hasPermission(subcommand.get().getPermission()) - ) { - invocationSource.sendMessage(Sonar.get().getConfig().getCommands().getSubCommandNoPerm() - .replace("%permission%", subcommand.get().getPermission())); - return; - } - } - } - - if (subcommand.isEmpty()) { - - // Re-use the old, cached help message since we don't want to scan - // for each subcommand and it's arguments/attributes every time - // someone runs /sonar since the subcommand don't change - for (final Component component : CACHED_HELP_MESSAGE) { - invocationSource.sendMessage(component); - } - } else { - subcommand.get().invoke(invocationSource, invocation.arguments()); - } + // Pass the invocation source and command arguments to our command handler + handle(invocationSource, invocation.arguments()); } @Override