Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
r0-negative committed Jun 7, 2022
0 parents commit 4adb570
Show file tree
Hide file tree
Showing 11 changed files with 396 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Project exclude paths
/target/
/StatTrack/
/.idea/
49 changes: 49 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>net.michel</groupId>
<artifactId>StatTrack</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>io.javalin</groupId>
<artifactId>javalin</artifactId>
<version>4.6.1</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20220320</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.36</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
</dependencies>

</project>
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# StatTrack (WIP)
It's a simple tool to track your minecraft server status.
83 changes: 83 additions & 0 deletions src/main/java/net/michel/stattrack/StatTrack.java
Original file line number Diff line number Diff line change
@@ -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<Server> 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;
}
}
134 changes: 134 additions & 0 deletions src/main/java/net/michel/stattrack/api/v1/ServerApi.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
58 changes: 58 additions & 0 deletions src/main/java/net/michel/stattrack/config/Config.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
13 changes: 13 additions & 0 deletions src/main/java/net/michel/stattrack/main/Bootstrap.java
Original file line number Diff line number Diff line change
@@ -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();
}

}
25 changes: 25 additions & 0 deletions src/main/java/net/michel/stattrack/objects/Server.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
14 changes: 14 additions & 0 deletions src/main/java/net/michel/stattrack/utils/StringUtils.java
Original file line number Diff line number Diff line change
@@ -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();
}
}
4 changes: 4 additions & 0 deletions src/main/resources/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"secretKey": "",
"port": 0
}
Loading

0 comments on commit 4adb570

Please sign in to comment.