From 1bfc5ffb2608a229857309ac586ff6c868c0c810 Mon Sep 17 00:00:00 2001 From: Tomut0 Date: Fri, 18 Oct 2024 19:17:39 +0300 Subject: [PATCH 1/7] Revert "fix: update the Mojang API endpoint (#413)" This reverts commit d4d144fe365e6ac6bd19ecc236265aa25315a197. --- .../net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java index 7ad24c312..c002b65e7 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java @@ -27,7 +27,7 @@ public class UUIDFetcher implements Callable> { private static final double PROFILES_PER_REQUEST = 100; - private static final String PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile/lookup/bulk/byname"; + private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft"; private final Gson gson = new Gson(); private final List names; private final boolean rateLimiting; From 23b3fd652ea727478b4aeab9cf8aca78d2d37191 Mon Sep 17 00:00:00 2001 From: Tomut0 Date: Fri, 18 Oct 2024 21:44:23 +0300 Subject: [PATCH 2/7] fix: uuid bulk fetching over Mojang API --- .../simpleclans/managers/StorageManager.java | 91 +++++++--- .../phaed/simpleclans/uuid/UUIDFetcher.java | 161 ++++++++++-------- .../simpleclans/uuid/UUIDFetcherTest.java | 6 - 3 files changed, 149 insertions(+), 109 deletions(-) diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/managers/StorageManager.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/managers/StorageManager.java index f693f6733..6729d1efc 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/managers/StorageManager.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/managers/StorageManager.java @@ -26,7 +26,9 @@ import java.sql.SQLException; import java.text.MessageFormat; import java.util.*; +import java.util.concurrent.ExecutionException; import java.util.logging.Level; +import java.util.stream.Collectors; import static net.sacredlabyrinth.phaed.simpleclans.SimpleClans.lang; import static net.sacredlabyrinth.phaed.simpleclans.managers.SettingsManager.ConfigField.*; @@ -541,7 +543,7 @@ public List retrieveClanPlayers() { if (last_seen == 0) { last_seen = (new Date()).getTime(); - } + } ClanPlayer cp = new ClanPlayer(); if (uuid != null) { @@ -1180,47 +1182,80 @@ private void updateDatabase() { * Updates the database to the latest version * */ - private void updatePlayersToUUID() { - plugin.getLogger().log(Level.WARNING, "Starting Migration to UUID Players !"); - plugin.getLogger().log(Level.WARNING, "==================== ATTENTION DONT STOP BUKKIT ! ==================== "); - plugin.getLogger().log(Level.WARNING, "==================== ATTENTION DONT STOP BUKKIT ! ==================== "); - plugin.getLogger().log(Level.WARNING, "==================== ATTENTION DONT STOP BUKKIT ! ==================== "); + logMigrationStart(); SimpleClans.getInstance().setUUID(false); + List cps = retrieveClanPlayers(); + Map uuidMap = fetchUUIDs(cps); - int i = 1; - for (ClanPlayer cp : cps) { + int totalPlayers = cps.size(); + for (int i = 0; i < totalPlayers; i++) { + ClanPlayer cp = cps.get(i); try { - UUID uuidPlayer; - if (SimpleClans.getInstance().getServer().getOnlineMode()) { - uuidPlayer = UUIDFetcher.getUUIDOfThrottled(cp.getName()); - } else { - uuidPlayer = UUID.nameUUIDFromBytes(("OfflinePlayer:" + cp.getName()).getBytes(Charsets.UTF_8)); - } - String query = "UPDATE `" + getPrefixedTable("players") + "` SET uuid = '" + uuidPlayer.toString() + "' WHERE name = '" + cp.getName() + "';"; - core.executeUpdate(query); + UUID uuid = uuidMap.getOrDefault(cp.getName(), cp.getUniqueId()); + updatePlayerInDatabase(cp.getName(), uuid); + logSuccess(i + 1, totalPlayers, cp.getName(), uuid); + } catch (Exception ex) { + logFailure(i + 1, totalPlayers, cp.getName(), ex); + } + } - String query2 = "UPDATE `" + getPrefixedTable("kills") + "` SET attacker_uuid = '" + uuidPlayer + "' WHERE attacker = '" + cp.getName() + "';"; - core.executeUpdate(query2); + logMigrationEnd(totalPlayers); + SimpleClans.getInstance().setUUID(true); + } - String query3 = "UPDATE `" + getPrefixedTable("kills") + "` SET victim_uuid = '" + uuidPlayer + "' WHERE victim = '" + cp.getName() + "';"; - core.executeUpdate(query3); - plugin.getLogger().info("[" + i + " / " + cps.size() + "] Success: " + cp.getName() + "; UUID: " + uuidPlayer); - } catch (Exception ex) { - plugin.getLogger().log(Level.WARNING, "[" + i + " / " + cps.size() + "] Failed [ERRO]: " + cp.getName() + "; UUID: ???"); + private void updatePlayerInDatabase(String playerName, UUID uuid) { + String[] tables = {"players", "kills", "kills"}; + String[] columns = {"uuid", "attacker_uuid", "victim_uuid"}; + String[] conditions = {"name", "attacker", "victim"}; + + for (int i = 0; i < tables.length; i++) { + String query = String.format("UPDATE `%s` SET %s = '%s' WHERE %s = '%s';", + getPrefixedTable(tables[i]), columns[i], uuid.toString(), conditions[i], playerName); + core.executeUpdate(query); + } + } + + private Map fetchUUIDs(List clanPlayers) { + Map uuidMap = new HashMap<>(); + + try { + if (SimpleClans.getInstance().getServer().getOnlineMode()) { + uuidMap = UUIDFetcher.fetchUUIDsForClanPlayers(clanPlayers); + } else { + uuidMap = clanPlayers.stream().collect(Collectors.toMap(ClanPlayer::getName, ClanPlayer::getUniqueId)); } - i++; + } catch (InterruptedException | ExecutionException ex) { + plugin.getLogger().log(Level.SEVERE, "Error fetching UUIDs in bulk: " + ex.getMessage(), ex); } + + return uuidMap; + } + + private void logSuccess(int current, int total, String playerName, UUID uuid) { + plugin.getLogger().info(String.format("[%d / %d] Success: %s; UUID: %s", current, total, playerName, uuid)); + } + + private void logFailure(int current, int total, String playerName, Exception ex) { + plugin.getLogger().log(Level.WARNING, String.format("[%d / %d] Failed [ERROR]: %s; UUID: ???", current, total, playerName), ex); + } + + private void logMigrationStart() { + plugin.getLogger().log(Level.WARNING, "Starting Migration to UUID Players!"); + plugin.getLogger().log(Level.WARNING, "==================== ATTENTION DON'T STOP BUKKIT! ===================="); + plugin.getLogger().log(Level.WARNING, "==================== ATTENTION DON'T STOP BUKKIT! ===================="); + plugin.getLogger().log(Level.WARNING, "==================== ATTENTION DON'T STOP BUKKIT! ===================="); + } + + private void logMigrationEnd(int totalPlayers) { plugin.getLogger().log(Level.WARNING, "==================== END OF MIGRATION ===================="); plugin.getLogger().log(Level.WARNING, "==================== END OF MIGRATION ===================="); plugin.getLogger().log(Level.WARNING, "==================== END OF MIGRATION ===================="); - - if (!cps.isEmpty()) { - plugin.getLogger().info(MessageFormat.format(lang("clan.players"), cps.size())); + if (totalPlayers > 0) { + plugin.getLogger().info(MessageFormat.format(lang("clan.players"), totalPlayers)); } - SimpleClans.getInstance().setUUID(true); } private String getPrefixedTable(String name) { diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java index c002b65e7..5bdfa3b7e 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java @@ -1,114 +1,125 @@ package net.sacredlabyrinth.phaed.simpleclans.uuid; -import com.google.common.collect.ImmutableList; import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import net.sacredlabyrinth.phaed.simpleclans.ClanPlayer; import java.io.IOException; import java.io.InputStreamReader; -import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; -import java.nio.ByteBuffer; import java.util.*; -import java.util.concurrent.Callable; +import java.util.concurrent.*; +import java.util.stream.Collectors; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.singletonList; /** - * @author evilmidget38 - * - * ... + * Previous authors: * - * ... + * @author evilmidget38 + * @see Bukkit Thread + * @see Github Gist */ -public class UUIDFetcher implements Callable> { +public final class UUIDFetcher { + private static final String PROFILE_URL = "https://api.minetools.eu/uuid/"; + private static final int BATCH_SIZE = 100; + private static final Gson gson = new Gson(); - private static final double PROFILES_PER_REQUEST = 100; - private static final String PROFILE_URL = "https://api.mojang.com/profiles/minecraft"; - private final Gson gson = new Gson(); - private final List names; - private final boolean rateLimiting; + private UUIDFetcher() { + // Private constructor to prevent instantiation + } - public UUIDFetcher(List names, boolean rateLimiting) { - this.names = ImmutableList.copyOf(names); - this.rateLimiting = rateLimiting; + /** + * Fetches UUIDs for a list of ClanPlayer objects. + * This method extracts the names from the ClanPlayer objects and fetches their corresponding UUIDs. + * + * @param clanPlayers A list of ClanPlayer objects for which to fetch UUIDs + * @return A Map where the keys are player names and the values are their corresponding UUIDs + * @throws InterruptedException If the operation is interrupted while waiting + * @throws ExecutionException If the computation threw an exception + */ + public static Map fetchUUIDsForClanPlayers(List clanPlayers) throws InterruptedException, ExecutionException { + List names = clanPlayers.stream().map(ClanPlayer::getName).collect(Collectors.toList()); + return fetchUUIDsConcurrently(names); } - public UUIDFetcher(List names) { - this(names, true); + /** + * Fetches the UUID for a single player name. + * This method is a convenience wrapper around the batch UUID fetching process. + * + * @param name The name of the player whose UUID is to be fetched + * @return The UUID of the specified player, or null if not found + * @throws InterruptedException If the operation is interrupted while waiting + * @throws ExecutionException If the computation threw an exception + */ + public static UUID getUUIDOf(String name) throws InterruptedException, ExecutionException { + return fetchUUIDsConcurrently(singletonList(name)).get(name); } - private static void writeBody(HttpURLConnection connection, String body) throws IOException { - OutputStream stream = connection.getOutputStream(); - stream.write(body.getBytes(UTF_8)); - stream.flush(); - stream.close(); + // Fetch UUIDs in batches with concurrency + private static Map fetchUUIDsConcurrently(List names) throws InterruptedException, ExecutionException { + Map resultMap = new ConcurrentHashMap<>(); + ExecutorService executorService = Executors.newFixedThreadPool(10); // Thread pool for parallel execution + List>> tasks = new ArrayList<>(); + + // Split the list of names into batches and create tasks + for (int i = 0; i < names.size(); i += BATCH_SIZE) { + List batch = names.subList(i, Math.min(i + BATCH_SIZE, names.size())); + tasks.add(createTask(batch)); + } + + // Execute all tasks in parallel + List>> futures = executorService.invokeAll(tasks); + + // Collect results from all batches + for (Future> future : futures) { + resultMap.putAll(future.get()); // Merge each batch result into the final result map + } + + // Shutdown the executor service + executorService.shutdown(); + + return resultMap; } - private static HttpURLConnection createConnection() throws IOException { - URL url = new URL(PROFILE_URL); - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - connection.setRequestMethod("POST"); + // Create connection for each name + private static HttpURLConnection createConnection(String name) throws IOException { + URL url = new URL(PROFILE_URL + name); + + var connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); connection.setRequestProperty("Content-Type", "application/json"); connection.setUseCaches(false); connection.setDoInput(true); - connection.setDoOutput(true); + return connection; } + // Get UUID by name private static UUID getUUID(String id) { return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(20, 32)); } - public static byte[] toBytes(UUID uuid) { - ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]); - byteBuffer.putLong(uuid.getMostSignificantBits()); - byteBuffer.putLong(uuid.getLeastSignificantBits()); - return byteBuffer.array(); - } - - public static UUID fromBytes(byte[] array) { - if (array.length != 16) { - throw new IllegalArgumentException("Illegal byte array length: " + array.length); - } - ByteBuffer byteBuffer = ByteBuffer.wrap(array); - long mostSignificant = byteBuffer.getLong(); - long leastSignificant = byteBuffer.getLong(); - return new UUID(mostSignificant, leastSignificant); - } - - public static UUID getUUIDOf(String name) throws IOException, InterruptedException { - return new UUIDFetcher(Collections.singletonList(name)).call().get(name); - } - - public static UUID getUUIDOfThrottled(String name) throws IOException, InterruptedException { - return new UUIDFetcher(Collections.singletonList(name), true).call().get(name); - } - - @Override - public Map call() throws IOException, InterruptedException { + // Handle single batch of names + private static Map fetchUUIDsForBatch(List batch) throws IOException { Map uuidMap = new HashMap<>(); - int requests = (int) Math.ceil(names.size() / PROFILES_PER_REQUEST); - for (int i = 0; i < requests; i++) { - HttpURLConnection connection = createConnection(); - List namesToFetch = names.subList(i * 100, Math.min((i + 1) * 100, names.size())); - String body = gson.toJson(namesToFetch); - writeBody(connection, body); - JsonArray array = gson.fromJson(new InputStreamReader(connection.getInputStream(), UTF_8), JsonArray.class); - for (JsonElement profile : array) { - JsonObject jsonProfile = profile.getAsJsonObject(); - String id = jsonProfile.get("id").getAsString(); - String name = jsonProfile.get("name").getAsString(); - UUID uuid = UUIDFetcher.getUUID(id); - uuidMap.put(name, uuid); - } - if (rateLimiting && i != requests - 1) { - Thread.sleep(10L); + for (String name : batch) { + HttpURLConnection connection = createConnection(name); + JsonObject response = gson.fromJson(new InputStreamReader(connection.getInputStream(), UTF_8), JsonObject.class); + + if (response.get("status").getAsString().equals("OK")) { + String id = response.get("id").getAsString(); + UUID uuid = getUUID(id); + uuidMap.put(response.get("name").getAsString(), uuid); } } return uuidMap; } -} + + // Callable task for batch processing + private static Callable> createTask(List batch) { + return () -> fetchUUIDsForBatch(batch); + } +} \ No newline at end of file diff --git a/src/test/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcherTest.java b/src/test/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcherTest.java index a889e0f59..5cc5fdfcd 100644 --- a/src/test/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcherTest.java +++ b/src/test/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcherTest.java @@ -3,7 +3,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.IOException; import java.util.UUID; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -21,9 +20,4 @@ public void setup() { public void getUUIDOf() throws Exception { assertEquals(ghostUUID, UUIDFetcher.getUUIDOf("GhostTheWolf")); } - - @Test - public void getUUIDOfThrottled() throws IOException, InterruptedException { - assertEquals(ghostUUID, UUIDFetcher.getUUIDOfThrottled("GhostTheWolf")); - } } \ No newline at end of file From e42fa9410cb1e80c5d33ef27215677f2242add0a Mon Sep 17 00:00:00 2001 From: Tomut0 Date: Fri, 18 Oct 2024 21:46:42 +0300 Subject: [PATCH 3/7] feat: add completions to clan create command --- .../commands/SCCommandManager.java | 41 +++++++++++-------- .../commands/general/GeneralCommands.java | 1 + .../simpleclans/managers/StorageManager.java | 1 - src/main/resources/messages.properties | 2 + 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/SCCommandManager.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/SCCommandManager.java index aad0d0d4f..9050460c9 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/SCCommandManager.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/SCCommandManager.java @@ -38,17 +38,18 @@ public class SCCommandManager extends PaperCommandManager { private final SimpleClans plugin; private static final List SUBCOMMANDS; + private static final List COMPLETIONS; public SCCommandManager(@NotNull SimpleClans plugin) { super(plugin); this.plugin = plugin; configure(); } - + private void configure() { enableUnstableAPI("help"); registerDependencies(); - addCommandReplacements(); + addReplacements(); registerContexts(); registerCommands(); registerConditions(); @@ -105,13 +106,10 @@ private void registerConditions() { } try { AbstractCondition obj = c.getConstructor(SimpleClans.class).newInstance(plugin); - if (obj instanceof AbstractParameterCondition) { - @SuppressWarnings("rawtypes") - AbstractParameterCondition condition = (AbstractParameterCondition) obj; + if (obj instanceof @SuppressWarnings("rawtypes")AbstractParameterCondition condition) { getCommandConditions().addCondition(condition.getType(), condition.getId(), condition); } - if (obj instanceof AbstractCommandCondition) { - AbstractCommandCondition condition = (AbstractCommandCondition) obj; + if (obj instanceof AbstractCommandCondition condition) { getCommandConditions().addCondition(condition.getId(), condition); } } catch (Exception ex) { @@ -162,7 +160,7 @@ private void registerCommands() { } } - private void addCommandReplacements() { + private void addReplacements() { SettingsManager sm = plugin.getSettingsManager(); getCommandReplacements().addReplacements( "basic_conditions", "not_blacklisted|not_banned", @@ -174,17 +172,10 @@ private void addCommandReplacements() { "clan_chat", sm.getString(COMMANDS_CLAN_CHAT) ); - SUBCOMMANDS.forEach(s -> { - String command = optionalLang(s + ".command", (ClanPlayer) null); - if (command == null) { - command = s; - } - command = command.replace(" ", ""); - String replacement = command.equals(s) ? s : command + "|" + s; - getCommandReplacements().addReplacement(s, replacement); - }); + SUBCOMMANDS.forEach(s -> processReplacement(s, "", ".command")); + COMPLETIONS.forEach(s -> processReplacement(s, "compl:", ".completion")); } - + @Override public BukkitLocales getLocales() { if (this.locales == null) { @@ -215,6 +206,18 @@ public String getMessage(CommandIssuer issuer, MessageKeyProvider key) { return this.locales; } + private void processReplacement(String key, String prefix, String suffix) { + String replacement = optionalLang(key + suffix, (ClanPlayer) null); + if (replacement == null) { + replacement = key; + } + + replacement = replacement.replace(" ", ""); + String finalReplacement = replacement.equals(key) ? key : replacement + "|" + key; + + getCommandReplacements().addReplacement(prefix + key, finalReplacement); + } + static { SUBCOMMANDS = Arrays.asList("setbanner", "resetkdr", "place", "rank", "home", "war", "regroup", "mostkilled", "kills", "globalff", "reload", "unban", "ban", "verify", "disband", "resign", "ff", @@ -225,5 +228,7 @@ public String getMessage(CommandIssuer issuer, MessageKeyProvider key) { "lookup", "roster", "profile", "list", "create", "description", "start", "end", "admin", "help", "mod", "setdefault", "removedefault", "land", "break", "interact", "place_block", "damage", "interact_entity", "container", "permanent", "take", "give", "join", "leave", "mute", "confirm", "balance", "discord", "rename", "locale"); + + COMPLETIONS = Arrays.asList("tag", "name"); } } diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/general/GeneralCommands.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/general/GeneralCommands.java index 79152cd79..930823248 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/general/GeneralCommands.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/general/GeneralCommands.java @@ -75,6 +75,7 @@ public void locale(ClanPlayer cp, @Values("@locales") @Name("locale") String loc @Subcommand("%create") @CommandPermission("simpleclans.leader.create") + @CommandCompletion("%compl:tag %compl:name") @Description("{@@command.description.create}") public void create(Player player, @Optional @Name("tag") String tag, @Optional @Name("name") String name) { ClanPlayer cp = cm.getAnyClanPlayer(player.getUniqueId()); diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/managers/StorageManager.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/managers/StorageManager.java index 6729d1efc..8c04474bb 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/managers/StorageManager.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/managers/StorageManager.java @@ -1,6 +1,5 @@ package net.sacredlabyrinth.phaed.simpleclans.managers; -import com.google.common.base.Charsets; import net.sacredlabyrinth.phaed.simpleclans.*; import net.sacredlabyrinth.phaed.simpleclans.events.ClanBalanceUpdateEvent; import net.sacredlabyrinth.phaed.simpleclans.loggers.BankLogger; diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index e56cbc37c..c33643b7c 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -94,6 +94,8 @@ help.command=help leave.command=leave join.command=join mute.command=mute +tag.completion=[tag] +name.completion=[name] acf-core.parameter.amount=amount acf-core.parameter.member=member acf-core.parameter.rank=rank From e76cd11dc691cf30815842f2506dcf60985faf83 Mon Sep 17 00:00:00 2001 From: Tomut0 Date: Fri, 18 Oct 2024 21:48:00 +0300 Subject: [PATCH 4/7] fix: clan disband should require a confirmation in gui --- .../phaed/simpleclans/ui/frames/ClanDetailsFrame.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/ui/frames/ClanDetailsFrame.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/ui/frames/ClanDetailsFrame.java index f1f7dcf21..f982228e6 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/ui/frames/ClanDetailsFrame.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/ui/frames/ClanDetailsFrame.java @@ -143,6 +143,7 @@ private void addDisband() { 50); disband.setListener(ClickType.DROP, () -> InventoryController.runSubcommand(getViewer(), "disband", false)); disband.setPermission(ClickType.DROP, "simpleclans.leader.disband"); + disband.setConfirmationRequired(ClickType.LEFT); add(disband); } From 5e95a583b6ba7e82da44b1676c83edf8c0408b24 Mon Sep 17 00:00:00 2001 From: Tomut0 Date: Tue, 22 Oct 2024 05:25:26 +0300 Subject: [PATCH 5/7] fix: completions have a fallback key --- .../phaed/simpleclans/commands/SCCommandManager.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/SCCommandManager.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/SCCommandManager.java index 9050460c9..3766a0e39 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/SCCommandManager.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/SCCommandManager.java @@ -172,8 +172,8 @@ private void addReplacements() { "clan_chat", sm.getString(COMMANDS_CLAN_CHAT) ); - SUBCOMMANDS.forEach(s -> processReplacement(s, "", ".command")); - COMPLETIONS.forEach(s -> processReplacement(s, "compl:", ".completion")); + SUBCOMMANDS.forEach(s -> processReplacement(s, "", ".command", true)); + COMPLETIONS.forEach(s -> processReplacement(s, "compl:", ".completion", false)); } @Override @@ -206,16 +206,18 @@ public String getMessage(CommandIssuer issuer, MessageKeyProvider key) { return this.locales; } - private void processReplacement(String key, String prefix, String suffix) { + private void processReplacement(String key, String prefix, String suffix, boolean hasFallback) { String replacement = optionalLang(key + suffix, (ClanPlayer) null); if (replacement == null) { replacement = key; } replacement = replacement.replace(" ", ""); - String finalReplacement = replacement.equals(key) ? key : replacement + "|" + key; + if (hasFallback) { + replacement = replacement + "|" + key; + } - getCommandReplacements().addReplacement(prefix + key, finalReplacement); + getCommandReplacements().addReplacement(prefix + key, replacement); } static { From 99bcbd4c2fafa374c0a2e7fa408a25d4a2932994 Mon Sep 17 00:00:00 2001 From: Tomut0 Date: Tue, 22 Oct 2024 05:26:38 +0300 Subject: [PATCH 6/7] fix: player can change locale even if selector is disabled --- .../simpleclans/commands/general/GeneralCommands.java | 9 +++++++-- .../phaed/simpleclans/commands/staff/StaffCommands.java | 2 +- src/main/resources/messages.properties | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/general/GeneralCommands.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/general/GeneralCommands.java index 930823248..7f1593860 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/general/GeneralCommands.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/general/GeneralCommands.java @@ -66,11 +66,16 @@ public void main(CommandSender sender) { @CommandPermission("simpleclans.anyone.locale") @Description("{@@command.description.locale}") @CommandCompletion("@locales") - public void locale(ClanPlayer cp, @Values("@locales") @Name("locale") String locale) { + public void locale(ClanPlayer cp, @Values("@locales") @Name("locale") @Single String locale) { + if (!settings.is(LANGUAGE_SELECTOR)) { + ChatBlock.sendMessageKey(cp, "locale.is.prohibited"); + return; + } + cp.setLocale(Helper.forLanguageTag(locale.replace("_", "-"))); plugin.getStorageManager().updateClanPlayer(cp); - ChatBlock.sendMessage(cp, lang("locale.has.been.changed")); + ChatBlock.sendMessageKey(cp, "locale.has.been.changed"); } @Subcommand("%create") diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/staff/StaffCommands.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/staff/StaffCommands.java index f23a9062b..a9cf8491f 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/staff/StaffCommands.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/commands/staff/StaffCommands.java @@ -379,7 +379,7 @@ public void rename(CommandSender sender, @Name("clan") ClanInput clanInput, @Nam @CommandPermission("simpleclans.mod.locale") @Description("{@@command.description.mod.locale}") @CommandCompletion("@locales") - public void locale(CommandSender sender, @Name("player") ClanPlayerInput input, @Values("@locales") @Name("locale") String locale) { + public void locale(CommandSender sender, @Name("player") ClanPlayerInput input, @Values("@locales") @Name("locale") @Single String locale) { ClanPlayer cp = input.getClanPlayer(); cp.setLocale(Helper.forLanguageTag(locale.replace("_", "-"))); plugin.getStorageManager().updateClanPlayer(cp); diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index c33643b7c..97b31dcc3 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -418,6 +418,7 @@ inactive.0=Inactive: {0} clan.commands=clan commands you.can.only.modify.the.color.and.case.of.the.tag=You can only modify the color and case of the tag locale.has.been.changed=&cYour language has been changed +locale.is.prohibited=&cLanguage selection on the server is prohibited. tag.changed.to.0=Tag changed to {0} 0.tag.changed.to.1=&bSuccessfully changed {0}''s tag to {1} name.0=Name: {0} From bdfb0c5d17d2cd8730eb95a29bddabfbe87c37a8 Mon Sep 17 00:00:00 2001 From: Tomut0 Date: Sat, 9 Nov 2024 04:39:41 +0300 Subject: [PATCH 7/7] feat: add Mojang API as UUID fetcher fallback --- .../phaed/simpleclans/uuid/UUIDFetcher.java | 79 ++++++++++++++----- .../simpleclans/uuid/UUIDFetcherTest.java | 6 +- 2 files changed, 65 insertions(+), 20 deletions(-) diff --git a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java index 5bdfa3b7e..6053b7238 100644 --- a/src/main/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java +++ b/src/main/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcher.java @@ -3,27 +3,31 @@ import com.google.gson.Gson; import com.google.gson.JsonObject; import net.sacredlabyrinth.phaed.simpleclans.ClanPlayer; +import net.sacredlabyrinth.phaed.simpleclans.SimpleClans; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; +import java.net.URI; import java.net.URL; import java.util.*; import java.util.concurrent.*; +import java.util.logging.Level; import java.util.stream.Collectors; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Collections.singletonList; /** - * Previous authors: - * - * @author evilmidget38 + * @author evilmidget38 (previous author) * @see Bukkit Thread * @see Github Gist */ public final class UUIDFetcher { private static final String PROFILE_URL = "https://api.minetools.eu/uuid/"; + private static final String FALLBACK_PROFILE_URL = "https://api.mojang.com/users/profiles/minecraft/"; private static final int BATCH_SIZE = 100; private static final Gson gson = new Gson(); @@ -54,7 +58,7 @@ public static Map fetchUUIDsForClanPlayers(List clanPl * @throws InterruptedException If the operation is interrupted while waiting * @throws ExecutionException If the computation threw an exception */ - public static UUID getUUIDOf(String name) throws InterruptedException, ExecutionException { + public static @Nullable UUID getUUIDOf(@NotNull String name) throws InterruptedException, ExecutionException { return fetchUUIDsConcurrently(singletonList(name)).get(name); } @@ -79,16 +83,18 @@ private static Map fetchUUIDsConcurrently(List names) thro } // Shutdown the executor service - executorService.shutdown(); + executorService.shutdownNow(); + if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) { + SimpleClans.getInstance().getLogger().warning("Executor did not terminate in time."); + } return resultMap; } // Create connection for each name - private static HttpURLConnection createConnection(String name) throws IOException { - URL url = new URL(PROFILE_URL + name); - + private static HttpURLConnection createConnection(@NotNull URL url) throws IOException { var connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("GET"); connection.setRequestProperty("Content-Type", "application/json"); connection.setUseCaches(false); @@ -98,26 +104,63 @@ private static HttpURLConnection createConnection(String name) throws IOExceptio } // Get UUID by name - private static UUID getUUID(String id) { + private static @NotNull UUID getUUID(@NotNull String id) { return UUID.fromString(id.substring(0, 8) + "-" + id.substring(8, 12) + "-" + id.substring(12, 16) + "-" + id.substring(16, 20) + "-" + id.substring(20, 32)); } // Handle single batch of names - private static Map fetchUUIDsForBatch(List batch) throws IOException { + private static Map fetchUUIDsForBatch(List batch) { Map uuidMap = new HashMap<>(); + for (String name : batch) { - HttpURLConnection connection = createConnection(name); - JsonObject response = gson.fromJson(new InputStreamReader(connection.getInputStream(), UTF_8), JsonObject.class); - - if (response.get("status").getAsString().equals("OK")) { - String id = response.get("id").getAsString(); - UUID uuid = getUUID(id); - uuidMap.put(response.get("name").getAsString(), uuid); - } + uuidMap.computeIfAbsent(name, k -> { + try { + return tryFetchUUIDWithFallback(k); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); } + return uuidMap; } + private static @Nullable UUID tryFetchUUIDWithFallback(@NotNull String name) throws IOException { + try { + // Try the primary URL + return fetchUUID(URI.create(PROFILE_URL + name).toURL()); + } catch (IOException e) { + // If the primary URL fails, attempt the fallback URL + SimpleClans.getInstance().getLogger().log(Level.WARNING, + String.format("Failed to fetch %s UUID by MineTools API. Trying to use Mojang API instead...", name), e); + return fetchUUID(URI.create(FALLBACK_PROFILE_URL + name).toURL()); + } + } + + private static @Nullable UUID fetchUUID(@NotNull URL url) throws IOException { + HttpURLConnection connection = createConnection(url); + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) { + throw new IOException(String.format("Unexpected response code: %d. Response: %s", + connection.getResponseCode(), connection.getResponseMessage())); + } + + JsonObject response = gson.fromJson(new InputStreamReader(connection.getInputStream(), UTF_8), JsonObject.class); + + if (!response.has("id")) { + return null; + } + + var id = response.get("id"); + var status = response.get("status"); + + if (id.isJsonNull() || + (response.has("status") && status.getAsString().equals("ERR"))) { + throw new IOException(String.format("Invalid UUID: %s", id)); + } + + return getUUID(id.getAsString()); + } + // Callable task for batch processing private static Callable> createTask(List batch) { return () -> fetchUUIDsForBatch(batch); diff --git a/src/test/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcherTest.java b/src/test/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcherTest.java index 5cc5fdfcd..7bbd51d32 100644 --- a/src/test/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcherTest.java +++ b/src/test/java/net/sacredlabyrinth/phaed/simpleclans/uuid/UUIDFetcherTest.java @@ -5,7 +5,7 @@ import java.util.UUID; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; public class UUIDFetcherTest { @@ -18,6 +18,8 @@ public void setup() { @Test public void getUUIDOf() throws Exception { - assertEquals(ghostUUID, UUIDFetcher.getUUIDOf("GhostTheWolf")); + assertEquals(ghostUUID, UUIDFetcher.getUUIDOf("GhostTheWolf"), "Assert fetching UUID from right name"); + + assertNull(UUIDFetcher.getUUIDOf("AnyNameLongerThan16Chars"), "Assert fetching UUID from wrong name"); } } \ No newline at end of file