diff --git a/core/src/main/java/com/rexcantor64/triton/Triton.java b/core/src/main/java/com/rexcantor64/triton/Triton.java index 79eb0eda..f77cba6d 100644 --- a/core/src/main/java/com/rexcantor64/triton/Triton.java +++ b/core/src/main/java/com/rexcantor64/triton/Triton.java @@ -1,5 +1,6 @@ package com.rexcantor64.triton; +import com.google.gson.JsonElement; import com.rexcantor64.triton.api.language.LanguageParser; import com.rexcantor64.triton.api.legacy.LegacyLanguageParser; import com.rexcantor64.triton.bridge.BridgeManager; @@ -19,8 +20,8 @@ import com.rexcantor64.triton.logger.TritonLogger; import com.rexcantor64.triton.migration.LanguageMigration; import com.rexcantor64.triton.packetinterceptor.PacketEventsManager; -import com.rexcantor64.triton.player.TritonLanguagePlayer; import com.rexcantor64.triton.player.PlayerManager; +import com.rexcantor64.triton.player.TritonLanguagePlayer; import com.rexcantor64.triton.plugin.Platform; import com.rexcantor64.triton.plugin.PluginLoader; import com.rexcantor64.triton.storage.LocalStorage; @@ -31,6 +32,7 @@ import com.rexcantor64.triton.web.TwinManager; import lombok.Getter; import lombok.val; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -193,6 +195,8 @@ public Configuration loadYAML(String fileName, String internalFileName) { public abstract String getVersion(); + public abstract @NotNull JsonElement getPlatformDebugInfo(); + protected abstract void startConfigRefreshTask(); public abstract void runAsync(Runnable runnable); diff --git a/core/src/main/java/com/rexcantor64/triton/commands/DebugCommand.java b/core/src/main/java/com/rexcantor64/triton/commands/DebugCommand.java index 87afeda1..63a34485 100644 --- a/core/src/main/java/com/rexcantor64/triton/commands/DebugCommand.java +++ b/core/src/main/java/com/rexcantor64/triton/commands/DebugCommand.java @@ -8,6 +8,7 @@ import com.rexcantor64.triton.commands.handler.exceptions.NoPermissionException; import com.rexcantor64.triton.commands.handler.exceptions.UnsupportedPlatformException; import com.rexcantor64.triton.debug.LoadDump; +import com.rexcantor64.triton.utils.DebugUtils; import lombok.val; import java.io.IOException; @@ -65,6 +66,9 @@ public void handleCommand(CommandEvent event) throws NoPermissionException, Unsu } switch (subcommand.get()) { + case INFO: + handleInfoCommand(event); + break; case DUMP: handleDumpCommand(event); break; @@ -74,6 +78,15 @@ public void handleCommand(CommandEvent event) throws NoPermissionException, Unsu } } + public void handleInfoCommand(CommandEvent event) { + val debugInfo = DebugUtils.generateDebugInfo(); + val path = DebugUtils.saveDebugInfo(debugInfo); + if (path == null) { + sendMessage(event, "debug.info.error"); + } else { + sendMessage(event, "debug.info.success", path); + } + } public void handleDumpCommand(CommandEvent event) { val sender = event.getSender(); @@ -302,6 +315,7 @@ private enum TargetPlatform { } private enum Subcommand { + INFO, DUMP, LOAD } diff --git a/core/src/main/java/com/rexcantor64/triton/config/MainConfig.java b/core/src/main/java/com/rexcantor64/triton/config/MainConfig.java index 583329c4..1f7106d4 100644 --- a/core/src/main/java/com/rexcantor64/triton/config/MainConfig.java +++ b/core/src/main/java/com/rexcantor64/triton/config/MainConfig.java @@ -9,6 +9,7 @@ import com.rexcantor64.triton.api.config.TritonConfig; import com.rexcantor64.triton.config.interfaces.Configuration; import com.rexcantor64.triton.language.Language; +import com.rexcantor64.triton.utils.DebugUtils.GsonExclude; import com.rexcantor64.triton.utils.YAMLUtils; import lombok.Cleanup; import lombok.Getter; @@ -42,6 +43,7 @@ public class MainConfig implements TritonConfig { }.getType(); @ToString.Exclude + @GsonExclude private transient final Triton main; @Setter private List languages; @@ -54,6 +56,7 @@ public class MainConfig implements TritonConfig { private boolean bungeecord; private int configAutoRefresh; @ToString.Exclude + @GsonExclude private String twinToken; private List commandAliases; private String parser = "adventure"; @@ -103,16 +106,22 @@ public class MainConfig implements TritonConfig { private String storageType = "local"; private String serverName; @ToString.Exclude + @GsonExclude private String databaseHost; @ToString.Exclude + @GsonExclude private int databasePort; @ToString.Exclude + @GsonExclude private String databaseName; @ToString.Exclude + @GsonExclude private String databaseUser; @ToString.Exclude + @GsonExclude private String databasePassword; @ToString.Exclude + @GsonExclude private String databaseTablePrefix; private int databaseMysqlPoolMaxSize; private int databaseMysqlPoolMinIdle; @@ -333,6 +342,8 @@ public static class FeatureSyntax implements com.rexcantor64.triton.api.config.F private final String lang; private final String args; private final String arg; + @ToString.Exclude + @GsonExclude private boolean interactive = false; private static FeatureSyntax fromSection(Configuration section) { diff --git a/core/src/main/java/com/rexcantor64/triton/utils/DebugUtils.java b/core/src/main/java/com/rexcantor64/triton/utils/DebugUtils.java new file mode 100644 index 00000000..460ca3df --- /dev/null +++ b/core/src/main/java/com/rexcantor64/triton/utils/DebugUtils.java @@ -0,0 +1,94 @@ +package com.rexcantor64.triton.utils; + +import com.google.gson.ExclusionStrategy; +import com.google.gson.FieldAttributes; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.rexcantor64.triton.Triton; +import lombok.Cleanup; +import lombok.val; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.nio.file.Path; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +public class DebugUtils { + + public static String generateDebugInfo() { + Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() { + @Override + public boolean shouldSkipField(FieldAttributes fieldAttributes) { + return fieldAttributes.getAnnotation(GsonExclude.class) != null; + } + + @Override + public boolean shouldSkipClass(Class clazz) { + return clazz.getAnnotation(GsonExclude.class) != null; + } + }).setPrettyPrinting().create(); + val instance = Triton.get(); + + JsonObject root = new JsonObject(); + root.addProperty("tritonVersion", instance.getVersion()); + root.addProperty("platform", Triton.platform().toString()); + root.add("loaderFlags", gson.toJsonTree(instance.getLoader().getLoaderFlags())); + root.add("platformData", instance.getPlatformDebugInfo()); + root.addProperty("textTranslationCount", instance.getTranslationManager().getTextTranslationCount()); + root.addProperty("signTranslationCount", instance.getTranslationManager().getSignTranslationCount()); + root.add("config", gson.toJsonTree(instance.getConfig())); + + return gson.toJson(root); + } + + /** + * + * @return The path of the saved file, relative to the plugin data dir + */ + public static @Nullable String saveDebugInfo(@NotNull String contents) { + String date = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH_mm_ss")); + + String fileName = String.format("triton-%s-%s.json", Triton.platform().toString().toLowerCase(Locale.ROOT), date); + + Path tritonFolderPath = Triton.get().getDataFolder().toPath(); + Path infoFolderPath = tritonFolderPath.resolve("debug-info"); + Path infoPath = infoFolderPath.resolve(fileName); + + File infoFolderFile = infoFolderPath.toFile(); + if (!infoFolderFile.isDirectory() && !infoFolderFile.mkdirs()) { + Triton.get().getLogger().logError("Failed to create \"%1\" folder!", infoFolderPath.toAbsolutePath().toString()); + return null; + } + + File infoFile = infoPath.toFile(); + + try { + @Cleanup + val writer = new BufferedWriter(new FileWriter(infoFile, true)); + + writer.write(contents); + writer.write("\n"); + + return tritonFolderPath.relativize(infoPath).toString(); + } catch (IOException exception) { + Triton.get().getLogger().logError(exception, "Failed writing to debug info %1!", infoPath.toString()); + return null; + } + } + + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.FIELD) + public @interface GsonExclude { + } +} diff --git a/core/src/main/resources/messages.yml b/core/src/main/resources/messages.yml index feacf60b..a61269cc 100644 --- a/core/src/main/resources/messages.yml +++ b/core/src/main/resources/messages.yml @@ -101,6 +101,9 @@ debug: invalid: "&cProvided line number '&4%1&c' is not a valid number" success: sent: '&aSend &6%1&a message(s)!' + info: + error: "&cThere was an error dumping info about the plugin. See console for details." + success: "&aDumped plugin info to /%1" info-command: - "&8-------[ &b&lTriton&8 ]-------" diff --git a/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/BungeeTriton.java b/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/BungeeTriton.java index 86c01bb8..b704c62d 100644 --- a/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/BungeeTriton.java +++ b/triton-bungeecord/src/main/java/com/rexcantor64/triton/bungeecord/BungeeTriton.java @@ -1,5 +1,7 @@ package com.rexcantor64.triton.bungeecord; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.rexcantor64.triton.Triton; import com.rexcantor64.triton.bungeecord.bridge.BungeeBridgeManager; import com.rexcantor64.triton.bungeecord.commands.handler.BungeeCommand; @@ -21,13 +23,16 @@ import net.md_5.bungee.api.connection.Connection; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.plugin.PluginDescription; import net.md_5.bungee.api.scheduler.ScheduledTask; import net.md_5.bungee.netty.PipelineUtils; import org.bstats.bungeecord.Metrics; import org.bstats.charts.SingleLineChart; +import org.jetbrains.annotations.NotNull; import java.io.File; import java.lang.reflect.Method; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -171,4 +176,20 @@ public UUID getPlayerUUIDFromString(String input) { protected String getConfigFileName() { return "config_bungeecord"; } + + private Optional getPacketEventsVersion() { + return Optional.ofNullable(getLoader().getPlugin().getProxy().getPluginManager().getPlugin("packetevents")) + .map(Plugin::getDescription) + .map(PluginDescription::getVersion); + } + + @Override + public @NotNull JsonElement getPlatformDebugInfo() { + val obj = new JsonObject(); + val proxy = getLoader().getPlugin().getProxy(); + obj.addProperty("serverName", proxy.getName()); + obj.addProperty("serverVersion", proxy.getVersion()); + getPacketEventsVersion().ifPresent(version -> obj.addProperty("packetEventsVersion", version)); + return obj; + } } diff --git a/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/SpigotTriton.java b/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/SpigotTriton.java index 0c5b5114..0856213b 100644 --- a/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/SpigotTriton.java +++ b/triton-spigot/src/main/java/com/rexcantor64/triton/spigot/SpigotTriton.java @@ -1,5 +1,7 @@ package com.rexcantor64.triton.spigot; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.rexcantor64.triton.Triton; import com.rexcantor64.triton.api.players.LanguagePlayer; import com.rexcantor64.triton.player.PlayerManager; @@ -30,7 +32,9 @@ import org.bukkit.command.CommandMap; import org.bukkit.command.PluginCommand; import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; @@ -281,4 +285,28 @@ public boolean isPaperProxyMode() { return false; } } + + private Optional getProtocolLibVersion() { + return getPluginVersion("ProtocolLib"); + } + + private Optional getPacketEventsVersion() { + return getPluginVersion("packetevents"); + } + + private Optional getPluginVersion(String name) { + return Optional.ofNullable(Bukkit.getPluginManager().getPlugin(name)) + .map(Plugin::getDescription) + .map(PluginDescriptionFile::getVersion); + } + + @Override + public @NotNull JsonElement getPlatformDebugInfo() { + val obj = new JsonObject(); + obj.addProperty("serverName", Bukkit.getName()); + obj.addProperty("serverVersion", Bukkit.getVersion()); + getProtocolLibVersion().ifPresent(version -> obj.addProperty("protocolLibVersion", version)); + getPacketEventsVersion().ifPresent(version -> obj.addProperty("packetEventsVersion", version)); + return obj; + } } diff --git a/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/VelocityTriton.java b/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/VelocityTriton.java index 4f93e76e..d8a1f353 100644 --- a/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/VelocityTriton.java +++ b/triton-velocity/src/main/java/com/rexcantor64/triton/velocity/VelocityTriton.java @@ -1,5 +1,7 @@ package com.rexcantor64.triton.velocity; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.rexcantor64.triton.Triton; import com.rexcantor64.triton.player.PlayerManager; import com.rexcantor64.triton.plugin.PluginLoader; @@ -10,6 +12,8 @@ import com.rexcantor64.triton.velocity.packetinterceptor.VelocityPacketEventsManager; import com.rexcantor64.triton.velocity.player.VelocityLanguagePlayer; import com.rexcantor64.triton.velocity.plugin.VelocityPlugin; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; @@ -19,8 +23,10 @@ import lombok.val; import org.bstats.charts.SingleLineChart; import org.bstats.velocity.Metrics; +import org.jetbrains.annotations.NotNull; import java.io.File; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -129,4 +135,21 @@ public UUID getPlayerUUIDFromString(String input) { protected String getConfigFileName() { return "config_velocity"; } + + private Optional getPacketEventsVersion() { + return getLoader().getServer().getPluginManager().getPlugin("packetevents") + .map(PluginContainer::getDescription) + .flatMap(PluginDescription::getVersion); + } + + @Override + public @NotNull JsonElement getPlatformDebugInfo() { + val obj = new JsonObject(); + val version = getLoader().getServer().getVersion(); + obj.addProperty("serverName", version.getName()); + obj.addProperty("serverVersion", version.getVersion()); + obj.addProperty("serverVendor", version.getVendor()); + getPacketEventsVersion().ifPresent(v -> obj.addProperty("packetEventsVersion", v)); + return obj; + } }