From 4adb5700c24a3e59dd8ade98d0b4cb5996e31ac6 Mon Sep 17 00:00:00 2001 From: r0-negative <50054605+r0-negative@users.noreply.github.com> Date: Tue, 7 Jun 2022 21:51:44 +0200 Subject: [PATCH] Initial commit --- .gitignore | 4 + pom.xml | 49 +++++++ readme.md | 2 + .../java/net/michel/stattrack/StatTrack.java | 83 +++++++++++ .../michel/stattrack/api/v1/ServerApi.java | 134 ++++++++++++++++++ .../net/michel/stattrack/config/Config.java | 58 ++++++++ .../net/michel/stattrack/main/Bootstrap.java | 13 ++ .../net/michel/stattrack/objects/Server.java | 25 ++++ .../michel/stattrack/utils/StringUtils.java | 14 ++ src/main/resources/config.json | 4 + src/main/resources/public/index.html | 10 ++ 11 files changed, 396 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 readme.md create mode 100644 src/main/java/net/michel/stattrack/StatTrack.java create mode 100644 src/main/java/net/michel/stattrack/api/v1/ServerApi.java create mode 100644 src/main/java/net/michel/stattrack/config/Config.java create mode 100644 src/main/java/net/michel/stattrack/main/Bootstrap.java create mode 100644 src/main/java/net/michel/stattrack/objects/Server.java create mode 100644 src/main/java/net/michel/stattrack/utils/StringUtils.java create mode 100644 src/main/resources/config.json create mode 100644 src/main/resources/public/index.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..688b074 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +# Project exclude paths +/target/ +/StatTrack/ +/.idea/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..caf4e17 --- /dev/null +++ b/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + + net.michel + StatTrack + 1.0-SNAPSHOT + + + 17 + 17 + + + + + io.javalin + javalin + 4.6.1 + + + org.json + json + 20220320 + + + org.projectlombok + lombok + 1.18.24 + + + commons-io + commons-io + 2.11.0 + + + org.slf4j + slf4j-simple + 1.7.36 + + + com.fasterxml.jackson.core + jackson-databind + 2.13.3 + + + + \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..21fa7d3 --- /dev/null +++ b/readme.md @@ -0,0 +1,2 @@ +# StatTrack (WIP) +It's a simple tool to track your minecraft server status. \ No newline at end of file diff --git a/src/main/java/net/michel/stattrack/StatTrack.java b/src/main/java/net/michel/stattrack/StatTrack.java new file mode 100644 index 0000000..229324d --- /dev/null +++ b/src/main/java/net/michel/stattrack/StatTrack.java @@ -0,0 +1,83 @@ +package net.michel.stattrack; + +import io.javalin.Javalin; +import io.javalin.http.staticfiles.Location; +import io.javalin.http.util.RateLimiter; +import lombok.Getter; +import lombok.Setter; +import net.michel.stattrack.api.v1.ServerApi; +import net.michel.stattrack.config.Config; +import net.michel.stattrack.objects.Server; + +import java.util.ArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static io.javalin.apibuilder.ApiBuilder.get; +import static io.javalin.apibuilder.ApiBuilder.path; + +@Getter +@Setter +public class StatTrack { + public static StatTrack instance; + private Config config; + private Javalin javalin; + private final ArrayList servers = new ArrayList<>(); + + public void init() { + instance = this; + this.config = new Config(); + this.javalin = Javalin.create(config -> { + config.addStaticFiles("/public", Location.CLASSPATH); + }); + } + + public void start() { + System.out.println("Loading config..."); + config.init(); + + System.out.println("Starting server..."); + + var rateLimit = new RateLimiter(TimeUnit.MINUTES); + + javalin.before(ctx -> { + if (ctx.path().startsWith("/api")) rateLimit.incrementCounter(ctx, 15); + }); + + javalin.routes(() -> { + path("/api/v1", () -> { + get("serverlist", ServerApi::getServerList); + get("addserver", ServerApi::addServerToList); + get("updateserver", ServerApi::serverUpdate); + get("serverinfo", ServerApi::serverInfo); + }); + }); + + javalin.start(config.getPort()); + + Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { + servers.forEach(server -> { + if (System.currentTimeMillis() - server.getLastUpdate() > TimeUnit.MINUTES.toMillis(10)) { + server.setOnline(false); + System.err.println("Server " + server.getName() + " is not responding"); + //todo: send a webhook to discord + } + }); + }, 5, 15, TimeUnit.MINUTES); + } + + + public boolean serverExists(String name) { + for (Server server : servers) { + if (server.getName().equals(name)) return true; + } + return false; + } + + public Server getServerByName(String name) { + for (Server server : servers) { + if (server.getName().equals(name)) return server; + } + return null; + } +} diff --git a/src/main/java/net/michel/stattrack/api/v1/ServerApi.java b/src/main/java/net/michel/stattrack/api/v1/ServerApi.java new file mode 100644 index 0000000..c6bb4c8 --- /dev/null +++ b/src/main/java/net/michel/stattrack/api/v1/ServerApi.java @@ -0,0 +1,134 @@ +package net.michel.stattrack.api.v1; + +import io.javalin.http.Context; +import net.michel.stattrack.StatTrack; +import net.michel.stattrack.objects.Server; +import org.json.JSONArray; +import org.json.JSONObject; + +public class ServerApi { + + /** + * Get a list of all servers + * + * @param ctx The context of the request. + */ + public static void getServerList(Context ctx) { + var servers = StatTrack.instance.getServers(); + + if (servers.size() == 0) { + responseError(ctx, "server list is empty"); + return; + } + + JSONObject json = new JSONObject(); + JSONArray array = new JSONArray(); + StatTrack.instance.getServers().forEach(server -> array.put(server.getName())); + json.put("servers", array); + + ctx.json(json.toString()); + } + + /** + * Adds a server to the list of servers. + * + * @param ctx The context of the request. + */ + public static void addServerToList(Context ctx) { + if (checkKey(ctx)) { + ctx.status(401); + return; + } + + String name = ctx.queryParamAsClass("name", String.class).getOrDefault(null); + if (name == null || name.isEmpty()) { + responseError(ctx, "name is required"); + return; + } + + if (StatTrack.instance.serverExists(name)) { + responseError(ctx, "server already exists"); + return; + } + + JSONObject json = new JSONObject(); + json.put("success", true); + json.put("name", name); + + var server = new Server(name, true, 0, 0, 0, System.currentTimeMillis()); + StatTrack.instance.getServers().add(server); + ctx.result(json.toString()); + } + + /** + * Updates the server status + * + * @param ctx The context of the request. + */ + public static void serverUpdate(Context ctx) { + if (checkKey(ctx)) { + ctx.status(401); + return; + } + + String name = ctx.queryParamAsClass("name", String.class).getOrDefault(null); + if (name == null || name.isEmpty()) { + responseError(ctx, "name is required"); + return; + } + + Boolean online = ctx.queryParamAsClass("online", Boolean.class).getOrDefault(false); + Integer players = ctx.queryParamAsClass("players", Integer.class).getOrDefault(0); + Integer maxPlayers = ctx.queryParamAsClass("maxPlayers", Integer.class).getOrDefault(0); + Integer ping = ctx.queryParamAsClass("ping", Integer.class).getOrDefault(0); + + var server = StatTrack.instance.getServerByName(name); + if (server == null) { + responseError(ctx, "server does not exist"); + return; + } + + server.updateServer(online, players, maxPlayers, ping); + + JSONObject json = new JSONObject(); + json.put("success", true); + json.put("name", name); + ctx.result(json.toString()); + } + + private static boolean checkKey(Context ctx) { + String key = ctx.queryParamAsClass("key", String.class).getOrDefault(null); + if (key == null || !key.equals(StatTrack.instance.getConfig().getSecretKey())) { + return true; + } + return false; + } + + private static void responseError(Context ctx, String error) { + JSONObject json = new JSONObject(); + json.put("error", error); + ctx.result(json.toString()); + } + + public static void serverInfo(Context context) { + String name = context.queryParamAsClass("name", String.class).getOrDefault(null); + if (name == null || name.isEmpty()) { + responseError(context, "name is required"); + return; + } + + var server = StatTrack.instance.getServerByName(name); + if (server == null) { + responseError(context, "server does not exist"); + return; + } + + JSONObject json = new JSONObject(); + json.put("name", server.getName()); + json.put("online", server.isOnline()); + json.put("players", server.getPlayers()); + json.put("maxPlayers", server.getMaxPlayers()); + json.put("ping", server.getPing()); + context.result(json.toString()); + } +} diff --git a/src/main/java/net/michel/stattrack/config/Config.java b/src/main/java/net/michel/stattrack/config/Config.java new file mode 100644 index 0000000..c51bacc --- /dev/null +++ b/src/main/java/net/michel/stattrack/config/Config.java @@ -0,0 +1,58 @@ +package net.michel.stattrack.config; + +import lombok.Getter; +import lombok.Setter; +import net.michel.stattrack.utils.StringUtils; +import org.apache.commons.io.IOUtils; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Objects; + +@Getter +@Setter +public class Config { + private JSONObject json; + private File configFile; + private String secretKey; + private int port; + + public void init() { + try { + configFile = new File("StatTrack/config.json"); + if (!configFile.exists()) { + configFile.getParentFile().mkdirs(); + Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/config.json")), configFile.toPath()); + } + + InputStream inputStream = new FileInputStream(configFile); + String jsonTxt = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + json = new JSONObject(jsonTxt); + + + this.secretKey = json.getString("secretKey"); + if (this.secretKey.isEmpty()) { + this.secretKey = StringUtils.generateRandomString(32); + saveField("secretKey", this.secretKey); + } + + this.port = json.getInt("port"); + if (this.port == 0) { + this.port = 8080; + saveField("port", this.port); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void saveField(String key, Object value) throws IOException { + json.put(key, value); + Files.write(configFile.toPath(), json.toString(4).getBytes()); + } +} diff --git a/src/main/java/net/michel/stattrack/main/Bootstrap.java b/src/main/java/net/michel/stattrack/main/Bootstrap.java new file mode 100644 index 0000000..02b112c --- /dev/null +++ b/src/main/java/net/michel/stattrack/main/Bootstrap.java @@ -0,0 +1,13 @@ +package net.michel.stattrack.main; + +import net.michel.stattrack.StatTrack; + +public class Bootstrap { + + public static void main(String[] args) { + StatTrack statTrack = new StatTrack(); + statTrack.init(); + statTrack.start(); + } + +} diff --git a/src/main/java/net/michel/stattrack/objects/Server.java b/src/main/java/net/michel/stattrack/objects/Server.java new file mode 100644 index 0000000..61d708b --- /dev/null +++ b/src/main/java/net/michel/stattrack/objects/Server.java @@ -0,0 +1,25 @@ +package net.michel.stattrack.objects; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +@AllArgsConstructor +public class Server { + private String name; + private boolean online; + private int players; + private int maxPlayers; + private int ping; + private long lastUpdate; + + public void updateServer(boolean online, int players, int maxPlayers, int ping) { + this.online = online; + this.players = players; + this.maxPlayers = maxPlayers; + this.ping = ping; + this.lastUpdate = System.currentTimeMillis(); + } +} diff --git a/src/main/java/net/michel/stattrack/utils/StringUtils.java b/src/main/java/net/michel/stattrack/utils/StringUtils.java new file mode 100644 index 0000000..8ac7e43 --- /dev/null +++ b/src/main/java/net/michel/stattrack/utils/StringUtils.java @@ -0,0 +1,14 @@ +package net.michel.stattrack.utils; + +public class StringUtils { + + public static String generateRandomString(int length) { + String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + StringBuilder result = new StringBuilder(); + for (int i = 0; i < length; i++) { + int index = (int) (Math.random() * characters.length()); + result.append(characters.charAt(index)); + } + return result.toString(); + } +} diff --git a/src/main/resources/config.json b/src/main/resources/config.json new file mode 100644 index 0000000..005d091 --- /dev/null +++ b/src/main/resources/config.json @@ -0,0 +1,4 @@ +{ + "secretKey": "", + "port": 0 +} \ No newline at end of file diff --git a/src/main/resources/public/index.html b/src/main/resources/public/index.html new file mode 100644 index 0000000..8ca86bc --- /dev/null +++ b/src/main/resources/public/index.html @@ -0,0 +1,10 @@ + + + + + StatTrack + + +hi + + \ No newline at end of file