minor fixes #4

Closed
Fascinated wants to merge 1 commits from (deleted):master into master
57 changed files with 0 additions and 4793 deletions
Showing only changes of commit ef4a74b4e7 - Show all commits

@ -1,77 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.info.License;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Objects;
/**
* @author Braydon
*/
@SpringBootApplication(exclude = { JacksonAutoConfiguration.class })
@Slf4j(topic = "RESTfulMC")
@OpenAPIDefinition(info = @Info(
title = "RESTfulMC",
description = "A simple, yet useful RESTful API for Minecraft utilizing Springboot.",
version = "1.0.0",
contact = @Contact(name = "Braydon (Rainnny)", url = "https://rainnny.club", email = "braydonrainnny@gmail.com"),
license = @License(name = "MIT License", url = "https://opensource.org/license/MIT")
))
public class RESTfulMC {
public static final Gson GSON = new GsonBuilder()
.setDateFormat("MM-dd-yyyy HH:mm:ss")
.create();
@SneakyThrows
public static void main(@NonNull String[] args) {
// Handle loading of our configuration file
File config = new File("application.yml");
if (!config.exists()) { // Saving the default config if it doesn't exist locally
Files.copy(Objects.requireNonNull(RESTfulMC.class.getResourceAsStream("/application.yml")), config.toPath(), StandardCopyOption.REPLACE_EXISTING);
log.info("Saved the default configuration to '{}', please re-launch the application", // Log the default config being saved
config.getAbsolutePath()
);
return;
}
log.info("Found configuration at '{}'", config.getAbsolutePath()); // Log the found config
// Start the app
SpringApplication.run(RESTfulMC.class, args);
}
}

@ -1,104 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.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<Character, String> 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.
* <p>
* This will replace each color code with
* a span tag with the respective color in
* hex format.
* </p>
*
* @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("<span style=\"color:").append(color).append("\">");
nextIsColor = false;
continue;
}
builder.append(character); // Append the char...
}
return builder.toString();
}
}

@ -1,76 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.xbill.DNS.Record;
import org.xbill.DNS.*;
import java.net.InetAddress;
import java.net.InetSocketAddress;
/**
* @author Braydon
*/
@UtilityClass
public final class DNSUtils {
private static final String SRV_QUERY_PREFIX = "_minecraft._tcp.%s";
/**
* Resolve the hostname to an {@link InetSocketAddress}.
*
* @param hostname the hostname to resolve
* @return the resolved {@link InetSocketAddress}
*/
@SneakyThrows
public static InetSocketAddress resolveSRV(@NonNull String hostname) {
Record[] records = new Lookup(SRV_QUERY_PREFIX.formatted(hostname), Type.SRV).run(); // Resolve SRV records
if (records == null) { // No records exist
return null;
}
String host = null;
int port = -1;
for (Record record : records) {
SRVRecord srv = (SRVRecord) record;
host = srv.getTarget().toString().replaceFirst("\\.$", "");
port = srv.getPort();
}
return host == null ? null : new InetSocketAddress(host, port);
}
@SneakyThrows
public static InetAddress resolveA(@NonNull String hostname) {
Record[] records = new Lookup(hostname, Type.A).run(); // Resolve A records
if (records == null) { // No records exist
return null;
}
InetAddress address = null;
for (Record record : records) {
address = ((ARecord) record).getAddress();
}
return address;
}
}

@ -1,49 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
/**
* @author Braydon
*/
@UtilityClass
public final class EnumUtils {
/**
* Get the enum constant of the specified enum type with the specified name.
*
* @param enumType the enum type
* @param name the name of the constant to return
* @param <T> the type of the enum
* @return the enum constant of the specified enum type with the specified name
*/
public <T extends Enum<T>> T getEnumConstant(@NonNull Class<T> enumType, @NonNull String name) {
try {
return Enum.valueOf(enumType, name);
} catch (IllegalArgumentException ex) {
return null;
}
}
}

@ -1,157 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common;
import lombok.NonNull;
import net.jodah.expiringmap.ExpirationPolicy;
import net.jodah.expiringmap.ExpiringMap;
import javax.annotation.concurrent.ThreadSafe;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
* A simple set that expires elements after a certain
* amount of time, utilizing the {@link ExpiringMap} library.
*
* @param <T> The type of element to store within this set
* @author Braydon
*/
@ThreadSafe
public final class ExpiringSet<T> implements Iterable<T> {
/**
* The internal cache for this set.
*/
@NonNull private final ExpiringMap<T, Long> cache;
/**
* The lifetime (in millis) of the elements in this set.
*/
private final long lifetime;
public ExpiringSet(@NonNull ExpirationPolicy expirationPolicy, long duration, @NonNull TimeUnit timeUnit) {
this(expirationPolicy, duration, timeUnit, ignored -> {});
}
public ExpiringSet(@NonNull ExpirationPolicy expirationPolicy, long duration, @NonNull TimeUnit timeUnit, @NonNull Consumer<T> onExpire) {
//noinspection unchecked
this.cache = ExpiringMap.builder()
.expirationPolicy(expirationPolicy)
.expiration(duration, timeUnit)
.expirationListener((key, ignored) -> onExpire.accept((T) key))
.build();
this.lifetime = timeUnit.toMillis(duration); // Get the lifetime in millis
}
/**
* Add an element to this set.
*
* @param element the element
* @return whether the element was added
*/
public boolean add(@NonNull T element) {
boolean contains = contains(element); // Does this set already contain the element?
this.cache.put(element, System.currentTimeMillis() + this.lifetime);
return !contains;
}
/**
* Get the entry time of an element in this set.
*
* @param element the element
* @return the entry time, -1 if not contained
*/
public long getEntryTime(@NonNull T element) {
return contains(element) ? this.cache.get(element) - this.lifetime : -1L;
}
/**
* Check if an element is
* contained within this set.
*
* @param element the element
* @return whether the element is contained
*/
public boolean contains(@NonNull T element) {
Long timeout = this.cache.get(element); // Get the timeout for the element
return timeout != null && (timeout > System.currentTimeMillis());
}
/**
* Check if this set is empty.
*
* @return whether this set is empty
*/
public boolean isEmpty() {
return this.cache.isEmpty();
}
/**
* Get the size of this set.
*
* @return the size
*/
public int size() {
return this.cache.size();
}
/**
* Remove an element from this set.
*
* @param element the element
* @return whether the element was removed
*/
public boolean remove(@NonNull T element) {
return this.cache.remove(element) != null;
}
/**
* Clear this set.
*/
public void clear() {
this.cache.clear();
}
/**
* Get the elements in this set.
*
* @return the elements
*/
@NonNull
public Set<T> getElements() {
return this.cache.keySet();
}
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
@Override @NonNull
public Iterator<T> iterator() {
return this.cache.keySet().iterator();
}
}

@ -1,67 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common;
import jakarta.servlet.http.HttpServletRequest;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
/**
* @author Braydon
*/
@UtilityClass
public final class IPUtils {
private static final String[] IP_HEADERS = new String[] {
"CF-Connecting-IP",
"X-Forwarded-For"
};
/**
* Get the real IP from the given request.
*
* @param request the request
* @return the real IP
*/
@NonNull
public static String getRealIp(@NonNull HttpServletRequest request) {
String ip = request.getRemoteAddr();
for (String headerName : IP_HEADERS) {
String header = request.getHeader(headerName);
if (header == null) {
continue;
}
if (!header.contains(",")) { // Handle single IP
ip = header;
break;
}
// Handle multiple IPs
String[] ips = header.split(",");
for (String ipHeader : ips) {
ip = ipHeader;
break;
}
}
return ip;
}
}

@ -1,91 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import me.braydon.mc.model.Skin;
import javax.imageio.ImageIO;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.net.URL;
/**
* @author Braydon
*/
@UtilityClass
public final class ImageUtils {
public static final int SKIN_TEXTURE_SIZE = 64; // The skin of a skin texture
/**
* Get the texture of a part of a skin.
*
* @param skin the skin to get the part texture from
* @param part the part of the skin to get
* @param size the size to scale the texture to
* @return the texture of the skin part
*/
@SneakyThrows
public static byte[] getSkinPart(@NonNull Skin skin, @NonNull Skin.Part part, int size) {
return getSkinPartTexture(skin, part.getX(), part.getY(), part.getWidth(), part.getHeight(), size);
}
/**
* Get the texture of a specific part of a skin.
*
* @param skin the skin to get the part from
* @param x the x position of the part
* @param y the y position of the part
* @param width the width of the part
* @param height the height of the part
* @param size the size to scale the part to
* @return the texture of the skin part
*/
@SneakyThrows
public static byte[] getSkinPartTexture(@NonNull Skin skin, int x, int y, int width, int height, int size) {
BufferedImage skinImage = ImageIO.read(new URL(skin.getUrl())); // The skin texture
// Create a new BufferedImage for the part of the skin texture
BufferedImage headTexture = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
// Crop just the part we want based on our x, y, width, and height
headTexture.getGraphics().drawImage(skinImage, 0, 0, width, height, x, y, x + width, y + height, null);
// Scale the skin part texture
double scale = (double) size / width;
AffineTransform transform = AffineTransform.getScaleInstance(scale, scale);
headTexture = new AffineTransformOp(transform, AffineTransformOp.TYPE_NEAREST_NEIGHBOR).filter(headTexture, null);
// Convert BufferedImage to byte array
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
ImageIO.write(headTexture, "png", outputStream);
outputStream.flush();
return outputStream.toByteArray();
}
}
}

@ -1,159 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
/**
* A list of versions for the
* Java edition of Minecraft.
*
* @author Braydon
* @see <a href="https://wiki.vg/Protocol_version_numbers">Protocol Version Numbers</a>
* @see <a href="https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-1-16">Spigot NMS (1.16+)</a>
* @see <a href="https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-1-10-1-15">Spigot NMS (1.10 - 1.15)</a>
* @see <a href="https://www.spigotmc.org/wiki/spigot-nms-and-minecraft-versions-legacy">Spigot NMS (1.8 - 1.9)</a>
*/
@RequiredArgsConstructor @Getter @ToString
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;
}
/**
* 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;
}
}

@ -1,53 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
/**
* @author Braydon
*/
@UtilityClass
public final class MiscUtils {
private static final Pattern USERNAME_REGEX = Pattern.compile("^[a-zA-Z0-9_]{2,16}$");
private static final List<String> WHITELISTED_NAMES = Arrays.asList("8", "g");
/**
* Check if the given username is a valid.
*
* @param username the username to check
* @return whether the username is valid
*/
public static boolean isUsernameValid(@NonNull String username) {
if (WHITELISTED_NAMES.contains(username.toLowerCase())) { // Name is whitelisted
return true;
}
return USERNAME_REGEX.matcher(username).matches();
}
}

@ -1,47 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
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;
}

@ -1,50 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common;
import lombok.NonNull;
import lombok.experimental.UtilityClass;
import java.util.UUID;
/**
* @author Braydon
*/
@UtilityClass
public final class UUIDUtils {
/**
* Add dashes to an untrimmed uuid.
*
* @param trimmed the untrimmed uuid
* @return the uuid with dashes
*/
@NonNull
public static UUID addDashes(@NonNull String trimmed) {
StringBuilder builder = new StringBuilder(trimmed);
for (int i = 0, pos = 20; i < 4; i++, pos -= 4) {
builder.insert(pos, "-");
}
return UUID.fromString(builder.toString());
}
}

@ -1,46 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common.packet;
import lombok.NonNull;
import java.io.IOException;
import java.net.DatagramSocket;
/**
* Represents a packet in the
* Minecraft Bedrock protocol.
*
* @author Braydon
* @see <a href="https://wiki.vg/Raknet_Protocol">Protocol Docs</a>
*/
public interface MinecraftBedrockPacket {
/**
* Process this packet.
*
* @param socket the socket to process the packet for
* @throws IOException if an I/O error occurs
*/
void process(@NonNull DatagramSocket socket) throws IOException;
}

@ -1,89 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common.packet;
import lombok.NonNull;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* Represents a packet in the
* Minecraft Java protocol.
*
* @author Braydon
* @see <a href="https://wiki.vg/Protocol">Protocol Docs</a>
*/
public abstract class MinecraftJavaPacket {
/**
* Process this packet.
*
* @param inputStream the input stream to read from
* @param outputStream the output stream to write to
* @throws IOException if an I/O error occurs
*/
public abstract void process(@NonNull DataInputStream inputStream, @NonNull DataOutputStream outputStream) throws IOException;
/**
* Write a variable integer to the output stream.
*
* @param outputStream the output stream to write to
* @param paramInt the integer to write
* @throws IOException if an I/O error occurs
*/
protected final void writeVarInt(DataOutputStream outputStream, int paramInt) throws IOException {
while (true) {
if ((paramInt & 0xFFFFFF80) == 0) {
outputStream.writeByte(paramInt);
return;
}
outputStream.writeByte(paramInt & 0x7F | 0x80);
paramInt >>>= 7;
}
}
/**
* Read a variable integer from the input stream.
*
* @param inputStream the input stream to read from
* @return the integer that was read
* @throws IOException if an I/O error occurs
*/
protected final int readVarInt(@NonNull DataInputStream inputStream) throws IOException {
int i = 0;
int j = 0;
while (true) {
int k = inputStream.readByte();
i |= (k & 0x7F) << j++ * 7;
if (j > 5) {
throw new RuntimeException("VarInt too big");
}
if ((k & 0x80) != 128) {
break;
}
}
return i;
}
}

@ -1,65 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common.packet.impl.bedrock;
import lombok.NonNull;
import me.braydon.mc.common.packet.MinecraftBedrockPacket;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* This packet is sent by the client to the server to
* request a pong response from the server. The server
* will respond with a string containing the server's status.
*
* @author Braydon
* @see <a href="https://wiki.vg/Raknet_Protocol#Unconnected_Ping">Protocol Docs</a>
*/
public final class BedrockPacketUnconnectedPing implements MinecraftBedrockPacket {
private static final byte ID = 0x01; // The ID of the packet
private static final byte[] MAGIC = { 0, -1, -1, 0, -2, -2, -2, -2, -3, -3, -3, -3, 18, 52, 86, 120 };
/**
* Process this packet.
*
* @param socket the socket to process the packet for
* @throws IOException if an I/O error occurs
*/
@Override
public void process(@NonNull DatagramSocket socket) throws IOException {
// Construct the packet buffer
ByteBuffer buffer = ByteBuffer.allocate(33).order(ByteOrder.LITTLE_ENDIAN);;
buffer.put(ID); // Packet ID
buffer.putLong(System.currentTimeMillis()); // Timestamp
buffer.put(MAGIC); // Magic
buffer.putLong(0L); // Client GUID
// Send the packet
socket.send(new DatagramPacket(buffer.array(), 0, buffer.limit()));
}
}

@ -1,73 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common.packet.impl.bedrock;
import lombok.Getter;
import lombok.NonNull;
import me.braydon.mc.common.packet.MinecraftBedrockPacket;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
/**
* This packet is sent by the server to the client in
* response to the {@link BedrockPacketUnconnectedPing}.
*
* @author Braydon
* @see <a href="https://wiki.vg/Raknet_Protocol#Unconnected_Pong">Protocol Docs</a>
*/
@Getter
public final class BedrockPacketUnconnectedPong implements MinecraftBedrockPacket {
private static final byte ID = 0x1C; // The ID of the packet
/**
* The response from the server, null if none.
*/
private String response;
/**
* Process this packet.
*
* @param socket the socket to process the packet for
* @throws IOException if an I/O error occurs
*/
@Override
public void process(@NonNull DatagramSocket socket) throws IOException {
// Handle receiving of the packet
byte[] receiveData = new byte[2048];
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
socket.receive(receivePacket);
// Construct a buffer from the received packet
ByteBuffer buffer = ByteBuffer.wrap(receivePacket.getData()).order(ByteOrder.LITTLE_ENDIAN);
byte id = buffer.get(); // The received packet id
if (id == ID) {
response = new String(buffer.array(), StandardCharsets.UTF_8).substring(34).trim();
}
}
}

@ -1,87 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common.packet.impl.java;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import me.braydon.mc.common.packet.MinecraftJavaPacket;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* This packet is sent by the client to the server to set
* the hostname, port, and protocol version of the client.
*
* @author Braydon
* @see <a href="https://wiki.vg/Protocol#Handshake">Protocol Docs</a>
*/
@AllArgsConstructor @ToString
public final class JavaPacketHandshakingInSetProtocol extends MinecraftJavaPacket {
private static final byte ID = 0x00; // The ID of the packet
private static final int STATUS_HANDSHAKE = 1; // The status handshake ID
/**
* The hostname of the server.
*/
@NonNull private final String hostname;
/**
* The port of the server.
*/
private final int port;
/**
* The protocol version of the server.
*/
private final int protocolVersion;
/**
* Process this packet.
*
* @param inputStream the input stream to read from
* @param outputStream the output stream to write to
* @throws IOException if an I/O error occurs
*/
@Override
public void process(@NonNull DataInputStream inputStream, @NonNull DataOutputStream outputStream) throws IOException {
try (ByteArrayOutputStream handshakeBytes = new ByteArrayOutputStream();
DataOutputStream handshake = new DataOutputStream(handshakeBytes)
) {
handshake.writeByte(ID); // Write the ID of the packet
writeVarInt(handshake, protocolVersion); // Write the protocol version
writeVarInt(handshake, hostname.length()); // Write the length of the hostname
handshake.writeBytes(hostname); // Write the hostname
handshake.writeShort(port); // Write the port
writeVarInt(handshake, STATUS_HANDSHAKE); // Write the status handshake ID
// Write the handshake bytes to the output stream
writeVarInt(outputStream, handshakeBytes.size());
outputStream.write(handshakeBytes.toByteArray());
}
}
}

@ -1,85 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common.packet.impl.java;
import lombok.Getter;
import lombok.NonNull;
import me.braydon.mc.common.packet.MinecraftJavaPacket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
/**
* This packet is sent by the client to the server to request the
* status of the server. The server will respond with a json object
* containing the server's status.
*
* @author Braydon
* @see <a href="https://wiki.vg/Protocol#Status_Request">Protocol Docs</a>
*/
@Getter
public final class JavaPacketStatusInStart extends MinecraftJavaPacket {
private static final byte ID = 0x00; // The ID of the packet
/**
* The response json from the server, null if none.
*/
private String response;
/**
* Process this packet.
*
* @param inputStream the input stream to read from
* @param outputStream the output stream to write to
* @throws IOException if an I/O error occurs
*/
@Override
public void process(@NonNull DataInputStream inputStream, @NonNull DataOutputStream outputStream) throws IOException {
// Send the status request
outputStream.writeByte(0x01); // Size of packet
outputStream.writeByte(ID);
// Read the status response
readVarInt(inputStream); // Size of the response
int id = readVarInt(inputStream);
if (id == -1) { // The stream was prematurely ended
throw new IOException("Server prematurely ended stream.");
} else if (id != ID) { // Invalid packet ID
throw new IOException("Server returned invalid packet ID.");
}
int length = readVarInt(inputStream); // Length of the response
if (length == -1) { // The stream was prematurely ended
throw new IOException("Server prematurely ended stream.");
} else if (length == 0) {
throw new IOException("Server returned unexpected value.");
}
// Get the json response
byte[] data = new byte[length];
inputStream.readFully(data);
response = new String(data);
}
}

@ -1,51 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common.web;
import lombok.Getter;
import lombok.NonNull;
/**
* This exception is raised when a
* {@link JsonWebRequest} encounters an error.
*
* @author Braydon
*/
@Getter
public class JsonWebException extends RuntimeException {
/**
* The status code of the response.
*/
private final int statusCode;
protected JsonWebException(int statusCode, @NonNull String message) {
super(message);
this.statusCode = statusCode;
}
protected JsonWebException(int statusCode, @NonNull Throwable cause) {
super(cause);
this.statusCode = statusCode;
}
}

@ -1,129 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.common.web;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.braydon.mc.RESTfulMC;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* A Json web request.
*
* @author Braydon
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) @Getter
public final class JsonWebRequest {
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
/**
* The endpoint to make the request to.
*/
@NonNull private final String endpoint;
/**
* The method to use for the request.
*/
@NonNull private final HttpMethod method;
/**
* The headers to send with the request.
*/
private final Map<String, String> headers = Collections.synchronizedMap(new HashMap<>());
/**
* Make a new web request.
*
* @param endpoint the endpoint to make the request to
* @param method the method of the request to make
* @return the web request
*/
@NonNull
public static JsonWebRequest makeRequest(@NonNull String endpoint, @NonNull HttpMethod method) {
return new JsonWebRequest(endpoint, method);
}
/**
* Set a header for this request.
*
* @param name the header name
* @param value the header value
* @return the request
*/
@NonNull
public JsonWebRequest header(@NonNull String name, @NonNull String value) {
headers.put(name, value);
return this;
}
/**
* Execute this request.
*
* @param responseType the response type
* @return the response
* @param <T> the type of the response
* @throws JsonWebException if an error is encountered while making the request
*/
public <T> T execute(@NonNull Class<T> responseType) throws JsonWebException {
// Build the request
HttpRequest.Builder request = HttpRequest.newBuilder()
.uri(URI.create(endpoint))
.method(method.name(), HttpRequest.BodyPublishers.noBody());
// Append headers
headers.put("User-Agent", "RESTfulMC");
headers.put("Content-Type", "application/json");
for (Map.Entry<String, String> header : headers.entrySet()) {
request.header(header.getKey(), header.getValue());
}
// Send the request and get the response
int status = -1; // The response status code
try {
HttpResponse<String> response = HTTP_CLIENT.send(request.build(), HttpResponse.BodyHandlers.ofString());
status = response.statusCode(); // Set the response status
if (status != HttpStatus.OK.value()) { // Status code is not OK, raise an exception
throw new IOException("Failed to make a %s request to %s: %s".formatted(method.name(), endpoint, status));
}
// Return with the response as the type
return RESTfulMC.GSON.fromJson(response.body(), responseType);
} catch (Exception ex) {
if (!(ex instanceof JsonWebException)) {
throw new JsonWebException(status, ex);
}
throw (JsonWebException) ex;
}
}
}

@ -1,47 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.config;
import jakarta.annotation.PostConstruct;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
/**
* The configuration for the app.
*
* @author Braydon
*/
@Configuration @Getter
public class AppConfig {
public static AppConfig INSTANCE;
@Value("${server.publicUrl}")
private String serverPublicUrl;
@PostConstruct
public void onInitialize() {
INSTANCE = this;
}
}

@ -1,96 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.config;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
/**
* @author Braydon
*/
@Configuration
@Log4j2(topic = "Redis")
public class RedisConfig {
/**
* The Redis server host.
*/
@Value("${spring.data.redis.host}")
private String host;
/**
* The Redis server port.
*/
@Value("${spring.data.redis.port}")
private int port;
/**
* The Redis database index.
*/
@Value("${spring.data.redis.database}")
private int database;
/**
* The optional Redis password.
*/
@Value("${spring.data.redis.auth}")
private String auth;
/**
* Build the config to use for Redis.
*
* @return the config
* @see RedisTemplate for config
*/
@Bean @NonNull
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
/**
* Build the connection factory to use
* when making connections to Redis.
*
* @return the built factory
* @see JedisConnectionFactory for factory
*/
@Bean @NonNull
public JedisConnectionFactory jedisConnectionFactory() {
log.info("Connecting to Redis at {}:{}/{}", host, port, database);
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
config.setDatabase(database);
if (!auth.trim().isEmpty()) { // Auth with our provided password
log.info("Using auth...");
config.setPassword(auth);
}
return new JedisConnectionFactory(config);
}
}

@ -1,52 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.config;
import com.google.gson.Gson;
import me.braydon.mc.RESTfulMC;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.GsonHttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
/**
* @author Braydon
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter();
gsonHttpMessageConverter.setGson(gson());
converters.add(gsonHttpMessageConverter);
}
@Bean
public Gson gson() {
return RESTfulMC.GSON;
}
}

@ -1,102 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.controller;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import me.braydon.mc.exception.impl.BadRequestException;
import me.braydon.mc.exception.impl.MojangRateLimitException;
import me.braydon.mc.exception.impl.ResourceNotFoundException;
import me.braydon.mc.model.Player;
import me.braydon.mc.model.cache.CachedPlayer;
import me.braydon.mc.service.MojangService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* The controller for handling
* {@link Player} related requests.
*
* @author Braydon
*/
@RestController
@RequestMapping(value = "/player", produces = MediaType.APPLICATION_JSON_VALUE)
@Log4j2(topic = "Player Controller")
@Tag(name = "Player Controller", description = "The controller for handling player related requests.")
public final class PlayerController {
/**
* The Mojang service to use for player information.
*/
@NonNull private final MojangService mojangService;
@Autowired
public PlayerController(@NonNull MojangService mojangService) {
this.mojangService = mojangService;
}
/**
* A GET route to get a player by their username or UUID.
*
* @param query the query to search for the player by
* @return the player response
* @throws BadRequestException if the UUID or username is invalid
* @throws ResourceNotFoundException if the player is not found
* @throws MojangRateLimitException if the Mojang API rate limit is reached
*/
@GetMapping("/{query}")
@ResponseBody
public ResponseEntity<CachedPlayer> getPlayer(@PathVariable @NonNull String query)
throws BadRequestException, ResourceNotFoundException, MojangRateLimitException {
return ResponseEntity.ofNullable(mojangService.getPlayer(query, false));
}
/**
* Get the part of a skin texture for
* a player by their username or UUID.
* <p>
* If the player being searched is
* invalid, the default Steve skin
* will be used.
* </p>
*
* @param partName the part of the player's skin texture to get
* @param query the query to search for the player by
* @param extension the skin part image extension
* @param size the size of the skin part image
* @return the skin part texture
* @throws BadRequestException if the extension is invalid
*/
@GetMapping(value = "/{partName}/{query}.{extension}", produces = { MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_JPEG_VALUE })
@ResponseBody
public ResponseEntity<byte[]> getPartTexture(@PathVariable @NonNull String partName, @PathVariable @NonNull String query,
@PathVariable @NonNull String extension, @RequestParam(required = false) String size
) throws BadRequestException {
return ResponseEntity.ok()
.contentType(extension.equalsIgnoreCase("png") ? MediaType.IMAGE_PNG : MediaType.IMAGE_JPEG)
.body(mojangService.getSkinPartTexture(partName, query, extension, size));
}
}

@ -1,112 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.controller;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import me.braydon.mc.exception.impl.BadRequestException;
import me.braydon.mc.exception.impl.ResourceNotFoundException;
import me.braydon.mc.model.MinecraftServer;
import me.braydon.mc.model.cache.CachedMinecraftServer;
import me.braydon.mc.service.MojangService;
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.*;
import java.util.Map;
/**
* The controller for handling
* {@link MinecraftServer} related requests.
*
* @author Braydon
*/
@RestController
@RequestMapping(value = "/server", produces = MediaType.APPLICATION_JSON_VALUE)
@Log4j2(topic = "Server Controller")
@Tag(name = "Server Controller", description = "The controller for handling server related requests.")
public final class ServerController {
/**
* The Mojang service to use for server information.
*/
@NonNull private final MojangService mojangService;
@Autowired
public ServerController(@NonNull MojangService mojangService) {
this.mojangService = mojangService;
}
/**
* Get a Minecraft server by its platform and hostname.
*
* @param platform the platform of the server
* @param hostname the hostname of the server
* @param port the port of the server, null for default
* @return the server
* @throws BadRequestException if the hostname, platform, or port is invalid
* @throws ResourceNotFoundException if the server isn't found
*/
@GetMapping("/{platform}/{hostname}")
@ResponseBody
public ResponseEntity<CachedMinecraftServer> getServer(@PathVariable @NonNull String platform, @PathVariable @NonNull String hostname,
@RequestParam(required = false) String port
) throws BadRequestException, ResourceNotFoundException {
return ResponseEntity.ofNullable(mojangService.getMinecraftServer(platform, hostname, port));
}
/**
* Check if the server with the
* given hostname is blocked by Mojang.
*
* @param hostname the server hostname to check
* @return whether the hostname is blocked
*/
@GetMapping("/blocked/{hostname}")
@ResponseBody
public ResponseEntity<Map<String, Object>> isServerBlocked(@PathVariable @NonNull String hostname) {
return ResponseEntity.ok(Map.of(
"blocked", mojangService.isServerBlocked(hostname)
));
}
/**
* Get the server icon of a Minecraft
* server by its platform and hostname.
*
* @param hostname the hostname of the server
* @param port the port of the server, null for default
* @return the server icon
*/
@GetMapping(value = "/icon/{hostname}", produces = MediaType.IMAGE_PNG_VALUE)
@ResponseBody
public ResponseEntity<byte[]> getServerFavicon(@PathVariable @NonNull String hostname, @RequestParam(required = false) String port) {
return ResponseEntity.ok()
.contentType(MediaType.IMAGE_PNG)
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=%s.png".formatted(hostname))
.body(mojangService.getServerFavicon(hostname, port));
}
}

@ -1,72 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.exception;
import lombok.NonNull;
import me.braydon.mc.model.response.ErrorResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.resource.NoResourceFoundException;
/**
* Advice for handling raised exceptions.
*
* @author Braydon
*/
@ControllerAdvice
public final class ExceptionControllerAdvice {
/**
* Handle a raised exception.
*
* @param ex the raised exception
* @return the error response
*/
@ExceptionHandler(Exception.class)
public ResponseEntity<?> handleException(@NonNull Exception ex) {
HttpStatus status = null; // Get the HTTP status
if (ex instanceof NoResourceFoundException) { // Not found
status = HttpStatus.NOT_FOUND;
} else if (ex instanceof UnsupportedOperationException) { // Not implemented
status = HttpStatus.NOT_IMPLEMENTED;
}
if (ex.getClass().isAnnotationPresent(ResponseStatus.class)) { // Get from the @ResponseStatus annotation
status = ex.getClass().getAnnotation(ResponseStatus.class).value();
}
String message = ex.getLocalizedMessage(); // Get the error message
if (message == null) { // Fallback
message = "An internal error has occurred.";
}
// Print the stack trace if no response status is present
if (status == null) {
ex.printStackTrace();
}
if (status == null) { // Fallback to 500
status = HttpStatus.INTERNAL_SERVER_ERROR;
}
return new ResponseEntity<>(new ErrorResponse(status, message), status);
}
}

@ -1,38 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.exception.impl;
import lombok.experimental.StandardException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* This exception is raised
* when a bad request is made.
*
* @author Braydon
*/
@StandardException
@ResponseStatus(HttpStatus.BAD_REQUEST)
public final class BadRequestException extends RuntimeException { }

@ -1,40 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.exception.impl;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* This exception is raised when the
* Mojang API rate limit is reached.
*
* @author Braydon
*/
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
public final class MojangRateLimitException extends RuntimeException {
public MojangRateLimitException() {
super("Mojang requests exhausted, please try again later.");
}
}

@ -1,38 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.exception.impl;
import lombok.experimental.StandardException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
/**
* This exception is raised
* when a resource is not found.
*
* @author Braydon
*/
@StandardException
@ResponseStatus(HttpStatus.NOT_FOUND)
public final class ResourceNotFoundException extends RuntimeException { }

@ -1,107 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.log;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import me.braydon.mc.common.IPUtils;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
/**
* Responsible for logging request and
* response transactions to the terminal.
*
* @author Braydon
*/
@ControllerAdvice
@Slf4j(topic = "Req/Res Transaction")
public class TransactionLogger implements ResponseBodyAdvice<Object> {
@Override
public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType,
@NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType, @NonNull ServerHttpRequest rawRequest,
@NonNull ServerHttpResponse rawResponse) {
HttpServletRequest request = ((ServletServerHttpRequest) rawRequest).getServletRequest();
HttpServletResponse response = ((ServletServerHttpResponse) rawResponse).getServletResponse();
// Get the request ip ip
String ip = IPUtils.getRealIp(request);
// Getting params
Map<String, String> params = new HashMap<>();
for (Entry<String, String[]> entry : request.getParameterMap().entrySet()) {
params.put(entry.getKey(), Arrays.toString(entry.getValue()));
}
// Getting headers
Map<String, String> headers = new HashMap<>();
Enumeration<String> headerNames = request.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headers.put(headerName, request.getHeader(headerName));
}
// Log the request
log.info(String.format("[Req] %s | %s | '%s', params=%s, headers=%s",
request.getMethod(),
ip,
request.getRequestURI(),
params,
headers
));
// Getting response headers
headers = new HashMap<>();
for (String headerName : response.getHeaderNames()) {
headers.put(headerName, response.getHeader(headerName));
}
// Log the response
log.info(String.format("[Res] %s, headers=%s",
response.getStatus(),
headers
));
return body;
}
@Override
public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
return true;
}
}

@ -1,53 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
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());
}
}

@ -1,167 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model;
import lombok.*;
import me.braydon.mc.common.ColorUtils;
import me.braydon.mc.service.pinger.MinecraftServerPinger;
import me.braydon.mc.service.pinger.impl.BedrockMinecraftServerPinger;
import me.braydon.mc.service.pinger.impl.JavaMinecraftServerPinger;
import java.util.Arrays;
import java.util.UUID;
/**
* A model representing a Minecraft server.
*
* @author Braydon
*/
@AllArgsConstructor @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
public class MinecraftServer {
/**
* The hostname of this server.
*/
@EqualsAndHashCode.Include @NonNull private final String hostname;
/**
* The IP address of this server, if resolved.
*/
private final String ip;
/**
* The port of this server.
*/
@EqualsAndHashCode.Include private final int port;
/**
* The player counts of this server.
*/
@NonNull private final Players players;
/**
* The MOTD of this server.
*/
@NonNull private final MOTD motd;
/**
* Player count data for a server.
*/
@AllArgsConstructor @Getter @ToString
public static class Players {
/**
* The online players on this server.
*/
private final int online;
/**
* The maximum allowed players on this server.
*/
private final int max;
/**
* A sample of players on this server, null or empty if no sample.
*/
private final Sample[] sample;
/**
* A sample player.
*/
@AllArgsConstructor @Getter @ToString
public static class Sample {
/**
* The unique id of this player.
*/
@NonNull private final UUID id;
/**
* The name of this player.
*/
@NonNull private final String name;
}
}
/**
* The MOTD for a server.
*/
@AllArgsConstructor @Getter @ToString
public static class MOTD {
/**
* The raw MOTD lines.
*/
@NonNull private final String[] raw;
/**
* The clean MOTD lines (no color codes).
*/
@NonNull private final String[] clean;
/**
* The HTML MOTD lines.
*/
@NonNull 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)
);
}
}
/**
* A platform a Minecraft
* server can operate on.
*/
@AllArgsConstructor @Getter
public enum Platform {
/**
* The Java edition of Minecraft.
*/
JAVA(new JavaMinecraftServerPinger(), 25565),
/**
* The Bedrock edition of Minecraft.
*/
BEDROCK(new BedrockMinecraftServerPinger(), 19132);
/**
* The server pinger for this platform.
*/
@NonNull private final MinecraftServerPinger<?> pinger;
/**
* The default server port for this platform.
*/
private final int defaultPort;
}
}

@ -1,64 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model;
import lombok.*;
import org.springframework.data.annotation.Id;
import java.util.UUID;
/**
* A model representing a player.
*
* @author Braydon
*/
@AllArgsConstructor @Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString
public class Player {
/**
* The unique id of this player.
*/
@Id @EqualsAndHashCode.Include @NonNull private final UUID uniqueId;
/**
* The username of this player.
*/
@NonNull private final String username;
/**
* The skin of this player.
*/
@NonNull private final Skin skin;
/**
* The cape of this player, null if none.
*/
private final Cape cape;
/**
* The profile actions this player has, null if none.
*/
private final ProfileAction[] profileActions;
}

@ -1,43 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model;
/**
* Profile actions that can
* be taken on a {@link Player}.
*
* @author Braydon
*/
public enum ProfileAction {
/**
* The player is required to change their
* username before accessing Multiplayer.
*/
FORCED_NAME_CHANGE,
/**
* The player is using a banned skin.
*/
USING_BANNED_SKIN
}

@ -1,116 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model;
import com.google.gson.JsonObject;
import com.google.gson.annotations.SerializedName;
import lombok.*;
import me.braydon.mc.common.ImageUtils;
import me.braydon.mc.config.AppConfig;
import java.util.HashMap;
import java.util.Map;
/**
* A skin for a {@link Player}.
*
* @author Braydon
*/
@RequiredArgsConstructor(access = AccessLevel.PRIVATE) @Setter @Getter @ToString
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;
/**
* URLs to the parts of this skin.
*/
@NonNull @SerializedName("parts") private Map<String, String> partUrls = new HashMap<>();
/**
* Populate the part URLs for this skin.
*
* @param playerUuid the UUID of the player
* @return the skin
*/
@NonNull
public Skin populatePartUrls(@NonNull String playerUuid) {
for (Part part : Part.values()) {
partUrls.put(part.name(), AppConfig.INSTANCE.getServerPublicUrl() + "/player/" + part.name().toLowerCase() + "/" + playerUuid + ".png");
}
return this;
}
/**
* 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
}
/**
* The part of a skin.
*/
@AllArgsConstructor @Getter @ToString
public enum Part {
HEAD(8, 8, ImageUtils.SKIN_TEXTURE_SIZE / 8, ImageUtils.SKIN_TEXTURE_SIZE / 8);
/**
* The coordinates of this part.
*/
private final int x, y;
/**
* The size of this part.
*/
private final int width, height;
}
}

@ -1,54 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model.cache;
import lombok.*;
import me.braydon.mc.model.MinecraftServer;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import java.io.Serializable;
/**
* @author Braydon
*/
@AllArgsConstructor @Setter @Getter @ToString
@RedisHash(value = "server", timeToLive = 60L) // 1 minute (in seconds)
public final class CachedMinecraftServer implements Serializable {
/**
* The id of this cached server.
*/
@Id @NonNull private transient final String id;
/**
* The cached server.
*/
@NonNull private final MinecraftServer value;
/**
* The unix timestamp of when this
* server was cached, -1 if not cached.
*/
private long cached;
}

@ -1,59 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model.cache;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.ToString;
import me.braydon.mc.model.Cape;
import me.braydon.mc.model.Player;
import me.braydon.mc.model.ProfileAction;
import me.braydon.mc.model.Skin;
import org.springframework.data.redis.core.RedisHash;
import java.io.Serializable;
import java.util.UUID;
/**
* A cacheable {@link Player}.
*
* @author Braydon
*/
@Setter @Getter
@ToString(callSuper = true)
@RedisHash(value = "player", timeToLive = 60L * 60L) // 1 hour (in seconds)
public final class CachedPlayer extends Player implements Serializable {
/**
* The unix timestamp of when this
* player was cached, -1 if not cached.
*/
private long cached;
public CachedPlayer(@NonNull UUID uniqueId, @NonNull String username,
@NonNull Skin skin, Cape cape, ProfileAction[] profileActions, long cached) {
super(uniqueId, username, skin, cape, profileActions);
this.cached = cached;
}
}

@ -1,50 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model.cache;
import lombok.*;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import java.util.UUID;
/**
* @author Braydon
*/
@AllArgsConstructor
@Getter
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
@ToString
@RedisHash(value = "playerName", timeToLive = 60L * 60L) // 1 hour (in seconds)
public final class CachedPlayerName {
/**
* The username of the player.
*/
@Id @NonNull private String username;
/**
* The unique id of the player.
*/
@NonNull private UUID uniqueId;
}

@ -1,66 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model.response;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import org.springframework.http.HttpStatus;
import java.util.Date;
/**
* A response representing an error.
*
* @author Braydon
*/
@Getter @ToString
public final class ErrorResponse {
/**
* The status code of this error.
*/
@NonNull private final HttpStatus status;
/**
* The HTTP code of this error.
*/
private final int code;
/**
* The message of this error.
*/
@NonNull private final String message;
/**
* The timestamp this error occurred.
*/
@NonNull private final Date timestamp;
public ErrorResponse(@NonNull HttpStatus status, @NonNull String message) {
this.status = status;
code = status.value();
this.message = message;
timestamp = new Date();
}
}

@ -1,142 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model.server;
import lombok.*;
import me.braydon.mc.model.MinecraftServer;
/**
* A Bedrock edition {@link MinecraftServer}.
*
* @author Braydon
*/
@Getter @ToString(callSuper = true) @EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = true)
public final class BedrockMinecraftServer extends MinecraftServer {
/**
* The unique ID of this server.
*/
@EqualsAndHashCode.Include @NonNull private final String uniqueId;
/**
* The edition of this server.
*/
@NonNull private final Edition edition;
/**
* The version information of this server.
*/
@NonNull private final Version version;
/**
* The gamemode of this server.
*/
@NonNull private final GameMode gamemode;
private BedrockMinecraftServer(@NonNull String uniqueId, @NonNull String hostname, String ip, int port,
@NonNull Edition edition, @NonNull Version version, @NonNull Players players,
@NonNull MOTD motd, @NonNull GameMode gamemode) {
super(hostname, ip, port, players, motd);
this.uniqueId = uniqueId;
this.edition = edition;
this.version = version;
this.gamemode = gamemode;
}
/**
* Create a new Bedrock Minecraft server.
*
* @param hostname the hostname of the server
* @param ip the IP address of the server
* @param port the port of the server
* @param token the status token
* @return the Bedrock Minecraft server
*/
@NonNull
public static BedrockMinecraftServer create(@NonNull String hostname, String ip, int port, @NonNull String token) {
String[] split = token.split(";"); // Split the token
Edition edition;
try {
edition = Edition.valueOf(split[0]);
} catch (IllegalArgumentException ex) {
// Funky fix, but sometimes the edition can contain an extra
// portion of the length of the edition, so we need to remove it
edition = Edition.valueOf(split[0].substring(1));
}
Version version = new Version(Integer.parseInt(split[2]), split[3]);
Players players = new Players(Integer.parseInt(split[4]), Integer.parseInt(split[5]), null);
MOTD motd = MOTD.create(split[1] + "\n" + split[7]);
GameMode gameMode = new GameMode(split[8], Integer.parseInt(split[9]));
return new BedrockMinecraftServer(split[6], hostname, ip, port, edition, version, players, motd, gameMode);
}
/**
* The edition of a Bedrock server.
*/
@AllArgsConstructor @Getter
public enum Edition {
/**
* Minecraft: Pocket Edition.
*/
MCPE,
/**
* Minecraft: Education Edition.
*/
MCEE
}
/**
* Version information for a server.
*/
@AllArgsConstructor @Getter @ToString
public static class Version {
/**
* The protocol version of the server.
*/
private final int protocol;
/**
* The version name of the server.
*/
@NonNull private final String name;
}
/**
* The gamemode of a server.
*/
@AllArgsConstructor @Getter @ToString
public static class GameMode {
/**
* The name of this gamemode.
*/
@NonNull private final String name;
/**
* The numeric of this gamemode.
*/
private final int numericId;
}
}

@ -1,224 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model.server;
import com.google.gson.annotations.SerializedName;
import lombok.*;
import me.braydon.mc.RESTfulMC;
import me.braydon.mc.common.JavaMinecraftVersion;
import me.braydon.mc.config.AppConfig;
import me.braydon.mc.model.MinecraftServer;
import me.braydon.mc.model.token.JavaServerStatusToken;
import me.braydon.mc.service.MojangService;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
/**
* A Java edition {@link MinecraftServer}.
*
* @author Braydon
*/
@Setter @Getter @ToString(callSuper = true)
public final class JavaMinecraftServer extends MinecraftServer {
/**
* The version information of this server.
*/
@NonNull private final Version version;
/**
* The favicon of this server, null if none.
*/
private final Favicon favicon;
/**
* The Forge mod information for this server, null if none.
*/
private final ModInfo modInfo;
/**
* Does this server enforce secure chat?
*/
private final boolean enforcesSecureChat;
/**
* Is this server preventing chat reports?
*/
private final boolean preventsChatReports;
/**
* Is this server on the list
* of blocked servers by Mojang?
* <p>
* This value is later set by the
* {@link MojangService} when a server
* is requested.
* </p>
*
* @see <a href="https://wiki.vg/Mojang_API#Blocked_Servers">Mojang API</a>
*/
private boolean mojangBanned;
private JavaMinecraftServer(@NonNull String hostname, String ip, int port, @NonNull Version version,
@NonNull Players players, @NonNull MOTD motd, Favicon favicon, ModInfo modInfo,
boolean enforcesSecureChat, boolean preventsChatReports, boolean mojangBanned) {
super(hostname, ip, port, players, motd);
this.version = version;
this.favicon = favicon;
this.modInfo = modInfo;
this.enforcesSecureChat = enforcesSecureChat;
this.preventsChatReports = preventsChatReports;
this.mojangBanned = mojangBanned;
}
/**
* Create a new Java Minecraft server.
*
* @param hostname the hostname of the server
* @param ip the IP address of the server
* @param port the port of the server
* @param token the status token
* @return the Java Minecraft server
*/
@NonNull
public static JavaMinecraftServer create(@NonNull String hostname, String ip, int port, @NonNull JavaServerStatusToken token) {
String motdString = token.getDescription() instanceof String ? (String) token.getDescription() : null;
if (motdString == null) { // Not a string motd, convert from Json
motdString = new TextComponent(ComponentSerializer.parse(RESTfulMC.GSON.toJson(token.getDescription()))).toLegacyText();
}
return new JavaMinecraftServer(hostname, ip, port, token.getVersion().detailedCopy(), token.getPlayers(),
MOTD.create(motdString), Favicon.create(token.getFavicon(), hostname), token.getModInfo(),
token.isEnforcesSecureChat(), token.isPreventsChatReports(), false
);
}
/**
* Version information for a server.
*/
@AllArgsConstructor @Getter @ToString
public static class Version {
/**
* The version name of the server.
*/
@NonNull private final String name;
/**
* The identified platform of the server, null if unknown.
*/
private String platform;
/**
* The protocol version of the server.
*/
private final int protocol;
/**
* The name of the version for the protocol, null if unknown.
*/
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());
}
}
/**
* The favicon for a server.
*/
@AllArgsConstructor @Getter @ToString
public static class Favicon {
/**
* The raw Base64 encoded favicon.
*/
@NonNull private final String base64;
/**
* The URL to the favicon.
*/
@NonNull private final String url;
/**
* Create a new favicon for a server.
*
* @param base64 the Base64 encoded favicon
* @param hostname the server hostname
* @return the favicon, null if none
*/
public static Favicon create(String base64, @NonNull String hostname) {
if (base64 == null) { // No favicon to create
return null;
}
return new Favicon(
base64,
AppConfig.INSTANCE.getServerPublicUrl() + "/server/icon/" + hostname
);
}
}
/**
* Forge mod information for a server.
*/
@AllArgsConstructor @Getter @ToString
public static class ModInfo {
/**
* The type of modded server this is.
*/
@NonNull private final String type;
/**
* The list of mods on this server, null or empty if none.
*/
private final ForgeMod[] modList;
}
/**
* A forge mod for a server.
*/
@AllArgsConstructor @Getter @ToString
private static class ForgeMod {
/**
* The id of this mod.
*/
@NonNull @SerializedName("modid") private final String id;
/**
* The version of this mod.
*/
private final String version;
}
}

@ -1,79 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model.token;
import com.google.gson.annotations.SerializedName;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import me.braydon.mc.model.MinecraftServer;
import me.braydon.mc.model.server.JavaMinecraftServer;
/**
* A token representing the response from
* pinging a {@link JavaMinecraftServer}.
*
* @author Braydon
*/
@AllArgsConstructor @Getter @ToString
public final class JavaServerStatusToken {
/**
* The description (MOTD) of this server.
* <p>
* Legacy: String, New: JSON Object
* </p>
*/
@NonNull private final Object description;
/**
* The base64 encoded favicon of this server, null if no favicon.
*/
private final String favicon;
/**
* The version information of this server.
*/
@NonNull private final JavaMinecraftServer.Version version;
/**
* The player counts of this server.
*/
@NonNull private final MinecraftServer.Players players;
/**
* The Forge mod information for this server, null if none.
*/
@SerializedName("modinfo") private final JavaMinecraftServer.ModInfo modInfo;
/**
* Does this server enforce secure chat?
*/
private final boolean enforcesSecureChat;
/**
* Is this server preventing chat reports?
*/
private final boolean preventsChatReports;
}

@ -1,140 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model.token;
import com.google.gson.JsonObject;
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.Skin;
import java.util.Base64;
/**
* A token representing a Mojang user profile.
*
* @author Braydon
* @see <a href="https://wiki.vg/Mojang_API#UUID_to_Profile_and_Skin.2FCape">Mojang API</a>
*/
@AllArgsConstructor @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
public final class MojangProfileToken {
/**
* The id of the profile.
*/
@EqualsAndHashCode.Include @NonNull private final String id;
/**
* The name of the profile.
*/
@NonNull private final String name;
/**
* The properties of the profile.
*/
@NonNull private final ProfileProperty[] properties;
/**
* The actions this profile has.
*/
@NonNull private final ProfileAction[] profileActions;
/**
* Get the skin and cape of this profile.
*
* @return the skin and cape of this profile
*/
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")).populatePartUrls(id),
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.
*/
@NoArgsConstructor @Setter @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
public static class ProfileProperty {
/**
* The name of this property.
*/
@EqualsAndHashCode.Include @NonNull private String name;
/**
* The base64 value of this property.
*/
@NonNull private String value;
/**
* The base64 signature of this property.
*/
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?
*
* @return whether this property has a signature
*/
public boolean isSigned() {
return signature != null;
}
}
}

@ -1,43 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.model.token;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
/**
* A token representing a Mojang username to UUID response.
*
* @author Braydon
* @see <a href="https://wiki.vg/Mojang_API#Username_to_UUID">Mojang API</a>
*/
@AllArgsConstructor @Getter @ToString
public final class MojangUsernameToUUIDToken {
/**
* The id of the username.
*/
@NonNull private final String id;
}

@ -1,34 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.repository;
import me.braydon.mc.model.cache.CachedMinecraftServer;
import org.springframework.data.repository.CrudRepository;
/**
* A cache repository for {@link CachedMinecraftServer}'s.
*
* @author Braydon
*/
public interface MinecraftServerCacheRepository extends CrudRepository<CachedMinecraftServer, String> { }

@ -1,36 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.repository;
import me.braydon.mc.model.cache.CachedPlayer;
import org.springframework.data.repository.CrudRepository;
import java.util.UUID;
/**
* A cache repository for {@link CachedPlayer}'s.
*
* @author Braydon
*/
public interface PlayerCacheRepository extends CrudRepository<CachedPlayer, UUID> { }

@ -1,38 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.repository;
import me.braydon.mc.model.cache.CachedPlayerName;
import org.springframework.data.repository.CrudRepository;
/**
* A cache repository for player usernames.
* <p>
* This will allow us to easily lookup a
* player's username and get their uuid.
* </p>
*
* @author Braydon
*/
public interface PlayerNameCacheRepository extends CrudRepository<CachedPlayerName, String> { }

@ -1,479 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.service;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.hash.Hashing;
import jakarta.annotation.PostConstruct;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2;
import me.braydon.mc.common.*;
import me.braydon.mc.common.web.JsonWebException;
import me.braydon.mc.common.web.JsonWebRequest;
import me.braydon.mc.exception.impl.BadRequestException;
import me.braydon.mc.exception.impl.MojangRateLimitException;
import me.braydon.mc.exception.impl.ResourceNotFoundException;
import me.braydon.mc.model.*;
import me.braydon.mc.model.cache.CachedMinecraftServer;
import me.braydon.mc.model.cache.CachedPlayer;
import me.braydon.mc.model.cache.CachedPlayerName;
import me.braydon.mc.model.server.JavaMinecraftServer;
import me.braydon.mc.model.token.MojangProfileToken;
import me.braydon.mc.model.token.MojangUsernameToUUIDToken;
import me.braydon.mc.repository.MinecraftServerCacheRepository;
import me.braydon.mc.repository.PlayerCacheRepository;
import me.braydon.mc.repository.PlayerNameCacheRepository;
import net.jodah.expiringmap.ExpirationPolicy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* A service for interacting with the Mojang API.
*
* @author Braydon
*/
@Service
@Log4j2(topic = "Mojang Service")
public final class MojangService {
private static final String SESSION_SERVER_ENDPOINT = "https://sessionserver.mojang.com";
private static final String API_ENDPOINT = "https://api.mojang.com";
private static final String UUID_TO_PROFILE = SESSION_SERVER_ENDPOINT + "/session/minecraft/profile/%s";
private static final String USERNAME_TO_UUID = API_ENDPOINT + "/users/profiles/minecraft/%s";
private static final String FETCH_BLOCKED_SERVERS = SESSION_SERVER_ENDPOINT + "/blockedservers";
private static final int DEFAULT_PART_TEXTURE_SIZE = 128;
private static final int MAX_PART_TEXTURE_SIZE = 512;
private static final String DEFAULT_SERVER_ICON = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAASFBMVEWwsLBBQUE9PT1JSUlFRUUuLi5MTEyzs7M0NDQ5OTlVVVVQUFAmJia5ubl+fn5zc3PFxcVdXV3AwMCJiYmUlJRmZmbQ0NCjo6OL5p+6AAAFVklEQVRYw+1W67K0KAzkJnIZdRAZ3/9NtzvgXM45dX7st1VbW7XBUVDSdEISRqn/5R+T82/+nsr/XZn/SHm/3x9/ArA/IP8qwPK433d44VubZ/XT6/cJy0L792VZfnDrcRznr86d748u92X5vtaxOe228zcCy+MSMpg/5SwRopsYMv8oigCwngbQhE/rzhwAYMpxnvMvHhgy/8AgByJolzb5pPqEbvtgMBBmtvkbgxKmaaIZ5TyPum6Viue6te241N+s+W6nOlucgjEx6Nay9zZta1XVxejW+Q5ZhhkDS31lgOTegjUBor33CQilbC2GYGy9y9bN8ytevjE4a2stajHDAgAcUkoYwzO6zQi8ZflC+XO0+exiuNa3OQtIJOCk13neUjv7VO7Asu/3LwDFeg37sQtQhy4lAQH6IR9ztca0E3oI5PtDAlJ1tHGplrJ12jjrrXPWYvXsU042Bl/qUr3B9qzPSKaovpvjgglYL2F1x+Zs7gIvpLYuq46wr3H5/RJxyvM6sXOY762oU4YZ3mAz1lpc9O3Y30VJUM/iWhBIib63II/LA4COEMxcSmrH4ddl/wTYe3RIO0vK2VI9wQy6AxRsJpb3AAALvXb6TxvUCYSdOQo5Mh0GySkJc7rB405GUEfzbbl/iFpPoNQVNUQAZG06nkI6RCABRqRA9IimH6Up5Mhybtu2IlewB2Sf6AmQ4ZU9rfBELvyA23Yub6LWWtUBgK3OB79L7FILLDKWd4wpxmMRAMoLQR1ItLoiWUmhFtjptab7LQDgRARliLITLrcBkHNp9VACUH1UDRQEYGuYxzyM9H0mBccQNnCkQ3Q1UHBaO6sNyw0CelEtBGXKSoE+fJWZh5GupyneMIkCOMESAniMAzMreLvuO+pnmBQSp4C+ELCiMSGVLPh7M023SSBAiAA5yPh2m0wigEbWKnw3qDrrscF00cciCATGwNQRAv2YGvyD4Y36QGhqOS4AcABAA88oGvBCRho5H2+UiW6EfyM1L5l8a56rqdvE6lFakc3ScVDOBNBUoFM8c1vgnhAG5VsAqMD6Q9IwwtAkR39iGEQF1ZBxgU+v9UGL6MBQYiTdJllIBtx5y0rixGdAZ1YysbS53TAVy3vf4aabEpt1T0HoB2Eg4Yv5OKNwyHgmNvPKaQAYLG3EIyIqcL6Fj5C2jhXL9EpCdRMROE5nCW3qm1vfR6wYh0HKGG3wY+JgLkUWQ/WMfI8oMvIWMY7aCncNxxpSmHRUCEzDdSR0+dRwIQaMWW1FE0AOGeKkx0OLwYanBK3qfC0BSmIlozkuFcvSkulckoIB2FbHWu0y9gMHsEapMMEoySNUA2RDrduxIqr5POQV2zZ++IBOwVrFO9THrtjU2uWsCMZjxXl88Hmeaz1rPdAqXyJl68F5RTtdvN1aIyYEAMAWJaCMHvon7s23jljlxoKBEgNv6LQ25/rZIQyOdwDO3jLsqE2nbVAil21LxqFpZ2xJ3CFuE33QCo7kfkfO8kpW6gdioxdzZDLOaMMwidzeKD0RxaD7cnHHsu0jVkW5oTwwMGI0lwwA36u2nMY8AKzErLW9JxFiteyzZsAAxY1vPe5Uf68lIDVjV8JZpPfjxbc/QuyRKdAQJaAdIA4tCTht+kQJ1I4nbdjfHxgpTSLyI19pb/iuK7+9YJaZCxEIKj79YZ6uDU8f97878teRN1FzA7OvquSrVKUgk+S6ROpJfA7GpN6RPkx4voshXgu91p7CGHeA+IY8dUUVXwT7PYw12Xsj0Lfh9X4ac9XgKW86cj8bPh8XmyDOD88FLoB+YPXp4YtyB3gBPXu98xeRI2zploVCBQAAAABJRU5ErkJggg==";
private static final Splitter DOT_SPLITTER = Splitter.on('.');
private static final Joiner DOT_JOINER = Joiner.on('.');
/**
* The cache repository for {@link Player}'s by their username.
*/
@NonNull private final PlayerNameCacheRepository playerNameCache;
/**
* The cache repository for {@link Player}'s.
*/
@NonNull private final PlayerCacheRepository playerCache;
/**
* The cache repository for {@link MinecraftServer}'s.
*/
@NonNull private final MinecraftServerCacheRepository minecraftServerCache;
/**
* A list of banned server hashes provided by Mojang.
* <p>
* This is periodically fetched from Mojang, see
* {@link #fetchBlockedServers()} for more info.
* </p>
*
* @see <a href="https://wiki.vg/Mojang_API#Blocked_Servers">Mojang API</a>
*/
private List<String> bannedServerHashes;
/**
* A cache of blocked server hostnames.
*
* @see #isServerHostnameBlocked(String) for more
*/
private final ExpiringSet<String> blockedServersCache = new ExpiringSet<>(ExpirationPolicy.CREATED, 10L, TimeUnit.MINUTES);
@Autowired
public MojangService(@NonNull PlayerNameCacheRepository playerNameCache, @NonNull PlayerCacheRepository playerCache,
@NonNull MinecraftServerCacheRepository minecraftServerCache) {
this.playerNameCache = playerNameCache;
this.playerCache = playerCache;
this.minecraftServerCache = minecraftServerCache;
}
@PostConstruct
public void onInitialize() {
// Schedule a task to fetch blocked
// servers from Mojang every 15 minutes.
new Timer().scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
fetchBlockedServers();
}
}, 0L, 60L * 15L * 1000L);
}
/**
* Get the part of a skin texture for
* a player by their username or UUID.
*
* @param partName the part of the player's skin texture to get
* @param query the query to search for the player by
* @param extension the skin part image extension
* @param sizeString the size of the skin part image
* @return the skin part texture
* @throws BadRequestException if the extension is invalid
* @throws MojangRateLimitException if the Mojang API rate limit is reached
*/
public byte[] getSkinPartTexture(@NonNull String partName, @NonNull String query, @NonNull String extension, String sizeString)
throws BadRequestException, MojangRateLimitException {
Integer size = null;
if (sizeString != null) { // Attempt to parse the size
try {
size = Integer.parseInt(sizeString);
} catch (NumberFormatException ignored) {
// Safely ignore, invalid number provided
}
}
Skin.Part part = EnumUtils.getEnumConstant(Skin.Part.class, partName.toUpperCase()); // The skin part to get
if (part == null) { // Default to the head part
part = Skin.Part.HEAD;
}
if (extension.isBlank()) { // Invalid extension
throw new BadRequestException("Invalid extension");
}
if (size == null || size <= 0) { // Invalid size
size = DEFAULT_PART_TEXTURE_SIZE;
}
size = Math.min(size, MAX_PART_TEXTURE_SIZE); // Limit the size to 512
Skin target = null; // The target skin to get the skin part of
try {
CachedPlayer player = getPlayer(query, false); // Retrieve the player
target = player.getSkin(); // Use the player's skin
} catch (Exception ignored) {
// Simply ignore, and fallback to the default skin
}
if (target == null) { // Fallback to the default skin
target = Skin.DEFAULT_STEVE;
}
return ImageUtils.getSkinPart(target, part, size);
}
/**
* Get a player by their username or UUID.
* <p>
* If the player is present within the cache, that will
* be returned. If the player is not cached, a request
* will be made to retrieve the player from Mojang, cache it
* and then return the response.
* </p>
*
* @param query the query to search for the player by
* @param bypassCache should the cache be bypassed?
* @return the player
* @throws BadRequestException if the UUID or username is invalid
* @throws ResourceNotFoundException if the player is not found
* @throws MojangRateLimitException if the Mojang API rate limit is reached
*/
@NonNull
public CachedPlayer getPlayer(@NonNull String query, boolean bypassCache)
throws BadRequestException, ResourceNotFoundException, MojangRateLimitException {
log.info("Requesting player with query: {}", query);
UUID uuid; // The player UUID to lookup
boolean isFullUuid = query.length() == 36; // Was a UUID provided?
if (query.length() == 32 || isFullUuid) { // Parse the query as a UUID
try {
uuid = isFullUuid ? UUID.fromString(query) : UUIDUtils.addDashes(query);
log.info("Parsed {}UUID: {} -> {}", isFullUuid ? "" : "trimmed ", query, uuid);
} catch (IllegalArgumentException ex) {
throw new BadRequestException("Malformed UUID provided: %s".formatted(query));
}
} else { // The query is a username, request from Mojang
if (!MiscUtils.isUsernameValid(query)) { // Ensure the username is valid
throw new BadRequestException("Invalid username provided: %s".formatted(query));
}
uuid = usernameToUUID(query);
log.info("Found UUID for username {}: {}", query, uuid);
}
// Check the cache for the player
if (!bypassCache) {
Optional<CachedPlayer> cached = playerCache.findById(uuid);
if (cached.isPresent()) { // Respond with the cache if present
log.info("Found player in cache: {}", uuid);
return cached.get();
}
}
// Send a request to Mojang requesting
// the player profile by their UUID
try {
log.info("Retrieving player profile for UUID: {}", uuid);
MojangProfileToken token = JsonWebRequest.makeRequest(
UUID_TO_PROFILE.formatted(uuid), HttpMethod.GET
).execute(MojangProfileToken.class);
Tuple<Skin, Cape> skinAndCape = token.getSkinAndCape(); // Get the skin and cape
ProfileAction[] profileActions = token.getProfileActions();
// Build our player model, cache it, and then return it
CachedPlayer player = new CachedPlayer(
uuid, token.getName(),
skinAndCape.getLeft() == null ? Skin.DEFAULT_STEVE : skinAndCape.getLeft(),
skinAndCape.getRight(),
profileActions.length == 0 ? null : profileActions,
System.currentTimeMillis()
);
if (!bypassCache) { // Store in the cache
playerCache.save(player);
log.info("Cached player: {}", uuid);
}
player.setCached(-1L); // Set to -1 to indicate it's not cached in the response
return player;
} catch (JsonWebException ex) {
// No profile found, return null
if (ex.getStatusCode() == 400) {
throw new ResourceNotFoundException("Player not found with query: %s".formatted(query));
}
throw ex;
}
}
/**
* Get the favicon of a Java
* server with the given hostname.
* <p>
* If the favicon of the server cannot be
* retrieved, the default icon will be used.
* </p>
*
* @param hostname the hostname of the server
* @param port the port of the server, null for default
* @return the server favicon
* @see #DEFAULT_SERVER_ICON for the default server icon
*/
public byte[] getServerFavicon(@NonNull String hostname, String port) {
String icon = null; // The server base64 icon
try {
JavaMinecraftServer.Favicon favicon = ((JavaMinecraftServer) getMinecraftServer(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
}
/**
* Check if the server with the
* given hostname is blocked by Mojang.
*
* @param hostname the server hostname to check
* @return whether the hostname is blocked
*/
public boolean isServerBlocked(@NonNull String hostname) {
// Remove trailing dots
while (hostname.charAt(hostname.length() - 1) == '.') {
hostname = hostname.substring(0, hostname.length() - 1);
}
// Is the hostname banned?
if (isServerHostnameBlocked(hostname)) {
return true;
}
List<String> splitDots = Lists.newArrayList(DOT_SPLITTER.split(hostname)); // Split the hostname by dots
boolean isIp = splitDots.size() == 4; // Is it an IP address?
if (isIp) {
for (String element : splitDots) {
try {
int part = Integer.parseInt(element);
if (part >= 0 && part <= 255) { // Ensure the part is within the valid range
continue;
}
} catch (NumberFormatException ignored) {
// Safely ignore, not a number
}
isIp = false;
break;
}
}
// Check if the hostname is blocked
if (!isIp && isServerHostnameBlocked("*." + hostname)) {
return true;
}
// Additional checks for the hostname
while (splitDots.size() > 1) {
splitDots.remove(isIp ? splitDots.size() - 1 : 0);
String starredPart = isIp ? DOT_JOINER.join(splitDots) + ".*" : "*." + DOT_JOINER.join(splitDots);
if (isServerHostnameBlocked(starredPart)) {
return true;
}
}
return false;
}
/**
* Resolve a Minecraft server on the given
* platform with the given hostname.
*
* @param platformName the name of the platform
* @param hostname the hostname of the server
* @param portString the port of the server, null for default
* @return the resolved Minecraft server
* @throws BadRequestException if the hostname, platform, or port is invalid
* @throws ResourceNotFoundException if the server isn't found
*/
@NonNull
public CachedMinecraftServer getMinecraftServer(@NonNull String platformName, @NonNull String hostname, String portString)
throws BadRequestException, ResourceNotFoundException {
MinecraftServer.Platform platform = EnumUtils.getEnumConstant(MinecraftServer.Platform.class, platformName.toUpperCase());
if (platform == null) { // Invalid platform
throw new BadRequestException("Invalid platform: %s".formatted(platformName));
}
String lookupHostname = hostname; // The hostname used to lookup the server
int port = platform.getDefaultPort(); // Port to ping
if (portString != null) {
try { // Try and parse the port
port = Integer.parseInt(portString);
} catch (NumberFormatException ex) { // Invalid port
throw new BadRequestException("Invalid port defined");
}
}
// Check the cache for the server
Optional<CachedMinecraftServer> cached = minecraftServerCache.findById(platform.name() + "-" + hostname + "-" + port);
if (cached.isPresent()) { // Respond with the cache if present
log.info("Found server in cache: {}", hostname);
return cached.get();
}
InetSocketAddress address = platform == MinecraftServer.Platform.JAVA ? DNSUtils.resolveSRV(hostname) : null; // Resolve the SRV record
if (address != null) { // SRV was resolved, use the hostname and port
hostname = address.getHostName();
port = address.getPort();
}
// Build our server model, cache it, and then return it
CachedMinecraftServer minecraftServer = new CachedMinecraftServer(
platform.name() + "-" + lookupHostname + "-" + (portString == null ? port : portString),
platform.getPinger().ping(hostname, port),
System.currentTimeMillis()
);
// Get the blocked status of the Java server
if (platform == MinecraftServer.Platform.JAVA) {
((JavaMinecraftServer) minecraftServer.getValue()).setMojangBanned(isServerBlocked(hostname));
}
minecraftServerCache.save(minecraftServer);
log.info("Cached server: {}", hostname);
minecraftServer.setCached(-1L); // Set to -1 to indicate it's not cached in the response
return minecraftServer;
}
/**
* Get the UUID of a player by their username.
*
* @param username the player's username
* @return the player's UUID
* @throws ResourceNotFoundException if the player isn't found
* @throws MojangRateLimitException if the Mojang rate limit is reached
*/
@NonNull
private UUID usernameToUUID(@NonNull String username) throws ResourceNotFoundException, MojangRateLimitException {
String originalUsername = username;
username = username.toLowerCase(); // Lowercase the username
// Check the cache for the player's UUID
Optional<CachedPlayerName> cached = playerNameCache.findById(username);
if (cached.isPresent()) { // Respond with the cache if present
log.info("Found UUID in cache for username {}: {}", originalUsername, cached.get().getUniqueId());
return cached.get().getUniqueId();
}
// Make a request to Mojang requesting the UUID
try {
MojangUsernameToUUIDToken token = JsonWebRequest.makeRequest(
USERNAME_TO_UUID.formatted(username), HttpMethod.GET
).execute(MojangUsernameToUUIDToken.class);
// Cache the UUID and return it
UUID uuid = UUIDUtils.addDashes(token.getId());
playerNameCache.save(new CachedPlayerName(username, uuid));
log.info("Cached UUID for username {}: {}", username, uuid);
return uuid;
} catch (JsonWebException ex) {
if (ex.getStatusCode() == 429) { // Mojang rate limit reached
throw new MojangRateLimitException();
} else if (ex.getStatusCode() == 404) { // Player not found
throw new ResourceNotFoundException("Player not found with username: %s".formatted(originalUsername));
}
throw ex;
}
}
/**
* Fetch a list of blocked servers from Mojang.
*/
@SneakyThrows
private void fetchBlockedServers() {
try (
InputStream inputStream = new URL(FETCH_BLOCKED_SERVERS).openStream();
Scanner scanner = new Scanner(inputStream, StandardCharsets.UTF_8).useDelimiter("\n");
) {
List<String> hashes = new ArrayList<>();
while (scanner.hasNext()) {
hashes.add(scanner.next());
}
bannedServerHashes = Collections.synchronizedList(hashes);
log.info("Fetched {} banned server hashes", bannedServerHashes.size());
}
}
/**
* Check if the hash for the given
* hostname is in the blocked server list.
*
* @param hostname the hostname to check
* @return whether the hostname is blocked
*/
private boolean isServerHostnameBlocked(@NonNull String hostname) {
// Check the cache first for the hostname
if (blockedServersCache.contains(hostname)) {
return true;
}
String hashed = Hashing.sha1().hashBytes(hostname.toLowerCase().getBytes(StandardCharsets.ISO_8859_1)).toString();
boolean blocked = bannedServerHashes.contains(hashed); // Is the hostname blocked?
if (blocked) { // Cache the blocked hostname
blockedServersCache.add(hostname);
}
return blocked;
}
}

@ -1,45 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.service.pinger;
import lombok.NonNull;
import me.braydon.mc.model.MinecraftServer;
/**
* A {@link MinecraftServerPinger} is
* used to ping a {@link MinecraftServer}.
*
* @author Braydon
* @param <T> the type of server to ping
*/
public interface MinecraftServerPinger<T extends MinecraftServer> {
/**
* Ping the server with the given hostname and port.
*
* @param hostname the hostname of the server
* @param port the port of the server
* @return the server that was pinged
*/
T ping(@NonNull String hostname, int port);
}

@ -1,95 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.service.pinger.impl;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import me.braydon.mc.common.DNSUtils;
import me.braydon.mc.common.packet.impl.bedrock.BedrockPacketUnconnectedPing;
import me.braydon.mc.common.packet.impl.bedrock.BedrockPacketUnconnectedPong;
import me.braydon.mc.exception.impl.BadRequestException;
import me.braydon.mc.exception.impl.ResourceNotFoundException;
import me.braydon.mc.model.server.BedrockMinecraftServer;
import me.braydon.mc.service.pinger.MinecraftServerPinger;
import java.io.IOException;
import java.net.*;
/**
* The {@link MinecraftServerPinger} for pinging
* {@link BedrockMinecraftServer} over UDP.
*
* @author Braydon
*/
@Log4j2(topic = "Bedrock MC Server Pinger")
public final class BedrockMinecraftServerPinger implements MinecraftServerPinger<BedrockMinecraftServer> {
private static final int TIMEOUT = 3000; // The timeout for the socket
/**
* Ping the server with the given hostname and port.
*
* @param hostname the hostname of the server
* @param port the port of the server
* @return the server that was pinged
*/
@Override
public BedrockMinecraftServer ping(@NonNull String hostname, int port) {
InetAddress inetAddress = DNSUtils.resolveA(hostname); // Resolve the hostname to an IP address
String ip = inetAddress == null ? null : inetAddress.getHostAddress(); // Get the IP address
if (ip != null) { // Was the IP resolved?
log.info("Resolved hostname: {} -> {}", hostname, ip);
}
log.info("Pinging {}:{}...", hostname, port);
long before = System.currentTimeMillis(); // Timestamp before pinging
// Open a socket connection to the server
try (DatagramSocket socket = new DatagramSocket()) {
socket.setSoTimeout(TIMEOUT);
socket.connect(new InetSocketAddress(hostname, port));
long ping = System.currentTimeMillis() - before; // Calculate the ping
log.info("Pinged {}:{} in {}ms", hostname, port, ping);
// Send the unconnected ping packet
new BedrockPacketUnconnectedPing().process(socket);
// Handle the received unconnected pong packet
BedrockPacketUnconnectedPong unconnectedPong = new BedrockPacketUnconnectedPong();
unconnectedPong.process(socket);
String response = unconnectedPong.getResponse();
if (response == null) { // No pong response
throw new ResourceNotFoundException("Server didn't respond to ping");
}
return BedrockMinecraftServer.create(hostname, ip, port, response); // Return the server
} catch (IOException ex) {
if (ex instanceof UnknownHostException) {
throw new BadRequestException("Unknown hostname: %s".formatted(hostname));
} else if (ex instanceof SocketTimeoutException) {
throw new ResourceNotFoundException(ex);
}
log.error("An error occurred pinging %s:%s:".formatted(hostname, port), ex);
}
return null;
}
}

@ -1,102 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.service.pinger.impl;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import me.braydon.mc.RESTfulMC;
import me.braydon.mc.common.DNSUtils;
import me.braydon.mc.common.packet.impl.java.JavaPacketHandshakingInSetProtocol;
import me.braydon.mc.common.packet.impl.java.JavaPacketStatusInStart;
import me.braydon.mc.exception.impl.BadRequestException;
import me.braydon.mc.exception.impl.ResourceNotFoundException;
import me.braydon.mc.model.server.JavaMinecraftServer;
import me.braydon.mc.model.token.JavaServerStatusToken;
import me.braydon.mc.service.pinger.MinecraftServerPinger;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.*;
/**
* The {@link MinecraftServerPinger} for pinging
* {@link JavaMinecraftServer}'s over TCP.
*
* @author Braydon
*/
@Log4j2(topic = "Java MC Server Pinger")
public final class JavaMinecraftServerPinger implements MinecraftServerPinger<JavaMinecraftServer> {
private static final int TIMEOUT = 3000; // The timeout for the socket
/**
* Ping the server with the given hostname and port.
*
* @param hostname the hostname of the server
* @param port the port of the server
* @return the server that was pinged
* @throws BadRequestException if the hostname is unknown
* @throws ResourceNotFoundException if the server could not be found
*/
@Override
public JavaMinecraftServer ping(@NonNull String hostname, int port) throws BadRequestException, ResourceNotFoundException {
InetAddress inetAddress = DNSUtils.resolveA(hostname); // Resolve the hostname to an IP address
String ip = inetAddress == null ? null : inetAddress.getHostAddress(); // Get the IP address
if (ip != null) { // Was the IP resolved?
log.info("Resolved hostname: {} -> {}", hostname, ip);
}
log.info("Pinging {}:{}...", hostname, port);
long before = System.currentTimeMillis(); // Timestamp before pinging
// Open a socket connection to the server
try (Socket socket = new Socket()) {
socket.setTcpNoDelay(true);
socket.connect(new InetSocketAddress(hostname, port), TIMEOUT);
long ping = System.currentTimeMillis() - before; // Calculate the ping
log.info("Pinged {}:{} in {}ms", hostname, port, ping);
// Open data streams to begin packet transaction
try (DataInputStream inputStream = new DataInputStream(socket.getInputStream());
DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream())) {
// Begin handshaking with the server
new JavaPacketHandshakingInSetProtocol(hostname, port, 47).process(inputStream, outputStream);
// Send the status request to the server, and await back the response
JavaPacketStatusInStart packetStatusInStart = new JavaPacketStatusInStart();
packetStatusInStart.process(inputStream, outputStream);
JavaServerStatusToken token = RESTfulMC.GSON.fromJson(packetStatusInStart.getResponse(), JavaServerStatusToken.class);
return JavaMinecraftServer.create(hostname, ip, port, token); // Return the server
}
} catch (IOException ex) {
if (ex instanceof UnknownHostException) {
throw new BadRequestException("Unknown hostname: %s".formatted(hostname));
} else if (ex instanceof ConnectException || ex instanceof SocketTimeoutException) {
throw new ResourceNotFoundException(ex);
}
log.error("An error occurred pinging %s:%s:".formatted(hostname, port), ex);
}
return null;
}
}

@ -1,28 +0,0 @@
# Server Configuration
server:
address: 0.0.0.0
port: 7500
publicUrl: "http://localhost:7500" # The publicly accessible URL for this app
servlet:
context-path: /
# Log Configuration
logging:
file:
path: "./logs"
# Spring Configuration
spring:
data:
# Redis - This is used for caching
redis:
host: "localhost"
port: 6379
database: 0
auth: "" # Leave blank for no auth
# Ignore
application:
name: "RESTfulMC"
banner:
location: "classpath:banner.txt"

@ -1,10 +0,0 @@
_____ ______ _____ _______ __ _ __ __ _____
| __ \| ____|/ ____|__ __/ _| | | \/ |/ ____|
| |__) | |__ | (___ | | | |_ _ _| | \ / | |
| _ /| __| \___ \ | | | _| | | | | |\/| | |
| | \ \| |____ ____) | | | | | | |_| | | | | | |____
|_| \_\______|_____/ |_| |_| \__,_|_|_| |_|\_____|
| API Version - v${application.version}
| Spring Version - ${spring-boot.formatted-version}
_______________________________________

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

@ -1,15 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="A simple, yet useful RESTful API for Minecraft utilizing Springboot.">
<meta name="theme-color" content="#3A34EB">
<title>RESTfulMC</title>
</head>
<body>
<h1>RESTfulMC</h1>
<p>A simple, yet useful RESTful API for Minecraft utilizing Springboot.</p>
<p>View the source <a href="https://git.rainnny.club/Rainnny/RESTfulMC">here</a>!</p>
</body>
</html>

@ -1,67 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.test.config;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.NonNull;
import org.springframework.boot.test.context.TestConfiguration;
import redis.embedded.RedisServer;
import java.io.IOException;
/**
* Test configuration for
* a mock Redis server.
*
* @author Braydon
*/
@TestConfiguration
public class TestRedisConfig {
@NonNull private final RedisServer server;
public TestRedisConfig() throws IOException {
server = new RedisServer(); // Construct the mock server
}
/**
* Start up the mock Redis server.
*
* @throws IOException if there was an issue starting the server
*/
@PostConstruct
public void onInitialize() throws IOException {
server.start();
}
/**
* Shutdown the running mock Redis server.
*
* @throws IOException if there was an issue stopping the server
*/
@PreDestroy
public void housekeeping() throws IOException {
server.stop();
}
}

@ -1,118 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.test.controller;
import lombok.NonNull;
import me.braydon.mc.controller.PlayerController;
import me.braydon.mc.test.config.TestRedisConfig;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for the {@link PlayerController}.
*
* @author Braydon
*/
@SpringBootTest(classes = TestRedisConfig.class)
@AutoConfigureMockMvc
public final class PlayerControllerTests {
/**
* The {@link MockMvc} instance to use for testing.
*/
@NonNull private final MockMvc mockMvc;
@Autowired
public PlayerControllerTests(@NonNull MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
/**
* Run a test to ensure retrieving
* a player's data is successful.
*
* @throws Exception if the test fails
*/
@Test
void ensurePlayerLookupSuccess() throws Exception {
mockMvc.perform(get("/player/g")
.accept(MediaType.APPLICATION_JSON) // Accept JSON
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
).andExpect(status().isOk()) // Expect 200 (OK)
.andExpect(jsonPath("$.username").value("g")) // Expect the player's username
.andReturn();
}
/**
* Run a test to ensure retrieving
* invalid player's results in a 404.
*
* @throws Exception if the test fails
*/
@Test
void ensurePlayerNotFound() throws Exception {
mockMvc.perform(get("/player/SDFSDFSDFSDFDDDG")
.accept(MediaType.APPLICATION_JSON) // Accept JSON
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
).andExpect(status().isNotFound()) // Expect 404 (Not Found)
.andReturn();
}
/**
* Run a test to ensure retrieving
* player's with invalid usernames
* results in a 400.
*
* @throws Exception if the test fails
*/
@Test
void ensureUsernameIsInvalid() throws Exception {
mockMvc.perform(get("/player/A")
.accept(MediaType.APPLICATION_JSON) // Accept JSON
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
).andExpect(status().isBadRequest()) // Expect 400 (Bad Request)
.andReturn();
}
/**
* Run a test to ensure retrieving
* a player's head texture is successful.
*
* @throws Exception if the test fails
*/
@Test
void ensureSkinPartTextureSuccess() throws Exception {
mockMvc.perform(get("/player/head/Rainnny.png")
.contentType(MediaType.IMAGE_PNG) // Content type is PNG
).andExpect(status().isOk()) // Expect 200 (OK)
.andReturn();
}
}

@ -1,167 +0,0 @@
/*
* MIT License
*
* Copyright (c) 2024 Braydon (Rainnny).
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.braydon.mc.test.controller;
import lombok.NonNull;
import me.braydon.mc.controller.ServerController;
import me.braydon.mc.test.config.TestRedisConfig;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for the {@link ServerController}.
*
* @author Braydon
*/
@SpringBootTest(classes = TestRedisConfig.class)
@AutoConfigureMockMvc
public final class ServerControllerTests {
/**
* The {@link MockMvc} instance to use for testing.
*/
@NonNull private final MockMvc mockMvc;
@Autowired
public ServerControllerTests(@NonNull MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
/**
* Run a test to ensure retrieving
* a Java server's status is successful.
*
* @throws Exception if the test fails
*/
@Test
void ensureJavaServerLookupSuccess() throws Exception {
mockMvc.perform(get("/server/java/hypixel.net")
.accept(MediaType.APPLICATION_JSON) // Accept JSON
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
).andExpect(status().isOk()) // Expect 200 (OK)
.andExpect(jsonPath("$.value.hostname") // Expect the server's resolved hostname
.value("mc.hypixel.net")
).andReturn();
}
/**
* Run a test to ensure retrieving
* a Bedrock server's status is successful.
*
* @throws Exception if the test fails
*/
@Test
void ensureBedrockServerLookupSuccess() throws Exception {
mockMvc.perform(get("/server/bedrock/gateway.wildnetwork.net")
.accept(MediaType.APPLICATION_JSON) // Accept JSON
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
).andExpect(status().isOk()) // Expect 200 (OK)
.andExpect(jsonPath("$.value.hostname") // Expect the server's resolved hostname
.value("gateway.wildnetwork.net")
).andReturn();
}
/**
* Run a test to ensure that requesting
* information about a server on an invalid
* platform results in a 400.
*
* @throws Exception if the test fails
*/
@Test
void ensureUnknownPlatform() throws Exception {
mockMvc.perform(get("/server/invalid/hypixel.net")
.accept(MediaType.APPLICATION_JSON) // Accept JSON
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
).andExpect(status().isBadRequest()) // Expect 400 (Bad Request)
.andReturn();
}
/**
* Run a test to ensure looking up
* an invalid hostname results in a 400.
*
* @throws Exception if the test fails
*/
@Test
void ensureUnknownHostname() throws Exception {
mockMvc.perform(get("/server/java/invalid")
.accept(MediaType.APPLICATION_JSON) // Accept JSON
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
).andExpect(status().isBadRequest()) // Expect 400 (Bad Request)
.andReturn();
}
/**
* Run a test to ensure looking up
* an invalid port results in a 400.
*
* @throws Exception if the test fails
*/
@Test
void ensureUnknownPort() throws Exception {
mockMvc.perform(get("/server/java/hypixel.net?port=A")
.accept(MediaType.APPLICATION_JSON) // Accept JSON
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
).andExpect(status().isBadRequest()) // Expect 400 (Bad Request)
.andReturn();
}
/**
* Run a test to ensure checking if
* a server is banned is successful.
*
* @throws Exception if the test fails
*/
@Test
void ensureServerBanCheckSuccess() throws Exception{
mockMvc.perform(get("/server/blocked/arkhamnetwork.org")
.accept(MediaType.APPLICATION_JSON) // Accept JSON
.contentType(MediaType.APPLICATION_JSON) // Content type is JSON
).andExpect(status().isOk()) // Expect 200 (OK)
.andExpect(jsonPath("$.blocked").value(true)) // Expect block
.andReturn();
}
/**
* Run a test to ensure retrieving
* a server's favicon is successful.
*
* @throws Exception if the test fails
*/
@Test
void ensureServerFaviconSuccess() throws Exception {
mockMvc.perform(get("/server/icon/hypixel.net")
.contentType(MediaType.IMAGE_PNG) // Content type is PNG
).andExpect(status().isOk()) // Expect 200 (OK)
.andReturn();
}
}