diff --git a/src/main/java/dev/plex/Plex.java b/src/main/java/dev/plex/Plex.java index cd19578..d999dad 100644 --- a/src/main/java/dev/plex/Plex.java +++ b/src/main/java/dev/plex/Plex.java @@ -8,6 +8,7 @@ import dev.plex.cache.player.PlayerCache; import dev.plex.cache.player.SQLPlayerData; import dev.plex.cache.sql.SQLNotes; import dev.plex.cache.sql.SQLPunishment; +import dev.plex.command.blocker.CommandBlockerManager; import dev.plex.config.Config; import dev.plex.handlers.CommandHandler; import dev.plex.handlers.ListenerHandler; @@ -44,6 +45,7 @@ public class Plex extends JavaPlugin public Config config; public Config messages; public Config indefBans; + public Config blockedCommands; public File modulesFolder; private StorageType storageType = StorageType.SQLITE; @@ -62,6 +64,7 @@ public class Plex extends JavaPlugin private RankManager rankManager; private ServiceManager serviceManager; private PunishmentManager punishmentManager; + private CommandBlockerManager commandBlockerManager; private AdminList adminList; private UpdateChecker updateChecker; @@ -79,6 +82,7 @@ public class Plex extends JavaPlugin config = new Config(this, "config.yml"); messages = new Config(this, "messages.yml"); indefBans = new Config(this, "indefbans.yml"); + blockedCommands = new Config(this, "commands.yml"); build.load(this); modulesFolder = new File(this.getDataFolder() + File.separator + "modules"); @@ -99,6 +103,7 @@ public class Plex extends JavaPlugin messages.load(); // Don't add default entries to indefinite ban file indefBans.load(false); + blockedCommands.load(); sqlConnection = new SQLConnection(); mongoConnection = new MongoConnection(); @@ -158,6 +163,9 @@ public class Plex extends JavaPlugin punishmentManager.mergeIndefiniteBans(); PlexLog.log("Punishment System initialized"); + commandBlockerManager = new CommandBlockerManager(); + PlexLog.log("Command Blocker initialized"); + generateWorlds(); serviceManager = new ServiceManager(); diff --git a/src/main/java/dev/plex/command/blocker/BaseCommand.java b/src/main/java/dev/plex/command/blocker/BaseCommand.java new file mode 100644 index 0000000..ae5850c --- /dev/null +++ b/src/main/java/dev/plex/command/blocker/BaseCommand.java @@ -0,0 +1,22 @@ +package dev.plex.command.blocker; + +import dev.plex.rank.enums.Rank; +import lombok.Getter; + +@Getter +public class BaseCommand +{ + private final Rank rank; + private final String message; + + public BaseCommand(Rank r, String m) + { + rank = r; + message = m; + } + + public String toString() + { + return "BaseCommand (Rank: " + (rank == null ? "ALL" : rank.name()) + ", Message: " + message + ")"; + } +} diff --git a/src/main/java/dev/plex/command/blocker/CommandBlockerManager.java b/src/main/java/dev/plex/command/blocker/CommandBlockerManager.java new file mode 100644 index 0000000..9fdf550 --- /dev/null +++ b/src/main/java/dev/plex/command/blocker/CommandBlockerManager.java @@ -0,0 +1,108 @@ +package dev.plex.command.blocker; + +import dev.plex.Plex; +import dev.plex.PlexBase; +import dev.plex.rank.enums.Rank; +import dev.plex.util.PlexLog; +import dev.plex.util.PlexUtils; +import lombok.Getter; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.PluginCommandYamlParser; +import org.bukkit.plugin.Plugin; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; + +@Getter +public class CommandBlockerManager extends PlexBase +{ + private List blockedCommands = new ArrayList<>(); + + public boolean loadedYet; + + public void syncCommands() + { + loadedYet = false; + blockedCommands.clear(); + + List raw = plugin.blockedCommands.getStringList("blockedCommands"); + + for (String cmd : raw) + { + int lastDelim = cmd.lastIndexOf(':'); + + String cmdWithoutMsg = cmd.substring(0, lastDelim); + String[] rawPieces = cmdWithoutMsg.split(":", 3); + + String rawType = rawPieces[0].toLowerCase(); + String rawRank = rawPieces[1].toLowerCase(); + String regexOrMatch = rawPieces[2]; + String message = cmd.substring(lastDelim + 1); + + if (message.equals("_")) + { + message = PlexUtils.messageString("commandBlocked"); + } + + Rank rank = switch (rawRank) + { + case "e" -> null; + case "a" -> Rank.ADMIN; + case "s" -> Rank.SENIOR_ADMIN; + default -> null; + }; + + if (rawType.equals("r")) + { + blockedCommands.add(new RegexCommand(Pattern.compile(regexOrMatch, Pattern.CASE_INSENSITIVE), rank, message)); + } + else if (rawType.equals("m")) + { + int ind = regexOrMatch.indexOf(' '); + if (ind == -1 && regexOrMatch.endsWith(":")) + { + String pluginName = regexOrMatch.substring(0, regexOrMatch.length() - 1); + Plugin plugin = Arrays.stream(Bukkit.getServer().getPluginManager().getPlugins()).filter(pl -> pl.getName().equalsIgnoreCase(pluginName)).findAny().orElse(null); + if (plugin != null) + { + List commandList = PluginCommandYamlParser.parse(plugin); + for (Command command : commandList) + { + blockedCommands.add(new MatchCommand(command.getName(), rank, message)); + blockedCommands.add(new MatchCommand(pluginName + ":" + command.getName(), rank, message)); + for (String alias : command.getAliases()) + { + blockedCommands.add(new MatchCommand(alias, rank, message)); + blockedCommands.add(new MatchCommand(pluginName + ":" + alias, rank, message)); + } + } + } + } + String blockedArgs = ind == -1 ? "" : regexOrMatch.substring(ind + 1); + if (!blockedArgs.isEmpty()) + { + blockedArgs = " " + blockedArgs; // necessary in case no args + } + PluginCommand pluginCommand = Plex.get().getServer().getPluginCommand(ind == -1 ? regexOrMatch : regexOrMatch.substring(0, ind)); + if (pluginCommand != null) + { + String pluginName = pluginCommand.getPlugin().getName(); + blockedCommands.add(new MatchCommand(pluginCommand.getName() + blockedArgs, rank, message)); + blockedCommands.add(new MatchCommand(pluginName + ":" + pluginCommand.getName() + blockedArgs, rank, message)); + List aliases = pluginCommand.getAliases(); + for (String alias : aliases) + { + blockedCommands.add(new MatchCommand(alias + blockedArgs, rank, message)); + blockedCommands.add(new MatchCommand(pluginName + ":" + alias + blockedArgs, rank, message)); + } + } + } + } + + loadedYet = true; + } +} diff --git a/src/main/java/dev/plex/command/blocker/MatchCommand.java b/src/main/java/dev/plex/command/blocker/MatchCommand.java new file mode 100644 index 0000000..8ad36ae --- /dev/null +++ b/src/main/java/dev/plex/command/blocker/MatchCommand.java @@ -0,0 +1,21 @@ +package dev.plex.command.blocker; + +import dev.plex.rank.enums.Rank; +import lombok.Getter; + +@Getter +public class MatchCommand extends BaseCommand +{ + private final String match; + + public MatchCommand(String r1, Rank r2, String m1) + { + super(r2, m1); + match = r1; + } + + public String toString() + { + return "MatchCommand (Rank: " + (getRank() == null ? "ALL" : getRank().name()) + ", Match: " + match + ", Message: " + getMessage() + ")"; + } +} diff --git a/src/main/java/dev/plex/command/blocker/RegexCommand.java b/src/main/java/dev/plex/command/blocker/RegexCommand.java new file mode 100644 index 0000000..ea75e8b --- /dev/null +++ b/src/main/java/dev/plex/command/blocker/RegexCommand.java @@ -0,0 +1,23 @@ +package dev.plex.command.blocker; + +import dev.plex.rank.enums.Rank; +import lombok.Getter; + +import java.util.regex.Pattern; + +@Getter +public class RegexCommand extends BaseCommand +{ + private final Pattern regex; + + public RegexCommand(Pattern r1, Rank r2, String m1) + { + super(r2, m1); + regex = r1; + } + + public String toString() + { + return "RegexCommand (Rank: " + (getRank() == null ? "ALL" : getRank().name()) + ", Regex: " + regex.toString() + ", Message: " + getMessage() + ")"; + } +} \ No newline at end of file diff --git a/src/main/java/dev/plex/command/impl/PlexCMD.java b/src/main/java/dev/plex/command/impl/PlexCMD.java index bec340b..8f90a0f 100644 --- a/src/main/java/dev/plex/command/impl/PlexCMD.java +++ b/src/main/java/dev/plex/command/impl/PlexCMD.java @@ -18,6 +18,7 @@ import dev.plex.util.PlexLog; import dev.plex.util.PlexUtils; import net.kyori.adventure.text.Component; import org.apache.commons.lang.StringUtils; +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -51,13 +52,16 @@ public class PlexCMD extends PlexCommand plugin.indefBans.load(false); plugin.getPunishmentManager().mergeIndefiniteBans(); send(sender, "Reloaded indefinite bans"); + plugin.blockedCommands.load(); + plugin.getCommandBlockerManager().syncCommands(); + send(sender, "Reloaded blocked commands file"); plugin.getRankManager().importDefaultRanks(); send(sender, "Imported ranks"); - send(sender, "Plex successfully reloaded."); plugin.setSystem(plugin.config.getString("system")); plugin.getServiceManager().endServices(); plugin.getServiceManager().startServices(); PlexLog.debug("Restarted services"); + send(sender, "Plex successfully reloaded."); return null; } else if (args[0].equalsIgnoreCase("redis")) diff --git a/src/main/java/dev/plex/listener/impl/CommandListener.java b/src/main/java/dev/plex/listener/impl/CommandListener.java index b393952..745eb46 100644 --- a/src/main/java/dev/plex/listener/impl/CommandListener.java +++ b/src/main/java/dev/plex/listener/impl/CommandListener.java @@ -1,7 +1,14 @@ package dev.plex.listener.impl; +import dev.plex.cache.DataUtils; import dev.plex.cache.player.PlayerCache; +import dev.plex.command.blocker.BaseCommand; +import dev.plex.command.blocker.MatchCommand; +import dev.plex.command.blocker.RegexCommand; import dev.plex.listener.PlexListener; +import dev.plex.player.PlexPlayer; +import dev.plex.util.PlexUtils; +import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.entity.Player; @@ -23,5 +30,44 @@ public class CommandListener extends PlexListener pl.sendMessage(ChatColor.GRAY + player.getName() + ": " + command); } }); + + if (!plugin.getCommandBlockerManager().loadedYet) + { + event.setCancelled(true); + return; + } + Player player = event.getPlayer(); + String message = event.getMessage().substring(1).stripLeading(); // stripLeading() is VITAL for workaround blocking (/ minecraft:summon) + for (BaseCommand blockedCommand : plugin.getCommandBlockerManager().getBlockedCommands()) + { + PlexPlayer plexPlayer = DataUtils.getPlayer(player.getUniqueId()); + if (blockedCommand.getRank() != null && plexPlayer.getRankFromString().isAtLeast(blockedCommand.getRank())) + { + continue; + } + + boolean isBlocked = false; + if (blockedCommand instanceof RegexCommand regexCommand) + { + if (regexCommand.getRegex().matcher(message).lookingAt()) + { + isBlocked = true; + } + } + else if (blockedCommand instanceof MatchCommand matchCommand) + { + if (message.toLowerCase().startsWith(matchCommand.getMatch().toLowerCase())) + { + isBlocked = true; + } + } + if (isBlocked) + { + event.setCancelled(true); + //PlexLog.debug("Command blocked."); + player.sendMessage(MiniMessage.miniMessage().deserialize(PlexUtils.messageString("blockedCommandColor") + blockedCommand.getMessage())); + return; + } + } } } diff --git a/src/main/java/dev/plex/player/PlexPlayer.java b/src/main/java/dev/plex/player/PlexPlayer.java index 62502b9..4ae582b 100644 --- a/src/main/java/dev/plex/player/PlexPlayer.java +++ b/src/main/java/dev/plex/player/PlexPlayer.java @@ -98,7 +98,7 @@ public class PlexPlayer public Rank getRankFromString() { OfflinePlayer player = Bukkit.getOfflinePlayer(uuid); - if (rank.isEmpty()) + if (rank.isEmpty() || !isAdminActive()) { if (player.isOp()) { diff --git a/src/main/java/dev/plex/services/ServiceManager.java b/src/main/java/dev/plex/services/ServiceManager.java index 10e9b83..2253f48 100644 --- a/src/main/java/dev/plex/services/ServiceManager.java +++ b/src/main/java/dev/plex/services/ServiceManager.java @@ -2,10 +2,7 @@ package dev.plex.services; import com.google.common.collect.Lists; import dev.plex.Plex; -import dev.plex.services.impl.AutoWipeService; -import dev.plex.services.impl.BanService; -import dev.plex.services.impl.GameRuleService; -import dev.plex.services.impl.UpdateCheckerService; +import dev.plex.services.impl.*; import org.bukkit.Bukkit; import org.bukkit.scheduler.BukkitTask; @@ -21,6 +18,7 @@ public class ServiceManager registerService(new GameRuleService()); registerService(new UpdateCheckerService()); registerService(new AutoWipeService()); + registerService(new CommandBlockerService()); } public void startServices() @@ -45,7 +43,16 @@ public class ServiceManager { if (!service.isRepeating()) { - BukkitTask task = Bukkit.getScheduler().runTask(Plex.get(), service::run); + int time = service.repeatInSeconds(); + BukkitTask task; + if (time == 0) + { + task = Bukkit.getScheduler().runTask(Plex.get(), service::run); + } + else + { + task = Bukkit.getScheduler().runTaskLater(Plex.get(), service::run, time); + } service.setTaskId(task.getTaskId()); } else if (service.isRepeating() && service.isAsynchronous()) diff --git a/src/main/java/dev/plex/services/impl/CommandBlockerService.java b/src/main/java/dev/plex/services/impl/CommandBlockerService.java new file mode 100644 index 0000000..6116927 --- /dev/null +++ b/src/main/java/dev/plex/services/impl/CommandBlockerService.java @@ -0,0 +1,25 @@ +package dev.plex.services.impl; + +import dev.plex.services.AbstractService; +import dev.plex.util.PlexLog; + +public class CommandBlockerService extends AbstractService +{ + public CommandBlockerService() + { + super(false, true); + } + + @Override + public void run() + { + plugin.getCommandBlockerManager().syncCommands(); + PlexLog.log("Command Blocker commands loaded"); + } + + @Override + public int repeatInSeconds() + { + return 0; + } +} \ No newline at end of file diff --git a/src/main/resources/commands.yml b/src/main/resources/commands.yml new file mode 100644 index 0000000..d168eeb --- /dev/null +++ b/src/main/resources/commands.yml @@ -0,0 +1,21 @@ +# +# Command Blocker +# +# Format: +# - "::command name no slash:Block message" +# +# Symbols to use: +# - r for RegEx +# - m for matching +# - The ranks are "e" for everyone, "s" for senior admin, and "a" for admin +# - MATCHING MODE: The command is just the command without slashes. Optional arguments are specified as well. It also accepts full plugins via specifying the plugin name followed by a ":" (e.g. "viaversion:") +# - REGEX MODE: The command is regex that matches the desired command. It matches case insensitively. +# - Finally the block message. MUST NOT CONTAIN ":". Use _ to use the default command blocked message as specified in messages.yml, or you can optionally put your own in +# +# So these would be valid: +# - "m:e:mail sendall:You cannot send messages to everyone on the server" +# - "r:e:(.*:):Plugin specific commands are disabled" +blockedCommands: + - "r:e:^[^ :]+::Plugin specific commands are disabled." + - "m:e:mail sendall:You cannot send messages to everyone on the server." + - "m:e:mail sendtempall:You cannot send messages to everyone on the server." \ No newline at end of file diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index 275138d..2870cc6 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -166,4 +166,6 @@ noRemovedEntities: "No entities were removed." removedMobs: "{0} - Removed {1} mobs" autoWipeDisabled: "Item wiping is currently disabled in the config!" allowDropsDisabled: "No longer allowing drops from players." -allowDropsEnabled: "Now allowing drops from players." \ No newline at end of file +allowDropsEnabled: "Now allowing drops from players." +blockedCommandColor: "" +commandBlocked: "That command is blocked." \ No newline at end of file