diff --git a/src/main/java/cc.fascinated/common/ColorUtils.java b/src/main/java/cc.fascinated/common/ColorUtils.java new file mode 100644 index 0000000..8afab40 --- /dev/null +++ b/src/main/java/cc.fascinated/common/ColorUtils.java @@ -0,0 +1,81 @@ +package cc.fascinated.common; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * @author Braydon + */ +@UtilityClass +public final class ColorUtils { + private static final Pattern STRIP_COLOR_PATTERN = Pattern.compile("(?i)§[0-9A-FK-OR]"); + private static final Map COLOR_MAP = new HashMap<>(); + static { + // Map each color to its corresponding hex code + COLOR_MAP.put('0', "#000000"); // Black + COLOR_MAP.put('1', "#0000AA"); // Dark Blue + COLOR_MAP.put('2', "#00AA00"); // Dark Green + COLOR_MAP.put('3', "#00AAAA"); // Dark Aqua + COLOR_MAP.put('4', "#AA0000"); // Dark Red + COLOR_MAP.put('5', "#AA00AA"); // Dark Purple + COLOR_MAP.put('6', "#FFAA00"); // Gold + COLOR_MAP.put('7', "#AAAAAA"); // Gray + COLOR_MAP.put('8', "#555555"); // Dark Gray + COLOR_MAP.put('9', "#5555FF"); // Blue + COLOR_MAP.put('a', "#55FF55"); // Green + COLOR_MAP.put('b', "#55FFFF"); // Aqua + COLOR_MAP.put('c', "#FF5555"); // Red + COLOR_MAP.put('d', "#FF55FF"); // Light Purple + COLOR_MAP.put('e', "#FFFF55"); // Yellow + COLOR_MAP.put('f', "#FFFFFF"); // White + } + + /** + * Strip the color codes + * from the given input. + * + * @param input the input to strip + * @return the stripped input + */ + @NonNull + public static String stripColor(@NonNull String input) { + return STRIP_COLOR_PATTERN.matcher(input).replaceAll(""); + } + + /** + * Convert the given input into HTML format. + *

+ * This will replace each color code with + * a span tag with the respective color in + * hex format. + *

+ * + * @param input the input to convert + * @return the converted input + */ + @NonNull + public static String toHTML(@NonNull String input) { + StringBuilder builder = new StringBuilder(); + boolean nextIsColor = false; // Is the next char a color code? + + for (char character : input.toCharArray()) { + // Found color symbol, next color is the color + if (character == '§') { + nextIsColor = true; + continue; + } + if (nextIsColor) { // Map the current color to its hex code + String color = COLOR_MAP.getOrDefault(Character.toLowerCase(character), ""); + builder.append(""); + nextIsColor = false; + continue; + } + builder.append(character); // Append the char... + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/cc.fascinated/EnumUtils.java b/src/main/java/cc.fascinated/common/EnumUtils.java similarity index 95% rename from src/main/java/cc.fascinated/EnumUtils.java rename to src/main/java/cc.fascinated/common/EnumUtils.java index 689eae1..8a4560e 100644 --- a/src/main/java/cc.fascinated/EnumUtils.java +++ b/src/main/java/cc.fascinated/common/EnumUtils.java @@ -1,4 +1,4 @@ -package cc.fascinated; +package cc.fascinated.common; import lombok.NonNull; import lombok.experimental.UtilityClass; diff --git a/src/main/java/cc.fascinated/common/JavaMinecraftVersion.java b/src/main/java/cc.fascinated/common/JavaMinecraftVersion.java new file mode 100644 index 0000000..62b1142 --- /dev/null +++ b/src/main/java/cc.fascinated/common/JavaMinecraftVersion.java @@ -0,0 +1,187 @@ +package cc.fascinated.common; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.extern.log4j.Log4j2; + +/** + * @author Braydon + * @see Protocol Version Numbers + * @see Spigot NMS (1.16+) + * @see Spigot NMS (1.10 - 1.15) + * @see Spigot NMS (1.8 - 1.9) + */ +@RequiredArgsConstructor @Getter @ToString @Log4j2(topic = "Minecraft Version") +public enum JavaMinecraftVersion { + V1_20_3(765, "v1_20_R3"), // 1.20.3 & 1.20.4 + V1_20_2(764, "v1_20_R2"), // 1.20.2 + V1_20(763, "v1_20_R1"), // 1.20 & 1.20.1 + + V1_19_4(762, "v1_19_R3"), // 1.19.4 + V1_19_3(761, "v1_19_R2"), // 1.19.3 + V1_19_1(760, "v1_19_R1"), // 1.19.1 & 1.19.2 + V1_19(759, "v1_19_R1"), // 1.19 + + V1_18_2(758, "v1_18_R2"), // 1.18.2 + V1_18(757, "v1_18_R1"), // 1.18 & 1.18.1 + + V1_17_1(756, "v1_17_R1"), // 1.17.1 + V1_17(755, "v1_17_R1"), // 1.17 + + V1_16_4(754, "v1_16_R3"), // 1.16.4 & 1.16.5 + V1_16_3(753, "v1_16_R2"), // 1.16.3 + V1_16_2(751, "v1_16_R2"), // 1.16.2 + V1_16_1(736, "v1_16_R1"), // 1.16.1 + V1_16(735, "v1_16_R1"), // 1.16 + + V1_15_2(578, "v1_15_R1"), // 1.15.2 + V1_15_1(575, "v1_15_R1"), // 1.15.1 + V1_15(573, "v1_15_R1"), // 1.15 + + V1_14_4(498, "v1_14_R1"), // 1.14.4 + V1_14_3(490, "v1_14_R1"), // 1.14.3 + V1_14_2(485, "v1_14_R1"), // 1.14.2 + V1_14_1(480, "v1_14_R1"), // 1.14.1 + V1_14(477, "v1_14_R1"), // 1.14 + + V1_13_2(404, "v1_13_R2"), // 1.13.2 + V1_13_1(401, "v1_13_R2"), // 1.13.1 + V1_13(393, "v1_13_R1"), // 1.13 + + V1_12_2(340, "v1_12_R1"), // 1.12.2 + V1_12_1(338, "v1_12_R1"), // 1.12.1 + V1_12(335, "v1_12_R1"), // 1.12 + + V1_11_1(316, "v1_11_R1"), // 1.11.1 & 1.11.2 + V1_11(315, "v1_11_R1"), // 1.11 + + V1_10(210, "v1_10_R1"), // 1.10.x + + V1_9_3(110, "v1_9_R2"), // 1.9.3 & 1.9.4 + V1_9_2(109, "v1_9_R1"), // 1.9.2 + V1_9_1(108, "v1_9_R1"), // 1.9.1 + V1_9(107, "v1_9_R1"), // 1.9 + + V1_8(47, "v1_8_R3"), // 1.8.x + + UNKNOWN(-1, "Unknown"); + + // Game Updates + public static final JavaMinecraftVersion TRAILS_AND_TALES = JavaMinecraftVersion.V1_20; + public static final JavaMinecraftVersion THE_WILD_UPDATE = JavaMinecraftVersion.V1_19; + public static final JavaMinecraftVersion CAVES_AND_CLIFFS_PT_2 = JavaMinecraftVersion.V1_18; + public static final JavaMinecraftVersion CAVES_AND_CLIFFS_PT_1 = JavaMinecraftVersion.V1_17; + public static final JavaMinecraftVersion NETHER_UPDATE = JavaMinecraftVersion.V1_16; + public static final JavaMinecraftVersion BUZZY_BEES = JavaMinecraftVersion.V1_15; + public static final JavaMinecraftVersion VILLAGE_AND_PILLAGE = JavaMinecraftVersion.V1_14; + public static final JavaMinecraftVersion UPDATE_AQUATIC = JavaMinecraftVersion.V1_13; + public static final JavaMinecraftVersion WORLD_OF_COLOR_UPDATE = JavaMinecraftVersion.V1_12; + public static final JavaMinecraftVersion EXPLORATION_UPDATE = JavaMinecraftVersion.V1_11; + public static final JavaMinecraftVersion FROSTBURN_UPDATE = JavaMinecraftVersion.V1_10; + public static final JavaMinecraftVersion THE_COMBAT_UPDATE = JavaMinecraftVersion.V1_9; + public static final JavaMinecraftVersion BOUNTIFUL_UPDATE = JavaMinecraftVersion.V1_8; + + /** + * The protocol number of this version. + */ + private final int protocol; + + /** + * The server version for this version. + */ + private final String nmsVersion; + + /** + * The cached name of this version. + */ + private String name; + + /** + * Get the name of this protocol version. + * + * @return the name + */ + public String getName() { + // We have a name + if (this.name != null) { + return this.name; + } + // Use the server version as the name if unknown + if (this == UNKNOWN) { + this.name = this.getNmsVersion(); + } else { // Parse the name + this.name = name().substring(1); + this.name = this.name.replace("_", "."); + } + return this.name; + } + + /** + * Is this version legacy? + * + * @return whether this version is legacy + */ + public boolean isLegacy() { + return this.isBelow(JavaMinecraftVersion.V1_16); + } + + /** + * Check if this version is + * above the one given. + * + * @param other the other version + * @return true if above, otherwise false + */ + public boolean isAbove(JavaMinecraftVersion other) { + return this.protocol > other.getProtocol(); + } + + /** + * Check if this version is + * or above the one given. + * + * @param other the other version + * @return true if is or above, otherwise false + */ + public boolean isOrAbove(JavaMinecraftVersion other) { + return this.protocol >= other.getProtocol(); + } + + /** + * Check if this version is + * below the one given. + * + * @param other the other version + * @return true if below, otherwise false + */ + public boolean isBelow(JavaMinecraftVersion other) { + return this.protocol < other.getProtocol(); + } + + /** + * Check if this version is + * or below the one given. + * + * @param other the other version + * @return true if is or below, otherwise false + */ + public boolean isOrBelow(JavaMinecraftVersion other) { + return this.protocol <= other.getProtocol(); + } + + /** + * Get the version from the given protocol. + * + * @param protocol the protocol to get the version for + * @return the version, null if none + */ + public static JavaMinecraftVersion byProtocol(int protocol) { + for (JavaMinecraftVersion version : values()) { + if (version.getProtocol() == protocol) { + return version; + } + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/cc.fascinated/common/ServerUtils.java b/src/main/java/cc.fascinated/common/ServerUtils.java new file mode 100644 index 0000000..97e45a6 --- /dev/null +++ b/src/main/java/cc.fascinated/common/ServerUtils.java @@ -0,0 +1,30 @@ +package cc.fascinated.common; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class ServerUtils { + + /** + * Get the hostname and port from a hostname string + * + * @param hostname the hostname string + * @return the hostname and port + */ + public static Tuple getHostnameAndPort(String hostname) { + String[] split = hostname.split(":"); + if (split.length == 1) { + return new Tuple<>(split[0], 25565); + } + return new Tuple<>(split[0], Integer.parseInt(split[1])); + } + + /** + * Gets the address of the server. + * + * @return the address of the server + */ + public static String getAddress(String ip, int port) { + return ip + (port == 25565 ? "" : ":" + port); + } +} diff --git a/src/main/java/cc.fascinated/controller/PlayerController.java b/src/main/java/cc.fascinated/controller/PlayerController.java index 249eba1..d7eb768 100644 --- a/src/main/java/cc.fascinated/controller/PlayerController.java +++ b/src/main/java/cc.fascinated/controller/PlayerController.java @@ -6,6 +6,7 @@ import cc.fascinated.model.player.Skin; import cc.fascinated.service.PlayerService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.CacheControl; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -43,6 +44,7 @@ public class PlayerController { return ResponseEntity.ok() .cacheControl(cacheControl) .contentType(MediaType.IMAGE_PNG) + .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=%s.png".formatted(player.getUsername())) .body(PlayerUtils.getSkinPartBytes(player.getSkin(), skinPart, size)); } } diff --git a/src/main/java/cc.fascinated/controller/ServerController.java b/src/main/java/cc.fascinated/controller/ServerController.java index b23416b..0e7b7b3 100644 --- a/src/main/java/cc.fascinated/controller/ServerController.java +++ b/src/main/java/cc.fascinated/controller/ServerController.java @@ -1,10 +1,14 @@ package cc.fascinated.controller; +import cc.fascinated.common.ServerUtils; +import cc.fascinated.common.Tuple; import cc.fascinated.model.cache.CachedMinecraftServer; +import cc.fascinated.model.server.JavaMinecraftServer; import cc.fascinated.model.server.MinecraftServer; import cc.fascinated.service.ServerService; import cc.fascinated.service.pinger.impl.JavaMinecraftServerPinger; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -19,14 +23,19 @@ public class ServerController { @ResponseBody @GetMapping(value = "/{platform}/{hostnameAndPort}", produces = MediaType.APPLICATION_JSON_VALUE) public CachedMinecraftServer getServer(@PathVariable String platform, @PathVariable String hostnameAndPort) { - String[] split = hostnameAndPort.split(":"); - String hostname = split[0]; - int port = 25565; - if (split.length == 2) { - try { - port = Integer.parseInt(split[1]); - } catch (NumberFormatException ignored) {} - } - return serverService.getServer(platform, hostname, port); + Tuple host = ServerUtils.getHostnameAndPort(hostnameAndPort); + return serverService.getServer(platform, host.getLeft(), host.getRight()); + } + + @ResponseBody + @GetMapping(value = "/icon/{hostnameAndPort}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity getServerIcon(@PathVariable String hostnameAndPort) { + Tuple host = ServerUtils.getHostnameAndPort(hostnameAndPort); + String hostname = host.getLeft(); + int port = host.getRight(); + return ResponseEntity.ok() + .contentType(MediaType.IMAGE_PNG) + .header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=%s.png".formatted(ServerUtils.getAddress(hostname, port))) + .body(serverService.getServerFavicon(hostname, port)); } } diff --git a/src/main/java/cc.fascinated/model/mojang/JavaServerStatusToken.java b/src/main/java/cc.fascinated/model/mojang/JavaServerStatusToken.java index ce98ab4..6b660a6 100644 --- a/src/main/java/cc.fascinated/model/mojang/JavaServerStatusToken.java +++ b/src/main/java/cc.fascinated/model/mojang/JavaServerStatusToken.java @@ -1,5 +1,6 @@ package cc.fascinated.model.mojang; +import cc.fascinated.model.server.JavaMinecraftServer; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @@ -9,5 +10,24 @@ import lombok.ToString; */ @AllArgsConstructor @Getter @ToString public final class JavaServerStatusToken { + + /** + * The version of the server. + */ + private final JavaMinecraftServer.Version version; + + /** + * The players on the server. + */ + private final JavaMinecraftServer.Players players; + + /** + * The motd of the server. + */ private final String description; -} \ No newline at end of file + + /** + * The favicon of the server. + */ + private final String favicon; +} diff --git a/src/main/java/cc.fascinated/model/server/JavaMinecraftServer.java b/src/main/java/cc.fascinated/model/server/JavaMinecraftServer.java index 86df0d6..f59012a 100644 --- a/src/main/java/cc.fascinated/model/server/JavaMinecraftServer.java +++ b/src/main/java/cc.fascinated/model/server/JavaMinecraftServer.java @@ -1,10 +1,128 @@ package cc.fascinated.model.server; +import cc.fascinated.common.JavaMinecraftVersion; +import cc.fascinated.config.Config; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import lombok.Setter; + /** * @author Braydon */ +@Setter @Getter public final class JavaMinecraftServer extends MinecraftServer { - public JavaMinecraftServer(String hostname, String ip, int port, String motd) { + + /** + * The version of the server. + */ + @NonNull private final Version version; + + /** + * The players on the server. + */ + private Players players; + + /** + * The favicon of the server. + */ + private Favicon favicon; + + public JavaMinecraftServer(String hostname, String ip, int port, MOTD motd, @NonNull Version version, Players players, Favicon favicon) { super(hostname, ip, port, motd); + this.version = version; + this.players = players; + this.favicon = favicon; + } + + @AllArgsConstructor @Getter + public static class Version { + /** + * The version name of the server. + */ + @NonNull + private final String name; + + /** + * The server platform. + */ + private String platform; + + /** + * The protocol version. + */ + private final int protocol; + + /** + * The name of the protocol, null if not found. + */ + private final String protocolName; + + /** + * Create a more detailed + * copy of this object. + * + * @return the detailed copy + */ + @NonNull + public Version detailedCopy() { + String platform = null; + if (name.contains(" ")) { // Parse the server platform + String[] split = name.split(" "); + if (split.length == 2) { + platform = split[0]; + } + } + JavaMinecraftVersion minecraftVersion = JavaMinecraftVersion.byProtocol(protocol); + return new Version(name, platform, protocol, minecraftVersion == null ? null : minecraftVersion.getName()); + } + + } + + @Getter @AllArgsConstructor + public static class Players { + + /** + * The maximum amount of players the server can hold. + */ + private final int max; + + /** + * The amount of players currently online. + */ + private final int online; + + /** + * The sample of players currently online. + */ + private final String[] sample; + } + + @Getter @AllArgsConstructor + public static class Favicon { + + /** + * The raw base64 of the favicon. + */ + private final String base64; + + /** + * The url to the favicon. + */ + private String url; + + /** + * Create a new favicon for a server. + * + * @param base64 the base64 of the favicon + * @param address the address of the server + * @return the new favicon + */ + public static Favicon create(String base64, @NonNull String address) { + if (base64 == null) { // The server doesn't have a favicon + return null; + } + return new Favicon(base64, Config.INSTANCE.getWebPublicUrl() + "/server/icon/%s".formatted(address)); + } } } \ No newline at end of file diff --git a/src/main/java/cc.fascinated/model/server/MinecraftServer.java b/src/main/java/cc.fascinated/model/server/MinecraftServer.java index 5a7eb11..0ee7e18 100644 --- a/src/main/java/cc.fascinated/model/server/MinecraftServer.java +++ b/src/main/java/cc.fascinated/model/server/MinecraftServer.java @@ -1,5 +1,6 @@ package cc.fascinated.model.server; +import cc.fascinated.common.ColorUtils; import cc.fascinated.service.pinger.MinecraftServerPinger; import cc.fascinated.service.pinger.impl.JavaMinecraftServerPinger; import io.micrometer.common.lang.NonNull; @@ -7,6 +8,8 @@ import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; +import java.util.Arrays; + /** * @author Braydon */ @@ -15,7 +18,7 @@ public class MinecraftServer { private final String hostname; private final String ip; private final int port; - private final String motd; + private final MOTD motd; /** * A platform a Minecraft @@ -39,4 +42,39 @@ public class MinecraftServer { */ private final int defaultPort; } + + @AllArgsConstructor @Getter + public static class MOTD { + + /** + * The raw motd lines + */ + private final String[] raw; + + /** + * The clean motd lines + */ + private final String[] clean; + + /** + * The html motd lines + */ + private final String[] html; + + /** + * Create a new MOTD from a raw string. + * + * @param raw the raw motd string + * @return the new motd + */ + @NonNull + public static MOTD create(@NonNull String raw) { + String[] rawLines = raw.split("\n"); // The raw lines + return new MOTD( + rawLines, + Arrays.stream(rawLines).map(ColorUtils::stripColor).toArray(String[]::new), + Arrays.stream(rawLines).map(ColorUtils::toHTML).toArray(String[]::new) + ); + } + } } \ No newline at end of file diff --git a/src/main/java/cc.fascinated/service/ServerService.java b/src/main/java/cc.fascinated/service/ServerService.java index 0867720..478cbdf 100644 --- a/src/main/java/cc.fascinated/service/ServerService.java +++ b/src/main/java/cc.fascinated/service/ServerService.java @@ -1,24 +1,24 @@ package cc.fascinated.service; -import cc.fascinated.EnumUtils; +import cc.fascinated.common.EnumUtils; import cc.fascinated.common.DNSUtils; -import cc.fascinated.common.WebRequest; import cc.fascinated.exception.impl.BadRequestException; +import cc.fascinated.exception.impl.ResourceNotFoundException; import cc.fascinated.model.cache.CachedMinecraftServer; -import cc.fascinated.model.mojang.MojangProfile; -import cc.fascinated.model.mojang.MojangUsernameToUuid; +import cc.fascinated.model.server.JavaMinecraftServer; import cc.fascinated.model.server.MinecraftServer; import cc.fascinated.repository.MinecraftServerCacheRepository; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.net.InetSocketAddress; +import java.util.Base64; import java.util.Optional; @Service @Log4j2 public class ServerService { + private static final String DEFAULT_SERVER_ICON = "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"; private final MinecraftServerCacheRepository serverCacheRepository; @@ -62,4 +62,28 @@ public class ServerService { server.setCached(-1); // Indicate that the server is not cached return server; } + + /** + * Gets the server favicon. + * + * @param hostname the hostname of the server + * @param port the port of the server + * @return the server favicon, null if not found + */ + public byte[] getServerFavicon(String hostname, int port) { + String icon = null; // The server base64 icon + try { + JavaMinecraftServer.Favicon favicon = ((JavaMinecraftServer) getServer(MinecraftServer.Platform.JAVA.name(), hostname, port).getValue()).getFavicon(); + if (favicon != null) { // Use the server's favicon + icon = favicon.getBase64(); + icon = icon.substring(icon.indexOf(",") + 1); // Remove the data type from the server icon + } + } catch (BadRequestException | ResourceNotFoundException ignored) { + // Safely ignore these, we will use the default server icon + } + if (icon == null) { // Use the default server icon + icon = DEFAULT_SERVER_ICON; + } + return Base64.getDecoder().decode(icon); // Return the decoded favicon + } } diff --git a/src/main/java/cc.fascinated/service/pinger/impl/JavaMinecraftServerPinger.java b/src/main/java/cc.fascinated/service/pinger/impl/JavaMinecraftServerPinger.java index ee9f458..f1dd3e3 100644 --- a/src/main/java/cc.fascinated/service/pinger/impl/JavaMinecraftServerPinger.java +++ b/src/main/java/cc.fascinated/service/pinger/impl/JavaMinecraftServerPinger.java @@ -2,12 +2,14 @@ package cc.fascinated.service.pinger.impl; import cc.fascinated.Main; import cc.fascinated.common.DNSUtils; +import cc.fascinated.common.ServerUtils; import cc.fascinated.common.packet.impl.java.JavaPacketHandshakingInSetProtocol; import cc.fascinated.common.packet.impl.java.JavaPacketStatusInStart; import cc.fascinated.exception.impl.BadRequestException; import cc.fascinated.exception.impl.ResourceNotFoundException; import cc.fascinated.model.mojang.JavaServerStatusToken; import cc.fascinated.model.server.JavaMinecraftServer; +import cc.fascinated.model.server.MinecraftServer; import cc.fascinated.service.pinger.MinecraftServerPinger; import lombok.extern.log4j.Log4j2; @@ -48,8 +50,17 @@ public final class JavaMinecraftServerPinger implements MinecraftServerPinger

Player Data: ???

+

Java Server: ???

\ No newline at end of file