|
5 | 5 | import com.earth2me.essentials.utils.EnumUtil; |
6 | 6 | import com.earth2me.essentials.utils.MaterialUtil; |
7 | 7 | import com.google.common.collect.Lists; |
| 8 | +import com.google.gson.JsonObject; |
| 9 | +import com.google.gson.JsonParser; |
8 | 10 | import org.bukkit.Material; |
9 | 11 | import org.bukkit.Server; |
10 | 12 | import org.bukkit.inventory.ItemStack; |
11 | 13 | import org.bukkit.inventory.meta.SkullMeta; |
| 14 | +import org.bukkit.profile.PlayerProfile; |
12 | 15 |
|
| 16 | +import java.net.MalformedURLException; |
| 17 | +import java.net.URL; |
| 18 | +import java.util.Base64; |
13 | 19 | import java.util.Collections; |
14 | 20 | import java.util.List; |
| 21 | +import java.util.UUID; |
15 | 22 | import java.util.regex.Pattern; |
16 | 23 |
|
17 | 24 | import static com.earth2me.essentials.I18n.tl; |
18 | 25 |
|
19 | 26 | public class Commandskull extends EssentialsCommand { |
20 | 27 |
|
21 | 28 | private static final Pattern NAME_PATTERN = Pattern.compile("^[A-Za-z0-9_]+$"); |
| 29 | + private static final Pattern URL_VALUE_PATTERN = Pattern.compile("^[0-9a-fA-F]{64}$"); |
| 30 | + private static final Pattern BASE_64_PATTERN = Pattern.compile("^[A-Za-z0-9+/=]{180}$"); |
| 31 | + |
22 | 32 | private static final Material SKULL_ITEM = EnumUtil.getMaterial("PLAYER_HEAD", "SKULL_ITEM"); |
23 | 33 |
|
| 34 | + private final boolean playerProfileSupported; |
| 35 | + |
24 | 36 | public Commandskull() { |
25 | 37 | super("skull"); |
| 38 | + |
| 39 | + // The player profile API is only available in newer versions of Spigot 1.18.1 and above |
| 40 | + boolean playerProfileSupported = true; |
| 41 | + try { |
| 42 | + Class.forName("org.bukkit.profile.PlayerProfile"); |
| 43 | + } catch (final ClassNotFoundException e) { |
| 44 | + playerProfileSupported = false; |
| 45 | + } |
| 46 | + this.playerProfileSupported = playerProfileSupported; |
26 | 47 | } |
27 | 48 |
|
28 | 49 | @Override |
29 | 50 | protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception { |
30 | 51 | final String owner; |
31 | 52 | if (args.length > 0 && user.isAuthorized("essentials.skull.others")) { |
32 | | - if (!NAME_PATTERN.matcher(args[0]).matches()) { |
| 53 | + if (BASE_64_PATTERN.matcher(args[0]).matches()) { |
| 54 | + try { |
| 55 | + final String decoded = new String(Base64.getDecoder().decode(args[0])); |
| 56 | + final JsonObject jsonObject = JsonParser.parseString(decoded).getAsJsonObject(); |
| 57 | + final String url = jsonObject |
| 58 | + .getAsJsonObject("textures") |
| 59 | + .getAsJsonObject("SKIN") |
| 60 | + .get("url") |
| 61 | + .getAsString(); |
| 62 | + owner = url.substring(url.lastIndexOf("/") + 1); |
| 63 | + } catch (final Exception e) { |
| 64 | + // Any exception that can realistically happen here is caused by an invalid texture value |
| 65 | + throw new IllegalArgumentException(tl("skullInvalidBase64")); |
| 66 | + } |
| 67 | + |
| 68 | + if (!URL_VALUE_PATTERN.matcher(owner).matches()) { |
| 69 | + throw new IllegalArgumentException(tl("skullInvalidBase64")); |
| 70 | + } |
| 71 | + } else if (!NAME_PATTERN.matcher(args[0]).matches()) { |
33 | 72 | throw new IllegalArgumentException(tl("alphaNames")); |
| 73 | + } else { |
| 74 | + owner = args[0]; |
34 | 75 | } |
35 | | - owner = args[0]; |
36 | 76 | } else { |
37 | 77 | owner = user.getName(); |
38 | 78 | } |
@@ -60,18 +100,43 @@ protected void run(final Server server, final User user, final String commandLab |
60 | 100 |
|
61 | 101 | private void editSkull(final User user, final ItemStack stack, final SkullMeta skullMeta, final String owner, final boolean spawn) { |
62 | 102 | ess.runTaskAsynchronously(() -> { |
63 | | - //Run this stuff async because SkullMeta#setOwner causes a http request. |
64 | | - skullMeta.setDisplayName("§fSkull of " + owner); |
65 | | - //noinspection deprecation |
66 | | - skullMeta.setOwner(owner); |
| 103 | + // Run this stuff async because it causes an HTTP request |
| 104 | + |
| 105 | + final String shortOwnerName; |
| 106 | + if (URL_VALUE_PATTERN.matcher(owner).matches()) { |
| 107 | + if (!playerProfileSupported) { |
| 108 | + user.sendMessage(tl("unsupportedFeature")); |
| 109 | + return; |
| 110 | + } |
| 111 | + |
| 112 | + final URL url; |
| 113 | + try { |
| 114 | + url = new URL("https://textures.minecraft.net/texture/" + owner); |
| 115 | + } catch (final MalformedURLException e) { |
| 116 | + // The URL should never be malformed |
| 117 | + throw new RuntimeException(e); |
| 118 | + } |
| 119 | + |
| 120 | + final PlayerProfile profile = ess.getServer().createPlayerProfile(UUID.randomUUID()); |
| 121 | + profile.getTextures().setSkin(url); |
| 122 | + skullMeta.setOwnerProfile(profile); |
| 123 | + |
| 124 | + shortOwnerName = owner.substring(0, 7); |
| 125 | + } else { |
| 126 | + //noinspection deprecation |
| 127 | + skullMeta.setOwner(owner); |
| 128 | + shortOwnerName = owner; |
| 129 | + } |
| 130 | + skullMeta.setDisplayName("§fSkull of " + shortOwnerName); |
| 131 | + |
67 | 132 | ess.scheduleSyncDelayedTask(() -> { |
68 | 133 | stack.setItemMeta(skullMeta); |
69 | 134 | if (spawn) { |
70 | 135 | Inventories.addItem(user.getBase(), stack); |
71 | | - user.sendMessage(tl("givenSkull", owner)); |
| 136 | + user.sendMessage(tl("givenSkull", shortOwnerName)); |
72 | 137 | return; |
73 | 138 | } |
74 | | - user.sendMessage(tl("skullChanged", owner)); |
| 139 | + user.sendMessage(tl("skullChanged", shortOwnerName)); |
75 | 140 | }); |
76 | 141 | }); |
77 | 142 | } |
|
0 commit comments