Skip to content

Commit

Permalink
Rework how skins are handled
Browse files Browse the repository at this point in the history
  • Loading branch information
OliverSchlueter committed Aug 5, 2024
1 parent b6050e5 commit daabbfd
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 170 deletions.
8 changes: 4 additions & 4 deletions api/src/main/java/de/oliver/fancynpcs/api/NpcData.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class NpcData {
private final String name;
private final UUID creator;
private String displayName;
private SkinFetcher skin;
private SkinFetcher.SkinData skin;
private Location location;
private boolean showInTab;
private boolean spawnEntity;
Expand All @@ -43,7 +43,7 @@ public NpcData(
String name,
UUID creator,
String displayName,
SkinFetcher skin,
SkinFetcher.SkinData skin,
Location location,
boolean showInTab,
boolean spawnEntity,
Expand Down Expand Up @@ -141,11 +141,11 @@ public NpcData setDisplayName(String displayName) {
return this;
}

public SkinFetcher getSkin() {
public SkinFetcher.SkinData getSkin() {
return skin;
}

public NpcData setSkin(SkinFetcher skin) {
public NpcData setSkin(SkinFetcher.SkinData skin) {
this.skin = skin;
isDirty = true;
return this;
Expand Down
211 changes: 127 additions & 84 deletions api/src/main/java/de/oliver/fancynpcs/api/utils/SkinFetcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,122 +3,165 @@

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import de.oliver.fancylib.UUIDFetcher;
import de.oliver.fancynpcs.api.FancyNpcsPlugin;
import me.dave.chatcolorhandler.ChatColorHandler;
import me.dave.chatcolorhandler.parsers.custom.PlaceholderAPIParser;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.*;

public class SkinFetcher {
public static Map<String, SkinFetcher> skinCache = new HashMap<>();
public final class SkinFetcher {
public static Map<String, SkinData> skinCache = new HashMap<>(); // identifier -> skinData

private final SkinType skinType;
private final String identifier; // uuid or url
private String value;
private String signature;
private boolean loaded;

public SkinFetcher(String identifier) {
this.skinType = SkinType.getType(identifier);
this.identifier = identifier;
private SkinFetcher() {
}

/**
* Fetches the skin data from the Mojang API.
*
* @param identifier The identifier of the skin. This can be a UUID, username, URL or a placeholder by PAPI.
* @throws IOException If the skin data could not be fetched.
*/
public static SkinData fetchSkin(String identifier) throws IOException {
if (skinCache.containsKey(identifier)) {
SkinFetcher cached = skinCache.get(identifier);
this.value = cached.getValue();
this.signature = cached.getSignature();
this.loaded = true;
return;
return skinCache.get(identifier);
}

this.loaded = false;
load();
}

public SkinFetcher(String identifier, String value, String signature) {
this.skinType = SkinType.getType(identifier);
this.identifier = identifier;
this.value = value;
this.signature = signature;
this.loaded = true;
}
if (isPlaceholder(identifier)) {
String parsedIdentifier = ChatColorHandler.translate(identifier, List.of(PlaceholderAPIParser.class));
return fetchSkin(parsedIdentifier);
}

if (isURL(identifier)) {
return fetchSkinByURL(identifier);
}

public void load() {
this.loaded = false;
try {
URL url = new URL(skinType.getRequestUrl().replace("{uuid}", identifier));
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod(skinType.getRequestMethod());
if (skinType == SkinType.URL) {
conn.setDoOutput(true);
DataOutputStream outputStream = new DataOutputStream(conn.getOutputStream());
outputStream.writeBytes("url=" + URLEncoder.encode(identifier, StandardCharsets.UTF_8));
outputStream.close();
}
if (isUUID(identifier)) {
return fetchSkinByUUID(identifier);
}

String json = new Scanner(conn.getInputStream(), StandardCharsets.UTF_8).useDelimiter("\\A").next();
JsonParser parser = new JsonParser();
JsonObject obj = parser.parse(json).getAsJsonObject();
if (skinType == SkinType.UUID) {
this.value = obj.getAsJsonArray("properties").get(0).getAsJsonObject().getAsJsonPrimitive("value").getAsString();
this.signature = obj.getAsJsonArray("properties").get(0).getAsJsonObject().getAsJsonPrimitive("signature").getAsString();
} else if (skinType == SkinType.URL) {
this.value = obj.getAsJsonObject("data").getAsJsonObject("texture").getAsJsonPrimitive("value").getAsString();
this.signature = obj.getAsJsonObject("data").getAsJsonObject("texture").getAsJsonPrimitive("signature").getAsString();
}
this.loaded = true;
skinCache.put(identifier, this);
} catch (Exception e) {
this.loaded = false;
// assume it's a username
UUID uuid = UUIDFetcher.getUUID(identifier);
if (uuid != null) {
return fetchSkinByUUID(uuid.toString());
}
}

public SkinType getSkinType() {
return skinType;
return null;
}

public String getIdentifier() {
return identifier;
/**
* Fetches the skin data from the Mojang API.
*
* @throws IOException If the skin data could not be fetched.
*/
public static SkinData fetchSkinByUUID(String uuid) throws IOException {
URL url = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + uuid + "?unsigned=false");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");

String json = new Scanner(conn.getInputStream(), StandardCharsets.UTF_8).useDelimiter("\\A").next();
JsonParser parser = new JsonParser();
JsonObject obj = parser.parse(json).getAsJsonObject();

String value = obj.getAsJsonArray("properties").get(0).getAsJsonObject().getAsJsonPrimitive("value").getAsString();
String signature = obj.getAsJsonArray("properties").get(0).getAsJsonObject().getAsJsonPrimitive("signature").getAsString();
SkinData skinData = new SkinData(uuid, value, signature);

skinCache.put(uuid, skinData);
return skinData;
}

public String getValue() {
return value;
/**
* Fetches the skin data from the Mojang API.
*
* @throws IOException If the skin data could not be fetched.
*/
public static SkinData fetchSkinByURL(String skinURL) throws IOException {
URL url = new URL("https://api.mineskin.org/generate/url");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
DataOutputStream outputStream = new DataOutputStream(conn.getOutputStream());
outputStream.writeBytes("url=" + URLEncoder.encode(skinURL, StandardCharsets.UTF_8));
outputStream.close();

String json = new Scanner(conn.getInputStream(), StandardCharsets.UTF_8).useDelimiter("\\A").next();
JsonParser parser = new JsonParser();
JsonObject obj = parser.parse(json).getAsJsonObject();

String value = obj.getAsJsonObject("data").getAsJsonObject("texture").getAsJsonPrimitive("value").getAsString();
String signature = obj.getAsJsonObject("data").getAsJsonObject("texture").getAsJsonPrimitive("signature").getAsString();
SkinData skinData = new SkinData(skinURL, value, signature);

skinCache.put(skinURL, skinData);
return skinData;
}

public String getSignature() {
return signature;
private static boolean isURL(String identifier) {
return identifier.startsWith("http");
}

public boolean isLoaded() {
return loaded;
private static boolean isPlaceholder(String identifier) {
return identifier.startsWith("%") && identifier.endsWith("%") || identifier.startsWith("{") && identifier.endsWith("}");
}

public enum SkinType {
UUID("https://sessionserver.mojang.com/session/minecraft/profile/{uuid}?unsigned=false", "GET"),
URL("https://api.mineskin.org/generate/url", "POST");

private final String requestUrl;
private final String requestMethod;
private static boolean isUUID(String identifier) {
return identifier.length() == 36 && identifier.contains("-");
}

SkinType(String requestUrl, String requestMethod) {
this.requestUrl = requestUrl;
this.requestMethod = requestMethod;
}
/**
* Represents all required data for a skin.
*
* @param identifier The identifier of the skin. This can be a UUID, username, URL or a placeholder by PAPI.
* @param value The value of the skin. If {@code null}, the skin will be fetched from the Mojang API.
* @param signature The signature of the skin. If {@code null}, the skin will be fetched from the Mojang API.
*/
public record SkinData(@NotNull String identifier, @Nullable String value, @Nullable String signature) {

/**
* Fetches the skin data from the Mojang API if the value or signature is {@code null}.
*
* @return The value of the skin or {@code null} if the skin data could not be fetched.
*/
@Override
public String value() {
if (value == null || value.isEmpty()) {
try {
SkinData skinData = fetchSkin(identifier);
return skinData == null ? null : skinData.value();
} catch (IOException e) {
FancyNpcsPlugin.get().getPlugin().getLogger().warning("Failed to fetch skin data for " + identifier);
}
}

public static SkinType getType(String s) {
return s.startsWith("http") ? URL : UUID;
return value;
}

public String getRequestUrl() {
return requestUrl;
}
/**
* Fetches the skin data from the Mojang API if the value or signature is {@code null}.
*
* @return The signature of the skin or {@code null} if the skin data could not be fetched.
*/
@Override
public String signature() {
if (signature == null || signature.isEmpty()) {
try {
SkinData skinData = fetchSkin(identifier);
return skinData == null ? null : skinData.signature();
} catch (IOException e) {
FancyNpcsPlugin.get().getPlugin().getLogger().warning("Failed to fetch skin data for " + identifier);
}
}

public String getRequestMethod() {
return requestMethod;
return signature;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ public void create() {
npc = new ServerPlayer(minecraftServer, serverLevel, new GameProfile(uuid, ""));
((ServerPlayer) npc).gameProfile = gameProfile;

if (data.getSkin() != null && data.getSkin().isLoaded()) {
if (data.getSkin() != null && data.getSkin().value() != null && data.getSkin().signature() != null) {
// sessionserver.mojang.com/session/minecraft/profile/<UUID>?unsigned=false
((ServerPlayer) npc).getGameProfile().getProperties().replaceValues("textures", ImmutableList.of(new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature())));
((ServerPlayer) npc).getGameProfile().getProperties().replaceValues("textures", ImmutableList.of(new Property("textures", data.getSkin().value(), data.getSkin().signature())));
}
} else {
EntityType<?> nmsType = BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(data.getType().getKey()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ public void create() {
npc = new ServerPlayer(minecraftServer, serverLevel, new GameProfile(uuid, ""));
((ServerPlayer) npc).gameProfile = gameProfile;

if (data.getSkin() != null && data.getSkin().isLoaded()) {
if (data.getSkin() != null && data.getSkin().value() != null && data.getSkin().signature() != null) {
// sessionserver.mojang.com/session/minecraft/profile/<UUID>?unsigned=false
((ServerPlayer) npc).getGameProfile().getProperties().replaceValues("textures", ImmutableList.of(new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature())));
((ServerPlayer) npc).getGameProfile().getProperties().replaceValues("textures", ImmutableList.of(new Property("textures", data.getSkin().value(), data.getSkin().signature())));
}
} else {
EntityType<?> nmsType = BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(data.getType().getKey()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ public void create() {
npc = new ServerPlayer(minecraftServer, serverLevel, new GameProfile(uuid, ""), ClientInformation.createDefault());
((ServerPlayer) npc).gameProfile = gameProfile;

if (data.getSkin() != null && data.getSkin().isLoaded()) {
if (data.getSkin() != null && data.getSkin().value() != null && data.getSkin().signature() != null) {
// sessionserver.mojang.com/session/minecraft/profile/<UUID>?unsigned=false
((ServerPlayer) npc).getGameProfile().getProperties().replaceValues("textures", ImmutableList.of(new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature())));
((ServerPlayer) npc).getGameProfile().getProperties().replaceValues("textures", ImmutableList.of(new Property("textures", data.getSkin().value(), data.getSkin().signature())));
}
} else {
EntityType<?> nmsType = BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(data.getType().getKey()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,9 @@ public void create() {
npc = new ServerPlayer(minecraftServer, serverLevel, new GameProfile(uuid, ""), ClientInformation.createDefault());
((ServerPlayer) npc).gameProfile = gameProfile;

if (data.getSkin() != null && data.getSkin().isLoaded()) {
if (data.getSkin() != null && data.getSkin().value() != null && data.getSkin().signature() != null) {
// sessionserver.mojang.com/session/minecraft/profile/<UUID>?unsigned=false
((ServerPlayer) npc).getGameProfile().getProperties().replaceValues("textures", ImmutableList.of(new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature())));
((ServerPlayer) npc).getGameProfile().getProperties().replaceValues("textures", ImmutableList.of(new Property("textures", data.getSkin().value(), data.getSkin().signature())));
}
} else {
EntityType<?> nmsType = BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(data.getType().getKey()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ public void create() {
npc = new ServerPlayer(minecraftServer, serverLevel, new GameProfile(uuid, ""), ClientInformation.createDefault());
((ServerPlayer) npc).gameProfile = gameProfile;

if (data.getSkin() != null && data.getSkin().isLoaded()) {
if (data.getSkin() != null && data.getSkin().value() != null && data.getSkin().signature() != null) {
// sessionserver.mojang.com/session/minecraft/profile/<UUID>?unsigned=false
((ServerPlayer) npc).getGameProfile().getProperties().replaceValues("textures", ImmutableList.of(new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature())));
((ServerPlayer) npc).getGameProfile().getProperties().replaceValues("textures", ImmutableList.of(new Property("textures", data.getSkin().value(), data.getSkin().signature())));
}
} else {
EntityType<?> nmsType = BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(data.getType().getKey()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ public void create() {
npc = new ServerPlayer(minecraftServer, serverLevel, new GameProfile(uuid, ""), ClientInformation.createDefault());
((ServerPlayer) npc).gameProfile = gameProfile;

if (data.getSkin() != null && data.getSkin().isLoaded()) {
if (data.getSkin() != null && data.getSkin().value() != null && data.getSkin().signature() != null) {
// sessionserver.mojang.com/session/minecraft/profile/<UUID>?unsigned=false
((ServerPlayer) npc).getGameProfile().getProperties().replaceValues("textures", ImmutableList.of(new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature())));
((ServerPlayer) npc).getGameProfile().getProperties().replaceValues("textures", ImmutableList.of(new Property("textures", data.getSkin().value(), data.getSkin().signature())));
}
} else {
EntityType<?> nmsType = BuiltInRegistries.ENTITY_TYPE.get(CraftNamespacedKey.toMinecraft(data.getType().getKey()));
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/de/oliver/fancynpcs/NpcManagerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ public void saveNpcs(boolean force) {
npcConfig.set("npcs." + data.getId() + ".mirrorSkin", data.isMirrorSkin());

if (data.getSkin() != null) {
npcConfig.set("npcs." + data.getId() + ".skin.identifier", data.getSkin().getIdentifier());
npcConfig.set("npcs." + data.getId() + ".skin.value", data.getSkin().getValue());
npcConfig.set("npcs." + data.getId() + ".skin.signature", data.getSkin().getSignature());
npcConfig.set("npcs." + data.getId() + ".skin.identifier", data.getSkin().identifier());
npcConfig.set("npcs." + data.getId() + ".skin.value", data.getSkin().value());
npcConfig.set("npcs." + data.getId() + ".skin.signature", data.getSkin().signature());
}

if (data.getEquipment() != null) {
Expand Down Expand Up @@ -242,9 +242,9 @@ public void loadNpcs() {
String skinIdentifier = npcConfig.getString("npcs." + id + ".skin.identifier", npcConfig.getString("npcs." + id + ".skin.uuid", ""));
String skinValue = npcConfig.getString("npcs." + id + ".skin.value");
String skinSignature = npcConfig.getString("npcs." + id + ".skin.signature");
SkinFetcher skin = null;
if (skinIdentifier.length() > 0) {
skin = new SkinFetcher(skinIdentifier, skinValue, skinSignature);
SkinFetcher.SkinData skin = null;
if (!skinIdentifier.isEmpty()) {
skin = new SkinFetcher.SkinData(skinIdentifier, skinValue, skinSignature);
}

boolean showInTab = npcConfig.getBoolean("npcs." + id + ".showInTab");
Expand Down
Loading

0 comments on commit daabbfd

Please sign in to comment.