diff --git a/build.gradle b/build.gradle index 47effec..12496e7 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,11 @@ subprojects { } } + maven { + url = uri("https://libraries.minecraft.net") + } + + mavenCentral() } diff --git a/server/build.gradle b/server/build.gradle index 1407cb8..8ea542c 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -23,9 +23,11 @@ dependencies { library "org.eclipse.jetty:jetty-proxy:11.0.15" library "com.google.code.gson:gson:2.10.1" compileOnly "io.papermc.paper:paper-api:1.20-R0.1-SNAPSHOT" + compileOnly "io.papermc.paper:paper-mojangapi:1.20-R0.1-SNAPSHOT" compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") { exclude group: "org.bukkit", module: "bukkit" } + compileOnly("com.mojang:brigadier:1.0.18") implementation "org.bstats:bstats-base:3.0.2" implementation "org.bstats:bstats-bukkit:3.0.2" } diff --git a/server/src/main/java/dev/plex/Plex.java b/server/src/main/java/dev/plex/Plex.java index 35a6417..cc4d4d8 100644 --- a/server/src/main/java/dev/plex/Plex.java +++ b/server/src/main/java/dev/plex/Plex.java @@ -4,6 +4,8 @@ import dev.plex.admin.Admin; import dev.plex.admin.AdminList; import dev.plex.cache.DataUtils; import dev.plex.cache.PlayerCache; +import dev.plex.command.PlexBrigadierCommand; +import dev.plex.command.impl.brigadier.PlexBrigadierCMD; import dev.plex.config.Config; import dev.plex.handlers.CommandHandler; import dev.plex.handlers.ListenerHandler; @@ -178,6 +180,7 @@ public class Plex extends JavaPlugin new ListenerHandler(); new CommandHandler(); + new PlexBrigadierCMD(); rankManager = new RankManager(); rankManager.generateDefaultRanks(); diff --git a/server/src/main/java/dev/plex/command/PlexBrigadierCommand.java b/server/src/main/java/dev/plex/command/PlexBrigadierCommand.java new file mode 100644 index 0000000..95e17a3 --- /dev/null +++ b/server/src/main/java/dev/plex/command/PlexBrigadierCommand.java @@ -0,0 +1,372 @@ +package dev.plex.command; + +import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; +import com.google.common.collect.Maps; +import com.google.gson.GsonBuilder; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import dev.plex.Plex; +import dev.plex.cache.DataUtils; +import dev.plex.command.annotation.CommandName; +import dev.plex.command.annotation.CommandPermission; +import dev.plex.command.annotation.Default; +import dev.plex.command.annotation.SubCommand; +import dev.plex.player.PlexPlayer; +import dev.plex.util.PlexLog; +import dev.plex.util.PlexUtils; +import dev.plex.util.ReflectionsUtil; +import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * @author Taah + * @project Plex + * @since 2:27 PM [07-07-2023] + */ +public abstract class PlexBrigadierCommand +{ + protected final Plex plugin; + private CommandDispatcher commandDispatcher; + + public PlexBrigadierCommand() + { + this.plugin = Plex.get(); + try + { + final Object dedicatedServer = ReflectionsUtil.callFunction(getCraftServer(), "getServer"); + final Object minecraftServer = Class.forName("net.minecraft.server.MinecraftServer").cast(dedicatedServer); + // System.out.println(Arrays.toString(Arrays.stream(minecraftServer.getClass().getDeclaredMethods()).map(Method::getName).toArray(String[]::new))); + + final Object serverFunctionsManager = ReflectionsUtil.callFunction(minecraftServer, "aA"); + this.commandDispatcher = ReflectionsUtil.callFunction(serverFunctionsManager, "b"); + } + catch (ClassNotFoundException e) + { + this.commandDispatcher = null; + PlexLog.error("Disabling commands as brigadier could not properly be located."); + } + + if (!this.getClass().isAnnotationPresent(CommandName.class)) + { + PlexLog.error("Cannot find command name for class " + this.getClass().getName()); + return; + } + + String[] commandName = this.getClass().getAnnotation(CommandName.class).value(); + + final HashMap subcommands = Maps.newHashMap(); + + Method defaultMethod = null; + for (Method declaredMethod : this.getClass().getDeclaredMethods()) + { + if (declaredMethod.isAnnotationPresent(SubCommand.class)) + { + String subcommand = declaredMethod.getAnnotation(SubCommand.class).value(); + subcommands.put(subcommand.toLowerCase(), declaredMethod); + } + if (declaredMethod.isAnnotationPresent(Default.class)) + { + if (defaultMethod != null) + { + PlexLog.error("There cannot be more than one default execution."); + continue; + } + defaultMethod = declaredMethod; + } + } + + if (this.commandDispatcher != null) + { + for (String s : commandName) + { + PlexLog.debug("registering command " + s); + LiteralArgumentBuilder builder = LiteralArgumentBuilder.literal(s.toLowerCase()); + for (Map.Entry stringMethodEntry : subcommands.entrySet()) + { + PlexLog.debug("registering subcommand " + stringMethodEntry.getKey()); + String[] subCommandArgs = stringMethodEntry.getKey().split(" "); + LiteralArgumentBuilder parentBuilder = LiteralArgumentBuilder.literal(subCommandArgs[0]); + LiteralArgumentBuilder currSubCommand = parentBuilder; + if (subCommandArgs.length == 1) + { + parentBuilder.executes(context -> + { + if (stringMethodEntry.getValue().isAnnotationPresent(CommandPermission.class)) + { + String permission = stringMethodEntry.getValue().getAnnotation(CommandPermission.class).value(); + if (!context.getSource().getBukkitSender().hasPermission(permission)) + { + send(context, PlexUtils.messageString("noPermissionNode", permission)); + return 0; + } + } + try + { + stringMethodEntry.getValue().invoke(this, context.getSource().getBukkitSender()); + } + catch (Exception e) + { + PlexLog.error(e.getMessage()); + for (StackTraceElement stackTraceElement : e.getStackTrace()) + { + PlexLog.error(stackTraceElement.toString()); + } + return 0; + } + return 1; + }); + } + else + { + for (int i = 1; i < subCommandArgs.length; i++) + { + LiteralArgumentBuilder curr = LiteralArgumentBuilder.literal(subCommandArgs[i]); + if (i == subCommandArgs.length - 1) + { + curr.executes(context -> + { + if (stringMethodEntry.getValue().isAnnotationPresent(CommandPermission.class)) + { + String permission = stringMethodEntry.getValue().getAnnotation(CommandPermission.class).value(); + if (!context.getSource().getBukkitSender().hasPermission(permission)) + { + send(context, PlexUtils.messageString("noPermissionNode", permission)); + return 0; + } + } + try + { + stringMethodEntry.getValue().invoke(this, context.getSource().getBukkitSender()); + } + catch (Exception e) + { + PlexLog.error(e.getMessage()); + for (StackTraceElement stackTraceElement : e.getStackTrace()) + { + PlexLog.error(stackTraceElement.toString()); + } + return 0; + } + return 1; + }); + } + currSubCommand.then(curr); + currSubCommand = curr; + } + } + + PlexLog.debug(new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create().toJson(parentBuilder)); + + builder = builder.then( + parentBuilder + ); + } + if (defaultMethod != null) + { + PlexLog.debug("registering default method"); + Method finalDefaultMethod = defaultMethod; + finalDefaultMethod.setAccessible(true); + builder = builder.executes(context -> + { + if (finalDefaultMethod.isAnnotationPresent(CommandPermission.class)) + { + String permission = finalDefaultMethod.getAnnotation(CommandPermission.class).value(); + if (!context.getSource().getBukkitSender().hasPermission(permission)) + { + send(context, PlexUtils.messageString("noPermissionNode", permission)); + return 0; + } + } + try + { + finalDefaultMethod.invoke(this, context.getSource().getBukkitSender()); + } + catch (Exception e) + { + PlexLog.error(e.getMessage()); + for (StackTraceElement stackTraceElement : e.getStackTrace()) + { + PlexLog.error(stackTraceElement.toString()); + } + return 0; + } + return 1; + }); + } + PlexLog.debug(new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create().toJson(builder)); + this.commandDispatcher.register(builder); + } + } + } + + /** + * Gets a PlexPlayer from Player object + * + * @param player The player object + * @return PlexPlayer Object + * @see PlexPlayer + */ + protected PlexPlayer getPlexPlayer(@NotNull Player player) + { + return DataUtils.getPlayer(player.getUniqueId()); + } + + protected void send(Audience audience, Component component) + { + audience.sendMessage(component); + } + + /** + * Sends a message to an Audience + * + * @param audience The Audience to send the message to + * @param s The message to send + */ + protected void send(Audience audience, String s) + { + audience.sendMessage(componentFromString(s)); + } + + /** + * Sends a message to a CommandSender + * + * @param context The Command Context's sender to send the message to + * @param s The message to send + */ + protected void send(CommandContext context, String s) + { + context.getSource().getBukkitSender().sendMessage(componentFromString(s)); + } + + /** + * Sends a message to a CommandSender + * + * @param context The Command Context's sender to send the message to + * @param component The Component to send + */ + protected void send(CommandContext context, Component component) + { + context.getSource().getBukkitSender().sendMessage(component); + } + + /** + * Converts a String to a MiniMessage Component + * + * @param s The String to convert + * @return A Kyori Component + */ + protected Component mmString(String s) + { + return PlexUtils.mmDeserialize(s); + } + + /** + * Converts a String to a legacy Kyori Component + * + * @param s The String to convert + * @return A Kyori component + */ + protected Component componentFromString(String s) + { + return LegacyComponentSerializer.legacyAmpersand().deserialize(s).colorIfAbsent(NamedTextColor.GRAY); + } + + /** + * Checks if a player is an admin + * + * @param plexPlayer The PlexPlayer object + * @return true if the player is an admin + * @see PlexPlayer + */ + protected boolean isAdmin(PlexPlayer plexPlayer) + { + return Plex.get().getRankManager().isAdmin(plexPlayer); + } + + /** + * Checks if a sender is an admin + * + * @param sender A command sender + * @return true if the sender is an admin or if console + */ + protected boolean isAdmin(CommandSender sender) + { + if (!(sender instanceof Player player)) + { + return true; + } + PlexPlayer plexPlayer = getPlexPlayer(player); + return plugin.getRankManager().isAdmin(plexPlayer); + } + + /** + * Checks if a username is an admin + * + * @param name The username + * @return true if the username is an admin + */ + protected boolean isAdmin(String name) + { + PlexPlayer plexPlayer = DataUtils.getPlayer(name); + return plugin.getRankManager().isAdmin(plexPlayer); + } + + /** + * Checks if a sender is a senior admin + * + * @param sender A command sender + * @return true if the sender is a senior admin or if console + */ + protected boolean isSeniorAdmin(CommandSender sender) + { + if (!(sender instanceof Player player)) + { + return true; + } + PlexPlayer plexPlayer = getPlexPlayer(player); + return plugin.getRankManager().isSeniorAdmin(plexPlayer); + } + + /** + * Gets the UUID of the sender + * + * @param sender A command sender + * @return A unique ID or null if the sender is console + * @see UUID + */ + protected UUID getUUID(CommandSender sender) + { + if (!(sender instanceof Player player)) + { + return null; + } + return player.getUniqueId(); + } + + private Object getCraftServer() + { + String nmsVersion = Bukkit.getServer().getClass().getPackage().getName(); + nmsVersion = nmsVersion.substring(nmsVersion.lastIndexOf('.') + 1); + try + { + Class craftServer = Class.forName("org.bukkit.craftbukkit." + nmsVersion + ".CraftServer"); + return craftServer.cast(Bukkit.getServer()); + } + catch (ClassNotFoundException e) + { + throw new RuntimeException(e); + } + } +} diff --git a/server/src/main/java/dev/plex/command/annotation/CommandName.java b/server/src/main/java/dev/plex/command/annotation/CommandName.java new file mode 100644 index 0000000..470d877 --- /dev/null +++ b/server/src/main/java/dev/plex/command/annotation/CommandName.java @@ -0,0 +1,19 @@ +package dev.plex.command.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Taah + * @project Plex + * @since 4:54 PM [07-07-2023] + */ + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface CommandName +{ + String[] value(); +} diff --git a/server/src/main/java/dev/plex/command/annotation/CommandPermission.java b/server/src/main/java/dev/plex/command/annotation/CommandPermission.java new file mode 100644 index 0000000..c5c0212 --- /dev/null +++ b/server/src/main/java/dev/plex/command/annotation/CommandPermission.java @@ -0,0 +1,19 @@ +package dev.plex.command.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Taah + * @project Plex + * @since 4:54 PM [07-07-2023] + */ + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface CommandPermission +{ + String value(); +} diff --git a/server/src/main/java/dev/plex/command/annotation/Default.java b/server/src/main/java/dev/plex/command/annotation/Default.java new file mode 100644 index 0000000..edfa9c5 --- /dev/null +++ b/server/src/main/java/dev/plex/command/annotation/Default.java @@ -0,0 +1,18 @@ +package dev.plex.command.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Taah + * @project Plex + * @since 4:54 PM [07-07-2023] + */ + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface Default +{ +} diff --git a/server/src/main/java/dev/plex/command/annotation/SubCommand.java b/server/src/main/java/dev/plex/command/annotation/SubCommand.java new file mode 100644 index 0000000..f6a93c9 --- /dev/null +++ b/server/src/main/java/dev/plex/command/annotation/SubCommand.java @@ -0,0 +1,19 @@ +package dev.plex.command.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author Taah + * @project Plex + * @since 4:46 PM [07-07-2023] + */ + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface SubCommand +{ + String value(); +} diff --git a/server/src/main/java/dev/plex/command/impl/PlexCMD.java b/server/src/main/java/dev/plex/command/impl/PlexCMD.java index 4fb46a5..e74f304 100644 --- a/server/src/main/java/dev/plex/command/impl/PlexCMD.java +++ b/server/src/main/java/dev/plex/command/impl/PlexCMD.java @@ -29,6 +29,9 @@ import java.util.stream.Collectors; @CommandParameters(name = "plex", usage = "/ [reload | redis | modules [reload]]", description = "Show information about Plex or reload it") public class PlexCMD extends PlexCommand { + public PlexCMD() { + super(false); + } // Don't modify this command @Override protected Component execute(@NotNull CommandSender sender, @Nullable Player playerSender, String[] args) @@ -73,14 +76,7 @@ public class PlexCMD extends PlexCommand else if (args[0].equalsIgnoreCase("redis")) { checkRank(sender, Rank.SENIOR_ADMIN, "plex.redis"); - if (!plugin.getRedisConnection().isEnabled()) - { - throw new CommandFailException("&cRedis is not enabled."); - } - plugin.getRedisConnection().getJedis().set("test", "123"); - send(sender, "Set test to 123. Now outputting key test..."); - send(sender, plugin.getRedisConnection().getJedis().get("test")); - plugin.getRedisConnection().getJedis().close(); + return null; } else if (args[0].equalsIgnoreCase("modules")) diff --git a/server/src/main/java/dev/plex/command/impl/brigadier/PlexBrigadierCMD.java b/server/src/main/java/dev/plex/command/impl/brigadier/PlexBrigadierCMD.java new file mode 100644 index 0000000..3a557fb --- /dev/null +++ b/server/src/main/java/dev/plex/command/impl/brigadier/PlexBrigadierCMD.java @@ -0,0 +1,96 @@ +package dev.plex.command.impl.brigadier; + +import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import dev.plex.command.PlexBrigadierCommand; +import dev.plex.command.annotation.CommandName; +import dev.plex.command.annotation.CommandPermission; +import dev.plex.command.annotation.Default; +import dev.plex.command.annotation.SubCommand; +import dev.plex.command.exception.CommandFailException; +import dev.plex.module.PlexModule; +import dev.plex.module.PlexModuleFile; +import dev.plex.util.BuildInfo; +import dev.plex.util.PlexUtils; +import dev.plex.util.TimeUtils; +import org.apache.commons.lang3.StringUtils; +import org.bukkit.command.CommandSender; + +import java.util.stream.Collectors; + +/** + * @author Taah + * @project Plex + * @since 3:46 PM [07-07-2023] + */ +@CommandName({"plex"}) +public class PlexBrigadierCMD extends PlexBrigadierCommand +{ + @SubCommand("reload") + @CommandPermission("plex.reload") + public void reloadPlex(CommandSender sender) + { + plugin.config.load(); + send(sender, "Reloaded config file"); + plugin.messages.load(); + send(sender, "Reloaded messages file"); + plugin.indefBans.load(false); + plugin.getPunishmentManager().mergeIndefiniteBans(); + send(sender, "Reloaded indefinite bans"); + plugin.commands.load(); + send(sender, "Reloaded blocked commands file"); + plugin.getRankManager().importDefaultRanks(); + send(sender, "Imported ranks"); + plugin.setSystem(plugin.config.getString("system")); + if (plugin.getSystem().equalsIgnoreCase("permissions") && !plugin.getServer().getPluginManager().isPluginEnabled("Vault")) + { + throw new RuntimeException("Vault is required to run on the server if you use permissions!"); + } + plugin.getServiceManager().endServices(); + plugin.getServiceManager().startServices(); + send(sender, "Restarted services."); + TimeUtils.TIMEZONE = plugin.config.getString("server.timezone"); + send(sender, "Set timezone to: " + TimeUtils.TIMEZONE); + send(sender, "Plex successfully reloaded."); + } + + @SubCommand("redis") + @CommandPermission("plex.redis") + public void testRedis(CommandSender sender) + { + if (!plugin.getRedisConnection().isEnabled()) + { + throw new CommandFailException("&cRedis is not enabled."); + } + plugin.getRedisConnection().getJedis().set("test", "123"); + send(sender, "Set test to 123. Now outputting key test..."); + send(sender, plugin.getRedisConnection().getJedis().get("test")); + plugin.getRedisConnection().getJedis().close(); + } + + @SubCommand("modules") + @CommandPermission("plex.modules") + public void viewModules(CommandSender sender) + { + send(sender, mmString("Modules (" + plugin.getModuleManager().getModules().size() + "): " + StringUtils.join(plugin.getModuleManager().getModules().stream().map(PlexModule::getPlexModuleFile).map(PlexModuleFile::getName).collect(Collectors.toList()), ", "))); + } + + @SubCommand("modules reload") + @CommandPermission("plex.modules.reload") + public void reloadModules(CommandSender sender) + { + plugin.getModuleManager().reloadModules(); + send(sender, mmString("All modules reloaded!")); + } + + @Default + public void defaultCommand(CommandSender sender) + { + send(sender, mmString("Plex - A new freedom plugin.")); + send(sender, mmString("Plugin version: " + plugin.getPluginMeta().getVersion() + " #" + BuildInfo.getNumber() + " Git: " + BuildInfo.getHead())); + send(sender, mmString("Authors: Telesphoreo, Taahh")); + send(sender, mmString("Built by: " + BuildInfo.getAuthor() + " on " + BuildInfo.getDate())); + send(sender, mmString("Run /plex modules to see a list of modules.")); + plugin.getUpdateChecker().getUpdateStatusMessage(sender, true, 2); + } +} diff --git a/server/src/main/java/dev/plex/util/ReflectionsUtil.java b/server/src/main/java/dev/plex/util/ReflectionsUtil.java index fc7198a..ee6e4e3 100644 --- a/server/src/main/java/dev/plex/util/ReflectionsUtil.java +++ b/server/src/main/java/dev/plex/util/ReflectionsUtil.java @@ -5,6 +5,8 @@ import com.google.common.reflect.ClassPath; import dev.plex.Plex; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; @@ -54,4 +56,31 @@ public class ReflectionsUtil }); return Collections.unmodifiableSet(classes); } + + public static void callVoid(Object obj, String function, Object... params) { + try + { + Method method = obj.getClass().getMethod(function, Arrays.stream(params).map(Object::getClass).toArray(Class[]::new)); + method.setAccessible(true); + method.invoke(obj, params); + } + catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) + { + throw new RuntimeException(e); + } + } + + public static T callFunction(Object obj, String function, Object... params) { + try + { + Method method = obj.getClass().getMethod(function, Arrays.stream(params).map(Object::getClass).toArray(Class[]::new)); + method.setAccessible(true); + return (T) method.invoke(obj, params); + } + catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) + { + throw new RuntimeException(e); + } + } + }