wow! avatars!!
This commit is contained in:
parent
50265b6228
commit
0819b228ba
18
src/main/java/cc/fascinated/Consts.java
Normal file
18
src/main/java/cc/fascinated/Consts.java
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package cc.fascinated;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import lombok.Getter;
|
|||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.experimental.Helper;
|
import lombok.experimental.Helper;
|
||||||
import lombok.extern.log4j.Log4j2;
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@ -2,13 +2,15 @@ package cc.fascinated.api.controller;
|
|||||||
|
|
||||||
import cc.fascinated.player.PlayerManagerService;
|
import cc.fascinated.player.PlayerManagerService;
|
||||||
import cc.fascinated.player.impl.Player;
|
import cc.fascinated.player.impl.Player;
|
||||||
|
import cc.fascinated.player.impl.Skin;
|
||||||
|
import cc.fascinated.player.impl.SkinPart;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
|
@RequestMapping(value = "/")
|
||||||
public class PlayerController {
|
public class PlayerController {
|
||||||
|
|
||||||
private final PlayerManagerService playerManagerService;
|
private final PlayerManagerService playerManagerService;
|
||||||
@ -18,7 +20,7 @@ public class PlayerController {
|
|||||||
this.playerManagerService = playerManagerService;
|
this.playerManagerService = playerManagerService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/{id}") @ResponseBody
|
@GetMapping(value = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody
|
||||||
public ResponseEntity<Player> getPlayer(@PathVariable String id) {
|
public ResponseEntity<Player> getPlayer(@PathVariable String id) {
|
||||||
Player player = playerManagerService.getPlayer(id);
|
Player player = playerManagerService.getPlayer(id);
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
@ -26,4 +28,17 @@ public class PlayerController {
|
|||||||
}
|
}
|
||||||
return ResponseEntity.ok(player);
|
return ResponseEntity.ok(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GetMapping(value = "/avatar/{id}")
|
||||||
|
public ResponseEntity<byte[]> getPlayerHead(@PathVariable String id) {
|
||||||
|
Player player = playerManagerService.getPlayer(id);
|
||||||
|
if (player == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Skin skin = player.getSkin();
|
||||||
|
SkinPart head = skin.getHead();
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.contentType(MediaType.IMAGE_PNG)
|
||||||
|
.body(head.getPartData());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package cc.fascinated.mojang;
|
|||||||
import cc.fascinated.Main;
|
import cc.fascinated.Main;
|
||||||
import cc.fascinated.mojang.types.MojangApiProfile;
|
import cc.fascinated.mojang.types.MojangApiProfile;
|
||||||
import cc.fascinated.mojang.types.MojangSessionServerProfile;
|
import cc.fascinated.mojang.types.MojangSessionServerProfile;
|
||||||
|
import com.google.gson.reflect.TypeToken;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -35,7 +36,7 @@ public class MojangAPIService {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
HttpResponse<String> response = Main.getCLIENT().send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = Main.getCLIENT().send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
return Main.getGSON().fromJson(response.body(), MojangSessionServerProfile.class);
|
return Main.getGSON().fromJson(response.body(), new TypeToken<MojangSessionServerProfile>(){}.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,6 +53,6 @@ public class MojangAPIService {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
HttpResponse<String> response = Main.getCLIENT().send(request, HttpResponse.BodyHandlers.ofString());
|
HttpResponse<String> response = Main.getCLIENT().send(request, HttpResponse.BodyHandlers.ofString());
|
||||||
return Main.getGSON().fromJson(response.body(), MojangApiProfile.class);
|
return Main.getGSON().fromJson(response.body(), new TypeToken<MojangApiProfile>(){}.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,4 +10,13 @@ public class MojangApiProfile {
|
|||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
public MojangApiProfile() {}
|
public MojangApiProfile() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the profile is valid.
|
||||||
|
*
|
||||||
|
* @return if the profile is valid
|
||||||
|
*/
|
||||||
|
public boolean isValid() {
|
||||||
|
return id != null && name != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,17 +5,21 @@ import cc.fascinated.mojang.types.MojangApiProfile;
|
|||||||
import cc.fascinated.mojang.types.MojangSessionServerProfile;
|
import cc.fascinated.mojang.types.MojangSessionServerProfile;
|
||||||
import cc.fascinated.player.impl.Player;
|
import cc.fascinated.player.impl.Player;
|
||||||
import cc.fascinated.util.UUIDUtils;
|
import cc.fascinated.util.UUIDUtils;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
import net.jodah.expiringmap.ExpirationPolicy;
|
import net.jodah.expiringmap.ExpirationPolicy;
|
||||||
import net.jodah.expiringmap.ExpiringMap;
|
import net.jodah.expiringmap.ExpiringMap;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@Service
|
@Service @Log4j2
|
||||||
public class PlayerManagerService {
|
public class PlayerManagerService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(PlayerManagerService.class);
|
||||||
/**
|
/**
|
||||||
* The cache of players.
|
* The cache of players.
|
||||||
*/
|
*/
|
||||||
@ -59,12 +63,13 @@ public class PlayerManagerService {
|
|||||||
MojangSessionServerProfile profile = uuid == null ? null : mojangAPIService.getSessionServerProfile(uuid.toString());
|
MojangSessionServerProfile profile = uuid == null ? null : mojangAPIService.getSessionServerProfile(uuid.toString());
|
||||||
if (profile == null) {
|
if (profile == null) {
|
||||||
MojangApiProfile apiProfile = mojangAPIService.getApiProfile(id);
|
MojangApiProfile apiProfile = mojangAPIService.getApiProfile(id);
|
||||||
if (apiProfile == null) {
|
if (apiProfile == null || !apiProfile.isValid()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
profile = mojangAPIService.getSessionServerProfile(apiProfile.getId().length() == 32 ? UUIDUtils.addUUIDDashes(apiProfile.getId()) : apiProfile.getId());
|
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
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
Player player = new Player(profile);
|
Player player = new Player(profile);
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
package cc.fascinated.player.impl;
|
package cc.fascinated.player.impl;
|
||||||
|
|
||||||
|
import cc.fascinated.Consts;
|
||||||
import cc.fascinated.Main;
|
import cc.fascinated.Main;
|
||||||
import cc.fascinated.mojang.types.MojangSessionServerProfile;
|
import cc.fascinated.mojang.types.MojangSessionServerProfile;
|
||||||
import cc.fascinated.mojang.types.MojangSessionServerProfileProperties;
|
import cc.fascinated.mojang.types.MojangSessionServerProfileProperties;
|
||||||
import cc.fascinated.util.UUIDUtils;
|
import cc.fascinated.util.UUIDUtils;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonObject;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@ -22,6 +24,11 @@ public class Player {
|
|||||||
*/
|
*/
|
||||||
private final String name;
|
private final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The avatar URL of the player
|
||||||
|
*/
|
||||||
|
private final String avatarUrl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The skin of the player
|
* The skin of the player
|
||||||
* <p>
|
* <p>
|
||||||
@ -41,6 +48,7 @@ public class Player {
|
|||||||
public Player(MojangSessionServerProfile profile) {
|
public Player(MojangSessionServerProfile profile) {
|
||||||
this.uuid = UUID.fromString(UUIDUtils.addUUIDDashes(profile.getId()));
|
this.uuid = UUID.fromString(UUIDUtils.addUUIDDashes(profile.getId()));
|
||||||
this.name = profile.getName();
|
this.name = profile.getName();
|
||||||
|
this.avatarUrl = Consts.getSITE_URL() + "/avatar/" + this.uuid;
|
||||||
|
|
||||||
MojangSessionServerProfileProperties textureProperty = profile.getTextureProperty();
|
MojangSessionServerProfileProperties textureProperty = profile.getTextureProperty();
|
||||||
if (textureProperty == null) {
|
if (textureProperty == null) {
|
||||||
|
@ -1,9 +1,16 @@
|
|||||||
package cc.fascinated.player.impl;
|
package cc.fascinated.player.impl;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import cc.fascinated.Main;
|
||||||
import lombok.Getter;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import lombok.*;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
@Getter @AllArgsConstructor
|
import java.net.URI;
|
||||||
|
import java.net.http.HttpRequest;
|
||||||
|
import java.net.http.HttpResponse;
|
||||||
|
|
||||||
|
@Getter
|
||||||
public class Skin {
|
public class Skin {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,4 +22,40 @@ public class Skin {
|
|||||||
* The model of the skin
|
* The model of the skin
|
||||||
*/
|
*/
|
||||||
private final SkinType model;
|
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, 8, 8, 8, 8, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
95
src/main/java/cc/fascinated/player/impl/SkinPart.java
Normal file
95
src/main/java/cc/fascinated/player/impl/SkinPart.java
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
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.awt.image.DataBufferByte;
|
||||||
|
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, int x, int y, int width, int height, int scale) {
|
||||||
|
this.data = data;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
this.scale = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,8 @@ server:
|
|||||||
address: 0.0.0.0
|
address: 0.0.0.0
|
||||||
port: 7500
|
port: 7500
|
||||||
|
|
||||||
|
public-url: http://localhost:7500
|
||||||
|
|
||||||
mojang:
|
mojang:
|
||||||
session-server: https://sessionserver.mojang.com
|
session-server: https://sessionserver.mojang.com
|
||||||
api: https://api.mojang.com
|
api: https://api.mojang.com
|
Loading…
Reference in New Issue
Block a user