From 5c98183e889cb8666096b6970d0f45b15162448a Mon Sep 17 00:00:00 2001 From: MrToirol <84342903+MrToirol@users.noreply.github.com> Date: Sun, 28 Jul 2024 20:17:45 +0200 Subject: [PATCH 1/6] Add SSE --- src/main/java/net/earthmc/emcapi/EMCAPI.java | 11 +++- .../emcapi/listeners/TownyListeners.java | 49 ++++++++++++++++++ .../earthmc/emcapi/manager/SSEManager.java | 50 +++++++++++++++++++ .../earthmc/emcapi/util/EndpointUtils.java | 8 +++ 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/earthmc/emcapi/listeners/TownyListeners.java create mode 100644 src/main/java/net/earthmc/emcapi/manager/SSEManager.java diff --git a/src/main/java/net/earthmc/emcapi/EMCAPI.java b/src/main/java/net/earthmc/emcapi/EMCAPI.java index 91fa7ad..395edd7 100644 --- a/src/main/java/net/earthmc/emcapi/EMCAPI.java +++ b/src/main/java/net/earthmc/emcapi/EMCAPI.java @@ -4,7 +4,9 @@ import io.javalin.util.JavalinLogger; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import net.earthmc.emcapi.listeners.TownyListeners; import net.earthmc.emcapi.manager.EndpointManager; +import net.earthmc.emcapi.manager.SSEManager; import net.milkbowl.vault.economy.Economy; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; @@ -42,13 +44,20 @@ public void onEnable() { if (getConfig().getBoolean("behaviour.enable_legacy_endpoints")) endpointManager.loadLegacyEndpoints(); + + SSEManager sseManager = new SSEManager(javalin, economy); + sseManager.loadSSE(); + + getServer().getPluginManager().registerEvents(new TownyListeners(), this); + } @Override public void onDisable() { + //sseManager.stop(); Should I add this or is it enough to simply shut down javalin? javalin.stop(); } - + private void initialiseJavalin() { javalin = Javalin.create(config -> { config.jetty.modifyServer(server -> { diff --git a/src/main/java/net/earthmc/emcapi/listeners/TownyListeners.java b/src/main/java/net/earthmc/emcapi/listeners/TownyListeners.java new file mode 100644 index 0000000..07166b0 --- /dev/null +++ b/src/main/java/net/earthmc/emcapi/listeners/TownyListeners.java @@ -0,0 +1,49 @@ +package net.earthmc.emcapi.listeners; + +import com.google.gson.JsonObject; +import com.palmergames.bukkit.towny.event.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +import net.earthmc.emcapi.manager.SSEManager; +import net.earthmc.emcapi.util.EndpointUtils; + + +public class TownyListeners implements Listener { + + // Will add more events later + + @EventHandler + public void onNewNation(NewNationEvent event) { + JsonObject message = new JsonObject(); + message.add("nation", EndpointUtils.getNationJsonObject(event.getNation())); + message.add("king", EndpointUtils.getResidentJsonObject(event.getNation().getKing())); + SSEManager.broadcastMessage("NewNation", message); + } + + @EventHandler + public void onNationDelete(DeleteNationEvent event) { + JsonObject message = new JsonObject(); + message.add("nation", EndpointUtils.generateNameUUIDJsonObject(event.getNationName(), event.getNationUUID())); + message.add("king", EndpointUtils.getResidentJsonObject(event.getLeader())); + SSEManager.broadcastMessage("DeleteNation", message); + } + + + @EventHandler + public void onNewTown(NewTownEvent event) { + JsonObject message = new JsonObject(); + message.add("town", EndpointUtils.getTownJsonObject(event.getTown())); + message.add("mayor", EndpointUtils.getResidentJsonObject(event.getTown().getMayor())); + SSEManager.broadcastMessage("NewTown", message); + } + + @EventHandler + 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())); + SSEManager.broadcastMessage("DeleteTown", message); + } + +} diff --git a/src/main/java/net/earthmc/emcapi/manager/SSEManager.java b/src/main/java/net/earthmc/emcapi/manager/SSEManager.java new file mode 100644 index 0000000..58d53c3 --- /dev/null +++ b/src/main/java/net/earthmc/emcapi/manager/SSEManager.java @@ -0,0 +1,50 @@ +package net.earthmc.emcapi.manager; + +import com.google.gson.JsonObject; +import io.javalin.Javalin; +import io.javalin.http.sse.SseClient; +import net.milkbowl.vault.economy.Economy; + +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + + +public class SSEManager { + + private final Javalin javalin; + private final Economy economy; + private final static Queue clients = new ConcurrentLinkedQueue<>(); + + public SSEManager(Javalin javalin, Economy economy) { + this.javalin = javalin; + this.economy = economy; + } + + public void loadSSE() { + javalin.sse("/events", client -> { + client.keepAlive(); + client.sendEvent("connected", "Connected to the EarthMC API."); + client.onClose(() -> clients.remove(client)); + clients.add(client); + }); + + // Testing purposes + javalin.get("/broadcast", ctx -> { + JsonObject message = new JsonObject(); + message.addProperty("text", "Hello to all connected clients!"); + broadcastMessage("Hello", message); + ctx.result("Message broadcasted to all %s clients".formatted(clients.size())); + }); + // Testing purposes + } + + public static void broadcastMessage(String type, JsonObject data) { + int timestamp = Math.toIntExact(System.currentTimeMillis() / 1000); + data.addProperty("timestamp", timestamp); + + for (SseClient client : clients) { + client.sendEvent(type, data.toString()); + } + } +} + diff --git a/src/main/java/net/earthmc/emcapi/util/EndpointUtils.java b/src/main/java/net/earthmc/emcapi/util/EndpointUtils.java index e3527bc..bd7f004 100644 --- a/src/main/java/net/earthmc/emcapi/util/EndpointUtils.java +++ b/src/main/java/net/earthmc/emcapi/util/EndpointUtils.java @@ -16,6 +16,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.UUID; public class EndpointUtils { @@ -164,4 +165,11 @@ public static JsonObject getQuarterObject(Quarter quarter) { return jsonObject; } + + public static JsonObject generateNameUUIDJsonObject(String name, UUID uuid) { + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("name", name); + jsonObject.addProperty("uuid", uuid.toString()); + return jsonObject; + } } From 081b2d656e36fdd902f4c2ca1dc98aebfe318727 Mon Sep 17 00:00:00 2001 From: MrToirol <84342903+MrToirol@users.noreply.github.com> Date: Sat, 3 Aug 2024 17:07:28 +0200 Subject: [PATCH 2/6] Add more SSE events --- docs/discord.md | 2 +- docs/events.md | 32 ++++++ src/main/java/net/earthmc/emcapi/EMCAPI.java | 4 +- .../listeners/PlayerConnectionListener.java | 32 ++++++ .../emcapi/listeners/TownyListeners.java | 99 ++++++++++++++++++- .../earthmc/emcapi/manager/SSEManager.java | 9 +- .../net/earthmc/emcapi/util/JSONUtil.java | 12 +++ 7 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 docs/events.md create mode 100644 src/main/java/net/earthmc/emcapi/listeners/PlayerConnectionListener.java diff --git a/docs/discord.md b/docs/discord.md index 2fa9f72..38f8586 100644 --- a/docs/discord.md +++ b/docs/discord.md @@ -1,5 +1,5 @@ # Discord Endpoint -Accessed at https://api.earthmc.net/v3/aurora/discord?query= +Accessed at https://api.earthmc.net/v3/aurora/discord Example POST request ```json5 diff --git a/docs/events.md b/docs/events.md new file mode 100644 index 0000000..0ca17af --- /dev/null +++ b/docs/events.md @@ -0,0 +1,32 @@ +# Server-Sent Events (SSE) Endpoint +Accessed at https://api.earthmc.net/v3/aurora/events + +Use this endpoint as an [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) to receive live events. + +You can easily connect to the event stream from your terminal using + +````bash +curl -H "Accept:text/event-steam" "https://api.earthmc.net/v3/aurora/events" +```` + + +Example `NewNation` event +```json5 +{ + "event": "NewNation", + + "data": { + "nation": { + "name": "Guinea", + "uuid": "6a458663-16ff-49c9-a27e-3ad5b3b9caf5" + }, + + "king": { + "name": "Czipsu35", + "uuid": "12a19eee-6539-4634-89bc-4398ab8de870" + }, + + "timestamp": "1656352097939" + } +} +``` \ No newline at end of file diff --git a/src/main/java/net/earthmc/emcapi/EMCAPI.java b/src/main/java/net/earthmc/emcapi/EMCAPI.java index 395edd7..d4b1faa 100644 --- a/src/main/java/net/earthmc/emcapi/EMCAPI.java +++ b/src/main/java/net/earthmc/emcapi/EMCAPI.java @@ -4,6 +4,7 @@ import io.javalin.util.JavalinLogger; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import net.earthmc.emcapi.listeners.PlayerConnectionListener; import net.earthmc.emcapi.listeners.TownyListeners; import net.earthmc.emcapi.manager.EndpointManager; import net.earthmc.emcapi.manager.SSEManager; @@ -45,10 +46,11 @@ public void onEnable() { if (getConfig().getBoolean("behaviour.enable_legacy_endpoints")) endpointManager.loadLegacyEndpoints(); - SSEManager sseManager = new SSEManager(javalin, economy); + SSEManager sseManager = new SSEManager(javalin); sseManager.loadSSE(); getServer().getPluginManager().registerEvents(new TownyListeners(), this); + getServer().getPluginManager().registerEvents(new PlayerConnectionListener(), this); } diff --git a/src/main/java/net/earthmc/emcapi/listeners/PlayerConnectionListener.java b/src/main/java/net/earthmc/emcapi/listeners/PlayerConnectionListener.java new file mode 100644 index 0000000..1c20211 --- /dev/null +++ b/src/main/java/net/earthmc/emcapi/listeners/PlayerConnectionListener.java @@ -0,0 +1,32 @@ +package net.earthmc.emcapi.listeners; + +import com.google.gson.JsonObject; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import net.earthmc.emcapi.util.EndpointUtils; +import net.earthmc.emcapi.manager.SSEManager; + + +public class PlayerConnectionListener implements Listener { + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + JsonObject message = new JsonObject(); + message.add("player", EndpointUtils.generateNameUUIDJsonObject(player.getName(), player.getUniqueId())); + SSEManager.broadcastMessage("PlayerJoin", message); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + Player player = event.getPlayer(); + JsonObject message = new JsonObject(); + message.add("player", EndpointUtils.generateNameUUIDJsonObject(player.getName(), player.getUniqueId())); + SSEManager.broadcastMessage("PlayerQuit", message); + } + +} diff --git a/src/main/java/net/earthmc/emcapi/listeners/TownyListeners.java b/src/main/java/net/earthmc/emcapi/listeners/TownyListeners.java index 07166b0..e755313 100644 --- a/src/main/java/net/earthmc/emcapi/listeners/TownyListeners.java +++ b/src/main/java/net/earthmc/emcapi/listeners/TownyListeners.java @@ -2,16 +2,27 @@ import com.google.gson.JsonObject; import com.palmergames.bukkit.towny.event.*; +import com.palmergames.bukkit.towny.event.nation.*; +import com.palmergames.bukkit.towny.event.town.*; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import net.earthmc.emcapi.manager.SSEManager; import net.earthmc.emcapi.util.EndpointUtils; +import net.earthmc.emcapi.util.JSONUtil; public class TownyListeners implements Listener { - // Will add more events later + @EventHandler + public void onNewDay(NewDayEvent event) { + JsonObject message = new JsonObject(); + message.add("fallenTowns", JSONUtil.getJsonArrayFromStringList(event.getFallenTowns())); + message.add("fallenNations", JSONUtil.getJsonArrayFromStringList(event.getFallenNations())); + SSEManager.broadcastMessage("NewDay", message); + } + + @EventHandler public void onNewNation(NewNationEvent event) { @@ -22,13 +33,48 @@ public void onNewNation(NewNationEvent event) { } @EventHandler - public void onNationDelete(DeleteNationEvent event) { + public void onDeleteNation(DeleteNationEvent event) { JsonObject message = new JsonObject(); message.add("nation", EndpointUtils.generateNameUUIDJsonObject(event.getNationName(), event.getNationUUID())); message.add("king", EndpointUtils.getResidentJsonObject(event.getLeader())); SSEManager.broadcastMessage("DeleteNation", message); } + @EventHandler + public void onRenameNation(RenameNationEvent event) { + JsonObject message = new JsonObject(); + message.add("nation", EndpointUtils.getNationJsonObject(event.getNation())); + message.addProperty("oldName", event.getOldName()); + SSEManager.broadcastMessage("RenameNation", message); + } + + @EventHandler + 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()); + SSEManager.broadcastMessage("NationKingChange", message); + } + + @EventHandler + public void onNationAddTown(NationAddTownEvent event) { + JsonObject message = new JsonObject(); + message.add("nation", EndpointUtils.getNationJsonObject(event.getNation())); + message.add("town", EndpointUtils.getTownJsonObject(event.getTown())); + SSEManager.broadcastMessage("NationAddTown", message); + } + + @EventHandler + public void onNationRemoveTown(NationRemoveTownEvent event) { + JsonObject message = new JsonObject(); + message.add("nation", EndpointUtils.getNationJsonObject(event.getNation())); + message.add("town", EndpointUtils.getTownJsonObject(event.getTown())); + SSEManager.broadcastMessage("NationRemoveTown", message); + } + + @EventHandler public void onNewTown(NewTownEvent event) { @@ -46,4 +92,53 @@ public void onDeleteTown(DeleteTownEvent event) { SSEManager.broadcastMessage("DeleteTown", message); } + @EventHandler + public void onRenameTown(RenameTownEvent event) { + JsonObject message = new JsonObject(); + message.add("town", EndpointUtils.getTownJsonObject(event.getTown())); + message.addProperty("oldName", event.getOldName()); + SSEManager.broadcastMessage("RenameTown", message); + } + + @EventHandler + 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())); + SSEManager.broadcastMessage("TownMayorChange", message); + } + + @EventHandler + public void onTownRuined(TownRuinedEvent event) { + JsonObject message = new JsonObject(); + message.add("town", EndpointUtils.getTownJsonObject(event.getTown())); + message.addProperty("oldMayor", event.getOldMayorName()); + SSEManager.broadcastMessage("TownRuined", message); + } + + @EventHandler + public void onTownReclaimed(TownReclaimedEvent event) { + JsonObject message = new JsonObject(); + message.add("town", EndpointUtils.getTownJsonObject(event.getTown())); + message.add("newMayor", EndpointUtils.getResidentJsonObject(event.getResident())); + SSEManager.broadcastMessage("TownReclaimed", message); + } + + @EventHandler + public void onTownAddResident(TownAddResidentEvent event) { + JsonObject message = new JsonObject(); + message.add("town", EndpointUtils.getTownJsonObject(event.getTown())); + message.add("resident", EndpointUtils.getResidentJsonObject(event.getResident())); + SSEManager.broadcastMessage("TownAddResident", message); + } + + @EventHandler + public void onTownRemoveResident(TownRemoveResidentEvent event) { + JsonObject message = new JsonObject(); + message.add("town", EndpointUtils.getTownJsonObject(event.getTown())); + message.add("resident", EndpointUtils.getResidentJsonObject(event.getResident())); + SSEManager.broadcastMessage("TownRemoveResident", message); + } + } diff --git a/src/main/java/net/earthmc/emcapi/manager/SSEManager.java b/src/main/java/net/earthmc/emcapi/manager/SSEManager.java index 58d53c3..335db76 100644 --- a/src/main/java/net/earthmc/emcapi/manager/SSEManager.java +++ b/src/main/java/net/earthmc/emcapi/manager/SSEManager.java @@ -3,7 +3,6 @@ import com.google.gson.JsonObject; import io.javalin.Javalin; import io.javalin.http.sse.SseClient; -import net.milkbowl.vault.economy.Economy; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; @@ -12,12 +11,10 @@ public class SSEManager { private final Javalin javalin; - private final Economy economy; private final static Queue clients = new ConcurrentLinkedQueue<>(); - public SSEManager(Javalin javalin, Economy economy) { + public SSEManager(Javalin javalin) { this.javalin = javalin; - this.economy = economy; } public void loadSSE() { @@ -38,12 +35,12 @@ public void loadSSE() { // Testing purposes } - public static void broadcastMessage(String type, JsonObject data) { + public static void broadcastMessage(String event, JsonObject data) { int timestamp = Math.toIntExact(System.currentTimeMillis() / 1000); data.addProperty("timestamp", timestamp); for (SseClient client : clients) { - client.sendEvent(type, data.toString()); + client.sendEvent(event, data.toString()); } } } diff --git a/src/main/java/net/earthmc/emcapi/util/JSONUtil.java b/src/main/java/net/earthmc/emcapi/util/JSONUtil.java index 7257c59..38acc3f 100644 --- a/src/main/java/net/earthmc/emcapi/util/JSONUtil.java +++ b/src/main/java/net/earthmc/emcapi/util/JSONUtil.java @@ -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) { @@ -13,6 +15,16 @@ public static JsonObject getJsonObjectFromString(String string) { } } + public static JsonArray getJsonArrayFromStringList(List 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; From 5b6fc5460ee78a8fb0cdc9a3aba330d73e211c2b Mon Sep 17 00:00:00 2001 From: MrToirol <84342903+MrToirol@users.noreply.github.com> Date: Tue, 27 Aug 2024 01:14:40 +0200 Subject: [PATCH 3/6] Final tweaks, remove debug broadcast and add docs --- docs/events.md | 236 ++++++++++++++++++ src/main/java/net/earthmc/emcapi/EMCAPI.java | 2 - .../emcapi/listeners/TownyListeners.java | 8 +- .../earthmc/emcapi/manager/SSEManager.java | 9 - 4 files changed, 242 insertions(+), 13 deletions(-) diff --git a/docs/events.md b/docs/events.md index 0ca17af..fe5a532 100644 --- a/docs/events.md +++ b/docs/events.md @@ -9,6 +9,242 @@ You can easily connect to the event stream from your terminal using curl -H "Accept:text/event-steam" "https://api.earthmc.net/v3/aurora/events" ```` +
+ +### Here is a list of all the events and their JSON structure: +*(Each event carries a UNIX timestamp)* + +- PlayerJoin (aurora) +```yaml +{ + player: { + name: str + uuid: str + } +} +``` +- PlayerQuit (aurora) +```yaml +{ + player: { + name: str + uuid: str + } +} +``` + +
+ +- NewDay +```yaml +{ + fallenTowns: str[] + fallenNations: str[] +} +``` + +
+ +- NewNation +```yaml +{ + nation: { + name: str + uuid: str + } + king: { + name: str + uuid: str + } +} +``` +- DeleteNation +```yaml +{ + nation: { + name: str + uuid: str + } + king: { + name: str + uuid: str + } +} +``` +- RenameNation +```yaml +{ + nation: { + name: str + uuid: str + } + oldName: str +} +``` +- NationKingChange +```yaml +{ + nation: { + name: str + uuid: str + } + newKing: { + name: str + uuid: str + } + oldKing: { + name: str + uuid: str + } + isCapitalChange: bool + + # if isCapitalChange is true: + newCapital: { + name: str + uuid: str + } + oldCapital: { + name: str + uuid: str + } +} +``` +- NationAddTown +```yaml +{ + nation: { + name: str + uuid: str + } + town: { + name: str + uuid: str + } +} +``` +- NationRemoveTown +```yaml +{ + nation: { + name: str + uuid: str + } + town: { + name: str + uuid: str + } +} +``` + +
+ +- NewTown +```yaml +{ + town: { + name: str + uuid: str + } + mayor: { + name: str + uuid: str + } +} +``` +- DeleteTown +```yaml +{ + town: { + name: str + uuid: str + } + mayor: { + name: str + uuid: str + } +} +``` +- RenameTown +```yaml +{ + town: { + name: str + uuid: str + } + oldName: str +} +``` +- TownMayorChanged +```yaml +{ + town: { + name: str + uuid: str + } + newMayor: { + name: str + uuid: str + } + oldMayor: { + name: str + uuid: str + } +} +``` +- TownRuined +```yaml +{ + town: { + name: str + uuid: str + } + oldMayor: { + name: str + uuid: str + } +} +``` +- TownReclaimed +```yaml +{ + town: { + name: str + uuid: str + } + newMayor: { + name: str + uuid: str + } +} +``` +- TownAddResident +```yaml +{ + town: { + name: str + uuid: str + } + resident: { + name: str + uuid: str + } +} +``` +- TownRemoveResident +```yaml +{ + town: { + name: str + uuid: str + } + resident: { + name: str + uuid: str + } +} +``` + +
Example `NewNation` event ```json5 diff --git a/src/main/java/net/earthmc/emcapi/EMCAPI.java b/src/main/java/net/earthmc/emcapi/EMCAPI.java index d4b1faa..5007022 100644 --- a/src/main/java/net/earthmc/emcapi/EMCAPI.java +++ b/src/main/java/net/earthmc/emcapi/EMCAPI.java @@ -51,12 +51,10 @@ public void onEnable() { getServer().getPluginManager().registerEvents(new TownyListeners(), this); getServer().getPluginManager().registerEvents(new PlayerConnectionListener(), this); - } @Override public void onDisable() { - //sseManager.stop(); Should I add this or is it enough to simply shut down javalin? javalin.stop(); } diff --git a/src/main/java/net/earthmc/emcapi/listeners/TownyListeners.java b/src/main/java/net/earthmc/emcapi/listeners/TownyListeners.java index e755313..b1598ba 100644 --- a/src/main/java/net/earthmc/emcapi/listeners/TownyListeners.java +++ b/src/main/java/net/earthmc/emcapi/listeners/TownyListeners.java @@ -55,6 +55,10 @@ public void onNationKingChange(NationKingChangeEvent event) { 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())); + } SSEManager.broadcastMessage("NationKingChange", message); } @@ -110,10 +114,10 @@ public void onTownMayorChanged(TownMayorChangedEvent event) { } @EventHandler - public void onTownRuined(TownRuinedEvent event) { + public void onTownRuined(TownPreRuinedEvent event) { JsonObject message = new JsonObject(); message.add("town", EndpointUtils.getTownJsonObject(event.getTown())); - message.addProperty("oldMayor", event.getOldMayorName()); + message.add("oldMayor", EndpointUtils.getResidentJsonObject(event.getTown().getMayor())); SSEManager.broadcastMessage("TownRuined", message); } diff --git a/src/main/java/net/earthmc/emcapi/manager/SSEManager.java b/src/main/java/net/earthmc/emcapi/manager/SSEManager.java index 335db76..6f7bb1e 100644 --- a/src/main/java/net/earthmc/emcapi/manager/SSEManager.java +++ b/src/main/java/net/earthmc/emcapi/manager/SSEManager.java @@ -24,15 +24,6 @@ public void loadSSE() { client.onClose(() -> clients.remove(client)); clients.add(client); }); - - // Testing purposes - javalin.get("/broadcast", ctx -> { - JsonObject message = new JsonObject(); - message.addProperty("text", "Hello to all connected clients!"); - broadcastMessage("Hello", message); - ctx.result("Message broadcasted to all %s clients".formatted(clients.size())); - }); - // Testing purposes } public static void broadcastMessage(String event, JsonObject data) { From cb7c6a7b78fa8c5a778de9d1063f11dab4a6ef4b Mon Sep 17 00:00:00 2001 From: MrToirol <84342903+MrToirol@users.noreply.github.com> Date: Fri, 8 Nov 2024 01:33:55 +0100 Subject: [PATCH 4/6] Improve SSEs (now async) and docs --- docs/events.md | 88 ++++++++++++------- .../earthmc/emcapi/manager/SSEManager.java | 12 ++- 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/docs/events.md b/docs/events.md index fe5a532..fa41847 100644 --- a/docs/events.md +++ b/docs/events.md @@ -1,19 +1,64 @@ # Server-Sent Events (SSE) Endpoint Accessed at https://api.earthmc.net/v3/aurora/events -Use this endpoint as an [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) to receive live events. +> **Server-Sent Events** (SSEs) are a simple, one-way communication method where a server can push real-time updates to clients over HTTP. Unlike WebSockets, SSEs use a persistent HTTP connection, making them ideal for continuous data streams, such as live notifications. +> [[MDN Reference]](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) You can easily connect to the event stream from your terminal using ````bash -curl -H "Accept:text/event-steam" "https://api.earthmc.net/v3/aurora/events" +curl -H "Accept:text/event-steam" "https://api.earthmc.net/events" ```` -
+If the connection was successful, you will receive a `open` event from the server. -### Here is a list of all the events and their JSON structure: -*(Each event carries a UNIX timestamp)* +--- +### Example usage (in JavaScript) +Use the endpoint as an [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) to receive live events. +```javascript +const sse = new EventSource('https://api.earthmc.net/events'); + +/* + * This will listen only for events + * similar to the following: + * + * event: NewNation + * data: Event data (see below) + */ + +sse.addEventListener('NewNation', (e) => { + console.log(e.data); +}); +``` +
Example `NewNation` event +```json5 +{ + "event": "NewNation", + + "data": { + "nation": { + "name": "Egypt", + "uuid": "e82b84fb-d3fd-4065-a43b-013d53416162" + }, + + "king": { + "name": "Lumpeeh", + "uuid": "a03f71f9-625e-419f-9d16-0e5ab50414e4" + }, + + "timestamp": "1651592417137" + } +} +``` + +--- + +### Event data +Below is a list of all the events and the JSON structure of their `data` field. +
*(Each `data` object additionally carries a UNIX timestamp)* + +**Player Connections** - PlayerJoin (aurora) ```yaml { @@ -33,17 +78,17 @@ curl -H "Accept:text/event-steam" "https://api.earthmc.net/v3/aurora/events" } ``` -
+
**Newday** - NewDay ```yaml { - fallenTowns: str[] - fallenNations: str[] + fallenTowns: str[] // Names + fallenNations: str[] // Names } ``` -
+
**Nation** - NewNation ```yaml @@ -136,7 +181,7 @@ curl -H "Accept:text/event-steam" "https://api.earthmc.net/v3/aurora/events" } ``` -
+
**Town** - NewTown ```yaml @@ -242,27 +287,4 @@ curl -H "Accept:text/event-steam" "https://api.earthmc.net/v3/aurora/events" uuid: str } } -``` - -
- -Example `NewNation` event -```json5 -{ - "event": "NewNation", - - "data": { - "nation": { - "name": "Guinea", - "uuid": "6a458663-16ff-49c9-a27e-3ad5b3b9caf5" - }, - - "king": { - "name": "Czipsu35", - "uuid": "12a19eee-6539-4634-89bc-4398ab8de870" - }, - - "timestamp": "1656352097939" - } -} ``` \ No newline at end of file diff --git a/src/main/java/net/earthmc/emcapi/manager/SSEManager.java b/src/main/java/net/earthmc/emcapi/manager/SSEManager.java index 6f7bb1e..9837690 100644 --- a/src/main/java/net/earthmc/emcapi/manager/SSEManager.java +++ b/src/main/java/net/earthmc/emcapi/manager/SSEManager.java @@ -5,6 +5,7 @@ import io.javalin.http.sse.SseClient; import java.util.Queue; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; @@ -20,7 +21,7 @@ public SSEManager(Javalin javalin) { public void loadSSE() { javalin.sse("/events", client -> { client.keepAlive(); - client.sendEvent("connected", "Connected to the EarthMC API."); + client.sendEvent("open", "Connected to the EarthMC API."); client.onClose(() -> clients.remove(client)); clients.add(client); }); @@ -29,10 +30,13 @@ public void loadSSE() { public static void broadcastMessage(String event, JsonObject data) { int timestamp = Math.toIntExact(System.currentTimeMillis() / 1000); data.addProperty("timestamp", timestamp); + String message = data.toString(); - for (SseClient client : clients) { - client.sendEvent(event, data.toString()); - } + CompletableFuture.allOf( + clients.stream() + .map(client -> CompletableFuture.runAsync(() -> client.sendEvent(event, message))) + .toArray(CompletableFuture[]::new) + ); } } From 95f9c1a0672955c3ef21b559d6ed6fdd99fbbe91 Mon Sep 17 00:00:00 2001 From: MrToirol <84342903+MrToirol@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:15:05 +0100 Subject: [PATCH 5/6] Move SSE endpoint to /v3/events --- src/main/java/net/earthmc/emcapi/manager/SSEManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/earthmc/emcapi/manager/SSEManager.java b/src/main/java/net/earthmc/emcapi/manager/SSEManager.java index 9837690..7cb150d 100644 --- a/src/main/java/net/earthmc/emcapi/manager/SSEManager.java +++ b/src/main/java/net/earthmc/emcapi/manager/SSEManager.java @@ -19,7 +19,7 @@ public SSEManager(Javalin javalin) { } public void loadSSE() { - javalin.sse("/events", client -> { + javalin.sse("v3/events", client -> { client.keepAlive(); client.sendEvent("open", "Connected to the EarthMC API."); client.onClose(() -> clients.remove(client)); From a76fa22e6d3eb891c7ab0c16763ddb26b1e2baf7 Mon Sep 17 00:00:00 2001 From: MrToirol <84342903+MrToirol@users.noreply.github.com> Date: Fri, 8 Nov 2024 14:21:32 +0100 Subject: [PATCH 6/6] Update docs --- docs/events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/events.md b/docs/events.md index fa41847..4423ba3 100644 --- a/docs/events.md +++ b/docs/events.md @@ -1,5 +1,5 @@ # Server-Sent Events (SSE) Endpoint -Accessed at https://api.earthmc.net/v3/aurora/events +Accessed at https://api.earthmc.net/v3/events > **Server-Sent Events** (SSEs) are a simple, one-way communication method where a server can push real-time updates to clients over HTTP. Unlike WebSockets, SSEs use a persistent HTTP connection, making them ideal for continuous data streams, such as live notifications. > [[MDN Reference]](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)