diff --git a/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/CommandWhitelistBukkit.java b/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/CommandWhitelistBukkit.java index 8046fc7..dc915fa 100644 --- a/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/CommandWhitelistBukkit.java +++ b/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/CommandWhitelistBukkit.java @@ -1,9 +1,9 @@ package me.youhavetrouble.commandwhitelist.bukkit; +import me.youhavetrouble.commandwhitelist.bukkit.listeners.CommandExecuteListener; import me.youhavetrouble.commandwhitelist.bukkit.listeners.CommandSendListener; -import me.youhavetrouble.commandwhitelist.common.CWCommandEntry; -import me.youhavetrouble.commandwhitelist.common.CWGroup; -import me.youhavetrouble.commandwhitelist.common.ConfigCache; +import me.youhavetrouble.commandwhitelist.bukkit.listeners.CommandTabCompleteListener; +import me.youhavetrouble.commandwhitelist.common.CWConfig; import me.youhavetrouble.commandwhitelist.common.commands.CWCommand; import net.kyori.adventure.platform.bukkit.BukkitAudiences; import org.bukkit.Bukkit; @@ -12,14 +12,11 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import java.io.File; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; import java.util.concurrent.CompletableFuture; public class CommandWhitelistBukkit extends JavaPlugin { - private ConfigCache configCache; + private CWConfig CWConfig; private BukkitAudiences audiences; @Override @@ -27,30 +24,20 @@ public class CommandWhitelistBukkit extends JavaPlugin { reloadPluginConfig(); audiences = BukkitAudiences.create(this); getServer().getPluginManager().registerEvents(new CommandSendListener(this), this); - } - - public Set getCommands(Player player) { - HashSet commands = new HashSet<>(); - Map groups = configCache.getGroupList(); - for (Map.Entry groupEntry : groups.entrySet()) { - CWGroup group = groupEntry.getValue(); - String groupId = groupEntry.getKey(); - if (groupId.equalsIgnoreCase("default")) commands.addAll(group.getCommands()); - else if (player.hasPermission(group.getPermission())) commands.addAll(group.getCommands()); - } - return commands; + getServer().getPluginManager().registerEvents(new CommandExecuteListener(this), this); + getServer().getPluginManager().registerEvents(new CommandTabCompleteListener(this), this); } private void reloadPluginConfig() { File configFile = new File("plugins/CommandWhitelist/config.yml"); - if (configCache != null) { - configCache.reloadConfig(); + if (CWConfig != null) { + CWConfig.reloadConfig(); return; } try { - configCache = new ConfigCache(configFile, true, getSLF4JLogger()); + CWConfig = new CWConfig(configFile, true, getSLF4JLogger()); } catch (NoSuchMethodError e) { - configCache = new ConfigCache(configFile, true, null); + CWConfig = new CWConfig(configFile, true, null); } } @@ -62,8 +49,12 @@ public class CommandWhitelistBukkit extends JavaPlugin { p.updateCommands(); } } catch (Exception ignored) {} - audiences.sender(sender).sendMessage(CWCommand.miniMessage.deserialize(configCache.prefix + configCache.config_reloaded)); + audiences.sender(sender).sendMessage(CWCommand.miniMessage.deserialize(CWConfig.prefix + CWConfig.config_reloaded)); }); } + public CWConfig getCWConfig() { + return CWConfig; + } + } diff --git a/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/listeners/CommandExecuteListener.java b/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/listeners/CommandExecuteListener.java new file mode 100644 index 0000000..ebae8f1 --- /dev/null +++ b/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/listeners/CommandExecuteListener.java @@ -0,0 +1,33 @@ +package me.youhavetrouble.commandwhitelist.bukkit.listeners; + +import me.youhavetrouble.commandwhitelist.bukkit.CommandWhitelistBukkit; +import me.youhavetrouble.commandwhitelist.common.CWCommandEntry; +import me.youhavetrouble.commandwhitelist.common.CWPermission; +import me.youhavetrouble.commandwhitelist.common.CWPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandPreprocessEvent; + +public class CommandExecuteListener implements Listener { + + private final CommandWhitelistBukkit plugin; + + public CommandExecuteListener(CommandWhitelistBukkit plugin ) { + this.plugin = plugin; + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onCommandPreProcess(PlayerCommandPreprocessEvent event) { + Player player = event.getPlayer(); + if (player.hasPermission(CWPermission.BYPASS.permission())) return; + CWPlayer cwPlayer = new CWPlayer(player); + for (CWCommandEntry entry : cwPlayer.getCommands(plugin.getCWConfig())) { + if (entry.matches(event.getMessage())) return; + } + event.setCancelled(true); + } + + +} diff --git a/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/listeners/CommandSendListener.java b/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/listeners/CommandSendListener.java index f58f0cd..e5f389a 100644 --- a/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/listeners/CommandSendListener.java +++ b/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/listeners/CommandSendListener.java @@ -3,6 +3,7 @@ package me.youhavetrouble.commandwhitelist.bukkit.listeners; import me.youhavetrouble.commandwhitelist.bukkit.CommandWhitelistBukkit; import me.youhavetrouble.commandwhitelist.common.CWCommandEntry; import me.youhavetrouble.commandwhitelist.common.CWPermission; +import me.youhavetrouble.commandwhitelist.common.CWPlayer; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -24,9 +25,10 @@ public class CommandSendListener implements Listener { Player player = event.getPlayer(); if (player.hasPermission(CWPermission.BYPASS.permission())) return; Iterator iterator = event.getCommands().iterator(); + CWPlayer cwPlayer = new CWPlayer(player); while (iterator.hasNext()) { String command = iterator.next(); - for (CWCommandEntry entry : plugin.getCommands(player)) { + for (CWCommandEntry entry : cwPlayer.getCommands(plugin.getCWConfig())) { if (entry.argumentMatches(command, 0)) continue; iterator.remove(); break; diff --git a/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/listeners/CommandTabCompleteListener.java b/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/listeners/CommandTabCompleteListener.java new file mode 100644 index 0000000..28b99ed --- /dev/null +++ b/src/main/java/me/youhavetrouble/commandwhitelist/bukkit/listeners/CommandTabCompleteListener.java @@ -0,0 +1,69 @@ +package me.youhavetrouble.commandwhitelist.bukkit.listeners; + +import me.youhavetrouble.commandwhitelist.bukkit.CommandWhitelistBukkit; +import me.youhavetrouble.commandwhitelist.common.CWCommandEntry; +import me.youhavetrouble.commandwhitelist.common.CWPermission; +import me.youhavetrouble.commandwhitelist.common.CWPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.server.TabCompleteEvent; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class CommandTabCompleteListener implements Listener { + + private final CommandWhitelistBukkit plugin; + + public CommandTabCompleteListener(CommandWhitelistBukkit plugin ) { + this.plugin = plugin; + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onCommandTabComplete(TabCompleteEvent event) { + if (!(event.getSender() instanceof Player)) return; + Player player = (Player) event.getSender(); + if (player.hasPermission(CWPermission.BYPASS.permission())) return; + String buffer = event.getBuffer(); + + if ((buffer.split(" ").length == 1 && !buffer.endsWith(" ")) || !buffer.startsWith("/")) { + plugin.getCWConfig().debug("Actively prevented "+event.getSender().getName()+"'s tab completion (sus packet)"); + event.setCancelled(true); + return; + } + + if (event.getCompletions().isEmpty()) return; + + String[] split = buffer.split(" "); + if (split.length == 0) return; + String[] splitWithoutLastArg = new String[split.length - 1]; + System.arraycopy(split, 0, splitWithoutLastArg, 0, splitWithoutLastArg.length); + String commandWithoutLastArg = String.join(" ", splitWithoutLastArg); + + CWPlayer cwPlayer = new CWPlayer(player); + List validCompletions = new ArrayList<>(); + for (CWCommandEntry entry : cwPlayer.getCommands(plugin.getCWConfig())) { + if (!entry.partiallyMatches(commandWithoutLastArg)) continue; + validCompletions.add(entry); + } + + int index = split.length - 1; + + List completions = event.getCompletions(); + Iterator iterator = completions.iterator(); + while (iterator.hasNext()) { + String completion = iterator.next(); + for (CWCommandEntry entry : validCompletions) { + if (entry.argumentMatches(completion, index)) continue; + iterator.remove(); + break; + } + } + + event.setCompletions(completions); + } + +} diff --git a/src/main/java/me/youhavetrouble/commandwhitelist/common/CWCommandEntry.java b/src/main/java/me/youhavetrouble/commandwhitelist/common/CWCommandEntry.java index 208ccf8..11fda7b 100644 --- a/src/main/java/me/youhavetrouble/commandwhitelist/common/CWCommandEntry.java +++ b/src/main/java/me/youhavetrouble/commandwhitelist/common/CWCommandEntry.java @@ -37,7 +37,8 @@ public class CWCommandEntry { } /** - * Checks if a full command input matches this command entry. + * Checks if a full command input matches this command entry. Input is a match if all entry slices match the + * corresponding command slices, even if there are more command slices than entry slices. * @param command full command input * @return true if the command matches this command entry */ @@ -45,9 +46,24 @@ public class CWCommandEntry { if (command == null) return false; if (command.startsWith("/")) command = command.substring(1); // Remove leading slash (if present) String[] parts = command.split(" "); - if (parts.length != this.parts.size()) return false; - for (int i = 0; i < parts.length; i++) { - if (!this.parts.get(i).matcher(parts[i]).matches()) return false; + if (parts.length < this.parts.size()) return false; + for (int i = 0; i < this.parts.size(); i++) { + if (!argumentMatches(parts[i], i)) return false; + } + return true; + } + + /** + * Checks if a command input partially matches this command entry. + * @param command command input + * @return true if the command partially matches this command entry + */ + public boolean partiallyMatches(String command) { + if (command == null) return false; + if (command.startsWith("/")) command = command.substring(1); // Remove leading slash (if present) + String[] parts = command.split(" "); + for (int i = 0; i < this.parts.size() || i < parts.length; i++) { + if (!argumentMatches(parts[i], i)) return false; } return true; } diff --git a/src/main/java/me/youhavetrouble/commandwhitelist/common/ConfigCache.java b/src/main/java/me/youhavetrouble/commandwhitelist/common/CWConfig.java similarity index 98% rename from src/main/java/me/youhavetrouble/commandwhitelist/common/ConfigCache.java rename to src/main/java/me/youhavetrouble/commandwhitelist/common/CWConfig.java index 5ac2fc8..91b94a4 100644 --- a/src/main/java/me/youhavetrouble/commandwhitelist/common/ConfigCache.java +++ b/src/main/java/me/youhavetrouble/commandwhitelist/common/CWConfig.java @@ -7,7 +7,7 @@ import java.io.File; import java.io.IOException; import java.util.*; -public class ConfigCache { +public class CWConfig { private final File configFile; private ConfigFile config; @@ -19,7 +19,7 @@ public class ConfigCache { public boolean useProtocolLib = false; public boolean debug = false; - public ConfigCache(File configFile, boolean canDoProtocolLib, Object logger) { + public CWConfig(File configFile, boolean canDoProtocolLib, Object logger) { this.configFile = configFile; this.canDoProtocolLib = canDoProtocolLib; this.logger = logger; diff --git a/src/main/java/me/youhavetrouble/commandwhitelist/common/CWPlayer.java b/src/main/java/me/youhavetrouble/commandwhitelist/common/CWPlayer.java new file mode 100644 index 0000000..f08f4a1 --- /dev/null +++ b/src/main/java/me/youhavetrouble/commandwhitelist/common/CWPlayer.java @@ -0,0 +1,57 @@ +package me.youhavetrouble.commandwhitelist.common; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Represents a player on any supported platform. + */ +public class CWPlayer { + + private org.bukkit.entity.Player bukkitPlayer; + private com.velocitypowered.api.proxy.Player velocityPlayer; + private net.md_5.bungee.api.connection.ProxiedPlayer waterfallPlayer; + + public CWPlayer(org.bukkit.entity.Player player) { + this.bukkitPlayer = player; + } + + public CWPlayer(com.velocitypowered.api.proxy.Player player) { + this.velocityPlayer = player; + } + + public CWPlayer(net.md_5.bungee.api.connection.ProxiedPlayer player) { + this.waterfallPlayer = player; + } + + /** + * Checks if this player has a permission. + * @param permission Permission to check + * @return Whether this player has the permission + */ + public boolean hasPermission(String permission) { + if (bukkitPlayer != null) return bukkitPlayer.hasPermission(permission); + if (velocityPlayer != null) return velocityPlayer.hasPermission(permission); + if (waterfallPlayer != null) return waterfallPlayer.hasPermission(permission); + return false; + } + + /** + * Get the commands that this player can use. + * @param CWConfig Current configuration cache + * @return Set of commands that this player can use + */ + public Set getCommands(CWConfig CWConfig) { + HashSet commands = new HashSet<>(); + Map groups = CWConfig.getGroupList(); + for (Map.Entry groupEntry : groups.entrySet()) { + CWGroup group = groupEntry.getValue(); + String groupId = groupEntry.getKey(); + if (groupId.equalsIgnoreCase("default")) commands.addAll(group.getCommands()); + else if (hasPermission(group.getPermission())) commands.addAll(group.getCommands()); + } + return commands; + } + +} diff --git a/src/main/java/me/youhavetrouble/commandwhitelist/common/commands/CWCommand.java b/src/main/java/me/youhavetrouble/commandwhitelist/common/commands/CWCommand.java index 79a1011..562aa0f 100644 --- a/src/main/java/me/youhavetrouble/commandwhitelist/common/commands/CWCommand.java +++ b/src/main/java/me/youhavetrouble/commandwhitelist/common/commands/CWCommand.java @@ -1,8 +1,7 @@ package me.youhavetrouble.commandwhitelist.common.commands; -import me.youhavetrouble.commandwhitelist.common.CWCommandEntry; import me.youhavetrouble.commandwhitelist.common.CWGroup; -import me.youhavetrouble.commandwhitelist.common.ConfigCache; +import me.youhavetrouble.commandwhitelist.common.CWConfig; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.format.NamedTextColor; @@ -15,20 +14,20 @@ public class CWCommand { public static MiniMessage miniMessage = MiniMessage.miniMessage(); - public static boolean addToWhitelist(ConfigCache configCache, String command, String group) { - CWGroup cwGroup = configCache.getGroupList().get(group); + public static boolean addToWhitelist(CWConfig CWConfig, String command, String group) { + CWGroup cwGroup = CWConfig.getGroupList().get(group); if (cwGroup == null) return false; cwGroup.addCommand(command); - configCache.saveCWGroup(group, cwGroup); + CWConfig.saveCWGroup(group, cwGroup); return true; } - public static boolean removeFromWhitelist(ConfigCache configCache, String command, String group) { - CWGroup cwGroup = configCache.getGroupList().get(group); + public static boolean removeFromWhitelist(CWConfig CWConfig, String command, String group) { + CWGroup cwGroup = CWConfig.getGroupList().get(group); if (cwGroup == null) return false; cwGroup.removeCommand(command); - configCache.saveCWGroup(group, cwGroup); + CWConfig.saveCWGroup(group, cwGroup); return true; } @@ -58,7 +57,7 @@ public class CWCommand { } public static List commandSuggestions( - ConfigCache config, + CWConfig config, Collection serverCommands, String[] args, boolean reloadPerm, boolean adminPerm,