diff options
| author | Myles <43725835+MylesAndMore@users.noreply.github.com> | 2023-06-18 21:41:17 +0200 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-06-18 21:41:17 +0200 | 
| commit | 88d7136310199e6c52c7a75108991b48630cf756 (patch) | |
| tree | f33251576ea8a479cc7fdfc0751455a1b858afd1 | |
| parent | ee11892063a796c602948676be4de22d3f717131 (diff) | |
| parent | 26daae32788c2229c06ee7eb9ce2061dcb2a327d (diff) | |
| download | Tumble-88d7136310199e6c52c7a75108991b48630cf756.tar.gz Tumble-88d7136310199e6c52c7a75108991b48630cf756.tar.bz2 Tumble-88d7136310199e6c52c7a75108991b48630cf756.zip | |
Merge pull request #8 from MylesAndMore/dev
v1.0.4
| -rw-r--r-- | README.md | 6 | ||||
| -rw-r--r-- | build.gradle | 24 | ||||
| -rw-r--r-- | src/main/java/com/MylesAndMore/Tumble/Main.java | 19 | ||||
| -rw-r--r-- | src/main/java/com/MylesAndMore/Tumble/plugin/Metrics.java | 865 | ||||
| -rw-r--r-- | src/main/resources/plugin.yml | 7 | 
5 files changed, 42 insertions, 879 deletions
| @@ -20,7 +20,7 @@ If you've never heard of it, [Tumble](https://minecraft-archive.fandom.com/wiki/  ## Setup -1. Simply [download](https://github.com/MylesAndMore/tumble/releases) the plugin's JAR file and place it in your server's plugins directory.   +1. Simply [download](https://github.com/MylesAndMore/Tumble/releases) the plugin's JAR file and place it in your server's plugins directory.        - *Note: Multiverse is also required for the plugin to run, you may download it [here](https://www.spigotmc.org/resources/multiverse-core.390/).*   @@ -36,7 +36,7 @@ If you've never heard of it, [Tumble](https://minecraft-archive.fandom.com/wiki/  Scroll down for more options to configure your game.   -## Commands +## Commands/Permissions  - ```/tumble:reload``` @@ -59,6 +59,8 @@ Scroll down for more options to configure your game.    - *Description:* Configures the auto start functions of Tumble.    - *Usage:* ```/tumble:autostart <playerAmount> [enable|disable]```    - *Permission:* ```tumble.autostart``` +- *Permission:* ```tumble.update``` +  - Players with this permission will receive a notification upon joining if Tumble is out of date.  ## Configuration   diff --git a/build.gradle b/build.gradle index f751f6b..8791ad3 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@  plugins {      id 'java-library' +    id "com.github.johnrengelman.shadow" version "7.1.0"  }  java { @@ -8,17 +9,28 @@ java {      }  } -group 'Tumble' -version '1.0.3' -  repositories {      mavenCentral() // Use Maven Central for resolving dependencies.      maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }      maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' } -    maven { url = "https://repo.onarandombox.com/content/groups/public/" } +    maven { url "https://repo.onarandombox.com/content/groups/public/" } +    maven { url "https://repo.jeff-media.com/public/"}  }  dependencies { -    compileOnly 'org.spigotmc:spigot-api:1.19.2-R0.1-SNAPSHOT' -    compileOnly 'com.onarandombox.multiversecore:Multiverse-Core:4.3.1' +    compileOnly('org.spigotmc:spigot-api:1.19.2-R0.1-SNAPSHOT') +    compileOnly('com.onarandombox.multiversecore:Multiverse-Core:4.3.1') +    implementation('org.bstats:bstats-bukkit:3.0.2') +    implementation('com.jeff_media:SpigotUpdateChecker:3.0.3') +} + +// Disable generation of the normal JAR to reduce confusion and only generate the correctly shadowed JAR including bStats +jar.enabled = false +jar.finalizedBy(shadowJar) +shadowJar { +    archiveBaseName.set('Tumble') +    archiveClassifier.set('') +    archiveVersion.set('1.0.4') +    relocate 'org.bstats', 'com.MylesAndMore.bstats' +    relocate 'com.jeff_media.updatechecker', 'com.MylesAndMore.updatechecker'  } diff --git a/src/main/java/com/MylesAndMore/Tumble/Main.java b/src/main/java/com/MylesAndMore/Tumble/Main.java index c4bb4f5..c264498 100644 --- a/src/main/java/com/MylesAndMore/Tumble/Main.java +++ b/src/main/java/com/MylesAndMore/Tumble/Main.java @@ -1,9 +1,14 @@  package com.MylesAndMore.Tumble;  import com.MylesAndMore.Tumble.commands.*; -import com.MylesAndMore.Tumble.plugin.Metrics;  import com.MylesAndMore.Tumble.plugin.Constants;  import com.MylesAndMore.Tumble.plugin.EventListener; + +import com.jeff_media.updatechecker.UpdateCheckSource; +import com.jeff_media.updatechecker.UpdateChecker; + +import org.bstats.bukkit.Metrics; +  import org.bukkit.Bukkit;  import org.bukkit.plugin.java.JavaPlugin; @@ -17,9 +22,8 @@ public class Main extends JavaPlugin{          this.getCommand("start").setExecutor(new StartGame());          this.getCommand("winlocation").setExecutor(new SetWinnerLoc());          this.getCommand("autostart").setExecutor(new SetAutoStart()); -        int pluginId = 16940; -        Metrics metrics = new Metrics(this, 16940); -        this.saveDefaultConfig(); // Saves the default config file (packaged in the JAR) if we haven't already\ +        new Metrics(this, 16940); +        this.saveDefaultConfig(); // Saves the default config file (packaged in the JAR) if we haven't already          // Check if worlds are null in config and throw warnings if so          if (Constants.getGameWorld() == null) { @@ -30,6 +34,13 @@ public class Main extends JavaPlugin{              Bukkit.getServer().getLogger().warning("[Tumble] It appears you have not configured a lobby world for Tumble.");              Bukkit.getServer().getLogger().info("[Tumble] If this is your first time running the plugin, you may disregard this message.");          } + +        new UpdateChecker(this, UpdateCheckSource.SPIGET, "106721") +                .setDownloadLink("https://github.com/MylesAndMore/Tumble/releases") +                .setNotifyByPermissionOnJoin("tumble.update") // only this permission node is notified NOT all OPs so people can unsubscribe if they wish +                .checkEveryXHours(336) // (every 2 weeks) +                .checkNow(); +          Bukkit.getServer().getLogger().info("[Tumble] Tumble successfully enabled!");      }  }
\ No newline at end of file diff --git a/src/main/java/com/MylesAndMore/Tumble/plugin/Metrics.java b/src/main/java/com/MylesAndMore/Tumble/plugin/Metrics.java deleted file mode 100644 index 3432a93..0000000 --- a/src/main/java/com/MylesAndMore/Tumble/plugin/Metrics.java +++ /dev/null @@ -1,865 +0,0 @@ -// Tumble--do not remove or modify this file! - -/* - * This Metrics class was auto-generated and can be copied into your project if you are - * not using a build tool like Gradle or Maven for dependency management. - * - * IMPORTANT: You are not allowed to modify this class, except changing the package. - * - * Unallowed modifications include but are not limited to: - *  - Remove the option for users to opt-out - *  - Change the frequency for data submission - *  - Obfuscate the code (every obfucator should allow you to make an exception for specific files) - *  - Reformat the code (if you use a linter, add an exception) - * - * Violations will result in a ban of your plugin and account from bStats. - */ -package com.MylesAndMore.Tumble.plugin; - -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.reflect.Method; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.zip.GZIPOutputStream; -import javax.net.ssl.HttpsURLConnection; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; - -public class Metrics { - -    private final Plugin plugin; - -    private final MetricsBase metricsBase; - -    /** -     * Creates a new Metrics instance. -     * -     * @param plugin Your plugin instance. -     * @param serviceId The id of the service. It can be found at <a -     *     href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a> -     */ -    public Metrics(JavaPlugin plugin, int serviceId) { -        this.plugin = plugin; -        // Get the config file -        File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); -        File configFile = new File(bStatsFolder, "config.yml"); -        YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); -        if (!config.isSet("serverUuid")) { -            config.addDefault("enabled", true); -            config.addDefault("serverUuid", UUID.randomUUID().toString()); -            config.addDefault("logFailedRequests", false); -            config.addDefault("logSentData", false); -            config.addDefault("logResponseStatusText", false); -            // Inform the server owners about bStats -            config -                    .options() -                    .header( -                            "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" -                                    + "many people use their plugin and their total player count. It's recommended to keep bStats\n" -                                    + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" -                                    + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" -                                    + "anonymous.") -                    .copyDefaults(true); -            try { -                config.save(configFile); -            } catch (IOException ignored) { -            } -        } -        // Load the data -        boolean enabled = config.getBoolean("enabled", true); -        String serverUUID = config.getString("serverUuid"); -        boolean logErrors = config.getBoolean("logFailedRequests", false); -        boolean logSentData = config.getBoolean("logSentData", false); -        boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); -        metricsBase = -                new MetricsBase( -                        "bukkit", -                        serverUUID, -                        serviceId, -                        enabled, -                        this::appendPlatformData, -                        this::appendServiceData, -                        submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), -                        plugin::isEnabled, -                        (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), -                        (message) -> this.plugin.getLogger().log(Level.INFO, message), -                        logErrors, -                        logSentData, -                        logResponseStatusText); -    } - -    /** -     * Adds a custom chart. -     * -     * @param chart The chart to add. -     */ -    public void addCustomChart(CustomChart chart) { -        metricsBase.addCustomChart(chart); -    } - -    private void appendPlatformData(JsonObjectBuilder builder) { -        builder.appendField("playerAmount", getPlayerAmount()); -        builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); -        builder.appendField("bukkitVersion", Bukkit.getVersion()); -        builder.appendField("bukkitName", Bukkit.getName()); -        builder.appendField("javaVersion", System.getProperty("java.version")); -        builder.appendField("osName", System.getProperty("os.name")); -        builder.appendField("osArch", System.getProperty("os.arch")); -        builder.appendField("osVersion", System.getProperty("os.version")); -        builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); -    } - -    private void appendServiceData(JsonObjectBuilder builder) { -        builder.appendField("pluginVersion", plugin.getDescription().getVersion()); -    } - -    private int getPlayerAmount() { -        try { -            // Around MC 1.8 the return type was changed from an array to a collection, -            // This fixes java.lang.NoSuchMethodError: -            // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; -            Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); -            return onlinePlayersMethod.getReturnType().equals(Collection.class) -                    ? ((Collection<?>) onlinePlayersMethod.invoke(Bukkit.getServer())).size() -                    : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; -        } catch (Exception e) { -            // Just use the new method if the reflection failed -            return Bukkit.getOnlinePlayers().size(); -        } -    } - -    public static class MetricsBase { - -        /** The version of the Metrics class. */ -        public static final String METRICS_VERSION = "3.0.0"; - -        private static final ScheduledExecutorService scheduler = -                Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); - -        private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; - -        private final String platform; - -        private final String serverUuid; - -        private final int serviceId; - -        private final Consumer<JsonObjectBuilder> appendPlatformDataConsumer; - -        private final Consumer<JsonObjectBuilder> appendServiceDataConsumer; - -        private final Consumer<Runnable> submitTaskConsumer; - -        private final Supplier<Boolean> checkServiceEnabledSupplier; - -        private final BiConsumer<String, Throwable> errorLogger; - -        private final Consumer<String> infoLogger; - -        private final boolean logErrors; - -        private final boolean logSentData; - -        private final boolean logResponseStatusText; - -        private final Set<CustomChart> customCharts = new HashSet<>(); - -        private final boolean enabled; - -        /** -         * Creates a new MetricsBase class instance. -         * -         * @param platform The platform of the service. -         * @param serviceId The id of the service. -         * @param serverUuid The server uuid. -         * @param enabled Whether or not data sending is enabled. -         * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and -         *     appends all platform-specific data. -         * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and -         *     appends all service-specific data. -         * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be -         *     used to delegate the data collection to a another thread to prevent errors caused by -         *     concurrency. Can be {@code null}. -         * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. -         * @param errorLogger A consumer that accepts log message and an error. -         * @param infoLogger A consumer that accepts info log messages. -         * @param logErrors Whether or not errors should be logged. -         * @param logSentData Whether or not the sent data should be logged. -         * @param logResponseStatusText Whether or not the response status text should be logged. -         */ -        public MetricsBase( -                String platform, -                String serverUuid, -                int serviceId, -                boolean enabled, -                Consumer<JsonObjectBuilder> appendPlatformDataConsumer, -                Consumer<JsonObjectBuilder> appendServiceDataConsumer, -                Consumer<Runnable> submitTaskConsumer, -                Supplier<Boolean> checkServiceEnabledSupplier, -                BiConsumer<String, Throwable> errorLogger, -                Consumer<String> infoLogger, -                boolean logErrors, -                boolean logSentData, -                boolean logResponseStatusText) { -            this.platform = platform; -            this.serverUuid = serverUuid; -            this.serviceId = serviceId; -            this.enabled = enabled; -            this.appendPlatformDataConsumer = appendPlatformDataConsumer; -            this.appendServiceDataConsumer = appendServiceDataConsumer; -            this.submitTaskConsumer = submitTaskConsumer; -            this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; -            this.errorLogger = errorLogger; -            this.infoLogger = infoLogger; -            this.logErrors = logErrors; -            this.logSentData = logSentData; -            this.logResponseStatusText = logResponseStatusText; -            checkRelocation(); -            if (enabled) { -                // WARNING: Removing the option to opt-out will get your plugin banned from bStats -                startSubmitting(); -            } -        } - -        public void addCustomChart(CustomChart chart) { -            this.customCharts.add(chart); -        } - -        private void startSubmitting() { -            final Runnable submitTask = -                    () -> { -                        if (!enabled || !checkServiceEnabledSupplier.get()) { -                            // Submitting data or service is disabled -                            scheduler.shutdown(); -                            return; -                        } -                        if (submitTaskConsumer != null) { -                            submitTaskConsumer.accept(this::submitData); -                        } else { -                            this.submitData(); -                        } -                    }; -            // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution -            // of requests on the -            // bStats backend. To circumvent this problem, we introduce some randomness into the initial -            // and second delay. -            // WARNING: You must not modify and part of this Metrics class, including the submit delay or -            // frequency! -            // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! -            long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); -            long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); -            scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); -            scheduler.scheduleAtFixedRate( -                    submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); -        } - -        private void submitData() { -            final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); -            appendPlatformDataConsumer.accept(baseJsonBuilder); -            final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); -            appendServiceDataConsumer.accept(serviceJsonBuilder); -            JsonObjectBuilder.JsonObject[] chartData = -                    customCharts.stream() -                            .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) -                            .filter(Objects::nonNull) -                            .toArray(JsonObjectBuilder.JsonObject[]::new); -            serviceJsonBuilder.appendField("id", serviceId); -            serviceJsonBuilder.appendField("customCharts", chartData); -            baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); -            baseJsonBuilder.appendField("serverUUID", serverUuid); -            baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); -            JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); -            scheduler.execute( -                    () -> { -                        try { -                            // Send the data -                            sendData(data); -                        } catch (Exception e) { -                            // Something went wrong! :( -                            if (logErrors) { -                                errorLogger.accept("Could not submit bStats metrics data", e); -                            } -                        } -                    }); -        } - -        private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { -            if (logSentData) { -                infoLogger.accept("Sent bStats metrics data: " + data.toString()); -            } -            String url = String.format(REPORT_URL, platform); -            HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); -            // Compress the data to save bandwidth -            byte[] compressedData = compress(data.toString()); -            connection.setRequestMethod("POST"); -            connection.addRequestProperty("Accept", "application/json"); -            connection.addRequestProperty("Connection", "close"); -            connection.addRequestProperty("Content-Encoding", "gzip"); -            connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); -            connection.setRequestProperty("Content-Type", "application/json"); -            connection.setRequestProperty("User-Agent", "Metrics-Service/1"); -            connection.setDoOutput(true); -            try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { -                outputStream.write(compressedData); -            } -            StringBuilder builder = new StringBuilder(); -            try (BufferedReader bufferedReader = -                         new BufferedReader(new InputStreamReader(connection.getInputStream()))) { -                String line; -                while ((line = bufferedReader.readLine()) != null) { -                    builder.append(line); -                } -            } -            if (logResponseStatusText) { -                infoLogger.accept("Sent data to bStats and received response: " + builder); -            } -        } - -        /** Checks that the class was properly relocated. */ -        private void checkRelocation() { -            // You can use the property to disable the check in your test environment -            if (System.getProperty("bstats.relocatecheck") == null -                    || !System.getProperty("bstats.relocatecheck").equals("false")) { -                // Maven's Relocate is clever and changes strings, too. So we have to use this little -                // "trick" ... :D -                final String defaultPackage = -                        new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'}); -                final String examplePackage = -                        new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); -                // We want to make sure no one just copy & pastes the example and uses the wrong package -                // names -                if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) -                        || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { -                    throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); -                } -            } -        } - -        /** -         * Gzips the given string. -         * -         * @param str The string to gzip. -         * @return The gzipped string. -         */ -        private static byte[] compress(final String str) throws IOException { -            if (str == null) { -                return null; -            } -            ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); -            try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { -                gzip.write(str.getBytes(StandardCharsets.UTF_8)); -            } -            return outputStream.toByteArray(); -        } -    } - -    public static class DrilldownPie extends CustomChart { - -        private final Callable<Map<String, Map<String, Integer>>> callable; - -        /** -         * Class constructor. -         * -         * @param chartId The id of the chart. -         * @param callable The callable which is used to request the chart data. -         */ -        public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) { -            super(chartId); -            this.callable = callable; -        } - -        @Override -        public JsonObjectBuilder.JsonObject getChartData() throws Exception { -            JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); -            Map<String, Map<String, Integer>> map = callable.call(); -            if (map == null || map.isEmpty()) { -                // Null = skip the chart -                return null; -            } -            boolean reallyAllSkipped = true; -            for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) { -                JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); -                boolean allSkipped = true; -                for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) { -                    valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); -                    allSkipped = false; -                } -                if (!allSkipped) { -                    reallyAllSkipped = false; -                    valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); -                } -            } -            if (reallyAllSkipped) { -                // Null = skip the chart -                return null; -            } -            return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); -        } -    } - -    public static class AdvancedPie extends CustomChart { - -        private final Callable<Map<String, Integer>> callable; - -        /** -         * Class constructor. -         * -         * @param chartId The id of the chart. -         * @param callable The callable which is used to request the chart data. -         */ -        public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) { -            super(chartId); -            this.callable = callable; -        } - -        @Override -        protected JsonObjectBuilder.JsonObject getChartData() throws Exception { -            JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); -            Map<String, Integer> map = callable.call(); -            if (map == null || map.isEmpty()) { -                // Null = skip the chart -                return null; -            } -            boolean allSkipped = true; -            for (Map.Entry<String, Integer> entry : map.entrySet()) { -                if (entry.getValue() == 0) { -                    // Skip this invalid -                    continue; -                } -                allSkipped = false; -                valuesBuilder.appendField(entry.getKey(), entry.getValue()); -            } -            if (allSkipped) { -                // Null = skip the chart -                return null; -            } -            return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); -        } -    } - -    public static class MultiLineChart extends CustomChart { - -        private final Callable<Map<String, Integer>> callable; - -        /** -         * Class constructor. -         * -         * @param chartId The id of the chart. -         * @param callable The callable which is used to request the chart data. -         */ -        public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) { -            super(chartId); -            this.callable = callable; -        } - -        @Override -        protected JsonObjectBuilder.JsonObject getChartData() throws Exception { -            JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); -            Map<String, Integer> map = callable.call(); -            if (map == null || map.isEmpty()) { -                // Null = skip the chart -                return null; -            } -            boolean allSkipped = true; -            for (Map.Entry<String, Integer> entry : map.entrySet()) { -                if (entry.getValue() == 0) { -                    // Skip this invalid -                    continue; -                } -                allSkipped = false; -                valuesBuilder.appendField(entry.getKey(), entry.getValue()); -            } -            if (allSkipped) { -                // Null = skip the chart -                return null; -            } -            return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); -        } -    } - -    public static class SimpleBarChart extends CustomChart { - -        private final Callable<Map<String, Integer>> callable; - -        /** -         * Class constructor. -         * -         * @param chartId The id of the chart. -         * @param callable The callable which is used to request the chart data. -         */ -        public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) { -            super(chartId); -            this.callable = callable; -        } - -        @Override -        protected JsonObjectBuilder.JsonObject getChartData() throws Exception { -            JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); -            Map<String, Integer> map = callable.call(); -            if (map == null || map.isEmpty()) { -                // Null = skip the chart -                return null; -            } -            for (Map.Entry<String, Integer> entry : map.entrySet()) { -                valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); -            } -            return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); -        } -    } - -    public abstract static class CustomChart { - -        private final String chartId; - -        protected CustomChart(String chartId) { -            if (chartId == null) { -                throw new IllegalArgumentException("chartId must not be null"); -            } -            this.chartId = chartId; -        } - -        public JsonObjectBuilder.JsonObject getRequestJsonObject( -                BiConsumer<String, Throwable> errorLogger, boolean logErrors) { -            JsonObjectBuilder builder = new JsonObjectBuilder(); -            builder.appendField("chartId", chartId); -            try { -                JsonObjectBuilder.JsonObject data = getChartData(); -                if (data == null) { -                    // If the data is null we don't send the chart. -                    return null; -                } -                builder.appendField("data", data); -            } catch (Throwable t) { -                if (logErrors) { -                    errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); -                } -                return null; -            } -            return builder.build(); -        } - -        protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; -    } - -    public static class SimplePie extends CustomChart { - -        private final Callable<String> callable; - -        /** -         * Class constructor. -         * -         * @param chartId The id of the chart. -         * @param callable The callable which is used to request the chart data. -         */ -        public SimplePie(String chartId, Callable<String> callable) { -            super(chartId); -            this.callable = callable; -        } - -        @Override -        protected JsonObjectBuilder.JsonObject getChartData() throws Exception { -            String value = callable.call(); -            if (value == null || value.isEmpty()) { -                // Null = skip the chart -                return null; -            } -            return new JsonObjectBuilder().appendField("value", value).build(); -        } -    } - -    public static class AdvancedBarChart extends CustomChart { - -        private final Callable<Map<String, int[]>> callable; - -        /** -         * Class constructor. -         * -         * @param chartId The id of the chart. -         * @param callable The callable which is used to request the chart data. -         */ -        public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) { -            super(chartId); -            this.callable = callable; -        } - -        @Override -        protected JsonObjectBuilder.JsonObject getChartData() throws Exception { -            JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); -            Map<String, int[]> map = callable.call(); -            if (map == null || map.isEmpty()) { -                // Null = skip the chart -                return null; -            } -            boolean allSkipped = true; -            for (Map.Entry<String, int[]> entry : map.entrySet()) { -                if (entry.getValue().length == 0) { -                    // Skip this invalid -                    continue; -                } -                allSkipped = false; -                valuesBuilder.appendField(entry.getKey(), entry.getValue()); -            } -            if (allSkipped) { -                // Null = skip the chart -                return null; -            } -            return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); -        } -    } - -    public static class SingleLineChart extends CustomChart { - -        private final Callable<Integer> callable; - -        /** -         * Class constructor. -         * -         * @param chartId The id of the chart. -         * @param callable The callable which is used to request the chart data. -         */ -        public SingleLineChart(String chartId, Callable<Integer> callable) { -            super(chartId); -            this.callable = callable; -        } - -        @Override -        protected JsonObjectBuilder.JsonObject getChartData() throws Exception { -            int value = callable.call(); -            if (value == 0) { -                // Null = skip the chart -                return null; -            } -            return new JsonObjectBuilder().appendField("value", value).build(); -        } -    } - -    /** -     * An extremely simple JSON builder. -     * -     * <p>While this class is neither feature-rich nor the most performant one, it's sufficient enough -     * for its use-case. -     */ -    public static class JsonObjectBuilder { - -        private StringBuilder builder = new StringBuilder(); - -        private boolean hasAtLeastOneField = false; - -        public JsonObjectBuilder() { -            builder.append("{"); -        } - -        /** -         * Appends a null field to the JSON. -         * -         * @param key The key of the field. -         * @return A reference to this object. -         */ -        public JsonObjectBuilder appendNull(String key) { -            appendFieldUnescaped(key, "null"); -            return this; -        } - -        /** -         * Appends a string field to the JSON. -         * -         * @param key The key of the field. -         * @param value The value of the field. -         * @return A reference to this object. -         */ -        public JsonObjectBuilder appendField(String key, String value) { -            if (value == null) { -                throw new IllegalArgumentException("JSON value must not be null"); -            } -            appendFieldUnescaped(key, "\"" + escape(value) + "\""); -            return this; -        } - -        /** -         * Appends an integer field to the JSON. -         * -         * @param key The key of the field. -         * @param value The value of the field. -         * @return A reference to this object. -         */ -        public JsonObjectBuilder appendField(String key, int value) { -            appendFieldUnescaped(key, String.valueOf(value)); -            return this; -        } - -        /** -         * Appends an object to the JSON. -         * -         * @param key The key of the field. -         * @param object The object. -         * @return A reference to this object. -         */ -        public JsonObjectBuilder appendField(String key, JsonObject object) { -            if (object == null) { -                throw new IllegalArgumentException("JSON object must not be null"); -            } -            appendFieldUnescaped(key, object.toString()); -            return this; -        } - -        /** -         * Appends a string array to the JSON. -         * -         * @param key The key of the field. -         * @param values The string array. -         * @return A reference to this object. -         */ -        public JsonObjectBuilder appendField(String key, String[] values) { -            if (values == null) { -                throw new IllegalArgumentException("JSON values must not be null"); -            } -            String escapedValues = -                    Arrays.stream(values) -                            .map(value -> "\"" + escape(value) + "\"") -                            .collect(Collectors.joining(",")); -            appendFieldUnescaped(key, "[" + escapedValues + "]"); -            return this; -        } - -        /** -         * Appends an integer array to the JSON. -         * -         * @param key The key of the field. -         * @param values The integer array. -         * @return A reference to this object. -         */ -        public JsonObjectBuilder appendField(String key, int[] values) { -            if (values == null) { -                throw new IllegalArgumentException("JSON values must not be null"); -            } -            String escapedValues = -                    Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); -            appendFieldUnescaped(key, "[" + escapedValues + "]"); -            return this; -        } - -        /** -         * Appends an object array to the JSON. -         * -         * @param key The key of the field. -         * @param values The integer array. -         * @return A reference to this object. -         */ -        public JsonObjectBuilder appendField(String key, JsonObject[] values) { -            if (values == null) { -                throw new IllegalArgumentException("JSON values must not be null"); -            } -            String escapedValues = -                    Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); -            appendFieldUnescaped(key, "[" + escapedValues + "]"); -            return this; -        } - -        /** -         * Appends a field to the object. -         * -         * @param key The key of the field. -         * @param escapedValue The escaped value of the field. -         */ -        private void appendFieldUnescaped(String key, String escapedValue) { -            if (builder == null) { -                throw new IllegalStateException("JSON has already been built"); -            } -            if (key == null) { -                throw new IllegalArgumentException("JSON key must not be null"); -            } -            if (hasAtLeastOneField) { -                builder.append(","); -            } -            builder.append("\"").append(escape(key)).append("\":").append(escapedValue); -            hasAtLeastOneField = true; -        } - -        /** -         * Builds the JSON string and invalidates this builder. -         * -         * @return The built JSON string. -         */ -        public JsonObject build() { -            if (builder == null) { -                throw new IllegalStateException("JSON has already been built"); -            } -            JsonObject object = new JsonObject(builder.append("}").toString()); -            builder = null; -            return object; -        } - -        /** -         * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. -         * -         * <p>This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. -         * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). -         * -         * @param value The value to escape. -         * @return The escaped value. -         */ -        private static String escape(String value) { -            final StringBuilder builder = new StringBuilder(); -            for (int i = 0; i < value.length(); i++) { -                char c = value.charAt(i); -                if (c == '"') { -                    builder.append("\\\""); -                } else if (c == '\\') { -                    builder.append("\\\\"); -                } else if (c <= '\u000F') { -                    builder.append("\\u000").append(Integer.toHexString(c)); -                } else if (c <= '\u001F') { -                    builder.append("\\u00").append(Integer.toHexString(c)); -                } else { -                    builder.append(c); -                } -            } -            return builder.toString(); -        } - -        /** -         * A super simple representation of a JSON object. -         * -         * <p>This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not -         * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, -         * JsonObject)}. -         */ -        public static class JsonObject { - -            private final String value; - -            private JsonObject(String value) { -                this.value = value; -            } - -            @Override -            public String toString() { -                return value; -            } -        } -    } -} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 42c4fc1..23d50ca 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ -main: com.MylesAndMore.tumble.Main +main: com.MylesAndMore.Tumble.Main  name: Tumble -version: 1.0.3 +version: 1.0.4  description: 'A Minecraft: Java Edition plugin recreating the Tumble minigame from Minecraft Legacy Console Edition.'  api-version: 1.19  load: STARTUP @@ -48,3 +48,6 @@ permissions:    tumble.autostart:      description: Allows you to set the autostart details of Tumble.      default: op +  tumble.update: +    description: Allows you to get a notification if Tumble is out of date. +    default: op | 
