Compare commits
74 Commits
17803410bd
...
renovate/o
Author | SHA1 | Date | |
---|---|---|---|
9a0b6e1635 | |||
0bc614ce39 | |||
499c54c8cf | |||
41f7ca07b0 | |||
5ec61940ac | |||
6665e8a655 | |||
07562eb94d | |||
a78adf67c7 | |||
fc1f51da75 | |||
c796875d8c | |||
82fb2a3d23 | |||
2e326bb7be | |||
c5bf941c54 | |||
0eb965a26d | |||
5481c9302c | |||
b7834ab389 | |||
bb651bd88b | |||
2b017f9ef7 | |||
d83391de33 | |||
76bef70473 | |||
4aa5b0a90d | |||
6a44618ae9 | |||
146d053af8 | |||
796146c039 | |||
00c83d9ae3 | |||
3bbab24e45 | |||
5034a11e63 | |||
3d11c65678 | |||
fd3da02159 | |||
0bdaefe4a2 | |||
ba167b4e56 | |||
493e7ce4c0 | |||
7f501431b1 | |||
fa92791b56 | |||
6750773640 | |||
cecc6bc94f | |||
20db1c1aff | |||
e62e7f0fc2 | |||
c9ed681204 | |||
c6642e85fe | |||
9dfbd1af47 | |||
c8629e4f27 | |||
a6209c45ff | |||
ee5b1f12d8 | |||
ff79372ead | |||
f0e1490463 | |||
9196ec3578 | |||
a8558578f2 | |||
67efda71d2 | |||
2ad5556041 | |||
3faf2d3319 | |||
bf992713dc | |||
23e240fce1 | |||
cca45057f0 | |||
beda7fa230 | |||
d394c21f69 | |||
7df4fda744 | |||
f85ed49545 | |||
69833bf560 | |||
6693fc6793 | |||
a6ea3ab143 | |||
f96e5d5426 | |||
e360ad4446 | |||
c913816447 | |||
5dccce9fc5 | |||
3cd5e32118 | |||
5ca707bef1 | |||
6fc02cd906 | |||
f4a9d7c31c | |||
7127794152 | |||
f664406299 | |||
c5b5b3b105 | |||
b5fa470801 | |||
0854c9e76a |
@ -27,7 +27,7 @@ jobs:
|
||||
|
||||
# Setup Java and Maven
|
||||
- name: Set up JDK and Maven
|
||||
uses: s4u/setup-maven-action@v1.12.0
|
||||
uses: s4u/setup-maven-action@v1.14.0
|
||||
with:
|
||||
java-version: ${{ matrix.java-version }}
|
||||
distribution: "zulu"
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -29,3 +29,6 @@ git.properties
|
||||
pom.xml.versionsBackup
|
||||
application.yml
|
||||
target/
|
||||
|
||||
### MaxMind GeoIP2
|
||||
data/
|
||||
|
@ -1,4 +1,8 @@
|
||||
FROM maven:3.8.5-openjdk-17-slim
|
||||
FROM maven:3.9.8-eclipse-temurin-17-alpine
|
||||
|
||||
RUN apk --update --upgrade --no-cache add fontconfig ttf-freefont font-noto terminus-font \
|
||||
&& fc-cache -f \
|
||||
&& fc-list | sort
|
||||
|
||||
# Set the working directory
|
||||
WORKDIR /home/container
|
||||
@ -17,4 +21,4 @@ ENV PORT=80
|
||||
ENV ENVIRONMENT=production
|
||||
|
||||
# Run the jar file
|
||||
CMD ["java", "-jar", "target/Minecraft-Utilities.jar"]
|
||||
CMD java -jar target/Minecraft-Utilities.jar -Djava.awt.headless=true
|
@ -1,3 +1,3 @@
|
||||
# Minecraft Utilities - Backend
|
||||
|
||||
See [The Website](https://mcutils.xyz) or [Minecraft Utilities Documentation](https://mcutils.xyz/documentation) for more information.
|
||||
See [The Website](https://mcutils.xyz) or [Minecraft Utilities Documentation](https://mcutils.xyz/docs) for more information.
|
37
pom.xml
37
pom.xml
@ -17,7 +17,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.2.5</version>
|
||||
<version>3.3.2</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
|
||||
@ -93,13 +93,13 @@
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.32</version>
|
||||
<version>1.18.34</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10.1</version>
|
||||
<version>2.11.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -122,24 +122,31 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
||||
<artifactId>httpclient5</artifactId>
|
||||
<version>5.3.1</version>
|
||||
<version>5.4.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sentry -->
|
||||
<dependency>
|
||||
<groupId>io.sentry</groupId>
|
||||
<artifactId>sentry-spring-boot-starter-jakarta</artifactId>
|
||||
<version>7.14.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- InfluxDB Metrics -->
|
||||
<dependency>
|
||||
<groupId>com.influxdb</groupId>
|
||||
<artifactId>influxdb-spring</artifactId>
|
||||
<version>7.0.0</version>
|
||||
<version>7.2.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.influxdb</groupId>
|
||||
<artifactId>influxdb-client-java</artifactId>
|
||||
<version>7.0.0</version>
|
||||
<version>7.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- DNS Lookup -->
|
||||
@ -154,10 +161,24 @@
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
<version>2.5.0</version>
|
||||
<version>2.6.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- GeoIP - IP Lookups -->
|
||||
<dependency>
|
||||
<groupId>com.maxmind.geoip2</groupId>
|
||||
<artifactId>geoip2</artifactId>
|
||||
<version>4.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Archive Utilities -->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.plexus</groupId>
|
||||
<artifactId>plexus-archiver</artifactId>
|
||||
<version>4.10.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Tests -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@ -173,7 +194,7 @@
|
||||
<dependency>
|
||||
<groupId>de.flapdoodle.embed</groupId>
|
||||
<artifactId>de.flapdoodle.embed.mongo.spring3x</artifactId>
|
||||
<version>4.12.6</version>
|
||||
<version>4.16.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@ -16,9 +16,9 @@ import java.util.Objects;
|
||||
@Log4j2(topic = "Main")
|
||||
@SpringBootApplication
|
||||
public class Main {
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.setDateFormat("MM-dd-yyyy HH:mm:ss")
|
||||
.create();
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.setDateFormat("MM-dd-yyyy HH:mm:ss")
|
||||
.create();
|
||||
public static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
|
||||
|
||||
@SneakyThrows
|
||||
|
@ -22,7 +22,7 @@ public final class AppConfig {
|
||||
private static boolean isRunningTest = true;
|
||||
static {
|
||||
try {
|
||||
Class.forName("org.junit.Test");
|
||||
Class.forName("org.junit.jupiter.engine.JupiterTestEngine");
|
||||
} catch (ClassNotFoundException e) {
|
||||
isRunningTest = false;
|
||||
}
|
||||
|
@ -104,8 +104,7 @@ public final class ColorUtils {
|
||||
public static Color getMinecraftColor(char colorCode) {
|
||||
String color = COLOR_MAP.getOrDefault(colorCode, null);
|
||||
if (color == null) {
|
||||
System.out.println("Unknown color code: " + colorCode);
|
||||
return Color.WHITE;
|
||||
throw new IllegalArgumentException("Invalid color code: " + colorCode);
|
||||
}
|
||||
return Color.decode(color);
|
||||
}
|
||||
|
@ -1,23 +1,28 @@
|
||||
package xyz.mcutils.backend.common;
|
||||
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import xyz.mcutils.backend.Main;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@Log4j2(topic = "Fonts")
|
||||
public class Fonts {
|
||||
|
||||
public static final Font MINECRAFT;
|
||||
public static final Font MINECRAFT_BOLD;
|
||||
public static final Font MINECRAFT_ITALIC;
|
||||
|
||||
static {
|
||||
InputStream stream = Main.class.getResourceAsStream("/fonts/minecraft-font.ttf");
|
||||
try {
|
||||
MINECRAFT = Font.createFont(Font.TRUETYPE_FONT, stream).deriveFont(18f);
|
||||
MINECRAFT_BOLD = MINECRAFT.deriveFont(Font.BOLD);
|
||||
MINECRAFT_ITALIC = MINECRAFT.deriveFont(Font.ITALIC);
|
||||
} catch (FontFormatException | IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
log.error("Failed to load Minecraft font", e);
|
||||
throw new RuntimeException("Failed to load Minecraft font", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
85
src/main/java/xyz/mcutils/backend/common/MojangServer.java
Normal file
85
src/main/java/xyz/mcutils/backend/common/MojangServer.java
Normal file
@ -0,0 +1,85 @@
|
||||
package xyz.mcutils.backend.common;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author Fascinated (fascinated7)
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@ToString
|
||||
public enum MojangServer {
|
||||
SESSION("Session Server", "https://sessionserver.mojang.com"),
|
||||
API("Mojang API", "https://api.mojang.com"),
|
||||
TEXTURES("Textures Server", "https://textures.minecraft.net"),
|
||||
ASSETS("Assets Server", "https://assets.mojang.com"),
|
||||
LIBRARIES("Libraries Server", "https://libraries.minecraft.net"),
|
||||
SERVICES("Minecraft Services", "https://api.minecraftservices.com");
|
||||
|
||||
private static final long STATUS_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
|
||||
|
||||
/**
|
||||
* The name of this server.
|
||||
*/
|
||||
@NonNull private final String name;
|
||||
|
||||
/**
|
||||
* The endpoint of this service.
|
||||
*/
|
||||
@NonNull private final String endpoint;
|
||||
|
||||
/**
|
||||
* Ping this service and get the status of it.
|
||||
*
|
||||
* @return the service status
|
||||
*/
|
||||
@NonNull
|
||||
public Status getStatus() {
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(endpoint.substring(8));
|
||||
long before = System.currentTimeMillis();
|
||||
if (address.isReachable((int) STATUS_TIMEOUT)) {
|
||||
// The time it took to reach the host is 75% of
|
||||
// the timeout, consider it to be degraded.
|
||||
if ((System.currentTimeMillis() - before) > STATUS_TIMEOUT * 0.75D) {
|
||||
return Status.DEGRADED;
|
||||
}
|
||||
return Status.ONLINE;
|
||||
}
|
||||
} catch (UnknownHostException ex) {
|
||||
ex.printStackTrace();
|
||||
} catch (IOException ignored) {
|
||||
// We can safely ignore any errors, we're simply checking
|
||||
// if the host is reachable, if it's not, then it's offline.
|
||||
}
|
||||
return Status.OFFLINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* The status of a service.
|
||||
*/
|
||||
public enum Status {
|
||||
/**
|
||||
* The service is online and accessible.
|
||||
*/
|
||||
ONLINE,
|
||||
|
||||
/**
|
||||
* The service is online, but is experiencing degraded performance.
|
||||
*/
|
||||
DEGRADED,
|
||||
|
||||
/**
|
||||
* The service is offline and inaccessible.
|
||||
*/
|
||||
OFFLINE
|
||||
}
|
||||
}
|
@ -18,6 +18,8 @@ import java.io.ByteArrayInputStream;
|
||||
@Log4j2
|
||||
public class ServerPreviewRenderer extends Renderer<MinecraftServer> {
|
||||
public static final ServerPreviewRenderer INSTANCE = new ServerPreviewRenderer();
|
||||
|
||||
|
||||
private static BufferedImage SERVER_BACKGROUND;
|
||||
private static BufferedImage PING_ICON;
|
||||
static {
|
||||
@ -78,14 +80,21 @@ public class ServerPreviewRenderer extends Renderer<MinecraftServer> {
|
||||
// Move x position to after the drawn text
|
||||
x += textWidth;
|
||||
// Set color based on color code
|
||||
char colorCode = line.charAt(colorIndex + 1);
|
||||
char colorCode = Character.toLowerCase(line.charAt(colorIndex + 1));
|
||||
|
||||
if (colorCode == 'l') {
|
||||
graphics.setFont(Fonts.MINECRAFT_BOLD);
|
||||
} else {
|
||||
Color color = ColorUtils.getMinecraftColor(colorCode);
|
||||
graphics.setColor(color);
|
||||
graphics.setFont(Fonts.MINECRAFT);
|
||||
// Set the color and font style
|
||||
switch (colorCode) {
|
||||
case 'l': graphics.setFont(Fonts.MINECRAFT_BOLD);
|
||||
case 'o': graphics.setFont(Fonts.MINECRAFT_ITALIC);
|
||||
default: {
|
||||
try {
|
||||
graphics.setFont(Fonts.MINECRAFT);
|
||||
Color color = ColorUtils.getMinecraftColor(colorCode);
|
||||
graphics.setColor(color);
|
||||
} catch (Exception ignored) {
|
||||
// Unknown color, can ignore the error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Move index to after the color code
|
||||
|
@ -12,23 +12,21 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import xyz.mcutils.backend.model.cache.CachedEndpointStatus;
|
||||
import xyz.mcutils.backend.service.MojangService;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RestController
|
||||
@Tag(name = "Mojang Controller", description = "The Mojang Controller is used to get information about the Mojang APIs.")
|
||||
@RequestMapping(value = "/mojang/", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
@Tag(name = "Mojang Controller", description = "The Mojang Controller is used to get information about the Mojang APIs.")
|
||||
public class MojangController {
|
||||
|
||||
@Autowired
|
||||
private MojangService mojangService;
|
||||
|
||||
@ResponseBody
|
||||
@GetMapping(value = "/status")
|
||||
public ResponseEntity<CachedEndpointStatus> getStatus() {
|
||||
CachedEndpointStatus status = mojangService.getMojangApiStatus();
|
||||
|
||||
public ResponseEntity<?> getStatus() {
|
||||
return ResponseEntity.ok()
|
||||
.cacheControl(CacheControl.maxAge(1, TimeUnit.MINUTES).cachePublic())
|
||||
.body(status);
|
||||
.body(Map.of("endpoints", mojangService.getMojangServerStatus()));
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,8 @@ import xyz.mcutils.backend.service.PlayerService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RestController
|
||||
@Tag(name = "Player Controller", description = "The Player Controller is used to get information about a player.")
|
||||
@RequestMapping(value = "/player/")
|
||||
@Tag(name = "Player Controller", description = "The Player Controller is used to get information about a player.")
|
||||
public class PlayerController {
|
||||
|
||||
private final PlayerService playerService;
|
||||
|
@ -16,8 +16,8 @@ import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RestController
|
||||
@Tag(name = "Server Controller", description = "The Server Controller is used to get information about a server.")
|
||||
@RequestMapping(value = "/server/")
|
||||
@Tag(name = "Server Controller", description = "The Server Controller is used to get information about a server.")
|
||||
public class ServerController {
|
||||
|
||||
private final ServerService serverService;
|
||||
@ -43,7 +43,7 @@ public class ServerController {
|
||||
|
||||
@ResponseBody
|
||||
@GetMapping(value = "/icon/{hostname}", produces = MediaType.IMAGE_PNG_VALUE)
|
||||
public ResponseEntity<?> getServerIcon(
|
||||
public ResponseEntity<byte[]> getServerIcon(
|
||||
@Parameter(description = "The hostname and port of the server", example = "aetheria.cc") @PathVariable String hostname,
|
||||
@Parameter(description = "Whether to download the image") @RequestParam(required = false, defaultValue = "false") boolean download) {
|
||||
String dispositionHeader = download ? "attachment; filename=%s.png" : "inline; filename=%s.png";
|
||||
@ -58,7 +58,7 @@ public class ServerController {
|
||||
|
||||
@ResponseBody
|
||||
@GetMapping(value = "/{platform}/preview/{hostname}", produces = MediaType.IMAGE_PNG_VALUE)
|
||||
public ResponseEntity<?> getServerPreview(
|
||||
public ResponseEntity<byte[]> getServerPreview(
|
||||
@Parameter(description = "The platform of the server", example = "java") @PathVariable String platform,
|
||||
@Parameter(description = "The hostname and port of the server", example = "aetheria.cc") @PathVariable String hostname,
|
||||
@Parameter(description = "Whether to download the image") @RequestParam(required = false, defaultValue = "false") boolean download,
|
||||
@ -67,7 +67,7 @@ public class ServerController {
|
||||
CachedMinecraftServer server = serverService.getServer(platform, hostname);
|
||||
|
||||
return ResponseEntity.ok()
|
||||
.cacheControl(CacheControl.maxAge(1, TimeUnit.HOURS).cachePublic())
|
||||
.cacheControl(CacheControl.maxAge(5, TimeUnit.MINUTES).cachePublic())
|
||||
.contentType(MediaType.IMAGE_PNG)
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, dispositionHeader.formatted(hostname))
|
||||
.body(serverService.getServerPreview(server, platform, size));
|
||||
|
@ -1,6 +1,7 @@
|
||||
package xyz.mcutils.backend.exception;
|
||||
|
||||
import io.micrometer.common.lang.NonNull;
|
||||
import io.sentry.Sentry;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
@ -39,6 +40,7 @@ public final class ExceptionControllerAdvice {
|
||||
}
|
||||
if (status == null) { // Fallback to 500
|
||||
status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
Sentry.captureException(ex); // Capture the exception with Sentry
|
||||
}
|
||||
return new ResponseEntity<>(new ErrorResponse(status, message), status);
|
||||
}
|
||||
|
@ -8,10 +8,13 @@ import lombok.Setter;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.redis.core.RedisHash;
|
||||
import xyz.mcutils.backend.common.CachedResponse;
|
||||
import xyz.mcutils.backend.model.mojang.EndpointStatus;
|
||||
import xyz.mcutils.backend.common.MojangServer;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Setter @Getter @EqualsAndHashCode(callSuper = false)
|
||||
@RedisHash(value = "mojangEndpointStatus", timeToLive = 60L) // 1 minute (in seconds)
|
||||
@ -26,11 +29,21 @@ public class CachedEndpointStatus extends CachedResponse implements Serializable
|
||||
/**
|
||||
* The endpoint cache.
|
||||
*/
|
||||
private final List<EndpointStatus> endpoints;
|
||||
private final List<Map<String, Object>> endpoints;
|
||||
|
||||
public CachedEndpointStatus(@NonNull String id, List<EndpointStatus> endpoints) {
|
||||
public CachedEndpointStatus(@NonNull String id, Map<MojangServer, MojangServer.Status> mojangServers) {
|
||||
super(Cache.defaultCache());
|
||||
this.id = id;
|
||||
this.endpoints = endpoints;
|
||||
this.endpoints = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<MojangServer, MojangServer.Status> entry : mojangServers.entrySet()) {
|
||||
MojangServer server = entry.getKey();
|
||||
|
||||
Map<String, Object> serverStatus = new HashMap<>();
|
||||
serverStatus.put("name", server.getName());
|
||||
serverStatus.put("endpoint", server.getEndpoint());
|
||||
serverStatus.put("status", entry.getValue().name());
|
||||
endpoints.add(serverStatus);
|
||||
}
|
||||
}
|
||||
}
|
@ -32,8 +32,8 @@ public final class BedrockMinecraftServer extends MinecraftServer {
|
||||
|
||||
private BedrockMinecraftServer(@NonNull String id, @NonNull String hostname, String ip, int port, @NonNull DNSRecord[] records,
|
||||
@NonNull Edition edition, @NonNull Version version, @NonNull Players players, @NonNull MOTD motd,
|
||||
@NonNull GameMode gamemode) {
|
||||
super(hostname, ip, port, records, motd, players);
|
||||
@NonNull GameMode gamemode, GeoLocation location) {
|
||||
super(hostname, ip, port, records, motd, players, location);
|
||||
this.id = id;
|
||||
this.edition = edition;
|
||||
this.version = version;
|
||||
@ -53,12 +53,12 @@ public final class BedrockMinecraftServer extends MinecraftServer {
|
||||
* @return the Bedrock Minecraft server
|
||||
*/
|
||||
@NonNull
|
||||
public static BedrockMinecraftServer create(@NonNull String hostname, String ip, int port, DNSRecord[] records, @NonNull String token) {
|
||||
public static BedrockMinecraftServer create(@NonNull String hostname, String ip, int port, DNSRecord[] records, GeoLocation location, @NonNull String token) {
|
||||
String[] split = token.split(";"); // Split the token
|
||||
Edition edition = Edition.valueOf(split[0]);
|
||||
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]);
|
||||
MOTD motd = MOTD.create(hostname, Platform.BEDROCK, split[1] + "\n" + split[7]);
|
||||
GameMode gameMode = new GameMode(split[8], split.length > 9 ? Integer.parseInt(split[9]) : -1);
|
||||
return new BedrockMinecraftServer(
|
||||
split[6],
|
||||
@ -70,7 +70,8 @@ public final class BedrockMinecraftServer extends MinecraftServer {
|
||||
version,
|
||||
players,
|
||||
motd,
|
||||
gameMode
|
||||
gameMode,
|
||||
location
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -65,10 +65,10 @@ public final class JavaMinecraftServer extends MinecraftServer {
|
||||
*/
|
||||
private boolean mojangBlocked;
|
||||
|
||||
public JavaMinecraftServer(String hostname, String ip, int port, MOTD motd, Players players, DNSRecord[] records,
|
||||
@NonNull Version version, Favicon favicon, ForgeModInfo modInfo, ForgeData forgeData,
|
||||
boolean preventsChatReports, boolean enforcesSecureChat, boolean previewsChat) {
|
||||
super(hostname, ip, port, records, motd, players);
|
||||
public JavaMinecraftServer(String hostname, String ip, int port, MOTD motd, Players players, GeoLocation location,
|
||||
DNSRecord[] records, @NonNull Version version, Favicon favicon, ForgeModInfo modInfo,
|
||||
ForgeData forgeData, boolean preventsChatReports, boolean enforcesSecureChat, boolean previewsChat) {
|
||||
super(hostname, ip, port, records, motd, players, location);
|
||||
this.version = version;
|
||||
this.favicon = favicon;
|
||||
this.modInfo = modInfo;
|
||||
@ -88,7 +88,7 @@ public final class JavaMinecraftServer extends MinecraftServer {
|
||||
* @return the Java Minecraft server
|
||||
*/
|
||||
@NonNull
|
||||
public static JavaMinecraftServer create(@NonNull String hostname, String ip, int port, DNSRecord[] records, @NonNull JavaServerStatusToken token) {
|
||||
public static JavaMinecraftServer create(@NonNull String hostname, String ip, int port, DNSRecord[] records, GeoLocation location, @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(Main.GSON.toJson(token.getDescription()))).toLegacyText();
|
||||
@ -97,8 +97,9 @@ public final class JavaMinecraftServer extends MinecraftServer {
|
||||
hostname,
|
||||
ip,
|
||||
port,
|
||||
MinecraftServer.MOTD.create(motdString),
|
||||
MinecraftServer.MOTD.create(hostname, Platform.JAVA, motdString),
|
||||
token.getPlayers(),
|
||||
location,
|
||||
records,
|
||||
token.getVersion().detailedCopy(),
|
||||
JavaMinecraftServer.Favicon.create(token.getFavicon(), ServerUtils.getAddress(hostname, port)),
|
||||
|
@ -1,8 +1,10 @@
|
||||
package xyz.mcutils.backend.model.server;
|
||||
|
||||
import com.maxmind.geoip2.model.CityResponse;
|
||||
import io.micrometer.common.lang.NonNull;
|
||||
import lombok.*;
|
||||
import xyz.mcutils.backend.common.ColorUtils;
|
||||
import xyz.mcutils.backend.config.Config;
|
||||
import xyz.mcutils.backend.model.dns.DNSRecord;
|
||||
import xyz.mcutils.backend.service.pinger.MinecraftServerPinger;
|
||||
import xyz.mcutils.backend.service.pinger.impl.BedrockMinecraftServerPinger;
|
||||
@ -48,6 +50,11 @@ public class MinecraftServer {
|
||||
*/
|
||||
private final Players players;
|
||||
|
||||
/**
|
||||
* The location of the server.
|
||||
*/
|
||||
private final GeoLocation location;
|
||||
|
||||
/**
|
||||
* A platform a Minecraft
|
||||
* server can operate on.
|
||||
@ -94,6 +101,11 @@ public class MinecraftServer {
|
||||
*/
|
||||
private final String[] html;
|
||||
|
||||
/**
|
||||
* The URL to the server preview image.
|
||||
*/
|
||||
private final String preview;
|
||||
|
||||
/**
|
||||
* Create a new MOTD from a raw string.
|
||||
*
|
||||
@ -101,12 +113,14 @@ public class MinecraftServer {
|
||||
* @return the new motd
|
||||
*/
|
||||
@NonNull
|
||||
public static MOTD create(@NonNull String raw) {
|
||||
public static MOTD create(@NonNull String hostname, @NonNull Platform platform, @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)
|
||||
Arrays.stream(rawLines).map(ColorUtils::toHTML).toArray(String[]::new),
|
||||
Config.INSTANCE.getWebPublicUrl() + "/server/%s/preview/%s".formatted(
|
||||
platform.name().toLowerCase(),hostname)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -147,4 +161,54 @@ public class MinecraftServer {
|
||||
@NonNull private final String name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The location of the server.
|
||||
*/
|
||||
@AllArgsConstructor @Getter
|
||||
public static class GeoLocation {
|
||||
/**
|
||||
* The country of the server.
|
||||
*/
|
||||
private final String country;
|
||||
|
||||
/**
|
||||
* The region of the server.
|
||||
*/
|
||||
private final String region;
|
||||
|
||||
/**
|
||||
* The city of the server.
|
||||
*/
|
||||
private final String city;
|
||||
|
||||
/**
|
||||
* The latitude of the server.
|
||||
*/
|
||||
private final double latitude;
|
||||
|
||||
/**
|
||||
* The longitude of the server.
|
||||
*/
|
||||
private final double longitude;
|
||||
|
||||
/**
|
||||
* Gets the location of the server from Maxmind.
|
||||
*
|
||||
* @param response the response from Maxmind
|
||||
* @return the location of the server
|
||||
*/
|
||||
public static GeoLocation fromMaxMind(CityResponse response) {
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
return new GeoLocation(
|
||||
response.getCountry().getName(),
|
||||
response.getMostSpecificSubdivision().getName(),
|
||||
response.getCity().getName(),
|
||||
response.getLocation().getLatitude(),
|
||||
response.getLocation().getLongitude()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
package xyz.mcutils.backend.repository.redis;
|
||||
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import xyz.mcutils.backend.model.cache.CachedEndpointStatus;
|
||||
|
||||
/**
|
||||
* A cache repository for {@link CachedEndpointStatus}'s.
|
||||
*
|
||||
* @author Braydon
|
||||
*/
|
||||
public interface EndpointStatusRepository extends CrudRepository<CachedEndpointStatus, String> { }
|
125
src/main/java/xyz/mcutils/backend/service/MaxMindService.java
Normal file
125
src/main/java/xyz/mcutils/backend/service/MaxMindService.java
Normal file
@ -0,0 +1,125 @@
|
||||
package xyz.mcutils.backend.service;
|
||||
|
||||
import com.maxmind.geoip2.DatabaseReader;
|
||||
import com.maxmind.geoip2.exception.GeoIp2Exception;
|
||||
import com.maxmind.geoip2.model.CityResponse;
|
||||
import io.sentry.Sentry;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.codehaus.plexus.archiver.tar.TarGZipUnArchiver;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
import xyz.mcutils.backend.Main;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Service
|
||||
@Log4j2(topic = "MaxMind Service")
|
||||
public class MaxMindService {
|
||||
/**
|
||||
* The MaxMind database.
|
||||
*/
|
||||
private static DatabaseReader database;
|
||||
|
||||
/**
|
||||
* The location of the MaxMind database.
|
||||
*/
|
||||
private final String databaseName = "maxmind.mmdb";
|
||||
|
||||
/**
|
||||
* The MaxMind license key.
|
||||
*/
|
||||
private final String maxMindLicense;
|
||||
|
||||
public MaxMindService(@Value("${maxmind.license}") String maxMindLicense) {
|
||||
this.maxMindLicense = maxMindLicense;
|
||||
if (maxMindLicense.isBlank()) {
|
||||
log.error("The MaxMind license key is not set, please set it in the configuration and try again, disabling the MaxMind service...");
|
||||
return;
|
||||
}
|
||||
|
||||
File databaseFile = loadDatabase();
|
||||
try {
|
||||
database = new DatabaseReader.Builder(databaseFile).build();
|
||||
log.info("Loaded the MaxMind database from '{}'", databaseFile.getAbsolutePath());
|
||||
} catch (Exception ex) {
|
||||
log.error("Failed to load the MaxMind database, please check the configuration and try again", ex);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lookup the GeoIP information for the ip.
|
||||
*
|
||||
* @param ip The query to lookup
|
||||
* @return The GeoIP information
|
||||
*/
|
||||
public static CityResponse lookup(String ip) {
|
||||
if (database == null) { // The database isn't loaded, return null
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return database.city(InetAddress.getByName(ip));
|
||||
} catch (IOException | GeoIp2Exception e) {
|
||||
log.error("Failed to lookup the GeoIP information for '{}'", ip, e);
|
||||
Sentry.captureException(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private File loadDatabase() {
|
||||
File database = new File("data", databaseName);
|
||||
if (database.exists()) {
|
||||
return database;
|
||||
}
|
||||
|
||||
// Ensure the parent directories exist
|
||||
database.getParentFile().mkdirs();
|
||||
|
||||
String downloadUrl = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz";
|
||||
HttpResponse<Path> response = Main.HTTP_CLIENT.send(HttpRequest.newBuilder()
|
||||
.uri(URI.create(downloadUrl.formatted(maxMindLicense)))
|
||||
.build(), HttpResponse.BodyHandlers.ofFile(Files.createTempFile("maxmind", ".tar.gz")));
|
||||
Path downloadedFile = response.body();
|
||||
|
||||
File tempDir = Files.createTempDirectory("maxmind").toFile();
|
||||
|
||||
TarGZipUnArchiver archiver = new TarGZipUnArchiver();
|
||||
archiver.setSourceFile(downloadedFile.toFile());
|
||||
archiver.setDestDirectory(tempDir);
|
||||
archiver.extract();
|
||||
|
||||
File[] files = tempDir.listFiles();
|
||||
if (files == null || files.length == 0) {
|
||||
log.error("Failed to extract the MaxMind database");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
// Search for the database file
|
||||
for (File file : files) {
|
||||
// The database is in a subdirectory
|
||||
if (!file.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get the database file
|
||||
File databaseFile = new File(file, "GeoLite2-City.mmdb");
|
||||
if (!databaseFile.exists()) {
|
||||
log.error("Failed to find the MaxMind database in the extracted files");
|
||||
continue;
|
||||
}
|
||||
Files.copy(databaseFile.toPath(), database.toPath());
|
||||
}
|
||||
|
||||
log.info("Downloaded and extracted the MaxMind database to '{}'", database.getAbsolutePath());
|
||||
return database;
|
||||
}
|
||||
}
|
@ -9,26 +9,23 @@ import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import net.jodah.expiringmap.ExpirationPolicy;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import xyz.mcutils.backend.common.AppConfig;
|
||||
import xyz.mcutils.backend.common.ExpiringSet;
|
||||
import xyz.mcutils.backend.common.MojangServer;
|
||||
import xyz.mcutils.backend.common.WebRequest;
|
||||
import xyz.mcutils.backend.model.cache.CachedEndpointStatus;
|
||||
import xyz.mcutils.backend.model.mojang.EndpointStatus;
|
||||
import xyz.mcutils.backend.model.token.MojangProfileToken;
|
||||
import xyz.mcutils.backend.model.token.MojangUsernameToUuidToken;
|
||||
import xyz.mcutils.backend.repository.redis.EndpointStatusRepository;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service @Log4j2(topic = "Mojang Service") @Getter
|
||||
@Service
|
||||
@Log4j2(topic = "Mojang Service")
|
||||
@Getter
|
||||
public class MojangService {
|
||||
|
||||
/**
|
||||
@ -50,19 +47,9 @@ public class MojangService {
|
||||
private static final long FETCH_BLOCKED_SERVERS_INTERVAL = TimeUnit.HOURS.toMillis(1L);
|
||||
|
||||
/**
|
||||
* Information about the Mojang API endpoints.
|
||||
* The interval to fetch the Mojang server status.
|
||||
*/
|
||||
private static final String MOJANG_ENDPOINT_STATUS_KEY = "mojang";
|
||||
private static final List<EndpointStatus> MOJANG_ENDPOINTS = List.of(
|
||||
new EndpointStatus("Minecraft Textures", "textures.minecraft.net"),
|
||||
new EndpointStatus("Minecraft Libraries", "libraries.minecraft.net"),
|
||||
new EndpointStatus("Minecraft Services", "api.minecraftservices.com"),
|
||||
new EndpointStatus("Mojang Assets", "assets.mojang.com"),
|
||||
new EndpointStatus("Mojang API", "api.mojang.com"),
|
||||
new EndpointStatus("Mojang Session Server", "sessionserver.mojang.com"));
|
||||
|
||||
@Autowired
|
||||
private EndpointStatusRepository mojangEndpointStatusRepository;
|
||||
private static final long FETCH_MOJANG_SERVERS_STATUS_INTERVAL = TimeUnit.MINUTES.toMillis(1L);
|
||||
|
||||
/**
|
||||
* A list of banned server hashes provided by Mojang.
|
||||
@ -82,6 +69,11 @@ public class MojangService {
|
||||
*/
|
||||
private final ExpiringSet<String> blockedServersCache = new ExpiringSet<>(ExpirationPolicy.CREATED, 10L, TimeUnit.MINUTES);
|
||||
|
||||
/**
|
||||
* The status of the Mojang API.
|
||||
*/
|
||||
private final List<Map<String, Object>> mojangServerStatus = new ArrayList<>();
|
||||
|
||||
public MojangService() {
|
||||
new Timer().scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
@ -89,6 +81,33 @@ public class MojangService {
|
||||
fetchBlockedServers();
|
||||
}
|
||||
}, 0L, FETCH_BLOCKED_SERVERS_INTERVAL);
|
||||
|
||||
new Timer().scheduleAtFixedRate(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
log.info("Fetching Mojang Server status...");
|
||||
Map<MojangServer, MojangServer.Status> mojangServers = new HashMap<>();
|
||||
Arrays.stream(MojangServer.values()).parallel().forEach(server -> {
|
||||
log.info("Pinging {}...", server.getEndpoint());
|
||||
MojangServer.Status status = server.getStatus(); // Retrieve the server status
|
||||
log.info("Retrieved status of {}: {}", server.getEndpoint(), status.name());
|
||||
mojangServers.put(server, status); // Cache the server status
|
||||
});
|
||||
|
||||
mojangServerStatus.clear();
|
||||
for (Map.Entry<MojangServer, MojangServer.Status> entry : mojangServers.entrySet()) {
|
||||
MojangServer server = entry.getKey();
|
||||
|
||||
Map<String, Object> serverStatus = new HashMap<>();
|
||||
serverStatus.put("name", server.getName());
|
||||
serverStatus.put("endpoint", server.getEndpoint());
|
||||
serverStatus.put("status", entry.getValue().name());
|
||||
mojangServerStatus.add(serverStatus);
|
||||
}
|
||||
|
||||
log.info("Fetched Mojang Server status for {} endpoints", mojangServers.size());
|
||||
}
|
||||
}, 0L, FETCH_MOJANG_SERVERS_STATUS_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,6 +126,8 @@ public class MojangService {
|
||||
}
|
||||
bannedServerHashes = Collections.synchronizedList(hashes);
|
||||
log.info("Fetched {} banned server hashes", bannedServerHashes.size());
|
||||
} catch (IOException e) {
|
||||
log.error("Failed to fetch blocked servers from Mojang", e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,48 +198,6 @@ public class MojangService {
|
||||
return blocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the status of the Mojang APIs.
|
||||
*
|
||||
* @return the status
|
||||
*/
|
||||
public CachedEndpointStatus getMojangApiStatus() {
|
||||
log.info("Getting Mojang API status");
|
||||
Optional<CachedEndpointStatus> endpointStatus = mojangEndpointStatusRepository.findById(MOJANG_ENDPOINT_STATUS_KEY);
|
||||
if (endpointStatus.isPresent() && AppConfig.isProduction()) {
|
||||
log.info("Got cached Mojang API status");
|
||||
return endpointStatus.get();
|
||||
}
|
||||
|
||||
MOJANG_ENDPOINTS.parallelStream().forEach(endpoint -> {
|
||||
try {
|
||||
long start = System.currentTimeMillis();
|
||||
InetAddress address = InetAddress.getByName(endpoint.getHostname());
|
||||
log.info("gotaddr {}, {}", endpoint.getHostname(), (System.currentTimeMillis() - start) + "ms");
|
||||
if (address.isReachable((int) TimeUnit.SECONDS.toMillis(4))) { // Check if the endpoint is reachable
|
||||
log.info("isreachable {}, {}", endpoint.getHostname(), (System.currentTimeMillis() - start) + "ms");
|
||||
endpoint.setStatus(EndpointStatus.Status.ONLINE);
|
||||
return;
|
||||
}
|
||||
// Check if the endpoint took too long to respond
|
||||
if (System.currentTimeMillis() - start > TimeUnit.SECONDS.toMillis(2)) {
|
||||
endpoint.setStatus(EndpointStatus.Status.DEGRADED);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
endpoint.setStatus(EndpointStatus.Status.OFFLINE);
|
||||
}
|
||||
});
|
||||
|
||||
log.info("Fetched Mojang API status for {} endpoints", MOJANG_ENDPOINTS.size());
|
||||
CachedEndpointStatus status = new CachedEndpointStatus(
|
||||
MOJANG_ENDPOINT_STATUS_KEY,
|
||||
MOJANG_ENDPOINTS
|
||||
);
|
||||
mojangEndpointStatusRepository.save(status);
|
||||
status.getCache().setCached(false);
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Session Server profile of the
|
||||
* player with the given UUID.
|
||||
|
@ -7,6 +7,8 @@ import xyz.mcutils.backend.exception.impl.BadRequestException;
|
||||
import xyz.mcutils.backend.exception.impl.ResourceNotFoundException;
|
||||
import xyz.mcutils.backend.model.dns.DNSRecord;
|
||||
import xyz.mcutils.backend.model.server.BedrockMinecraftServer;
|
||||
import xyz.mcutils.backend.model.server.MinecraftServer;
|
||||
import xyz.mcutils.backend.service.MaxMindService;
|
||||
import xyz.mcutils.backend.service.pinger.MinecraftServerPinger;
|
||||
|
||||
import java.io.IOException;
|
||||
@ -55,7 +57,8 @@ public final class BedrockMinecraftServerPinger implements MinecraftServerPinger
|
||||
if (response == null) { // No pong response
|
||||
throw new ResourceNotFoundException("Server '%s' didn't respond to ping".formatted(hostname));
|
||||
}
|
||||
return BedrockMinecraftServer.create(hostname, ip, port, records, response); // Return the server
|
||||
return BedrockMinecraftServer.create(hostname, ip, port, records,
|
||||
MinecraftServer.GeoLocation.fromMaxMind(MaxMindService.lookup(ip)), response); // Return the server
|
||||
} catch (IOException ex ) {
|
||||
if (ex instanceof UnknownHostException) {
|
||||
throw new BadRequestException("Unknown hostname '%s'".formatted(hostname));
|
||||
|
@ -9,7 +9,9 @@ import xyz.mcutils.backend.exception.impl.BadRequestException;
|
||||
import xyz.mcutils.backend.exception.impl.ResourceNotFoundException;
|
||||
import xyz.mcutils.backend.model.dns.DNSRecord;
|
||||
import xyz.mcutils.backend.model.server.JavaMinecraftServer;
|
||||
import xyz.mcutils.backend.model.server.MinecraftServer;
|
||||
import xyz.mcutils.backend.model.token.JavaServerStatusToken;
|
||||
import xyz.mcutils.backend.service.MaxMindService;
|
||||
import xyz.mcutils.backend.service.pinger.MinecraftServerPinger;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
@ -24,6 +26,13 @@ import java.net.*;
|
||||
public final class JavaMinecraftServerPinger implements MinecraftServerPinger<JavaMinecraftServer> {
|
||||
private static final int TIMEOUT = 1500; // 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(String hostname, String ip, int port, DNSRecord[] records) {
|
||||
log.info("Pinging {}:{}...", hostname, port);
|
||||
@ -43,7 +52,8 @@ public final class JavaMinecraftServerPinger implements MinecraftServerPinger<Ja
|
||||
JavaPacketStatusInStart packetStatusInStart = new JavaPacketStatusInStart();
|
||||
packetStatusInStart.process(inputStream, outputStream);
|
||||
JavaServerStatusToken token = Main.GSON.fromJson(packetStatusInStart.getResponse(), JavaServerStatusToken.class);
|
||||
return JavaMinecraftServer.create(hostname, ip, port, records, token);
|
||||
return JavaMinecraftServer.create(hostname, ip, port, records,
|
||||
MinecraftServer.GeoLocation.fromMaxMind(MaxMindService.lookup(ip)), token);
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
if (ex instanceof UnknownHostException) {
|
||||
|
@ -40,14 +40,22 @@ public abstract class WebSocket extends TextWebSocketHandler {
|
||||
session.sendMessage(new TextMessage(message));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a session connects to the WebSocket.
|
||||
*
|
||||
* @param session the session that connected
|
||||
*/
|
||||
abstract public void onSessionConnect(WebSocketSession session);
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(@NotNull WebSocketSession session) throws Exception {
|
||||
public final void afterConnectionEstablished(@NotNull WebSocketSession session) {
|
||||
this.sessions.add(session);
|
||||
log.info("Connection established: {}", session.getId());
|
||||
this.onSessionConnect(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionClosed(@NotNull WebSocketSession session, @NotNull CloseStatus status) throws Exception {
|
||||
public final void afterConnectionClosed(@NotNull WebSocketSession session, @NotNull CloseStatus status) {
|
||||
this.sessions.remove(session);
|
||||
log.info("Connection closed: {}", session.getId());
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
package xyz.mcutils.backend.websocket.impl;
|
||||
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.springframework.web.socket.TextMessage;
|
||||
import org.springframework.web.socket.WebSocketSession;
|
||||
import xyz.mcutils.backend.Main;
|
||||
import xyz.mcutils.backend.common.Timer;
|
||||
@ -32,8 +30,8 @@ public class MetricsWebSocket extends WebSocket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterConnectionEstablished(@NotNull WebSocketSession session) {
|
||||
sendMetrics(session); // Send metrics to the client when they connect
|
||||
public void onSessionConnect(WebSocketSession session) {
|
||||
sendMetrics(session);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -43,11 +41,11 @@ public class MetricsWebSocket extends WebSocket {
|
||||
*/
|
||||
private void sendMetrics(WebSocketSession session) {
|
||||
try {
|
||||
session.sendMessage(new TextMessage(Main.GSON.toJson(Map.of(
|
||||
this.sendMessage(session, Main.GSON.toJson(Map.of(
|
||||
"totalRequests", metricService.getMetric(TotalRequestsMetric.class).getValue(),
|
||||
"uniqueServerLookups", metricService.getMetric(UniqueServerLookupsMetric.class).getValue(),
|
||||
"uniquePlayerLookups", metricService.getMetric(UniquePlayerLookupsMetric.class).getValue()
|
||||
))));
|
||||
)));
|
||||
} catch (Exception e) {
|
||||
log.error("An error occurred while sending metrics to the client", e);
|
||||
}
|
||||
|
@ -23,9 +23,18 @@ spring:
|
||||
database: test
|
||||
port: 27017
|
||||
|
||||
# Sentry Configuration
|
||||
sentry:
|
||||
dsn: ""
|
||||
|
||||
# The URL of the API
|
||||
public-url: http://localhost
|
||||
|
||||
# MaxMind Configuration
|
||||
# This is used for IP Geolocation
|
||||
maxmind:
|
||||
license: ""
|
||||
|
||||
# InfluxDB Configuration
|
||||
influx:
|
||||
url: http://localhost
|
||||
|
@ -59,7 +59,7 @@ class ServerControllerTests {
|
||||
|
||||
@Test
|
||||
public void ensureServerPreviewLookupSuccess() throws Exception {
|
||||
mockMvc.perform(get("/server/preview/java/" + testServer)
|
||||
mockMvc.perform(get("/server/java/preview/" + testServer)
|
||||
.contentType(MediaType.IMAGE_PNG))
|
||||
.andExpect(status().isOk());
|
||||
}
|
||||
|
Reference in New Issue
Block a user