All source projects

MrUltimateLibrary Source

Standalone source project / 68 files

MrUltimateLibrary is the backbone of all my plugins; it offers features that facilitate plugin creation, maintenance, and updates.

java

MrUltimateLibrary/src/main/java/eu/mrneznamy/mrultimatelibrary/config/DevMrNeznamyConfigs.java

JAVA · Read-only source

package eu.mrneznamy.mrultimatelibrary.config;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import eu.mrneznamy.mrultimatelibrary.stats.MrNeznamyStats;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * Generalized dynamic template retrieval mechanism.
 */
public class DevMrNeznamyConfigs {

    private static final String BASE_API_URL = "";
    private static final String MRSELLCHESTS_GITHUB_URL =
            "https://raw.githubusercontent.com/MrNeznamy/my-plugin-configs/main/MrSellChests/";
    private static final int TIMEOUT = 10000;

    /**
     * Safely queries API directories and triggers incremental additions.
     */
    public static void checkAndDownloadConfigs(JavaPlugin plugin, String slug) {
        Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
            checkAndDownloadConfigsSync(plugin, slug);
        });
    }

    /**
     * Synchronously queries API directories and triggers incremental additions.
     */
    public static void checkAndDownloadConfigsSync(JavaPlugin plugin, String slug) {
        try {
            
            File engineFile = new File(plugin.getDataFolder(), "engine.yml");
            if (!engineFile.exists()) return;
            
            YamlConfiguration engine = YamlConfiguration.loadConfiguration(engineFile);
            String mainKey = plugin.getName();
            
            if (!engine.contains(mainKey)) {
                for (String key : engine.getKeys(false)) {
                    if (engine.isConfigurationSection(key)) {
                        mainKey = key;
                        break;
                    }
                }
            }
            
            boolean allowAutoEdit = true;
            if (engine.contains(mainKey + ".Engine.ConfigManager.AllowAutoEditConfigs")) {
                allowAutoEdit = engine.getBoolean(mainKey + ".Engine.ConfigManager.AllowAutoEditConfigs");
            } else if (engine.contains("Engine.ConfigManager.AllowAutoEditConfigs")) {
                allowAutoEdit = engine.getBoolean("Engine.ConfigManager.AllowAutoEditConfigs");
            }

            List<String> manifest = engine.getStringList(mainKey + ".Engine.ConfigManager.Files");
            if (manifest == null || manifest.isEmpty()) {
                manifest = engine.getStringList("Engine.ConfigManager.Files");
            }
            if (manifest == null) {
                manifest = new ArrayList<>();
            }
            boolean manifestChanged = false;

            
            List<RemoteConfigInfo> remoteFiles = getRemoteFiles(slug);
            if (remoteFiles.isEmpty()) {
                plugin.getLogger().warning("No remote configuration files found for '" + slug + "'.");
                return;
            }

            for (RemoteConfigInfo remote : remoteFiles) {
                String filename = remote.getFilename();
                String remoteVersion = remote.getVersion();
                String remoteLang = remote.getLanguage();
                
                
                if (filename.equals("data.yml") || (filename.equals("boosters.yml") && !slug.equalsIgnoreCase("MrUltimateFishing") && !slug.equalsIgnoreCase("MrSellChests"))) {
                    continue;
                }

                
                String manifestEntry = null;
                int manifestIndex = -1;
                for (int i = 0; i < manifest.size(); i++) {
                    String entry = manifest.get(i);
                    if (entry.startsWith(filename + ";")) {
                        manifestEntry = entry;
                        manifestIndex = i;
                        break;
                    }
                }

                File localFile = new File(plugin.getDataFolder(), filename);
                
                if (manifestEntry != null) {
                    String[] parts = manifestEntry.split(";");
                    String localVer = parts[1];
                    String lang = parts.length > 2 ? parts[2] : "global";

                    if (lang.equalsIgnoreCase("onetime")) {
                        continue;
                    }

                    if (!localFile.exists()) {
                        if (allowAutoEdit) {
                            plugin.getLogger().info("Downloading missing remote asset: " + filename);
                            if (downloadDirect(plugin, slug, filename)) {
                                manifest.set(manifestIndex, filename + ";" + remoteVersion + ";" + lang);
                                manifestChanged = true;
                            }
                        }
                    } else if (allowAutoEdit && isNewerVersion(remoteVersion, localVer)) {
                        Bukkit.getLogger().info("[" + plugin.getName() + "] Updating remote asset: " + filename + " (" + localVer + " -> " + remoteVersion + ")");
                        if (downloadAndMerge(plugin, slug, filename)) {
                            manifest.set(manifestIndex, filename + ";" + remoteVersion + ";" + lang);
                            manifestChanged = true;
                        }
                    }
                } else {
                    
                    if (allowAutoEdit) {
                        if (localFile.exists()) {
                            manifest.add(filename + ";" + remoteVersion + ";" + remoteLang);
                            manifestChanged = true;
                        } else {
                            plugin.getLogger().info("Downloading missing remote asset: " + filename);
                            if (downloadDirect(plugin, slug, filename)) {
                                manifest.add(filename + ";" + remoteVersion + ";" + remoteLang);
                                manifestChanged = true;
                            }
                        }
                    }
                }
            }

            if (manifestChanged) {
                if (engine.contains(mainKey + ".Engine.ConfigManager.Files")) {
                    engine.set(mainKey + ".Engine.ConfigManager.Files", manifest);
                } else {
                    engine.set("Engine.ConfigManager.Files", manifest);
                }
                engine.save(engineFile);
            }

        } catch (Exception e) {
            
        }
    }

    public static List<RemoteConfigInfo> getRemoteFiles(String slug) {
        List<RemoteConfigInfo> list = new ArrayList<>();
        if (slug.equalsIgnoreCase("mrsellchests")) {
            return getMrSellChestsGitHubFiles();
        }
        
        return list;
        /*
        try {
            URL url = new URL(BASE_API_URL + slug.toLowerCase() + "/files");
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setRequestMethod("GET");
            con.setRequestProperty("X-Api-Key", MrNeznamyStats.getApiKey() != null ? MrNeznamyStats.getApiKey() : "");
            con.setConnectTimeout(TIMEOUT);
            con.setReadTimeout(TIMEOUT);

            if (con.getResponseCode() == 200) {
                try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
                    StringBuilder content = new StringBuilder();
                    String line;
                    while ((line = in.readLine()) != null) {
                        content.append(line);
                    }

                    JsonObject json = JsonParser.parseString(content.toString()).getAsJsonObject();
                    if (json.has("files")) {
                        JsonArray filesArray = json.getAsJsonArray("files");
                        for (int i = 0; i < filesArray.size(); i++) {
                            JsonObject obj = filesArray.get(i).getAsJsonObject();
                            String filename = obj.get("filename").getAsString();
                            String version = obj.get("version").getAsString();
                            
                            
                            String language = "global";
                            if (obj.has("lang") && !obj.get("lang").isJsonNull()) {
                                String langVal = obj.get("lang").getAsString();
                                if (langVal != null && !langVal.equalsIgnoreCase("null") && !langVal.isEmpty()) {
                                    language = langVal;
                                }
                            }
                            
                            list.add(new RemoteConfigInfo(filename, version, language));
                        }
                    }
                }
            }
        } catch (Exception e) {
            Bukkit.getLogger().warning("[" + slug + "] Failed to read remote configuration manifest: " + e.getMessage());
        }
        return list;
        */
    }

    private static List<RemoteConfigInfo> getMrSellChestsGitHubFiles() {
        List<RemoteConfigInfo> list = new ArrayList<>();
        try {
            URL url = new URL(MRSELLCHESTS_GITHUB_URL + "versions.json");
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setRequestMethod("GET");
            con.setConnectTimeout(TIMEOUT);
            con.setReadTimeout(TIMEOUT);

            if (con.getResponseCode() == 200) {
                try (BufferedReader in = new BufferedReader(new InputStreamReader(
                        con.getInputStream(), StandardCharsets.UTF_8))) {
                    JsonObject versions = JsonParser.parseReader(in).getAsJsonObject();
                    if (versions.has("configs") && versions.get("configs").isJsonObject()) {
                        versions = versions.getAsJsonObject("configs");
                    }
                    for (String filename : versions.keySet()) {
                        JsonElement version = versions.get(filename);
                        if (version.isJsonObject() && version.getAsJsonObject().has("version")) {
                            version = version.getAsJsonObject().get("version");
                        }
                        list.add(new RemoteConfigInfo(filename, version.getAsString(), "global"));
                    }
                }
            }
        } catch (Exception e) {
            Bukkit.getLogger().warning("[MrSellChests] Failed to read GitHub versions.json: " + e.getMessage());
        }
        return list;
    }

    public static String getLocalConfigVersion(File file) {
        if (!file.exists()) return "0.0.0";

        try {
            FileConfiguration cfg = YamlConfiguration.loadConfiguration(file);
            if (cfg.contains("MrUltimateLibrary-Configs-Version")) {
                return cfg.getString("MrUltimateLibrary-Configs-Version", "0.0.0");
            }
        } catch (Exception ignored) {}

        try (BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8))) {
            String line;
            int lineCount = 0;
            while ((line = br.readLine()) != null && lineCount < 10) {
                lineCount++;
                if (line.contains("MrUltimateLibrary-Configs-Version:")) {
                    int idx = line.indexOf(":");
                    String ver = line.substring(idx + 1).replace("\"", "").replace("'", "").trim();
                    if (!ver.isEmpty()) {
                        return ver;
                    }
                }
            }
        } catch (Exception ignored) {}

        return "0.0.0";
    }

    private static boolean isNewerVersion(String remote, String local) {
        try {
            String[] remoteParts = remote.split("\\.");
            String[] localParts = local.split("\\.");
            int length = Math.max(remoteParts.length, localParts.length);
            for (int i = 0; i < length; i++) {
                int r = i < remoteParts.length ? Integer.parseInt(remoteParts[i]) : 0;
                int l = i < localParts.length ? Integer.parseInt(localParts[i]) : 0;
                if (r > l) return true;
                if (r < l) return false;
            }
        } catch (Exception ignored) {}
        return false;
    }

    private static boolean downloadDirect(JavaPlugin plugin, String slug, String filename) {
        try {
            String content = fetchTemplate(slug, filename);
            if (content == null || content.trim().isEmpty()) return false;

            File localFile = new File(plugin.getDataFolder(), filename);
            if (localFile.getParentFile() != null) {
                localFile.getParentFile().mkdirs();
            }

            try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
                    new FileOutputStream(localFile), StandardCharsets.UTF_8))) {
                writer.write(content);
            }
            return true;
        } catch (Exception e) {
            plugin.getLogger().warning("Failed to download remote asset '" + filename + "': " + e.getMessage());
            return false;
        }
    }

    private static final java.util.regex.Pattern KEY_PATTERN = java.util.regex.Pattern.compile("^(\\s*)([\"']?)([a-zA-Z0-9_.-]+)\\2\\s*:(.*)$");

    private static boolean downloadAndMerge(JavaPlugin plugin, String slug, String filename) {
        try {
            String remoteContent = fetchTemplate(slug, filename);
            if (remoteContent == null || remoteContent.trim().isEmpty()) return false;

            File localFile = new File(plugin.getDataFolder(), filename);
            YamlConfiguration localConfig = YamlConfiguration.loadConfiguration(localFile);
            YamlConfiguration remoteConfig = new YamlConfiguration();
            remoteConfig.loadFromString(remoteContent);

            List<String> missingKeys = new ArrayList<>();
            for (String key : remoteConfig.getKeys(true)) {
                if (!remoteConfig.isConfigurationSection(key) && !localConfig.contains(key)) {
                    missingKeys.add(key);
                }
            }

            if (missingKeys.isEmpty()) {
                if (remoteConfig.contains("MrUltimateLibrary-Configs-Version")) {
                    String localVer = getLocalConfigVersion(localFile);
                    String remoteVer = remoteConfig.getString("MrUltimateLibrary-Configs-Version");
                    if (isNewerVersion(remoteVer, localVer)) {
                        MrLibYamlUpdater.setKeyValue(localFile, "MrUltimateLibrary-Configs-Version", remoteVer);
                    }
                }
                removeLegacyKeys(localFile, filename);
                return true;
            }

            List<String> remoteLines = java.util.Arrays.asList(remoteContent.split("\\r?\\n"));

            for (String key : missingKeys) {
                int keyLineIdx = -1;
                String[] parts = key.split("\\.");
                String leafKey = parts[parts.length - 1];

                for (int i = 0; i < remoteLines.size(); i++) {
                    String line = remoteLines.get(i);
                    String trimmed = line.trim();
                    if (trimmed.startsWith(leafKey + ":") || trimmed.startsWith("\"" + leafKey + "\":") || trimmed.startsWith("'" + leafKey + "':")) {
                        if (matchPath(remoteLines, i, key)) {
                            keyLineIdx = i;
                            break;
                        }
                    }
                }

                if (keyLineIdx != -1) {
                    List<String> block = new ArrayList<>();
                    int startIdx = keyLineIdx;
                    while (startIdx > 0) {
                        String prev = remoteLines.get(startIdx - 1).trim();
                        if (prev.startsWith("#") || prev.isEmpty()) {
                            startIdx--;
                        } else {
                            break;
                        }
                    }

                    int endIdx = keyLineIdx;
                    int keyIndent = getIndent(remoteLines.get(keyLineIdx));
                    while (endIdx + 1 < remoteLines.size()) {
                        String nextLine = remoteLines.get(endIdx + 1);
                        String trimmedNext = nextLine.trim();
                        if (trimmedNext.isEmpty() || trimmedNext.startsWith("#")) {
                            endIdx++;
                        } else {
                            int nextIndent = getIndent(nextLine);
                            if (nextIndent > keyIndent) {
                                endIdx++;
                            } else {
                                break;
                            }
                        }
                    }

                    for (int i = startIdx; i <= endIdx; i++) {
                        block.add(remoteLines.get(i));
                    }

                    insertBlock(localFile, key, block, plugin.getName());
                }
            }

            if (remoteConfig.contains("MrUltimateLibrary-Configs-Version")) {
                String remoteVer = remoteConfig.getString("MrUltimateLibrary-Configs-Version");
                MrLibYamlUpdater.setKeyValue(localFile, "MrUltimateLibrary-Configs-Version", remoteVer);
            }

            removeLegacyKeys(localFile, filename);
            return true;
        } catch (Exception e) {
            return false;
        }
    }

    private static boolean matchPath(List<String> lines, int lineIdx, String targetPath) {
        List<String> pathParts = new ArrayList<>();
        int currentIndent = getIndent(lines.get(lineIdx));
        pathParts.add(getKeyName(lines.get(lineIdx)));

        for (int i = lineIdx - 1; i >= 0; i--) {
            String line = lines.get(i);
            String trimmed = line.trim();
            if (trimmed.isEmpty() || trimmed.startsWith("#")) continue;

            int indent = getIndent(line);
            if (indent < currentIndent) {
                pathParts.add(0, getKeyName(line));
                currentIndent = indent;
            }
        }

        String path = String.join(".", pathParts);
        return path.equals(targetPath);
    }

    private static int getIndent(String line) {
        int count = 0;
        while (count < line.length() && line.charAt(count) == ' ') {
            count++;
        }
        return count;
    }

    private static String getKeyName(String line) {
        String trimmed = line.trim();
        int colon = trimmed.indexOf(':');
        if (colon == -1) return trimmed;
        String key = trimmed.substring(0, colon).trim();
        if ((key.startsWith("\"") && key.endsWith("\"")) || (key.startsWith("'") && key.endsWith("'"))) {
            key = key.substring(1, key.length() - 1);
        }
        return key;
    }

    private static void insertBlock(File file, String key, List<String> block, String pluginName) throws IOException {
        List<String> lines = java.nio.file.Files.readAllLines(file.toPath(), java.nio.charset.StandardCharsets.UTF_8);
        List<String> newLines = new ArrayList<>();

        int lastDot = key.lastIndexOf('.');
        if (lastDot == -1) {
            newLines.addAll(lines);
            if (!newLines.isEmpty() && !newLines.get(newLines.size() - 1).trim().isEmpty()) {
                newLines.add("");
            }
            newLines.addAll(block);
        } else {
            String parentPath = key.substring(0, lastDot);
            int parentLineIdx = -1;
            List<PathSegment> pathStack = new ArrayList<>();

            for (int i = 0; i < lines.size(); i++) {
                String line = lines.get(i);
                String trimmed = line.trim();
                if (trimmed.isEmpty() || trimmed.startsWith("#")) continue;

                java.util.regex.Matcher matcher = KEY_PATTERN.matcher(line);
                if (matcher.find()) {
                    int indent = matcher.group(1).length();
                    String keyName = matcher.group(3);

                    pathStack.removeIf(segment -> segment.indent >= indent);
                    pathStack.add(new PathSegment(keyName, indent));

                    String currentPath = getFullPath(pathStack);
                    if (currentPath.equals(parentPath)) {
                        parentLineIdx = i;
                    }
                }
            }

            if (parentLineIdx != -1) {
                int parentIndent = getIndent(lines.get(parentLineIdx));
                int insertIdx = parentLineIdx + 1;
                for (int i = parentLineIdx + 1; i < lines.size(); i++) {
                    String line = lines.get(i);
                    String trimmed = line.trim();
                    if (trimmed.isEmpty() || trimmed.startsWith("#")) continue;

                    int indent = getIndent(line);
                    if (indent <= parentIndent) {
                        break;
                    }
                    insertIdx = i + 1;
                }

                for (int i = 0; i < lines.size(); i++) {
                    if (i == insertIdx) {
                        newLines.addAll(block);
                    }
                    newLines.add(lines.get(i));
                }
                if (insertIdx == lines.size()) {
                    newLines.addAll(block);
                }
            } else {
                newLines.addAll(lines);
                if (!newLines.isEmpty() && !newLines.get(newLines.size() - 1).trim().isEmpty()) {
                    newLines.add("");
                }
                newLines.addAll(block);
            }
        }

        java.nio.file.Files.write(file.toPath(), newLines, java.nio.charset.StandardCharsets.UTF_8);
    }

    private static String getFullPath(List<PathSegment> pathStack) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < pathStack.size(); i++) {
            sb.append(pathStack.get(i).key);
            if (i < pathStack.size() - 1) {
                sb.append(".");
            }
        }
        return sb.toString();
    }

    private static void removeLegacyKeys(File file, String filename) {
        try {
            List<String> lines = java.nio.file.Files.readAllLines(file.toPath(), java.nio.charset.StandardCharsets.UTF_8);
            List<String> newLines = new ArrayList<>();
            boolean changed = false;
            String oldKey = filename.replace(".yml", "").replace("/", "_").replace("\\", "_") + "-version";

            for (String line : lines) {
                String trimmed = line.trim();
                if (trimmed.startsWith("config-version:") || trimmed.startsWith("verze:") || trimmed.startsWith(oldKey + ":")) {
                    changed = true;
                    continue;
                }
                newLines.add(line);
            }
            if (changed) {
                java.nio.file.Files.write(file.toPath(), newLines, java.nio.charset.StandardCharsets.UTF_8);
            }
        } catch (Exception ignored) {}
    }

    private static String fetchTemplate(String slug, String filename) {
        if (!slug.equalsIgnoreCase("mrsellchests")) return null;
        /*
        try {
            
            String webPath = filename.replace("\\", "/");
            URL url;
            if (slug.equalsIgnoreCase("mrsellchests")) {
                url = new URL(MRSELLCHESTS_GITHUB_URL + webPath);
            } else {
                url = new URL(BASE_API_URL + slug.toLowerCase() + "/config?filename="
                        + URLEncoder.encode(webPath, "UTF-8"));
            }
            HttpURLConnection con = (HttpURLConnection) url.openConnection();
            con.setRequestMethod("GET");
            if (!slug.equalsIgnoreCase("mrsellchests")) {
                con.setRequestProperty("X-Api-Key", MrNeznamyStats.getApiKey() != null
                        ? MrNeznamyStats.getApiKey() : "");
            }
            con.setConnectTimeout(TIMEOUT);
            con.setReadTimeout(TIMEOUT);

            if (con.getResponseCode() == 200) {
                try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), StandardCharsets.UTF_8))) {
                    StringBuilder content = new StringBuilder();
                    String line;
                    while ((line = in.readLine()) != null) {
                        content.append(line).append("\n");
                    }
                    return content.toString();
                }
            }
        } catch (Exception ignored) {}
        return null;
        */
        return null;
    }

    public static class RemoteConfigInfo {
        private final String filename;
        private final String version;
        private final String language;

        public RemoteConfigInfo(String filename, String version, String language) {
            this.filename = filename;
            this.version = version;
            this.language = language;
        }

        public String getFilename() { return filename; }
        public String getVersion() { return version; }
        public String getLanguage() { return language; }
    }

    private static class PathSegment {
        String key;
        int indent;
        PathSegment(String key, int indent) {
            this.key = key;
            this.indent = indent;
        }
    }
}
616 linesGNU GENERAL PUBLIC LICENSE · Version 3, 29 June 2007 · UTF-8 · Read only