parent
ac29beca3a
commit
2dd055d156
@ -1,17 +0,0 @@
|
|||||||
package cc.fascinated;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class Consts {
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private static String SITE_URL;
|
|
||||||
|
|
||||||
@Value("${site-url}")
|
|
||||||
public void setSiteUrl(String name) {
|
|
||||||
SITE_URL = name;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package cc.fascinated.api.model;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
|
||||||
import io.micrometer.common.lang.NonNull;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
@NoArgsConstructor
|
|
||||||
@Setter
|
|
||||||
@Getter
|
|
||||||
@ToString
|
|
||||||
public final class ErrorResponse {
|
|
||||||
/**
|
|
||||||
* The status code of this error.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
private HttpStatus status;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message of this error.
|
|
||||||
*/
|
|
||||||
@NonNull private String message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The timestamp this error occurred.
|
|
||||||
*/
|
|
||||||
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
|
|
||||||
private Date timestamp;
|
|
||||||
|
|
||||||
public ErrorResponse(@NonNull HttpStatus status, @NonNull String message) {
|
|
||||||
this.status = status;
|
|
||||||
this.message = message;
|
|
||||||
timestamp = new Date();
|
|
||||||
}
|
|
||||||
}
|
|
20
src/main/java/cc/fascinated/config/Config.java
Normal file
20
src/main/java/cc/fascinated/config/Config.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package cc.fascinated.config;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Getter
|
||||||
|
public class Config {
|
||||||
|
public static Config INSTANCE;
|
||||||
|
|
||||||
|
@Value("${public-url}")
|
||||||
|
private String webPublicUrl;
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void onInitialize() {
|
||||||
|
INSTANCE = this;
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package cc.fascinated.api.controller;
|
package cc.fascinated.controller;
|
||||||
|
|
||||||
import cc.fascinated.Consts;
|
import cc.fascinated.config.Config;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
@ -17,8 +17,8 @@ public class HomeController {
|
|||||||
|
|
||||||
@RequestMapping(value = "/")
|
@RequestMapping(value = "/")
|
||||||
public String home(Model model) {
|
public String home(Model model) {
|
||||||
model.addAttribute("url", Consts.getSITE_URL() + "/player/" + exampleUuid);
|
model.addAttribute("url", Config.INSTANCE.getWebPublicUrl() + "/player/" + exampleUuid);
|
||||||
model.addAttribute("avatar_url", Consts.getSITE_URL() + "/player/avatar/" + exampleUuid);
|
model.addAttribute("avatar_url", Config.INSTANCE.getWebPublicUrl() + "/player/avatar/" + exampleUuid);
|
||||||
return "index";
|
return "index";
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package cc.fascinated.api.controller;
|
package cc.fascinated.controller;
|
||||||
|
|
||||||
import cc.fascinated.player.PlayerManagerService;
|
import cc.fascinated.player.PlayerService;
|
||||||
import cc.fascinated.player.impl.Player;
|
import cc.fascinated.player.impl.Player;
|
||||||
import cc.fascinated.player.impl.Skin;
|
import cc.fascinated.player.impl.Skin;
|
||||||
import cc.fascinated.player.impl.SkinPart;
|
import cc.fascinated.player.impl.SkinPart;
|
||||||
@ -22,10 +22,10 @@ public class PlayerController {
|
|||||||
|
|
||||||
private final CacheControl cacheControl = CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic();
|
private final CacheControl cacheControl = CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic();
|
||||||
@NonNull private final SkinPart defaultHead = Objects.requireNonNull(Skin.getDefaultHead(), "Default head is null");
|
@NonNull private final SkinPart defaultHead = Objects.requireNonNull(Skin.getDefaultHead(), "Default head is null");
|
||||||
private final PlayerManagerService playerManagerService;
|
private final PlayerService playerManagerService;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public PlayerController(PlayerManagerService playerManagerService) {
|
public PlayerController(PlayerService playerManagerService) {
|
||||||
this.playerManagerService = playerManagerService;
|
this.playerManagerService = playerManagerService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,17 +41,24 @@ public class PlayerController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping(value = "/avatar/{id}")
|
@GetMapping(value = "/{part}/{id}")
|
||||||
public ResponseEntity<byte[]> getPlayerHead(@PathVariable String id) {
|
public ResponseEntity<byte[]> getPlayerHead(@PathVariable String part,
|
||||||
|
@PathVariable String id,
|
||||||
|
@RequestParam(required = false, defaultValue = "250") int size) {
|
||||||
Player player = playerManagerService.getPlayer(id);
|
Player player = playerManagerService.getPlayer(id);
|
||||||
byte[] headBytes;
|
byte[] headBytes = new byte[0];
|
||||||
if (player == null) {
|
if (player != null) {
|
||||||
headBytes = defaultHead.getPartData();
|
|
||||||
} else {
|
|
||||||
Skin skin = player.getSkin();
|
Skin skin = player.getSkin();
|
||||||
SkinPart head = skin.getHead();
|
SkinPart skinPart = skin.getPart(part);
|
||||||
headBytes = head.getPartData();
|
if (skinPart != null) {
|
||||||
|
headBytes = skinPart.getPartData(size);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headBytes == null) {
|
||||||
|
headBytes = defaultHead.getPartData(size);
|
||||||
|
}
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.cacheControl(cacheControl)
|
.cacheControl(cacheControl)
|
||||||
.contentType(MediaType.IMAGE_PNG)
|
.contentType(MediaType.IMAGE_PNG)
|
@ -1,6 +1,6 @@
|
|||||||
package cc.fascinated.api.controller;
|
package cc.fascinated.exception;
|
||||||
|
|
||||||
import cc.fascinated.api.model.ErrorResponse;
|
import cc.fascinated.model.ErrorResponse;
|
||||||
import io.micrometer.common.lang.NonNull;
|
import io.micrometer.common.lang.NonNull;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
@ -1,38 +0,0 @@
|
|||||||
package cc.fascinated.mojang;
|
|
||||||
|
|
||||||
import cc.fascinated.mojang.types.MojangApiProfile;
|
|
||||||
import cc.fascinated.mojang.types.MojangSessionServerProfile;
|
|
||||||
import cc.fascinated.util.WebRequest;
|
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service @Log4j2
|
|
||||||
public class MojangAPIService {
|
|
||||||
|
|
||||||
@Value("${mojang.session-server}")
|
|
||||||
private String mojangSessionServerUrl;
|
|
||||||
|
|
||||||
@Value("${mojang.api}")
|
|
||||||
private String mojangApiUrl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the Session Server profile of the player with the given UUID.
|
|
||||||
*
|
|
||||||
* @param id the uuid or name of the player
|
|
||||||
* @return the profile
|
|
||||||
*/
|
|
||||||
public MojangSessionServerProfile getSessionServerProfile(String id) {
|
|
||||||
return WebRequest.get(mojangSessionServerUrl + "/session/minecraft/profile/" + id, MojangSessionServerProfile.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the Mojang API profile of the player with the given UUID.
|
|
||||||
*
|
|
||||||
* @param id the name of the player
|
|
||||||
* @return the profile
|
|
||||||
*/
|
|
||||||
public MojangApiProfile getApiProfile(String id) {
|
|
||||||
return WebRequest.get(mojangApiUrl + "/users/profiles/minecraft/" + id, MojangApiProfile.class);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package cc.fascinated.mojang.types;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
@Getter @ToString
|
|
||||||
public class MojangApiProfile {
|
|
||||||
|
|
||||||
private String id;
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
public MojangApiProfile() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the profile is valid.
|
|
||||||
*
|
|
||||||
* @return if the profile is valid
|
|
||||||
*/
|
|
||||||
public boolean isValid() {
|
|
||||||
return id != null && name != null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package cc.fascinated.mojang.types;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Getter @ToString
|
|
||||||
public class MojangSessionServerProfile {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The UUID of the player.
|
|
||||||
*/
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the player.
|
|
||||||
*/
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The properties for the player.
|
|
||||||
*/
|
|
||||||
private final List<MojangSessionServerProfileProperties> properties = new ArrayList<>();
|
|
||||||
|
|
||||||
public MojangSessionServerProfile() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the texture property for the player.
|
|
||||||
*
|
|
||||||
* @return the texture property
|
|
||||||
*/
|
|
||||||
public MojangSessionServerProfileProperties getTextureProperty() {
|
|
||||||
for (MojangSessionServerProfileProperties property : properties) {
|
|
||||||
if (property.getName().equals("textures")) {
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package cc.fascinated.mojang.types;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.ToString;
|
|
||||||
|
|
||||||
@Getter @ToString
|
|
||||||
public class MojangSessionServerProfileProperties {
|
|
||||||
private String name;
|
|
||||||
private String value;
|
|
||||||
|
|
||||||
public MojangSessionServerProfileProperties() {}
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
package cc.fascinated.player;
|
|
||||||
|
|
||||||
import cc.fascinated.mojang.MojangAPIService;
|
|
||||||
import cc.fascinated.mojang.types.MojangApiProfile;
|
|
||||||
import cc.fascinated.mojang.types.MojangSessionServerProfile;
|
|
||||||
import cc.fascinated.player.impl.Player;
|
|
||||||
import cc.fascinated.util.UUIDUtils;
|
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
import net.jodah.expiringmap.ExpirationPolicy;
|
|
||||||
import net.jodah.expiringmap.ExpiringMap;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
@Service @Log4j2
|
|
||||||
public class PlayerManagerService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The cache of players.
|
|
||||||
*/
|
|
||||||
private final Map<UUID, Player> players = ExpiringMap.builder()
|
|
||||||
.expiration(1, TimeUnit.HOURS)
|
|
||||||
.expirationPolicy(ExpirationPolicy.CREATED)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The cache of player names to UUIDs.
|
|
||||||
*/
|
|
||||||
private final Map<String, UUID> playerNameToUUIDCache = ExpiringMap.builder()
|
|
||||||
.expiration(1, TimeUnit.DAYS)
|
|
||||||
.expirationPolicy(ExpirationPolicy.CREATED)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private final MojangAPIService mojangAPIService;
|
|
||||||
|
|
||||||
public PlayerManagerService(MojangAPIService mojangAPIService) {
|
|
||||||
this.mojangAPIService = mojangAPIService;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a player by their UUID.
|
|
||||||
*
|
|
||||||
* @param id the uuid or name of the player
|
|
||||||
* @return the player or null if the player does not exist
|
|
||||||
*/
|
|
||||||
public Player getPlayer(String id) {
|
|
||||||
UUID uuid = null;
|
|
||||||
if (id.length() == 32 || id.length() == 36) {
|
|
||||||
try {
|
|
||||||
uuid = UUID.fromString(id.length() == 32 ? UUIDUtils.addUUIDDashes(id) : id);
|
|
||||||
} catch (Exception ignored) {}
|
|
||||||
} else {
|
|
||||||
uuid = playerNameToUUIDCache.get(id.toUpperCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uuid != null && players.containsKey(uuid)) {
|
|
||||||
return players.get(uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
MojangSessionServerProfile profile = uuid == null ? null : mojangAPIService.getSessionServerProfile(uuid.toString());
|
|
||||||
if (profile == null) {
|
|
||||||
MojangApiProfile apiProfile = mojangAPIService.getApiProfile(id);
|
|
||||||
if (apiProfile == null || !apiProfile.isValid()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
profile = mojangAPIService.getSessionServerProfile(apiProfile.getId().length() == 32 ? UUIDUtils.addUUIDDashes(apiProfile.getId()) : apiProfile.getId());
|
|
||||||
}
|
|
||||||
if (profile == null) { // The player cannot be found using their name or UUID
|
|
||||||
log.info("Player with id {} could not be found", id);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
Player player = new Player(profile);
|
|
||||||
players.put(player.getUuid(), player);
|
|
||||||
playerNameToUUIDCache.put(player.getName().toUpperCase(), player.getUuid());
|
|
||||||
return player;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package cc.fascinated.player.impl;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter @AllArgsConstructor
|
|
||||||
public class Cape {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The URL of the cape
|
|
||||||
*/
|
|
||||||
private final String url;
|
|
||||||
}
|
|
@ -1,70 +0,0 @@
|
|||||||
package cc.fascinated.player.impl;
|
|
||||||
|
|
||||||
import cc.fascinated.Consts;
|
|
||||||
import cc.fascinated.Main;
|
|
||||||
import cc.fascinated.mojang.types.MojangSessionServerProfile;
|
|
||||||
import cc.fascinated.mojang.types.MojangSessionServerProfileProperties;
|
|
||||||
import cc.fascinated.util.UUIDUtils;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
public class Player {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The UUID of the player
|
|
||||||
*/
|
|
||||||
private final UUID uuid;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the player
|
|
||||||
*/
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The avatar URL of the player
|
|
||||||
*/
|
|
||||||
private final String avatarUrl;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The skin of the player
|
|
||||||
* <p>
|
|
||||||
* This will be null if the player does not have a skin.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
private Skin skin;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The cape of the player
|
|
||||||
* <p>
|
|
||||||
* This will be null if the player does not have a cape.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
private Cape cape;
|
|
||||||
|
|
||||||
public Player(MojangSessionServerProfile profile) {
|
|
||||||
this.uuid = UUID.fromString(UUIDUtils.addUUIDDashes(profile.getId()));
|
|
||||||
this.name = profile.getName();
|
|
||||||
this.avatarUrl = Consts.getSITE_URL() + "/avatar/" + this.uuid;
|
|
||||||
|
|
||||||
MojangSessionServerProfileProperties textureProperty = profile.getTextureProperty();
|
|
||||||
if (textureProperty == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode the texture property
|
|
||||||
String decoded = new String(java.util.Base64.getDecoder().decode(textureProperty.getValue()));
|
|
||||||
|
|
||||||
// Parse the decoded JSON
|
|
||||||
JsonObject json = Main.getGSON().fromJson(decoded, JsonObject.class);
|
|
||||||
JsonObject texturesJson = json.getAsJsonObject("textures");
|
|
||||||
JsonObject skinJson = texturesJson.getAsJsonObject("SKIN");
|
|
||||||
JsonObject capeJson = texturesJson.getAsJsonObject("CAPE");
|
|
||||||
JsonObject metadataJson = skinJson.get("metadata").getAsJsonObject();
|
|
||||||
|
|
||||||
this.skin = new Skin(skinJson.get("url").getAsString(), SkinType.fromString(metadataJson.get("model").getAsString()));
|
|
||||||
this.cape = new Cape(capeJson.get("url").getAsString());
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
package cc.fascinated.player.impl;
|
|
||||||
|
|
||||||
import cc.fascinated.Main;
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
|
|
||||||
@Getter @Log4j2
|
|
||||||
public class Skin {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The URL of the skin
|
|
||||||
*/
|
|
||||||
private final String url;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The model of the skin
|
|
||||||
*/
|
|
||||||
private final SkinType model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The bytes of the skin
|
|
||||||
*/
|
|
||||||
@JsonIgnore
|
|
||||||
private final byte[] skinBytes;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The head of the skin
|
|
||||||
*/
|
|
||||||
@JsonIgnore
|
|
||||||
private final SkinPart head;
|
|
||||||
|
|
||||||
public Skin(String url, SkinType model) {
|
|
||||||
this.url = url;
|
|
||||||
this.model = model;
|
|
||||||
this.skinBytes = this.getSkinData();
|
|
||||||
|
|
||||||
// The skin parts
|
|
||||||
this.head = new SkinPart(this.skinBytes, SkinPartEnum.HEAD);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the skin data from the URL.
|
|
||||||
*
|
|
||||||
* @return the skin data
|
|
||||||
*/
|
|
||||||
@SneakyThrows @JsonIgnore
|
|
||||||
public byte[] getSkinData() {
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
|
||||||
.uri(new URI(this.url))
|
|
||||||
.GET()
|
|
||||||
.build();
|
|
||||||
|
|
||||||
return Main.getCLIENT().send(request, HttpResponse.BodyHandlers.ofByteArray()).body();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the default/fallback head.
|
|
||||||
*
|
|
||||||
* @return the default head
|
|
||||||
*/
|
|
||||||
public static SkinPart getDefaultHead() {
|
|
||||||
try (InputStream stream = Main.class.getClassLoader().getResourceAsStream("images/default_head.png")) {
|
|
||||||
if (stream == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
byte[] bytes = stream.readAllBytes();
|
|
||||||
return new SkinPart(bytes, SkinPartEnum.HEAD);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.warn("Failed to load default head", ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,95 +0,0 @@
|
|||||||
package cc.fascinated.player.impl;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.extern.log4j.Log4j2;
|
|
||||||
|
|
||||||
import javax.imageio.ImageIO;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.image.BufferedImage;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
|
|
||||||
@Getter @Log4j2
|
|
||||||
public class SkinPart {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The whole skin data.
|
|
||||||
*/
|
|
||||||
private final byte[] data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The X coordinate of the part.
|
|
||||||
*/
|
|
||||||
private final int x;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Y coordinate of the part.
|
|
||||||
*/
|
|
||||||
private final int y;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The width of the part.
|
|
||||||
*/
|
|
||||||
private final int width;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The height of the part.
|
|
||||||
*/
|
|
||||||
private final int height;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The scale of the part output.
|
|
||||||
*/
|
|
||||||
private final int scale;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The part data from the skin.
|
|
||||||
*/
|
|
||||||
private byte[] partBytes;
|
|
||||||
|
|
||||||
public SkinPart(byte[] data, SkinPartEnum skinPartEnum) {
|
|
||||||
this.data = data;
|
|
||||||
this.x = skinPartEnum.getX();
|
|
||||||
this.y = skinPartEnum.getY();
|
|
||||||
this.width = skinPartEnum.getWidth();
|
|
||||||
this.height = skinPartEnum.getHeight();
|
|
||||||
this.scale = skinPartEnum.getScale();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the part data from the skin.
|
|
||||||
*
|
|
||||||
* @return the part data
|
|
||||||
*/
|
|
||||||
public byte[] getPartData() {
|
|
||||||
if (this.partBytes != null) {
|
|
||||||
return this.partBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(this.data));
|
|
||||||
if (image == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// Get the part of the image (e.g. the head)
|
|
||||||
BufferedImage partImage = image.getSubimage(this.x, this.y, this.width, this.height);
|
|
||||||
|
|
||||||
// Scale the image
|
|
||||||
int width = partImage.getWidth() * this.scale;
|
|
||||||
int height = partImage.getHeight() * this.scale;
|
|
||||||
BufferedImage scaledImage = new BufferedImage(width, height, partImage.getType());
|
|
||||||
Graphics2D graphics2D = scaledImage.createGraphics();
|
|
||||||
graphics2D.drawImage(partImage, 0, 0, width, height, null);
|
|
||||||
graphics2D.dispose();
|
|
||||||
partImage = scaledImage;
|
|
||||||
|
|
||||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
|
||||||
ImageIO.write(partImage, "png", byteArrayOutputStream);
|
|
||||||
this.partBytes = byteArrayOutputStream.toByteArray();
|
|
||||||
return this.partBytes;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
log.error("Failed to read image from skin data.", ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package cc.fascinated.player.impl;
|
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
|
|
||||||
@Getter @AllArgsConstructor
|
|
||||||
public enum SkinPartEnum {
|
|
||||||
|
|
||||||
HEAD(8, 8, 8, 8, 20);
|
|
||||||
|
|
||||||
private final int x;
|
|
||||||
private final int y;
|
|
||||||
private final int width;
|
|
||||||
private final int height;
|
|
||||||
private final int scale;
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package cc.fascinated.player.impl;
|
|
||||||
|
|
||||||
public enum SkinType {
|
|
||||||
|
|
||||||
DEFAULT,
|
|
||||||
SLIM;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the skin type from a string
|
|
||||||
*
|
|
||||||
* @param string the string
|
|
||||||
* @return the skin type
|
|
||||||
*/
|
|
||||||
public static SkinType fromString(String string) {
|
|
||||||
for (SkinType type : values()) {
|
|
||||||
if (type.name().equalsIgnoreCase(string)) {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,7 +5,7 @@ server:
|
|||||||
whitelabel:
|
whitelabel:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
site-url: http://localhost:80
|
public-url: http://localhost:80
|
||||||
|
|
||||||
mojang:
|
mojang:
|
||||||
session-server: https://sessionserver.mojang.com
|
session-server: https://sessionserver.mojang.com
|
||||||
|
BIN
src/main/resources/public/favicon.ico
Normal file
BIN
src/main/resources/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
@ -1,19 +1,23 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Minecraft Helper</title>
|
<title>Minecraft Utilities</title>
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport"
|
<meta name="viewport"
|
||||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
|
||||||
|
<!-- Discord Meta Tags -->
|
||||||
|
<meta name="description" content="Wrapper for the Minecraft APIs to make them easier to use.">
|
||||||
|
<meta name="theme-color" content="#3498DB">
|
||||||
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="flex flex-col h-screen mt-5 items-center bg-neutral-900 text-white text-center">
|
<body class="flex flex-col h-screen mt-5 items-center bg-neutral-900 text-white text-center">
|
||||||
<p class="font-bold text-red-600">Oh, no!</p>
|
<p class="font-bold text-red-600">Oh, no!</p>
|
||||||
<p>You have encountered an error.</p>
|
<p>You have encountered an error.</p>
|
||||||
|
|
||||||
<img class="mt-5 h-[30rem]" src="https://cdn.fascinated.cc/Ft2OVY.gif" alt="Error Gif"/>
|
<img class="mt-5 h-[30rem]" src="https://cdn.fascinated.cc/Dc0g0o3lP1j.gif" alt="Error Gif"/>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -1,13 +1,17 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
|
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
|
||||||
<head>
|
<head>
|
||||||
<title>Minecraft Helper</title>
|
<title>Minecraft Utilities</title>
|
||||||
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport"
|
<meta name="viewport"
|
||||||
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
|
||||||
|
<!-- Discord Meta Tags -->
|
||||||
|
<meta name="description" content="Wrapper for the Minecraft APIs to make them easier to use.">
|
||||||
|
<meta name="theme-color" content="#3498DB">
|
||||||
|
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="flex flex-col h-screen mt-5 items-center bg-neutral-900 text-white text-center">
|
<body class="flex flex-col h-screen mt-5 items-center bg-neutral-900 text-white text-center">
|
||||||
|
Loading…
Reference in New Issue
Block a user