From a614a048b4cb7b760d46c91a6dc82892b54dcca7 Mon Sep 17 00:00:00 2001 From: Luke Chambers Date: Sat, 1 Oct 2022 21:33:22 -0400 Subject: [PATCH 1/2] Add custom skins to /skull --- .../essentials/commands/Commandskull.java | 79 +++++++++++++++++-- .../src/main/resources/messages.properties | 1 + 2 files changed, 73 insertions(+), 7 deletions(-) diff --git a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandskull.java b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandskull.java index af5d3a20ab4..b921c14449b 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/commands/Commandskull.java +++ b/Essentials/src/main/java/com/earth2me/essentials/commands/Commandskull.java @@ -5,14 +5,21 @@ import com.earth2me.essentials.utils.EnumUtil; import com.earth2me.essentials.utils.MaterialUtil; import com.google.common.collect.Lists; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import org.bukkit.Material; import org.bukkit.Server; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.profile.PlayerProfile; import org.bukkit.scheduler.BukkitRunnable; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Base64; import java.util.Collections; import java.util.List; +import java.util.UUID; import java.util.regex.Pattern; import static com.earth2me.essentials.I18n.tl; @@ -20,20 +27,53 @@ public class Commandskull extends EssentialsCommand { private static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$"); + private static final Pattern URL_VALUE_PATTERN = Pattern.compile("^[0-9a-fA-F]{64}$"); + private static final Pattern BASE_64_PATTERN = Pattern.compile("^[A-Za-z0-9+/=]{180}$"); + private static final Material SKULL_ITEM = EnumUtil.getMaterial("PLAYER_HEAD", "SKULL_ITEM"); + private final boolean playerProfileSupported; + public Commandskull() { super("skull"); + + // The player profile API is only available in newer versions of Spigot 1.18.1 and above + boolean playerProfileSupported = true; + try { + Class.forName("org.bukkit.profile.PlayerProfile"); + } catch (final ClassNotFoundException e) { + playerProfileSupported = false; + } + this.playerProfileSupported = playerProfileSupported; } @Override protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception { final String owner; if (args.length > 0 && user.isAuthorized("essentials.skull.others")) { - if (!NAME_PATTERN.matcher(args[0]).matches()) { + if (BASE_64_PATTERN.matcher(args[0]).matches()) { + try { + final String decoded = new String(Base64.getDecoder().decode(args[0])); + final JsonObject jsonObject = JsonParser.parseString(decoded).getAsJsonObject(); + final String url = jsonObject + .getAsJsonObject("textures") + .getAsJsonObject("SKIN") + .get("url") + .getAsString(); + owner = url.substring(url.lastIndexOf("/") + 1); + } catch (final Exception e) { + // Any exception that can realistically happen here is caused by an invalid texture value + throw new IllegalArgumentException(tl("skullInvalidBase64")); + } + + if (!URL_VALUE_PATTERN.matcher(owner).matches()) { + throw new IllegalArgumentException(tl("skullInvalidBase64")); + } + } else if (!NAME_PATTERN.matcher(args[0]).matches()) { throw new IllegalArgumentException(tl("alphaNames")); + } else { + owner = args[0]; } - owner = args[0]; } else { owner = user.getName(); } @@ -63,19 +103,44 @@ private void editSkull(final User user, final ItemStack stack, final SkullMeta s new BukkitRunnable() { @Override public void run() { - //Run this stuff async because SkullMeta#setOwner causes a http request. - skullMeta.setDisplayName("§fSkull of " + owner); - skullMeta.setOwner(owner); + // Run this stuff async because it causes an HTTP request + + final String shortOwnerName; + if (URL_VALUE_PATTERN.matcher(owner).matches()) { + if (!playerProfileSupported) { + user.sendMessage(tl("unsupportedFeature")); + return; + } + + final URL url; + try { + url = new URL("https://textures.minecraft.net/texture/" + owner); + } catch (final MalformedURLException e) { + // The URL should never be malformed + throw new RuntimeException(e); + } + + final PlayerProfile profile = ess.getServer().createPlayerProfile(UUID.randomUUID()); + profile.getTextures().setSkin(url); + skullMeta.setOwnerProfile(profile); + + shortOwnerName = owner.substring(0, 7); + } else { + skullMeta.setOwner(owner); + shortOwnerName = owner; + } + skullMeta.setDisplayName("§fSkull of " + shortOwnerName); + new BukkitRunnable() { @Override public void run() { stack.setItemMeta(skullMeta); if (spawn) { InventoryWorkaround.addItems(user.getBase().getInventory(), stack); - user.sendMessage(tl("givenSkull", owner)); + user.sendMessage(tl("givenSkull", shortOwnerName)); return; } - user.sendMessage(tl("skullChanged", owner)); + user.sendMessage(tl("skullChanged", shortOwnerName)); } }.runTask(ess); } diff --git a/Essentials/src/main/resources/messages.properties b/Essentials/src/main/resources/messages.properties index 226c8484b7c..ad1b1f7d04b 100644 --- a/Essentials/src/main/resources/messages.properties +++ b/Essentials/src/main/resources/messages.properties @@ -1161,6 +1161,7 @@ skullCommandUsage1=/ skullCommandUsage1Description=Gets your own skull skullCommandUsage2=/ skullCommandUsage2Description=Gets the skull of the specified player +skullInvalidBase64=\u00a74The texture value is invalid. slimeMalformedSize=\u00a74Malformed size. smithingtableCommandDescription=Opens up a smithing table. smithingtableCommandUsage=/ From b9afe74c6057af2e7a7a443e718c41874793ccee Mon Sep 17 00:00:00 2001 From: Luke Chambers Date: Mon, 31 Oct 2022 18:38:09 -0400 Subject: [PATCH 2/2] Add extra usage message for using /skull with custom textures --- Essentials/src/main/resources/messages.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Essentials/src/main/resources/messages.properties b/Essentials/src/main/resources/messages.properties index ad1b1f7d04b..97c4f224805 100644 --- a/Essentials/src/main/resources/messages.properties +++ b/Essentials/src/main/resources/messages.properties @@ -1161,6 +1161,8 @@ skullCommandUsage1=/ skullCommandUsage1Description=Gets your own skull skullCommandUsage2=/ skullCommandUsage2Description=Gets the skull of the specified player +skullCommandUsage3=/ +skullCommandUsage3Description=Gets a skull with the specified texture (either the hash from a texture URL or a Base64 texture value) skullInvalidBase64=\u00a74The texture value is invalid. slimeMalformedSize=\u00a74Malformed size. smithingtableCommandDescription=Opens up a smithing table.