Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group = net.earthmc.emcapi
version = 3.0.0-SNAPSHOT
version = 3.1.0-SNAPSHOT
description = EMCAPI

org.gradle.configuration-cache=true
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/net/earthmc/emcapi/EMCAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import jakarta.servlet.http.HttpServletResponse;
import net.earthmc.emcapi.integration.Integrations;
import net.earthmc.emcapi.manager.EndpointManager;
import net.earthmc.emcapi.sse.SSEManager;
import net.earthmc.emcapi.sse.listeners.TownySSEListeners;
import net.earthmc.emcapi.util.EndpointUtils;
import net.earthmc.emcapi.command.OptOutCommand;
import org.bukkit.command.PluginCommand;
Expand All @@ -25,6 +27,7 @@ public final class EMCAPI extends JavaPlugin {
public static EMCAPI instance;
private Javalin javalin;
private Integrations pluginIntegrations;
private SSEManager sseManager;

@Override
public void onLoad() {
Expand Down Expand Up @@ -58,6 +61,10 @@ public void onEnable() {
} catch (IOException e) {
getLogger().warning("IOException while loading opted-out players: " + e);
}

sseManager = new SSEManager(this);
sseManager.loadSSE();
getServer().getPluginManager().registerEvents(new TownySSEListeners(sseManager), this);
}

@Override
Expand Down Expand Up @@ -120,4 +127,9 @@ public Javalin getJavalin() {
public Integrations integrations() {
return this.pluginIntegrations;
}

public String getURLPath() {
String version = getConfig().getString("networking.api_version", "3");
return "v" + version + "/" + getConfig().getString("networking.url_path");
}
}
34 changes: 17 additions & 17 deletions src/main/java/net/earthmc/emcapi/manager/EndpointManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,23 +24,23 @@ public class EndpointManager {

private final EMCAPI plugin;
private final Javalin javalin;
private final String v3URLPath;
private final String URLPath;

public EndpointManager(EMCAPI plugin) {
this.plugin = plugin;
this.javalin = plugin.getJavalin();
this.v3URLPath = "v3/" + plugin.getConfig().getString("networking.url_path");
this.URLPath = plugin.getURLPath();
}

public void loadEndpoints() {
DocumentationEndpoint documentationEndpoint = new DocumentationEndpoint();
javalin.get("/", ctx -> ctx.json(documentationEndpoint.lookup()));

ServerEndpoint serverEndpoint = new ServerEndpoint(plugin);
javalin.get(v3URLPath, ctx -> ctx.json(serverEndpoint.lookup()));
javalin.get(URLPath, ctx -> ctx.json(serverEndpoint.lookup()));

MysteryMasterEndpoint mysteryMasterEndpoint = new MysteryMasterEndpoint(plugin);
javalin.get(v3URLPath + "/mm", ctx -> {
javalin.get(URLPath + "/mm", ctx -> {
plugin.integrations().mysteryMasterIntegration().throwIfDisabled();
ctx.json(mysteryMasterEndpoint.lookup());
});
Expand Down Expand Up @@ -80,32 +80,32 @@ private Pair<JsonArray, JsonObject> parseBody(String body) {

private void loadPlayersEndpoint() {
PlayersListEndpoint ple = new PlayersListEndpoint();
javalin.get(v3URLPath + "/players", ctx -> ctx.json(ple.lookup()));
javalin.get(URLPath + "/players", ctx -> ctx.json(ple.lookup()));

PlayersEndpoint playersEndpoint = new PlayersEndpoint();
javalin.post(v3URLPath + "/players", ctx -> {
javalin.post(URLPath + "/players", ctx -> {
Pair<JsonArray, JsonObject> parsedBody = parseBody(ctx.body());
ctx.json(playersEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond()));
});
}

private void loadTownsEndpoint() {
TownsListEndpoint tle = new TownsListEndpoint();
javalin.get(v3URLPath + "/towns", ctx -> ctx.json(tle.lookup()));
javalin.get(URLPath + "/towns", ctx -> ctx.json(tle.lookup()));

TownsEndpoint townsEndpoint = new TownsEndpoint(plugin);
javalin.post(v3URLPath + "/towns", ctx -> {
javalin.post(URLPath + "/towns", ctx -> {
Pair<JsonArray, JsonObject> parsedBody = parseBody(ctx.body());
ctx.json(townsEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond()));
});
}

private void loadNationsEndpoint() {
NationsListEndpoint nle = new NationsListEndpoint();
javalin.get(v3URLPath + "/nations", ctx -> ctx.json(nle.lookup()));
javalin.get(URLPath + "/nations", ctx -> ctx.json(nle.lookup()));

NationsEndpoint nationsEndpoint = new NationsEndpoint();
javalin.post(v3URLPath + "/nations", ctx -> {
javalin.post(URLPath + "/nations", ctx -> {
Pair<JsonArray, JsonObject> parsedBody = parseBody(ctx.body());
ctx.json(nationsEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond()));
});
Expand All @@ -115,13 +115,13 @@ private void loadQuartersEndpoint() {
QuartersIntegration quartersIntegration = plugin.integrations().quartersIntegration();
QuartersListEndpoint qle = new QuartersListEndpoint(quartersIntegration);

javalin.get(v3URLPath + "/quarters", ctx -> {
javalin.get(URLPath + "/quarters", ctx -> {
quartersIntegration.throwIfDisabled();
ctx.json(qle.lookup());
});

QuartersEndpoint quartersEndpoint = new QuartersEndpoint();
javalin.post(v3URLPath + "/quarters", ctx -> {
javalin.post(URLPath + "/quarters", ctx -> {
quartersIntegration.throwIfDisabled();
Pair<JsonArray, JsonObject> parsedBody = parseBody(ctx.body());
ctx.json(quartersEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond()));
Expand All @@ -130,15 +130,15 @@ private void loadQuartersEndpoint() {

private void loadLocationEndpoint() {
LocationEndpoint locationEndpoint = new LocationEndpoint();
javalin.post(v3URLPath + "/location", ctx -> {
javalin.post(URLPath + "/location", ctx -> {
Pair<JsonArray, JsonObject> parsedBody = parseBody(ctx.body());
ctx.json(locationEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond()));
});
}

private void loadNearbyEndpoint() {
NearbyEndpoint nearbyEndpoint = new NearbyEndpoint();
javalin.post(v3URLPath + "/nearby", ctx -> {
javalin.post(URLPath + "/nearby", ctx -> {
Pair<JsonArray, JsonObject> parsedBody = parseBody(ctx.body());
ctx.json(nearbyEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond()));
});
Expand All @@ -148,7 +148,7 @@ private void loadDiscordEndpoint() {
DiscordEndpoint discordEndpoint = new DiscordEndpoint();
final DiscordIntegration discordIntegration = plugin.integrations().discordIntegration();

javalin.post(v3URLPath + "/discord", ctx -> {
javalin.post(URLPath + "/discord", ctx -> {
discordIntegration.throwIfDisabled();

Pair<JsonArray, JsonObject> parsedBody = parseBody(ctx.body());
Expand All @@ -159,14 +159,14 @@ private void loadDiscordEndpoint() {
private void loadPlayerStatsEndpoint() {
PlayerStatsEndpoint playerStatsEndpoint = new PlayerStatsEndpoint(this.plugin);
playerStatsEndpoint.initialize();
javalin.get(v3URLPath + "/player-stats", ctx -> {
javalin.get(URLPath + "/player-stats", ctx -> {
ctx.json(playerStatsEndpoint.latestCachedStatistics());
});
}

private void loadOnlinePlayersEndpoint() {
OnlineEndpoint onlineEndpoint = new OnlineEndpoint();
javalin.get(v3URLPath + "/online", ctx -> {
javalin.get(URLPath + "/online", ctx -> {
ctx.json(onlineEndpoint.lookup());
});
}
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/net/earthmc/emcapi/sse/SSEManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package net.earthmc.emcapi.sse;

import com.google.gson.JsonObject;
import io.javalin.Javalin;
import io.javalin.http.sse.SseClient;
import net.earthmc.emcapi.EMCAPI;

import java.time.Instant;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class SSEManager {
private final EMCAPI plugin;
private final Javalin javalin;
private static final Set<SseClient> clients = ConcurrentHashMap.newKeySet();

public SSEManager(EMCAPI plugin) {
this.plugin = plugin;
this.javalin = plugin.getJavalin();
}

public void loadSSE() {
javalin.sse(plugin.getURLPath() + "/events", client -> {
client.keepAlive();
client.sendEvent("open", "Connected to the EarthMC API.");
client.onClose(() -> clients.remove(client));
clients.add(client);
});
}

public void sendEvent(String event, JsonObject data) {
long timestamp = Instant.now().getEpochSecond();
data.addProperty("timestamp", timestamp);
String message = data.toString();

for (SseClient client : clients) {
plugin.getServer().getAsyncScheduler().runNow(plugin, t -> client.sendEvent(event, message));
}
}
}
146 changes: 146 additions & 0 deletions src/main/java/net/earthmc/emcapi/sse/listeners/TownySSEListeners.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package net.earthmc.emcapi.sse.listeners;

import com.google.gson.JsonObject;
import com.palmergames.bukkit.towny.event.DeleteTownEvent;
import com.palmergames.bukkit.towny.event.NewDayEvent;
import com.palmergames.bukkit.towny.event.NewNationEvent;
import com.palmergames.bukkit.towny.event.NewTownEvent;
import com.palmergames.bukkit.towny.event.PreDeleteNationEvent;
import com.palmergames.bukkit.towny.event.RenameNationEvent;
import com.palmergames.bukkit.towny.event.RenameTownEvent;
import com.palmergames.bukkit.towny.event.nation.NationKingChangeEvent;
import com.palmergames.bukkit.towny.event.nation.NationMergeEvent;
import com.palmergames.bukkit.towny.event.town.TownMayorChangedEvent;
import com.palmergames.bukkit.towny.event.town.TownMergeEvent;
import com.palmergames.bukkit.towny.event.town.TownPreRuinedEvent;
import com.palmergames.bukkit.towny.event.town.TownReclaimedEvent;
import com.palmergames.bukkit.towny.object.Nation;
import net.earthmc.emcapi.sse.SSEManager;
import net.earthmc.emcapi.util.EndpointUtils;
import net.earthmc.emcapi.util.JSONUtil;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;

public class TownySSEListeners implements Listener {
private final SSEManager sse;

public TownySSEListeners(SSEManager sse) {
this.sse = sse;
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onNewDay(NewDayEvent event) {
JsonObject message = new JsonObject();
message.add("fallenTowns", JSONUtil.getJsonArrayFromStringList(event.getFallenTowns()));
message.add("fallenNations", JSONUtil.getJsonArrayFromStringList(event.getFallenNations()));
sse.sendEvent("NewDay", message);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onNewNation(NewNationEvent event) {
JsonObject message = new JsonObject();
Nation nation = event.getNation();
message.add("nation", EndpointUtils.getNationJsonObject(nation));
message.add("king", EndpointUtils.getResidentJsonObject(nation.getKing()));
message.add("capital", EndpointUtils.getTownJsonObject(nation.getCapital()));
sse.sendEvent("NationCreated", message);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onDeleteNation(PreDeleteNationEvent event) {
JsonObject message = new JsonObject();
Nation nation = event.getNation();
message.add("nation", EndpointUtils.generateNameUUIDJsonObject(nation.getName(), nation.getUUID()));
message.add("king", EndpointUtils.getResidentJsonObject(nation.getKing()));
message.add("capital", EndpointUtils.getTownJsonObject(nation.getCapital()));
sse.sendEvent("NationDeleted", message);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onRenameNation(RenameNationEvent event) {
JsonObject message = new JsonObject();
message.add("nation", EndpointUtils.getNationJsonObject(event.getNation()));
message.addProperty("oldName", event.getOldName());
sse.sendEvent("NationRenamed", message);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onNationKingChange(NationKingChangeEvent event) {
JsonObject message = new JsonObject();
message.add("nation", EndpointUtils.getNationJsonObject(event.getNation()));
message.add("newKing", EndpointUtils.getResidentJsonObject(event.getNewKing()));
message.add("oldKing", EndpointUtils.getResidentJsonObject(event.getOldKing()));
message.addProperty("isCapitalChange", event.isCapitalChange());
if (event.isCapitalChange()) {
message.add("newCapital", EndpointUtils.getTownJsonObject(event.getNewKing().getTownOrNull()));
message.add("oldCapital", EndpointUtils.getTownJsonObject(event.getOldKing().getTownOrNull()));
}
sse.sendEvent("NationKingChanged", message);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onNationMerge(NationMergeEvent event) {
JsonObject message = new JsonObject();
message.add("oldNation", EndpointUtils.getNationJsonObject(event.getNation()));
message.add("remainingNation", EndpointUtils.getNationJsonObject(event.getRemainingnation()));
sse.sendEvent("NationMerged", message);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onNewTown(NewTownEvent event) {
JsonObject message = new JsonObject();
message.add("town", EndpointUtils.getTownJsonObject(event.getTown()));
message.add("mayor", EndpointUtils.getResidentJsonObject(event.getTown().getMayor()));
sse.sendEvent("TownCreated", message);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onDeleteTown(DeleteTownEvent event) {
JsonObject message = new JsonObject();
message.add("town", EndpointUtils.generateNameUUIDJsonObject(event.getTownName(), event.getTownUUID()));
message.add("mayor", EndpointUtils.getResidentJsonObject(event.getMayor()));
sse.sendEvent("TownDeleted", message);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onRenameTown(RenameTownEvent event) {
JsonObject message = new JsonObject();
message.add("town", EndpointUtils.getTownJsonObject(event.getTown()));
message.addProperty("oldName", event.getOldName());
sse.sendEvent("TownRenamed", message);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTownMayorChanged(TownMayorChangedEvent event) {
JsonObject message = new JsonObject();
message.add("town", EndpointUtils.getTownJsonObject(event.getTown()));
message.add("newMayor", EndpointUtils.getResidentJsonObject(event.getNewMayor()));
message.add("oldMayor", EndpointUtils.getResidentJsonObject(event.getOldMayor()));
sse.sendEvent("TownMayorChanged", message);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTownMerge(TownMergeEvent event) {
JsonObject message = new JsonObject();
message.add("oldTown", EndpointUtils.generateNameUUIDJsonObject(event.getSuccumbingTownName(), event.getSuccumbingTownUUID()));
message.add("remainingTown", EndpointUtils.getTownJsonObject(event.getRemainingTown()));
sse.sendEvent("TownMerged", message);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTownRuined(TownPreRuinedEvent event) {
JsonObject message = new JsonObject();
message.add("town", EndpointUtils.getTownJsonObject(event.getTown()));
message.add("oldMayor", EndpointUtils.getResidentJsonObject(event.getTown().getMayor()));
sse.sendEvent("TownRuined", message);
}

@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onTownReclaimed(TownReclaimedEvent event) {
JsonObject message = new JsonObject();
message.add("town", EndpointUtils.getTownJsonObject(event.getTown()));
message.add("newMayor", EndpointUtils.getResidentJsonObject(event.getResident()));
sse.sendEvent("TownReclaimed", message);
}
}
7 changes: 7 additions & 0 deletions src/main/java/net/earthmc/emcapi/util/EndpointUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,11 @@ public static void saveOptOut(Path path) throws IOException {

Files.write(path.resolve(optOutFile), lines, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}

public static JsonObject generateNameUUIDJsonObject(String name, UUID uuid) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("name", name);
jsonObject.addProperty("uuid", uuid.toString());
return jsonObject;
}
}
12 changes: 12 additions & 0 deletions src/main/java/net/earthmc/emcapi/util/JSONUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.google.gson.*;
import io.javalin.http.BadRequestResponse;

import java.util.List;

public class JSONUtil {

public static JsonObject getJsonObjectFromString(String string) {
Expand All @@ -13,6 +15,16 @@ public static JsonObject getJsonObjectFromString(String string) {
}
}

public static JsonArray getJsonArrayFromStringList(List<String> stringList) {
JsonArray jsonArray = new JsonArray();
if (stringList == null) return jsonArray;

for (String item : stringList) {
jsonArray.add(item);
}
return jsonArray;
}

public static String getJsonElementAsStringOrNull(JsonElement element) {
if (element == null) return null;

Expand Down
Loading