Skip to content

Commit 4af39da

Browse files
TheLukeGuymdcfeJRoy
authored andcommitted
Add ability to use texture url in /skull (EssentialsX#5120)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com> Co-authored-by: Josh Roy <10731363+JRoy@users.noreply.github.com>
1 parent 556d821 commit 4af39da

2 files changed

Lines changed: 76 additions & 8 deletions

File tree

Essentials/src/main/java/com/earth2me/essentials/commands/Commandskull.java

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,34 +5,74 @@
55
import com.earth2me.essentials.utils.EnumUtil;
66
import com.earth2me.essentials.utils.MaterialUtil;
77
import com.google.common.collect.Lists;
8+
import com.google.gson.JsonObject;
9+
import com.google.gson.JsonParser;
810
import org.bukkit.Material;
911
import org.bukkit.Server;
1012
import org.bukkit.inventory.ItemStack;
1113
import org.bukkit.inventory.meta.SkullMeta;
14+
import org.bukkit.profile.PlayerProfile;
1215

16+
import java.net.MalformedURLException;
17+
import java.net.URL;
18+
import java.util.Base64;
1319
import java.util.Collections;
1420
import java.util.List;
21+
import java.util.UUID;
1522
import java.util.regex.Pattern;
1623

1724
import static com.earth2me.essentials.I18n.tl;
1825

1926
public class Commandskull extends EssentialsCommand {
2027

2128
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+
2232
private static final Material SKULL_ITEM = EnumUtil.getMaterial("PLAYER_HEAD", "SKULL_ITEM");
2333

34+
private final boolean playerProfileSupported;
35+
2436
public Commandskull() {
2537
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;
2647
}
2748

2849
@Override
2950
protected void run(final Server server, final User user, final String commandLabel, final String[] args) throws Exception {
3051
final String owner;
3152
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()) {
3372
throw new IllegalArgumentException(tl("alphaNames"));
73+
} else {
74+
owner = args[0];
3475
}
35-
owner = args[0];
3676
} else {
3777
owner = user.getName();
3878
}
@@ -60,18 +100,43 @@ protected void run(final Server server, final User user, final String commandLab
60100

61101
private void editSkull(final User user, final ItemStack stack, final SkullMeta skullMeta, final String owner, final boolean spawn) {
62102
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+
67132
ess.scheduleSyncDelayedTask(() -> {
68133
stack.setItemMeta(skullMeta);
69134
if (spawn) {
70135
Inventories.addItem(user.getBase(), stack);
71-
user.sendMessage(tl("givenSkull", owner));
136+
user.sendMessage(tl("givenSkull", shortOwnerName));
72137
return;
73138
}
74-
user.sendMessage(tl("skullChanged", owner));
139+
user.sendMessage(tl("skullChanged", shortOwnerName));
75140
});
76141
});
77142
}

Essentials/src/main/resources/messages.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,9 @@ skullCommandUsage1=/<command>
12081208
skullCommandUsage1Description=Gets your own skull
12091209
skullCommandUsage2=/<command> <player>
12101210
skullCommandUsage2Description=Gets the skull of the specified player
1211+
skullCommandUsage3=/<command> <texture>
1212+
skullCommandUsage3Description=Gets a skull with the specified texture (either the hash from a texture URL or a Base64 texture value)
1213+
skullInvalidBase64=\u00a74The texture value is invalid.
12111214
slimeMalformedSize=\u00a74Malformed size.
12121215
smithingtableCommandDescription=Opens up a smithing table.
12131216
smithingtableCommandUsage=/<command>

0 commit comments

Comments
 (0)