diff --git a/src/main/java/pw/kaboom/extras/commands/CommandSkin.java b/src/main/java/pw/kaboom/extras/commands/CommandSkin.java index 8209541..af61ada 100644 --- a/src/main/java/pw/kaboom/extras/commands/CommandSkin.java +++ b/src/main/java/pw/kaboom/extras/commands/CommandSkin.java @@ -1,5 +1,7 @@ package pw.kaboom.extras.commands; +import java.util.HashMap; +import java.util.Map; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.command.Command; @@ -7,12 +9,12 @@ import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; -import pw.kaboom.extras.helpers.SkinDownloader; +import pw.kaboom.extras.skin.SkinManager; import javax.annotation.Nonnull; public final class CommandSkin implements CommandExecutor { - private long millis; + private final Map lastUsedMillis = new HashMap<>(); @Override public boolean onCommand(final @Nonnull CommandSender sender, @@ -26,11 +28,12 @@ public final class CommandSkin implements CommandExecutor { } final Player player = (Player) sender; + final long millis = lastUsedMillis.getOrDefault(player, 0L); final long millisDifference = System.currentTimeMillis() - millis; if (args.length == 0) { player.sendMessage(Component - .text("Usage: /" + label + " ", + .text("Usage: /" + label + " \n/" + label + "off", NamedTextColor.RED)); return true; } @@ -41,13 +44,25 @@ public final class CommandSkin implements CommandExecutor { return true; } + lastUsedMillis.put(player, System.currentTimeMillis()); + final String name = args[0]; + + if (name.equalsIgnoreCase("off") || name.equalsIgnoreCase("remove") + || name.equalsIgnoreCase("disable")) { + SkinManager.resetSkin(player, true); + return true; + } + + if (name.equalsIgnoreCase("auto") || name.equalsIgnoreCase("default") + || name.equalsIgnoreCase("reset")) { + SkinManager.applySkin(player, player.getName(), true); + return true; + } + final boolean shouldSendMessage = true; - SkinDownloader skinDownloader = new SkinDownloader(); - skinDownloader.applySkin(player, name, shouldSendMessage); - - millis = System.currentTimeMillis(); + SkinManager.applySkin(player, name, shouldSendMessage); return true; } } diff --git a/src/main/java/pw/kaboom/extras/commands/CommandUsername.java b/src/main/java/pw/kaboom/extras/commands/CommandUsername.java index 7eaa5b0..2befeb7 100644 --- a/src/main/java/pw/kaboom/extras/commands/CommandUsername.java +++ b/src/main/java/pw/kaboom/extras/commands/CommandUsername.java @@ -1,6 +1,8 @@ package pw.kaboom.extras.commands; import com.destroystokyo.paper.profile.PlayerProfile; +import java.util.HashMap; +import java.util.Map; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.bukkit.Bukkit; @@ -14,7 +16,7 @@ import org.bukkit.entity.Player; import javax.annotation.Nonnull; public final class CommandUsername implements CommandExecutor { - private long millis; + private final Map lastUsedMillis = new HashMap<>(); @Override public boolean onCommand(final @Nonnull CommandSender sender, @@ -31,6 +33,7 @@ public final class CommandUsername implements CommandExecutor { final String nameColor = ChatColor.translateAlternateColorCodes( '&', String.join(" ", args)); final String name = nameColor.substring(0, Math.min(16, nameColor.length())); + final long millis = lastUsedMillis.getOrDefault(player, 0L); final long millisDifference = System.currentTimeMillis() - millis; if (args.length == 0) { @@ -62,7 +65,7 @@ public final class CommandUsername implements CommandExecutor { profile.setName(name); // FIXME: Marked for removal player.setPlayerProfile(profile); - millis = System.currentTimeMillis(); + lastUsedMillis.put(player, System.currentTimeMillis()); player.sendMessage( Component.text("Successfully set your username to \"") diff --git a/src/main/java/pw/kaboom/extras/helpers/SkinDownloader.java b/src/main/java/pw/kaboom/extras/helpers/SkinDownloader.java deleted file mode 100644 index 9dc4ea3..0000000 --- a/src/main/java/pw/kaboom/extras/helpers/SkinDownloader.java +++ /dev/null @@ -1,115 +0,0 @@ -package pw.kaboom.extras.helpers; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.util.HashMap; -import java.util.UUID; - -import javax.net.ssl.HttpsURLConnection; - -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; -import org.bukkit.scheduler.BukkitRunnable; - -import com.destroystokyo.paper.profile.PlayerProfile; -import com.destroystokyo.paper.profile.ProfileProperty; - -import net.kyori.adventure.text.Component; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import pw.kaboom.extras.Main; - -public final class SkinDownloader { - private static HashMap skinProfiles = new HashMap(); - - private HttpsURLConnection skinConnection; - private InputStreamReader skinStream; - - private String texture; - private String signature; - - public void applySkin(final Player player, final String name, final boolean shouldSendMessage) { - new BukkitRunnable() { - @Override - public void run() { - final PlayerProfile profile = player.getPlayerProfile(); - - try { - fetchSkinData(name); - profile.setProperty(new ProfileProperty("textures", texture, signature)); - - if (shouldSendMessage) { - player.sendMessage( - Component.text("Successfully set your skin to ") - .append(Component.text(name)) - .append(Component.text("'s")) - ); - } - } catch (Exception exception) { - try { - skinStream.close(); - skinConnection.disconnect(); - } catch (Exception ignored) { - } - - if (shouldSendMessage) { - player.sendMessage(Component - .text("A player with that username doesn't exist")); - } - - return; - } - - new BukkitRunnable() { - @Override - public void run() { - try { - player.setPlayerProfile(profile); - } catch (Exception ignored) { - } - } - }.runTask(JavaPlugin.getPlugin(Main.class)); - } - }.runTaskAsynchronously(JavaPlugin.getPlugin(Main.class)); - } - - public void fillJoinProfile(final PlayerProfile profile, final String name, final UUID uuid) { - try { - fetchSkinData(name); - profile.setProperty(new ProfileProperty("textures", texture, signature)); - skinProfiles.put(uuid, profile); - } catch (Exception exception) { - try { - skinStream.close(); - skinConnection.disconnect(); - } catch (Exception ignored) { - } - } - } - - private void fetchSkinData(final String playerName) throws IOException { - final URL skinUrl = new URL("https://api.ashcon.app/mojang/v2/user/" + playerName); - skinConnection = (HttpsURLConnection) skinUrl.openConnection(); - skinConnection.setConnectTimeout(0); - - skinStream = new InputStreamReader(skinConnection.getInputStream()); - final JsonObject responseJson = new JsonParser().parse(skinStream).getAsJsonObject(); - final JsonObject rawSkin = responseJson.getAsJsonObject("textures").getAsJsonObject("raw"); - texture = rawSkin.get("value").getAsString(); - signature = rawSkin.get("signature").getAsString(); - - skinStream.close(); - skinConnection.disconnect(); - } - - public static PlayerProfile getProfile(final UUID uuid) { - return skinProfiles.get(uuid); - } - - public static void removeProfile(final UUID uuid) { - skinProfiles.remove(uuid); - } -} diff --git a/src/main/java/pw/kaboom/extras/modules/player/PlayerConnection.java b/src/main/java/pw/kaboom/extras/modules/player/PlayerConnection.java index 4472a70..a15df64 100644 --- a/src/main/java/pw/kaboom/extras/modules/player/PlayerConnection.java +++ b/src/main/java/pw/kaboom/extras/modules/player/PlayerConnection.java @@ -6,10 +6,12 @@ import java.util.concurrent.ThreadLocalRandom; import org.bukkit.Bukkit; import org.bukkit.Location; +import org.bukkit.Server; import org.bukkit.World; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.PlayerJoinEvent; @@ -28,6 +30,7 @@ import com.google.common.base.Charsets; import pw.kaboom.extras.Main; import pw.kaboom.extras.modules.server.ServerTabComplete; +import pw.kaboom.extras.skin.SkinManager; public final class PlayerConnection implements Listener { private static final FileConfiguration CONFIG = JavaPlugin.getPlugin(Main.class).getConfig(); @@ -115,11 +118,12 @@ public final class PlayerConnection implements Listener { player.setOp(true); } - /*try { - player.setPlayerProfile(SkinDownloader.getProfile(player.getUniqueId())); - SkinDownloader.removeProfile(player.getUniqueId()); - } catch (Exception ignored) { - }*/ + final Server server = Bukkit.getServer(); + + + if (!server.getOnlineMode()) { + SkinManager.applySkin(player, player.getName(), false); + } } @EventHandler diff --git a/src/main/java/pw/kaboom/extras/skin/SkinData.java b/src/main/java/pw/kaboom/extras/skin/SkinData.java new file mode 100644 index 0000000..b78fffc --- /dev/null +++ b/src/main/java/pw/kaboom/extras/skin/SkinData.java @@ -0,0 +1,5 @@ +package pw.kaboom.extras.skin; + +public record SkinData(String texture, String signature) { + +} \ No newline at end of file diff --git a/src/main/java/pw/kaboom/extras/skin/SkinManager.java b/src/main/java/pw/kaboom/extras/skin/SkinManager.java new file mode 100644 index 0000000..be76dea --- /dev/null +++ b/src/main/java/pw/kaboom/extras/skin/SkinManager.java @@ -0,0 +1,159 @@ +package pw.kaboom.extras.skin; + +import com.google.gson.Gson; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.HttpResponse.BodyHandlers; +import java.util.List; +import java.util.UUID; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import com.destroystokyo.paper.profile.PlayerProfile; +import com.destroystokyo.paper.profile.ProfileProperty; + +import net.kyori.adventure.text.Component; + +import org.bukkit.scheduler.BukkitScheduler; +import pw.kaboom.extras.Main; +import pw.kaboom.extras.skin.response.ProfileResponse; +import pw.kaboom.extras.skin.response.SkinResponse; + +public final class SkinManager { + private static final HttpClient httpClient = HttpClient.newHttpClient(); + private static final Gson GSON = new Gson(); + private static final ExecutorService executorService = Executors + .newCachedThreadPool(); + + public static void resetSkin(final Player player, final boolean shouldSendMessage) { + executorService.submit(() -> { + final PlayerProfile playerProfile = player.getPlayerProfile(); + playerProfile.removeProperty("textures"); + + final BukkitScheduler bukkitScheduler = Bukkit.getScheduler(); + final Main plugin = JavaPlugin.getPlugin(Main.class); + + bukkitScheduler.runTask(plugin, () -> player.setPlayerProfile(playerProfile)); + + if(!shouldSendMessage) { + return; + } + + player.sendMessage(Component.text("Successfully removed your skin")); + }); + } + + public static void applySkin(final Player player, final String name, + final boolean shouldSendMessage) { + executorService.submit(() -> { + final PlayerProfile profile = player.getPlayerProfile(); + final SkinData skinData; + + try { + skinData = getSkinData(name).get(); + } catch (Exception e) { + if(!shouldSendMessage) { + return; + } + + player.sendMessage(Component.text("A player with that username doesn't exist")); + return; + } + + final String texture = skinData.texture(); + final String signature = skinData.signature(); + profile.setProperty(new ProfileProperty("textures", texture, signature)); + + final BukkitScheduler bukkitScheduler = Bukkit.getScheduler(); + final Main plugin = JavaPlugin.getPlugin(Main.class); + + bukkitScheduler.runTask(plugin, + () -> player.setPlayerProfile(profile)); + + + if(!shouldSendMessage) { + return; + } + + player.sendMessage(Component.text("Successfully set your skin to ") + .append(Component.text(name)) + .append(Component.text("'s"))); + }); + } + + public static CompletableFuture getSkinData(final String playerName) { + return CompletableFuture.supplyAsync(() -> { + final UUID uuid; + try { + uuid = getUUID(playerName).get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + try { + return getSkinData(uuid).get(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }, executorService); + } + + public static CompletableFuture getSkinData(final UUID uuid) { + return CompletableFuture.supplyAsync(() -> { + final SkinResponse response = sendRequestForJSON( + "https://sessionserver.mojang.com/session/minecraft/profile/" + + uuid + "?unsigned=false", SkinResponse.class); + + final List properties = response.properties(); + + for (ProfileProperty property : properties) { + if(!property.getName().equals("textures")) { + continue; + } + + return new SkinData(property.getValue(), property.getSignature()); + } + + throw new RuntimeException("No textures property"); + }, executorService); + } + + private static T sendRequestForJSON(String url, Class clazz) { + final HttpRequest request = HttpRequest.newBuilder() + .GET() + .uri(URI.create(url)) + .build(); + + final HttpResponse response; + + try { + response = httpClient.send(request, BodyHandlers.ofString()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return GSON.fromJson(response.body(), clazz); + } + + private static CompletableFuture getUUID(final String playerName) { + return CompletableFuture.supplyAsync(() -> { + final ProfileResponse parsedResponse = sendRequestForJSON + ("https://api.mojang.com/users/profiles/minecraft/" + playerName, + ProfileResponse.class); + + final String dashedUuid = parsedResponse + .id() + .replaceAll("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})", "$1-$2-$3-$4-$5"); + + return UUID.fromString(dashedUuid); + }, executorService); + } +} diff --git a/src/main/java/pw/kaboom/extras/skin/response/ProfileResponse.java b/src/main/java/pw/kaboom/extras/skin/response/ProfileResponse.java new file mode 100644 index 0000000..70da361 --- /dev/null +++ b/src/main/java/pw/kaboom/extras/skin/response/ProfileResponse.java @@ -0,0 +1,20 @@ +package pw.kaboom.extras.skin.response; + +public class ProfileResponse { + + public ProfileResponse(String name, String id) { + this.name = name; + this.id = id; + } + + private final String name; + private final String id; + + public String name() { + return name; + } + + public String id() { + return id; + } +} diff --git a/src/main/java/pw/kaboom/extras/skin/response/SkinResponse.java b/src/main/java/pw/kaboom/extras/skin/response/SkinResponse.java new file mode 100644 index 0000000..340b8de --- /dev/null +++ b/src/main/java/pw/kaboom/extras/skin/response/SkinResponse.java @@ -0,0 +1,32 @@ +package pw.kaboom.extras.skin.response; + +import com.destroystokyo.paper.profile.ProfileProperty; +import java.util.List; +import java.util.Objects; + +public final class SkinResponse { + + private final String id; + private final String name; + private final List properties; + + public SkinResponse(String id, String name, List properties) { + this.id = id; + this.name = name; + this.properties = properties; + } + + public String id() { + return id; + } + + public String name() { + return name; + } + + public List properties() { + return properties; + } + + +}