diff --git a/src/main/java/me/braydon/mc/service/MojangService.java b/src/main/java/me/braydon/mc/service/MojangService.java index 94ba091..dec4dab 100644 --- a/src/main/java/me/braydon/mc/service/MojangService.java +++ b/src/main/java/me/braydon/mc/service/MojangService.java @@ -2,16 +2,15 @@ package me.braydon.mc.service; import lombok.NonNull; import lombok.extern.log4j.Log4j2; +import me.braydon.mc.common.EnumUtils; import me.braydon.mc.common.Tuple; import me.braydon.mc.common.UUIDUtils; 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.InvalidMinecraftServerPlatform; import me.braydon.mc.exception.impl.ResourceNotFoundException; -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 me.braydon.mc.model.*; import me.braydon.mc.model.cache.CachedPlayer; import me.braydon.mc.model.cache.CachedPlayerName; import me.braydon.mc.model.token.MojangProfileToken; @@ -130,6 +129,14 @@ public final class MojangService { } } + public MinecraftServer getMinecraftServer(@NonNull String platformName, @NonNull String hostname) throws InvalidMinecraftServerPlatform { + MinecraftServer.Platform platform = EnumUtils.getEnumConstant(MinecraftServer.Platform.class, platformName.toUpperCase()); + if (platform == null) { // Invalid platform + throw new InvalidMinecraftServerPlatform(); + } + return platform.getPinger().ping(hostname, 25565); + } + /** * Get the UUID of a player by their username. * diff --git a/src/main/java/me/braydon/mc/service/pinger/MinecraftServerPinger.java b/src/main/java/me/braydon/mc/service/pinger/MinecraftServerPinger.java new file mode 100644 index 0000000..c09d2e6 --- /dev/null +++ b/src/main/java/me/braydon/mc/service/pinger/MinecraftServerPinger.java @@ -0,0 +1,22 @@ +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 the type of server to ping + */ +public interface MinecraftServerPinger { + /** + * 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); +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/service/pinger/impl/BedrockMinecraftServerPinger.java b/src/main/java/me/braydon/mc/service/pinger/impl/BedrockMinecraftServerPinger.java new file mode 100644 index 0000000..c7095e3 --- /dev/null +++ b/src/main/java/me/braydon/mc/service/pinger/impl/BedrockMinecraftServerPinger.java @@ -0,0 +1,27 @@ +package me.braydon.mc.service.pinger.impl; + +import lombok.NonNull; +import lombok.extern.log4j.Log4j2; +import me.braydon.mc.model.server.BedrockMinecraftServer; +import me.braydon.mc.service.pinger.MinecraftServerPinger; + +/** + * The {@link MinecraftServerPinger} for + * pinging {@link BedrockMinecraftServer}'s. + * + * @author Braydon + */ +@Log4j2(topic = "Bedrock MC Server Pinger") +public final class BedrockMinecraftServerPinger implements MinecraftServerPinger { + /** + * 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) { + throw new UnsupportedOperationException("Not yet implemented."); + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/service/pinger/impl/JavaMinecraftServerPinger.java b/src/main/java/me/braydon/mc/service/pinger/impl/JavaMinecraftServerPinger.java new file mode 100644 index 0000000..89529e1 --- /dev/null +++ b/src/main/java/me/braydon/mc/service/pinger/impl/JavaMinecraftServerPinger.java @@ -0,0 +1,64 @@ +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.packet.impl.PacketHandshakingInSetProtocol; +import me.braydon.mc.common.packet.impl.PacketStatusInStart; +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.InetSocketAddress; +import java.net.Socket; + +/** + * The {@link MinecraftServerPinger} for + * pinging {@link JavaMinecraftServer}'s. + * + * @author Braydon + */ +@Log4j2(topic = "Java MC Server Pinger") +public final class JavaMinecraftServerPinger implements MinecraftServerPinger { + 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 JavaMinecraftServer ping(@NonNull String hostname, int port) { + 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 PacketHandshakingInSetProtocol(hostname, port, 47).process(inputStream, outputStream); + + // Send the status request to the server, and await back the response + PacketStatusInStart packetStatusInStart = new PacketStatusInStart(); + packetStatusInStart.process(inputStream, outputStream); + JavaServerStatusToken token = RESTfulMC.GSON.fromJson(packetStatusInStart.getResponse(), JavaServerStatusToken.class); + return JavaMinecraftServer.create(hostname, port, token); // Return the server + } + } catch (IOException ex) { + log.error("An error occurred pinging %s:%s:".formatted(hostname, port), ex); + } + return null; + } +} \ No newline at end of file