Add Skin and Cape textures to players

This commit is contained in:
Braydon 2024-04-06 17:23:22 -04:00
parent 035b86920a
commit 16403673d3
7 changed files with 169 additions and 4 deletions

@ -0,0 +1,24 @@
package me.braydon.mc.common;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* A simple tuple utility.
*
* @author Braydon
*/
@NoArgsConstructor @AllArgsConstructor @Setter @Getter
public class Tuple<L, R> {
/**
* The left value.
*/
private L left;
/**
* The right value.
*/
private R right;
}

@ -0,0 +1,30 @@
package me.braydon.mc.model;
import com.google.gson.JsonObject;
import lombok.*;
/**
* A cape for a {@link Player}.
*
* @author Braydon
*/
@AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter @ToString
public final class Cape {
/**
* The texture URL of this cape.
*/
@NonNull private final String url;
/**
* Build a cape from the given Json object.
*
* @param jsonObject the json object to build from
* @return the built cape
*/
public static Cape fromJsonObject(JsonObject jsonObject) {
if (jsonObject == null) { // No object to parse
return null;
}
return new Cape(jsonObject.get("url").getAsString());
}
}

@ -26,6 +26,16 @@ public class Player {
*/ */
@NonNull private String username; @NonNull private String username;
/**
* The skin of this player.
*/
@NonNull private Skin skin;
/**
* The cape of this player, null if none.
*/
private Cape cape;
/** /**
* The profile actions this player has, null if none. * The profile actions this player has, null if none.
*/ */

@ -1,7 +1,50 @@
package me.braydon.mc.model; package me.braydon.mc.model;
import com.google.gson.JsonObject;
import lombok.*;
/** /**
* A skin for a {@link Player}.
*
* @author Braydon * @author Braydon
*/ */
@AllArgsConstructor(access = AccessLevel.PRIVATE) @Getter @ToString
public final class Skin { public final class Skin {
public static final Skin DEFAULT_STEVE = new Skin("http://textures.minecraft.net/texture/60a5bd016b3c9a1b9272e4929e30827a67be4ebb219017adbbc4a4d22ebd5b1", Model.DEFAULT);
/**
* The texture URL of this skin.
*/
@NonNull private final String url;
/**
* The model of this skin.
*/
@NonNull private final Model model;
/**
* Build a skin from the given Json object.
*
* @param jsonObject the json object to build from
* @return the built skin
*/
public static Skin fromJsonObject(JsonObject jsonObject) {
if (jsonObject == null) { // No object to parse
return null;
}
Model model = Model.DEFAULT; // The skin model
JsonObject metadataJsonObject = jsonObject.getAsJsonObject("metadata");
if (metadataJsonObject != null) { // Parse the skin model
model = Model.valueOf(metadataJsonObject.get("model").getAsString().toUpperCase());
}
return new Skin(jsonObject.get("url").getAsString(), model);
}
/**
* Possible models for a skin.
*/
public enum Model {
SLIM, DEFAULT
}
} }

@ -4,8 +4,10 @@ import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;
import me.braydon.mc.model.Cape;
import me.braydon.mc.model.Player; import me.braydon.mc.model.Player;
import me.braydon.mc.model.ProfileAction; import me.braydon.mc.model.ProfileAction;
import me.braydon.mc.model.Skin;
import org.springframework.data.redis.core.RedisHash; import org.springframework.data.redis.core.RedisHash;
import java.io.Serializable; import java.io.Serializable;
@ -26,8 +28,9 @@ public final class CachedPlayer extends Player implements Serializable {
*/ */
private long cached; private long cached;
public CachedPlayer(@NonNull UUID uniqueId, @NonNull String username, ProfileAction[] profileActions, long cached) { public CachedPlayer(@NonNull UUID uniqueId, @NonNull String username,
super(uniqueId, username, profileActions); @NonNull Skin skin, Cape cape, ProfileAction[] profileActions, long cached) {
super(uniqueId, username, skin, cape, profileActions);
this.cached = cached; this.cached = cached;
} }
} }

@ -1,7 +1,14 @@
package me.braydon.mc.model.token; package me.braydon.mc.model.token;
import com.google.gson.JsonObject;
import lombok.*; import lombok.*;
import me.braydon.mc.RESTfulMC;
import me.braydon.mc.common.Tuple;
import me.braydon.mc.model.Cape;
import me.braydon.mc.model.ProfileAction; import me.braydon.mc.model.ProfileAction;
import me.braydon.mc.model.Skin;
import java.util.Base64;
/** /**
* A token representing a Mojang user profile. * A token representing a Mojang user profile.
@ -31,6 +38,37 @@ public final class MojangProfileToken {
*/ */
@NonNull private ProfileAction[] profileActions; @NonNull private ProfileAction[] profileActions;
public Tuple<Skin, Cape> getSkinAndCape() {
ProfileProperty textures = getPropertyByName("textures"); // Get the profile textures
if (textures == null) { // No profile textures
return new Tuple<>();
}
JsonObject jsonObject = RESTfulMC.GSON.fromJson(textures.getDecodedValue(), JsonObject.class); // Get the Json object
JsonObject texturesJsonObject = jsonObject.getAsJsonObject("textures"); // Get the textures object
// Return the tuple containing the skin and cape
return new Tuple<>(
Skin.fromJsonObject(texturesJsonObject.getAsJsonObject("SKIN")),
Cape.fromJsonObject(texturesJsonObject.getAsJsonObject("CAPE"))
);
}
/**
* Get the profile property
* with the given name.
*
* @param name the property name
* @return the profile property, null if none
*/
public ProfileProperty getPropertyByName(@NonNull String name) {
for (ProfileProperty property : properties) {
if (property.getName().equalsIgnoreCase(name)) {
return property;
}
}
return null;
}
/** /**
* A property of a Mojang profile. * A property of a Mojang profile.
*/ */
@ -51,6 +89,17 @@ public final class MojangProfileToken {
*/ */
private String signature; private String signature;
/**
* Get the decoded Base64
* value of this property.
*
* @return the decoded value
*/
@NonNull
public String getDecodedValue() {
return new String(Base64.getDecoder().decode(value));
}
/** /**
* Is this property signed? * Is this property signed?
* *

@ -2,13 +2,16 @@ package me.braydon.mc.service;
import lombok.NonNull; import lombok.NonNull;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import me.braydon.mc.common.Tuple;
import me.braydon.mc.common.UUIDUtils; import me.braydon.mc.common.UUIDUtils;
import me.braydon.mc.common.web.JsonWebException; import me.braydon.mc.common.web.JsonWebException;
import me.braydon.mc.common.web.JsonWebRequest; import me.braydon.mc.common.web.JsonWebRequest;
import me.braydon.mc.exception.impl.BadRequestException; import me.braydon.mc.exception.impl.BadRequestException;
import me.braydon.mc.exception.impl.ResourceNotFoundException; import me.braydon.mc.exception.impl.ResourceNotFoundException;
import me.braydon.mc.model.Cape;
import me.braydon.mc.model.Player; import me.braydon.mc.model.Player;
import me.braydon.mc.model.ProfileAction; import me.braydon.mc.model.ProfileAction;
import me.braydon.mc.model.Skin;
import me.braydon.mc.model.cache.CachedPlayer; import me.braydon.mc.model.cache.CachedPlayer;
import me.braydon.mc.model.cache.CachedPlayerName; import me.braydon.mc.model.cache.CachedPlayerName;
import me.braydon.mc.model.token.MojangProfileToken; import me.braydon.mc.model.token.MojangProfileToken;
@ -100,11 +103,14 @@ public final class MojangService {
MojangProfileToken token = JsonWebRequest.makeRequest( MojangProfileToken token = JsonWebRequest.makeRequest(
UUID_TO_PROFILE.formatted(uuid), HttpMethod.GET UUID_TO_PROFILE.formatted(uuid), HttpMethod.GET
).execute(MojangProfileToken.class); ).execute(MojangProfileToken.class);
Tuple<Skin, Cape> skinAndCape = token.getSkinAndCape(); // Get the skin and cape
ProfileAction[] profileActions = token.getProfileActions(); ProfileAction[] profileActions = token.getProfileActions();
// Build our player model, cache it, and then return it // Build our player model, cache it, and then return it
CachedPlayer player = new CachedPlayer( CachedPlayer player = new CachedPlayer(
uuid, token.getName(), uuid, token.getName(),
skinAndCape.getLeft() == null ? Skin.DEFAULT_STEVE : skinAndCape.getLeft(),
skinAndCape.getRight(),
profileActions.length == 0 ? null : profileActions, profileActions.length == 0 ? null : profileActions,
System.currentTimeMillis() System.currentTimeMillis()
); );