diff --git a/build.gradle.kts b/build.gradle.kts
index 2b1006c..ff38bff 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -28,6 +28,10 @@ repositories {
maven("https://repo.codemc.io/repository/maven-public/") {
mavenContent { includeGroup("com.ghostchu") }
}
+
+ maven("https://nexus.neetgames.com/repository/maven-releases/") {
+ mavenContent { includeGroup("com.gmail.nossr50.mcMMO") }
+ }
}
dependencies {
@@ -45,6 +49,13 @@ dependencies {
implementation(libs.hikaricp) {
exclude(group = "org.slf4j", module = "slf4j-api")
}
+ compileOnly(libs.mcmmo) {
+ exclude("com.sk89q.worldguard")
+ exclude("com.comphenix.protocol")
+ }
+ compileOnly(libs.lynchpin.pursuits)
+ compileOnly(libs.lynchpin.towny)
+ compileOnly(libs.lynchpin.advancements)
}
tasks {
diff --git a/docs/advancements.md b/docs/advancements.md
new file mode 100644
index 0000000..c92f726
--- /dev/null
+++ b/docs/advancements.md
@@ -0,0 +1,42 @@
+# Advancements Endpoint
+Accessed at https://api.earthmc.net/v4/advancements
+
+The advancements endpoint provides information about the first time an advancement was completed. The information here is live.
+
+Example **GET** response
+```json5
+{
+ "minecraft:story/mine_diamond": { // Advancement name
+ "player": "e25ad129-fe8a-4306-b1af-1dee1ff59841", // UUID of the player who completed it first
+ "date": "2026-05-10" // The date it was completed
+ },
+ "minecraft:adventure/revaulting": {
+ "player": "e25ad129-fe8a-4306-b1af-1dee1ff59841",
+ "date": "2026-05-10"
+ },
+ "minecraft:adventure/root": {
+ "player": "0bacd488-bc41-4f76-ba8b-50dc843efe49",
+ "date": "2026-04-29"
+ },
+ "minecraft:adventure/under_lock_and_key": {
+ "player": "e25ad129-fe8a-4306-b1af-1dee1ff59841",
+ "date": "2026-05-10"
+ },
+ "minecraft:adventure/trade_at_world_height": {
+ "player": "5b8274bf-b162-4336-85a0-48f9d5380a78",
+ "date": "2026-04-12"
+ },
+ "minecraft:story/smelt_iron": {
+ "player": "e25ad129-fe8a-4306-b1af-1dee1ff59841",
+ "date": "2026-05-10"
+ },
+ "minecraft:adventure/sleep_in_bed": {
+ "player": "5b8274bf-b162-4336-85a0-48f9d5380a78",
+ "date": "2026-04-12"
+ },
+ "minecraft:adventure/fall_from_world_height": {
+ "player": "5b8274bf-b162-4336-85a0-48f9d5380a78",
+ "date": "2026-04-07"
+ }
+}
+```
diff --git a/docs/discord.md b/docs/discord.md
deleted file mode 100644
index af8cf24..0000000
--- a/docs/discord.md
+++ /dev/null
@@ -1,40 +0,0 @@
-# Discord Endpoint
-Accessed at https://api.earthmc.net/v4/discord
-
-Determine a player's Discord ID from their Minecraft UUID and vice versa using DiscordSRV's link feature.
-The player needs to have linked their account beforehand (`/discord link` in game).
-
-Use a **POST** request, and specify an array of discord queries in the body.
-Use `type` to specify the nature of your `target`.
-
-
-
-Example **POST** request
-```json5
-{
- "query": [
- {
- "type": "minecraft",
- "target": "fed0ec4a-f1ad-4b97-9443-876391668b34" // <- Minecraft UUID
- },
- {
- "type": "discord",
- "target": "160374716928884736" // <- Discord ID
- }
- ]
-}
-```
-
-Example **POST** response
-```json5
-[
- {
- "id": "160374716928884736", // Discord ID
- "uuid": "fed0ec4a-f1ad-4b97-9443-876391668b34" // Minecraft UUID
- },
- {
- "id": "160374716928884736",
- "uuid": "f17d77ab-aed4-44e7-96ef-ec9cd473eda3"
- }
-]
-```
\ No newline at end of file
diff --git a/docs/keys.md b/docs/keys.md
index 18e855d..37d3b15 100644
--- a/docs/keys.md
+++ b/docs/keys.md
@@ -6,4 +6,5 @@ You may at anytime copy your existing API key with `/api key copy`
Keys currently serve 2 purposes:
1. Allowing access to the server's SSE endpoint (`/events`)
-2. Allowing players to query their own data - Players can query their own resident information even if they have opted out, and they may view information about their shops in the `/shop` endpoint.
+2. Allowing players to query their own data - Players can query their own resident information even if they have opted out, and they may view additional information about themselves. Example endpoints: `/shop`, `/mcmmo`.
+3. Allowing players to query 'private' endpoints which have a stricter rate limit. Example endpoints: `/pursuits`, `/mcmmo-top`
diff --git a/docs/mcmmo.md b/docs/mcmmo.md
new file mode 100644
index 0000000..a4666d8
--- /dev/null
+++ b/docs/mcmmo.md
@@ -0,0 +1,84 @@
+# mcMMO Endpoints
+## Personal endpoint
+Accessed at https://api.earthmc.net/v4/mcmmo
+
+The mcMMO endpoint provides information about a player's mcMMO levels.
+It is important to note that the information here is not public, and players can only access their own information, using their API key.
+
+The rate limit for this endpoint is one request per hour per API key (and thus per player).
+
+Example **POST** request
+```json5
+{
+ "query": ["PLAYER_UUID"],
+ "key": "API_KEY"
+}
+```
+The player UUID must match the API key's owner.
+
+Example **POST** response
+```json5
+[
+ {
+ "name": "Veyronity",
+ "ACROBATICS": 21,
+ "ALCHEMY": 0,
+ "ARCHERY": 0,
+ "AXES": 0,
+ "CROSSBOWS": 0,
+ "EXCAVATION": 0,
+ "FISHING": 0,
+ "HERBALISM": 0,
+ "MACES": 0,
+ "MINING": 1,
+ "REPAIR": 0,
+ "SALVAGE": 0,
+ "SMELTING": 0,
+ "SPEARS": 0,
+ "SWORDS": 0,
+ "TAMING": 0,
+ "TRIDENTS": 0,
+ "UNARMED": 0,
+ "WOODCUTTING": 50
+ }
+]
+```
+The player's name and their level in each primary skill. The overall power level can be calculated by adding all the values.
+
+## Top endpoint
+Accessed at https://api.earthmc.net/v4/mcmmo-top
+
+The mcMMO-top endpoint provides the leaderboard of a specified skill.
+
+The rate limit for this endpoint is one request per 2 minutes per API key.
+
+Example **POST** request
+```json5
+{
+ "query": ["SKILL"], // Skill name (E.g. 'FISHING', 'MINING'), or 'POWER' for the overall power leaderboard
+ "key": "API_KEY"
+}
+```
+
+Example **POST** response
+```json5
+[
+ {
+ "skill": "power",
+ "1": {
+ "player": "K1kimor",
+ "level": 12078
+ },
+ "2": {
+ "player": "Veyronity",
+ "level": 72
+ },
+ "3": {
+ "player": "Andre1098",
+ "level": 29
+ },
+ "lastUpdated": 1779619278
+ }
+]
+```
+The information is not live, it is cached & updated every 15 minutes. The field `lastUpdated` provides the epoch second that the information was last updated at.
diff --git a/docs/nations.md b/docs/nations.md
index 4d3d267..7a02c6a 100644
--- a/docs/nations.md
+++ b/docs/nations.md
@@ -50,6 +50,7 @@ Example **POST** response
"dynmapColour": "FFA500", // Nation's hex Dynmap colour
"dynmapOutline": "FFFF00", // Nation's hex Dynmap outline
"wiki": null, // The nation's wiki URL as a string if set, returns null if not
+ "discord": null, // The nation's discord URL
"king": {
"name": "tuzzzie",
"uuid": "8391474f-4b57-412a-a835-96bd2c253219"
@@ -185,6 +186,30 @@ Example **POST** response
],
"Colonist": [],
"Diplomat": []
+ },
+ "embargoes": {
+ "own": [ // A JSON array representing all nations that this nation has placed an embargo on
+ {
+ "name": "woodland",
+ "uuid": "625a3d73-b179-4335-a512-58366c90dcfe"
+ }
+ ],
+ "against": [] // A JSON array representing all nations that have placed an embargo on this nation
+ },
+ "pacts": { // Container for 'active' and 'pending' JSON dictionaries for pacts this nation is involved in, where the key is the name of the other nation in the pact, and the value is a detailed pact object
+ "active": { // A JSON dictionary representing active pacts this nation has
+ "LizardLand": {
+ "sender": "dev",
+ "receiver": "LizardLand",
+ "status": "ACTIVE",
+ "stats": {
+ "createdAt": 1779459092037,
+ "expiresAt": 1779545525988,
+ "duration": 1
+ }
+ }
+ },
+ "pending": {} // A JSON dictionary representing pending pacts this nation has.
}
}
]
diff --git a/docs/pursuits.md b/docs/pursuits.md
new file mode 100644
index 0000000..d72c56c
--- /dev/null
+++ b/docs/pursuits.md
@@ -0,0 +1,60 @@
+# Pursuit Endpoint
+Accessed at https://api.earthmc.net/v4/pursuits
+
+The pursuits endpoint provides information about pursuits' leaderboards.
+Only the top 10 entries are included.
+
+The rate limit for this endpoint is one request per minute per API key.
+
+Example **POST** request
+```json5
+{
+ "query": ["TYPE"], // The pursuit type (PLAYER, TOWN, or NATION) - 'ALL' for all
+ "key": "API_KEY"
+}
+```
+
+Example **POST** response
+```json5
+[
+ {
+ "PLAYER": {
+ "name": "mcmmo-smelting",
+ "isActive": true,
+ "top": {
+ "1": {
+ "player": "686b1bfa-38de-4eb9-9628-43ed3b56b76e",
+ "score": 50.0
+ },
+ "2": {
+ "player": "5b8274bf-b162-4336-85a0-48f9d5380a78",
+ "score": 25.0
+ },
+ "3": {
+ "player": "be66697b-bde8-4d70-b339-4ba15b390fa9",
+ "score": 10.0
+ }
+ }
+ },
+ "NATION": {
+ "name": "votes",
+ "isActive": true,
+ "top": {}
+ },
+ "TOWN": {
+ "name": "votes",
+ "isActive": true,
+ "top": {
+ "1": {
+ "town": "f6bc3242-77a6-4020-ad1c-7df4a4c0e436",
+ "score": 35.0
+ },
+ "2": {
+ "town": "fa14cbc6-3215-481c-b427-a411b519178f",
+ "score": 12.0
+ }
+ }
+ }
+ }
+]
+```
diff --git a/docs/shop.md b/docs/shop.md
index 7ac8885..51119bc 100644
--- a/docs/shop.md
+++ b/docs/shop.md
@@ -4,6 +4,8 @@ Accessed at https://api.earthmc.net/v4/shop
The shop endpoint provides information about player-owned QuickShops.
It is important to note that the information here is not public, and players can only access their own shops' information, using their API key.
+The rate limit for this endpoint is one request per hour per API key (and thus per player).
+
Each shop object has the following properties:
- `id` - The unique identifier of the shop
- `item` - The Material/name of the item being traded
diff --git a/docs/towns.md b/docs/towns.md
index 7fe7455..e38cbac 100644
--- a/docs/towns.md
+++ b/docs/towns.md
@@ -49,6 +49,7 @@ Example **POST** response
"board": "Fishing every Friday! Join our Discord, link can be found on the signs!", // Town's board as seen on /t, null if none is present
"founder": "tuzzzie", // The founder of the town as seen on /t
"wiki": null, // The town's wiki URL as a string if set, returns null if not
+ "discord": null, // The town's wiki URL
"mayor": {
"name": "Fruitloopins",
"uuid": "fed0ec4a-f1ad-4b97-9443-876391668b34"
@@ -71,8 +72,10 @@ Example **POST** response
"isRuined": false, // True if the town is ruined
"isForSale": false, // True if the town is for sale
"hasNation": true, // True if the town has a nation
- "hasOverclaimShield": false, // True if the town currently has an overclaim shield
"canOutsidersSpawn": false, // True if the town allows outsiders to teleport
+ "canPassiveMobsSpawn": true, // True if the town has passive mob spawn enabled
+ "hasSnowAccumulation": true, // True if the town has snow accumulation enabled
+ "hasFriendlyFire": false, // True if the town has friendly-fire enabled
},
"stats": {
"numTownBlocks": 473, // The total number of town blocks the town has
@@ -270,7 +273,22 @@ Example **POST** response
"Treasurer": [],
"Realtor": [],
"Settler": []
- }
+ },
+ "warps": [
+ {
+ "name": "cool_warp",
+ "uuid": "936fe2be-1461-4a77-828f-a9aa073f827e",
+ "createdAt": 1779458705887,
+ "createdBy": "Veyronity",
+ "access": "RESIDENT",
+ "location": {
+ "x": 1,
+ "y": 185,
+ "z": -9
+ }
+ }
+ ]
+ }
}
]
```
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index f4a5c4c..7d0a20a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -8,6 +8,8 @@ mysterymaster-api = "1.0.0"
superbvote = "0.6.0"
quickshop = "6.2.0.11"
hikaricp = "7.0.2"
+mcmmo = "2.2.040"
+lynchpin-api = "1.0-SNAPSHOT"
# plugins
conventions = "1.0.8"
@@ -24,6 +26,10 @@ superbvote = { group = "net.earthmc.superbvote", name = "SuperbVote", version.re
quickshop = { group = "com.ghostchu", name = "quickshop-bukkit", version.ref = "quickshop" }
quickshop-api = { group = "com.ghostchu", name = "quickshop-api", version.ref = "quickshop" }
hikaricp = { group = "com.zaxxer", name = "HikariCP", version.ref = "hikaricp" }
+mcmmo = { group = "com.gmail.nossr50.mcMMO", name = "mcMMO", version.ref = "mcmmo" }
+lynchpin-pursuits = { group = "net.earthmc.lynchpin.api", name = "pursuits", version.ref = "lynchpin-api"}
+lynchpin-towny = { group = "net.earthmc.lynchpin.api", name = "towny", version.ref = "lynchpin-api"}
+lynchpin-advancements = { group = "net.earthmc.lynchpin.api", name = "advancements", version.ref = "lynchpin-api"}
[plugins]
conventions-java = { id = "net.earthmc.conventions.java", version.ref = "conventions" }
diff --git a/src/main/java/net/earthmc/emcapi/EMCAPI.java b/src/main/java/net/earthmc/emcapi/EMCAPI.java
index 3bb43b7..02328cd 100644
--- a/src/main/java/net/earthmc/emcapi/EMCAPI.java
+++ b/src/main/java/net/earthmc/emcapi/EMCAPI.java
@@ -11,12 +11,12 @@
import net.earthmc.emcapi.integration.Integrations;
import net.earthmc.emcapi.manager.EndpointManager;
import net.earthmc.emcapi.manager.KeyManager;
-import net.earthmc.emcapi.manager.LegacyEndpointManager;
import net.earthmc.emcapi.manager.OptOut;
import net.earthmc.emcapi.sse.SSEManager;
import net.earthmc.emcapi.sse.listeners.ShopSSEListener;
import net.earthmc.emcapi.sse.listeners.TownySSEListener;
import net.earthmc.emcapi.command.ApiCommand;
+import net.earthmc.emcapi.util.CooldownUtil;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.eclipse.jetty.server.Connector;
@@ -30,12 +30,12 @@
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
+import java.util.concurrent.TimeUnit;
public final class EMCAPI extends JavaPlugin {
public static EMCAPI instance;
private Javalin javalin;
- private Integrations pluginIntegrations;
private SSEManager sseManager;
private final APIDatabase database = new APIDatabase();
private final OptOut optOut = new OptOut(this);
@@ -46,6 +46,7 @@ public void onLoad() {
JavalinLogger.startupInfo = false;
}
+ @SuppressWarnings("UnstableApiUsage")
@Override
public void onEnable() {
instance = this;
@@ -54,12 +55,8 @@ public void onEnable() {
loadDatabase();
initialiseJavalin();
- this.pluginIntegrations = new Integrations(this);
- getServer().getPluginManager().registerEvents(this.pluginIntegrations, this);
+ getServer().getPluginManager().registerEvents(new Integrations(), this);
- if (getConfig().getBoolean("behaviour.load_legacy")) {
- new LegacyEndpointManager(this).loadEndpoints(); // Load retired endpoints and still serve current endpoints at /v3/aurora/
- }
new EndpointManager(this).loadEndpoints();
this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(ApiCommand.create(this), "Allows you to opt in or out of your information being visible in the API."));
@@ -81,6 +78,8 @@ public void onEnable() {
} catch (SQLException e) {
getSLF4JLogger().warn("exception while loading API keys: ", e);
}
+
+ getServer().getAsyncScheduler().runAtFixedRate(this, t -> CooldownUtil.refresh(), 5, 5, TimeUnit.MINUTES);
}
@Override
@@ -170,10 +169,6 @@ public Javalin getJavalin() {
return javalin;
}
- public Integrations integrations() {
- return this.pluginIntegrations;
- }
-
public String getURLPath() {
String version = getConfig().getString("networking.api_version", "3");
return "v" + version;
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/AdvancementsEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/AdvancementsEndpoint.java
new file mode 100644
index 0000000..179a826
--- /dev/null
+++ b/src/main/java/net/earthmc/emcapi/endpoint/AdvancementsEndpoint.java
@@ -0,0 +1,33 @@
+package net.earthmc.emcapi.endpoint;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import net.earthmc.emcapi.integration.AdvancementsIntegration;
+import net.earthmc.emcapi.integration.Integrations;
+import net.earthmc.emcapi.object.endpoint.GetEndpoint;
+import net.earthmc.lynchpin.api.advancements.AdvancementEntry;
+
+import java.text.SimpleDateFormat;
+
+public class AdvancementsEndpoint extends GetEndpoint {
+ private final AdvancementsIntegration integration;
+ private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
+
+ public AdvancementsEndpoint() {
+ integration = Integrations.getIntegration("lynchpin-advancements");
+ }
+
+ @Override
+ public JsonElement getJsonElement() {
+ JsonObject outer = new JsonObject();
+ for (AdvancementEntry entry : integration.getAdvancements()) {
+ JsonObject json = new JsonObject();
+ json.addProperty("player", entry.player().toString());
+ json.addProperty("date", formatter.format(entry.date()));
+
+ outer.add(entry.advancement(), json);
+ }
+
+ return outer;
+ }
+}
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/McMMOEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/McMMOEndpoint.java
new file mode 100644
index 0000000..cb68ee8
--- /dev/null
+++ b/src/main/java/net/earthmc/emcapi/endpoint/McMMOEndpoint.java
@@ -0,0 +1,63 @@
+package net.earthmc.emcapi.endpoint;
+
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import io.javalin.http.BadRequestResponse;
+import net.earthmc.emcapi.EMCAPI;
+import net.earthmc.emcapi.integration.Integrations;
+import net.earthmc.emcapi.integration.McMMOIntegration;
+import net.earthmc.emcapi.manager.KeyManager;
+import net.earthmc.emcapi.object.endpoint.PostEndpoint;
+import net.earthmc.emcapi.util.CooldownUtil;
+import net.earthmc.emcapi.util.HttpExceptions;
+import net.earthmc.emcapi.util.JSONUtil;
+import org.jspecify.annotations.Nullable;
+
+import java.util.UUID;
+
+public class McMMOEndpoint extends PostEndpoint {
+ private final McMMOIntegration integration;
+ private static final long COOLDOWN_SECONDS = 3600;
+
+ public McMMOEndpoint(EMCAPI plugin) {
+ super(plugin);
+ this.integration = Integrations.getIntegration("mcMMO");
+ }
+
+ @Override
+ public PlayerProfile getObjectOrNull(JsonElement element, @Nullable String key) {
+ String string = JSONUtil.getJsonElementAsStringOrNull(element);
+ if (string == null) throw new BadRequestResponse("Your query contains a value that is not a string");
+
+ UUID player;
+ try {
+ player = UUID.fromString(string);
+ } catch (IllegalArgumentException ignored) {
+ throw new BadRequestResponse("Your query contains an invalid UUID");
+ }
+
+ UUID keyOwner = KeyManager.getKeyOwner(key);
+ if (keyOwner == null) {
+ throw HttpExceptions.MISSING_API_KEY;
+ }
+ if (!player.equals(keyOwner)) {
+ throw HttpExceptions.FORBIDDEN;
+ }
+
+ CooldownUtil.checkAndAddCooldownOrThrow("mcmmo", keyOwner.toString(), COOLDOWN_SECONDS);
+ return integration.getPlayerProfile(player);
+ }
+
+ @Override
+ public JsonElement getJsonElement(PlayerProfile object, @Nullable String key) {
+ JsonObject json = new JsonObject();
+ json.addProperty("name", object.getPlayerName());
+ for (PrimarySkillType skill : PrimarySkillType.values()) {
+ json.addProperty(skill.name(), object.getSkillLevel(skill));
+ }
+
+ return json;
+ }
+}
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/McMMOTopEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/McMMOTopEndpoint.java
new file mode 100644
index 0000000..840850e
--- /dev/null
+++ b/src/main/java/net/earthmc/emcapi/endpoint/McMMOTopEndpoint.java
@@ -0,0 +1,107 @@
+package net.earthmc.emcapi.endpoint;
+
+import com.gmail.nossr50.datatypes.database.PlayerStat;
+import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.skills.SkillTools;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import io.javalin.http.BadRequestResponse;
+import net.earthmc.emcapi.EMCAPI;
+import net.earthmc.emcapi.integration.Integrations;
+import net.earthmc.emcapi.manager.KeyManager;
+import net.earthmc.emcapi.object.endpoint.PostEndpoint;
+import net.earthmc.emcapi.util.CooldownUtil;
+import net.earthmc.emcapi.util.HttpExceptions;
+import net.earthmc.emcapi.util.JSONUtil;
+import org.jspecify.annotations.Nullable;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+public class McMMOTopEndpoint extends PostEndpoint {
+ private static final Map> CACHE = new ConcurrentHashMap<>();
+ private static final long COOLDOWN_SECONDS = 120;
+ private Long lastUpdated;
+
+ public McMMOTopEndpoint(EMCAPI plugin) {
+ super(plugin);
+ if (Integrations.getIntegration("mcMMO").isEnabled()) {
+ plugin.getServer().getAsyncScheduler().runAtFixedRate(plugin, t -> {
+ for (PrimarySkillType skill : PrimarySkillType.values()) {
+ if (SkillTools.isChildSkill(skill)) continue;
+ loadSkill(skill);
+ }
+ loadSkill(null); // Main power
+ lastUpdated = Instant.now().getEpochSecond();
+ }, 1, 15, TimeUnit.MINUTES);
+ }
+ }
+
+ private static List loadSkill(@Nullable PrimarySkillType skill) {
+ List data = mcMMO.getDatabaseManager().readLeaderboard(skill, 1, 25);
+ CACHE.put(skillName(skill), data);
+ return data;
+ }
+
+ @Override
+ public McMMOLeaderboard getObjectOrNull(JsonElement element, @Nullable String key) {
+ String string = JSONUtil.getJsonElementAsStringOrNull(element);
+ if (string == null) throw new BadRequestResponse("Your query contains a value that is not a string");
+
+ UUID keyOwner = KeyManager.getKeyOwner(key);
+ if (keyOwner == null) {
+ throw HttpExceptions.MISSING_API_KEY;
+ }
+
+ PrimarySkillType skill;
+ if (string.equalsIgnoreCase("power")) {
+ skill = null;
+ } else {
+ try {
+ skill = PrimarySkillType.valueOf(string.toUpperCase());
+ } catch (IllegalArgumentException ignored) {
+ throw new BadRequestResponse("Invalid skill name queried.");
+ }
+ if (SkillTools.isChildSkill(skill)) {
+ throw new BadRequestResponse(skill.name() + " is a child skill and does not have a leaderboard!");
+ }
+ }
+ CooldownUtil.checkAndAddCooldownOrThrow("mcmmo-top", keyOwner.toString(), COOLDOWN_SECONDS);
+
+ String skillName = skillName(skill);
+ if (CACHE.containsKey(skillName)) {
+ return new McMMOLeaderboard(skill, CACHE.get(skillName));
+ }
+
+ return new McMMOLeaderboard(skill, loadSkill(skill));
+ }
+
+ @Override
+ public JsonElement getJsonElement(McMMOLeaderboard object, @Nullable String key) {
+ JsonObject json = new JsonObject();
+ json.addProperty("skill", skillName(object.skill()));
+
+ int index = 1;
+ for (PlayerStat entry : object.data()) {
+ JsonObject element = new JsonObject();
+ element.addProperty("player", entry.playerName());
+ element.addProperty("level", entry.value());
+
+ json.add(String.valueOf(index++), element);
+ }
+
+ json.addProperty("lastUpdated", lastUpdated);
+ return json;
+ }
+
+ private static String skillName(@Nullable PrimarySkillType skill) {
+ return skill != null ? skill.name() : "power";
+ }
+
+ public record McMMOLeaderboard(@Nullable PrimarySkillType skill, List data) {}
+}
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/MysteryMasterEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/MysteryMasterEndpoint.java
index 4d0c05e..31e0b02 100644
--- a/src/main/java/net/earthmc/emcapi/endpoint/MysteryMasterEndpoint.java
+++ b/src/main/java/net/earthmc/emcapi/endpoint/MysteryMasterEndpoint.java
@@ -23,11 +23,6 @@ public MysteryMasterEndpoint(final EMCAPI plugin) {
}
}
- @Override
- public String lookup() {
- return getJsonElement().toString();
- }
-
@Override
public JsonElement getJsonElement() {
JsonArray jsonArray = new JsonArray();
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/OnlineEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/OnlineEndpoint.java
index 5533c12..4a54df8 100644
--- a/src/main/java/net/earthmc/emcapi/endpoint/OnlineEndpoint.java
+++ b/src/main/java/net/earthmc/emcapi/endpoint/OnlineEndpoint.java
@@ -9,11 +9,6 @@
public class OnlineEndpoint extends GetEndpoint {
- @Override
- public String lookup() {
- return getJsonElement().toString();
- }
-
@Override
public JsonElement getJsonElement() {
return EndpointUtils.getOnlinePlayerArray(new ArrayList<>(Bukkit.getOnlinePlayers()));
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/PursuitsEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/PursuitsEndpoint.java
new file mode 100644
index 0000000..31c5949
--- /dev/null
+++ b/src/main/java/net/earthmc/emcapi/endpoint/PursuitsEndpoint.java
@@ -0,0 +1,104 @@
+package net.earthmc.emcapi.endpoint;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import io.javalin.http.BadRequestResponse;
+import io.javalin.http.NotFoundResponse;
+import net.earthmc.emcapi.EMCAPI;
+import net.earthmc.emcapi.integration.Integrations;
+import net.earthmc.emcapi.integration.PursuitsIntegration;
+import net.earthmc.emcapi.manager.KeyManager;
+import net.earthmc.emcapi.object.endpoint.PostEndpoint;
+import net.earthmc.emcapi.util.CooldownUtil;
+import net.earthmc.emcapi.util.HttpExceptions;
+import net.earthmc.emcapi.util.JSONUtil;
+import net.earthmc.lynchpin.api.pursuits.Pursuit;
+import org.jspecify.annotations.Nullable;
+
+import java.util.List;
+import java.util.UUID;
+import java.util.ArrayList;
+import java.util.Map;
+
+public class PursuitsEndpoint extends PostEndpoint {
+ private static final long COOLDOWN_SECONDS = 60;
+ private final PursuitsIntegration integration;
+
+ public PursuitsEndpoint(EMCAPI plugin) {
+ super(plugin);
+ integration = Integrations.getIntegration("lynchpin-pursuits");
+ }
+
+ @Override
+ public PursuitsLeaderboard getObjectOrNull(JsonElement element, @Nullable String key) {
+ String string = JSONUtil.getJsonElementAsStringOrNull(element);
+ if (string == null) throw new BadRequestResponse("Your query contains a value that is not a string");
+
+ UUID keyOwner = KeyManager.getKeyOwner(key);
+ if (keyOwner == null) {
+ throw HttpExceptions.MISSING_API_KEY;
+ }
+
+ Pursuit.Type specifiedType;
+ if (string.equalsIgnoreCase("all")) {
+ specifiedType = null;
+ } else {
+ try {
+ specifiedType = Pursuit.Type.valueOf(string.toUpperCase());
+ } catch (IllegalArgumentException ignored) {
+ throw new BadRequestResponse("Invalid Pursuit Type specified. ");
+ }
+ }
+
+ CooldownUtil.checkAndAddCooldownOrThrow("pursuits", keyOwner.toString(), COOLDOWN_SECONDS);
+ if (specifiedType != null) {
+ Pursuit pursuit = integration.getPursuit(specifiedType);
+ if (pursuit == null) {
+ throw new NotFoundResponse("No pursuit found for type " + specifiedType.name());
+ }
+ return new PursuitsLeaderboard(List.of(pursuit));
+ }
+
+ List pursuits = new ArrayList<>(integration.getPursuits().values());
+ if (pursuits.isEmpty()) {
+ throw new NotFoundResponse("No pursuits found");
+ }
+ return new PursuitsLeaderboard(pursuits);
+ }
+
+ @Override
+ public JsonElement getJsonElement(PursuitsLeaderboard object, @Nullable String key) {
+ JsonObject json = new JsonObject();
+ for (Pursuit pursuit : object.pursuits) {
+ json.add(pursuit.type().name(), formatPursuit(pursuit));
+ }
+
+ return json;
+ }
+
+ private JsonObject formatPursuit(Pursuit pursuit) {
+ JsonObject json = new JsonObject();
+
+ json.addProperty("name", pursuit.name());
+ json.addProperty("isActive", pursuit.isActive());
+
+ json.add("top", formatTop(pursuit));
+ return json;
+ }
+
+ private JsonObject formatTop(Pursuit pursuit) {
+ JsonObject top = new JsonObject();
+ int index = 1;
+ String typeName = pursuit.type().name().toLowerCase();
+ for (Map.Entry entry : pursuit.top(10).entrySet()) {
+ JsonObject element = new JsonObject();
+ element.addProperty(typeName, entry.getKey().toString());
+ element.addProperty("score", entry.getValue());
+
+ top.add(String.valueOf(index++), element);
+ }
+ return top;
+ }
+
+ public record PursuitsLeaderboard(List pursuits) {}
+}
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/ServerEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/ServerEndpoint.java
index d83814e..ed905f4 100644
--- a/src/main/java/net/earthmc/emcapi/endpoint/ServerEndpoint.java
+++ b/src/main/java/net/earthmc/emcapi/endpoint/ServerEndpoint.java
@@ -5,6 +5,7 @@
import com.palmergames.bukkit.towny.TownySettings;
import com.palmergames.bukkit.towny.object.Resident;
import net.earthmc.emcapi.EMCAPI;
+import net.earthmc.emcapi.integration.Integrations;
import net.earthmc.emcapi.integration.QuartersIntegration;
import net.earthmc.emcapi.integration.SuperbVoteIntegration;
import net.earthmc.emcapi.object.endpoint.GetEndpoint;
@@ -25,7 +26,7 @@ public class ServerEndpoint extends GetEndpoint {
public ServerEndpoint(final EMCAPI plugin) {
this.plugin = plugin;
- final QuartersIntegration quartersIntegration = plugin.integrations().quartersIntegration();
+ final QuartersIntegration quartersIntegration = Integrations.getIntegration("Quarters");
plugin.getServer().getAsyncScheduler().runAtFixedRate(plugin, task -> {
final QuartersIntegration.QuarterStatistics statistics = quartersIntegration.retrieveQuarterStatistics();
@@ -35,11 +36,6 @@ public ServerEndpoint(final EMCAPI plugin) {
}, 0L, 1L, TimeUnit.HOURS);
}
- @Override
- public String lookup() {
- return getJsonElement().toString();
- }
-
@Override
public JsonObject getJsonElement() {
JsonObject serverObject = new JsonObject();
@@ -80,7 +76,7 @@ public JsonObject getJsonElement() {
int target;
int currentVotes;
- final SuperbVoteIntegration superbVote = plugin.integrations().superbVoteIntegration();
+ final SuperbVoteIntegration superbVote = Integrations.getIntegration("SuperbVote");
if (superbVote.isEnabled()) {
target = superbVote.votesNeeded();
currentVotes = superbVote.currentVotes();
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/ShopEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/ShopEndpoint.java
index bda2b31..7d4a858 100644
--- a/src/main/java/net/earthmc/emcapi/endpoint/ShopEndpoint.java
+++ b/src/main/java/net/earthmc/emcapi/endpoint/ShopEndpoint.java
@@ -4,40 +4,31 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.javalin.http.BadRequestResponse;
-import io.javalin.http.ForbiddenResponse;
-import io.javalin.http.TooManyRequestsResponse;
-import io.javalin.http.UnauthorizedResponse;
import net.earthmc.emcapi.EMCAPI;
+import net.earthmc.emcapi.integration.Integrations;
import net.earthmc.emcapi.integration.QuickShopIntegration;
import net.earthmc.emcapi.manager.KeyManager;
import net.earthmc.emcapi.object.endpoint.PostEndpoint;
+import net.earthmc.emcapi.util.CooldownUtil;
import net.earthmc.emcapi.util.EndpointUtils;
+import net.earthmc.emcapi.util.HttpExceptions;
import net.earthmc.emcapi.util.JSONUtil;
import org.jetbrains.annotations.Nullable;
-import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.TimeUnit;
public class ShopEndpoint extends PostEndpoint> {
private final QuickShopIntegration integration;
- private static final Map LAST_QUERY_MAP = new ConcurrentHashMap<>();
private static final int COOLDOWN_SECONDS = 3600;
public ShopEndpoint(EMCAPI plugin) {
super(plugin);
- this.integration = plugin.integrations().quickShopIntegration();
- plugin.getServer().getAsyncScheduler().runAtFixedRate(plugin, t ->
- LAST_QUERY_MAP.entrySet().removeIf(entry -> entry.getValue() < Instant.now().getEpochSecond() - COOLDOWN_SECONDS),
- 1,
- 1,
- TimeUnit.MINUTES // Check more often
- );
+ this.integration = Integrations.getIntegration("QuickShop-Hikari");
}
@Override
@@ -52,19 +43,14 @@ public List getObjectOrNull(JsonElement element, @Nullable String key) {
throw new BadRequestResponse("Your query contains an invalid UUID");
}
- integration.throwIfDisabled();
UUID keyOwner = KeyManager.getKeyOwner(key);
if (keyOwner == null) {
- throw new UnauthorizedResponse("Could not find an owner for this API key");
+ throw HttpExceptions.MISSING_API_KEY;
}
if (!player.equals(KeyManager.getKeyOwner(key))) {
- throw new ForbiddenResponse("This API key is not owned by the player queried");
- }
- Long next = LAST_QUERY_MAP.containsKey(keyOwner) ? LAST_QUERY_MAP.get(keyOwner) + COOLDOWN_SECONDS : null;
- Long now = Instant.now().getEpochSecond();
- if (next != null && next > now) { // Rate limiting should happen before even querying the shops, not before formatting the data
- throw new TooManyRequestsResponse("Too Many Requests. Try again in " + (next - now) + " seconds");
+ throw HttpExceptions.FORBIDDEN;
}
+ CooldownUtil.checkAndAddCooldownOrThrow("shop", keyOwner.toString(), COOLDOWN_SECONDS);
return integration.getPlayerShops(player);
}
@@ -74,12 +60,11 @@ public JsonElement getJsonElement(List object, @Nullable String key) {
int counter = 0;
UUID keyOwner = KeyManager.getKeyOwner(key);
if (keyOwner == null) {
- throw new UnauthorizedResponse("Could not find an owner for this API key");
+ throw HttpExceptions.MISSING_API_KEY;
}
if (object.isEmpty()) {
return null;
}
- LAST_QUERY_MAP.put(keyOwner, Instant.now().getEpochSecond()); // Mark as a successful query if at least 1 shop was loaded
final List> shopFutures = new ArrayList<>();
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/legacy/DocumentationEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/legacy/DocumentationEndpoint.java
deleted file mode 100644
index f673011..0000000
--- a/src/main/java/net/earthmc/emcapi/endpoint/legacy/DocumentationEndpoint.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package net.earthmc.emcapi.endpoint.legacy;
-
-import com.google.gson.JsonObject;
-import net.earthmc.emcapi.object.endpoint.GetEndpoint;
-
-public class DocumentationEndpoint extends GetEndpoint {
-
- @Override
- public String lookup() {
- return getJsonElement().toString();
- }
-
- @Override
- public JsonObject getJsonElement() {
- JsonObject jsonObject = new JsonObject();
-
- jsonObject.addProperty("documentation", "https://earthmc.net/docs/api");
-
- JsonObject authorObject = new JsonObject();
- JsonObject discordObject = new JsonObject();
- discordObject.addProperty("username", "fruitloopins");
- discordObject.addProperty("id", "160374716928884736");
- authorObject.add("discord", discordObject);
-
- authorObject.addProperty("github", "https://github.com/jwkerr");
- authorObject.addProperty("note", "Feel free to get in contact if you need any help with using the API, or send a message in the Official API Discussion thread (https://discord.com/channels/219863747248914433/1218363271367622717)");
- jsonObject.add("author", authorObject);
-
- jsonObject.addProperty("fish", "><>");
-
- return jsonObject;
- }
-}
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/towny/NationsEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/towny/NationsEndpoint.java
index 950cd41..ce58280 100644
--- a/src/main/java/net/earthmc/emcapi/endpoint/towny/NationsEndpoint.java
+++ b/src/main/java/net/earthmc/emcapi/endpoint/towny/NationsEndpoint.java
@@ -1,5 +1,6 @@
package net.earthmc.emcapi.endpoint.towny;
+import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.palmergames.bukkit.towny.TownyAPI;
@@ -10,10 +11,15 @@
import com.palmergames.bukkit.towny.permissions.TownyPerms;
import io.javalin.http.BadRequestResponse;
import net.earthmc.emcapi.EMCAPI;
+import net.earthmc.emcapi.integration.EmbargoesIntegration;
+import net.earthmc.emcapi.integration.Integrations;
+import net.earthmc.emcapi.integration.PactsIntegration;
import net.earthmc.emcapi.manager.NationMetadataManager;
import net.earthmc.emcapi.object.endpoint.PostEndpoint;
import net.earthmc.emcapi.util.EndpointUtils;
import net.earthmc.emcapi.util.JSONUtil;
+import net.earthmc.lynchpin.api.towny.embargoes.Embargo;
+import net.earthmc.lynchpin.api.towny.pacts.Pact;
import org.jetbrains.annotations.Nullable;
import java.util.List;
@@ -50,6 +56,7 @@ public JsonElement getJsonElement(Nation nation, @Nullable String key) {
nationObject.addProperty("dynmapColour", NationMetadataManager.getDynmapColour(nation));
nationObject.addProperty("dynmapOutline", NationMetadataManager.getDynmapOutline(nation));
nationObject.addProperty("wiki", NationMetadataManager.getWikiURL(nation));
+ nationObject.addProperty("discord", NationMetadataManager.getDiscordURL(nation));
nationObject.add("king", EndpointUtils.getResidentJsonObject(nation.getKing()));
nationObject.add("capital", EndpointUtils.getTownJsonObject(nation.getCapital()));
@@ -90,6 +97,57 @@ public JsonElement getJsonElement(Nation nation, @Nullable String key) {
}
nationObject.add("ranks", ranksObject);
+ nationObject.add("embargoes", getEmbargoesObject(nation));
+ nationObject.add("pacts", getPactsObject(nation));
+
return nationObject;
}
+
+ private JsonObject getEmbargoesObject(Nation nation) {
+ JsonObject json = new JsonObject();
+ EmbargoesIntegration integration = Integrations.getIntegration("lynchpin-towny-embargoes");
+ if (integration == null || !integration.isEnabled()) {
+ return json;
+ }
+
+ JsonArray byNation = new JsonArray();
+ JsonArray againstNation = new JsonArray();
+ for (Embargo embargo : integration.getEmbargoes(nation)) {
+ if (nation.equals(embargo.getSender())) {
+ Nation other = embargo.getAgainst();
+ byNation.add(EndpointUtils.getNationJsonObject(other));
+ } else {
+ Nation other = embargo.getSender();
+ againstNation.add(EndpointUtils.getNationJsonObject(other));
+ }
+ }
+ json.add("own", byNation);
+ json.add("against", againstNation);
+
+ return json;
+ }
+
+ private JsonObject getPactsObject(Nation nation) {
+ JsonObject json = new JsonObject();
+ PactsIntegration integration = Integrations.getIntegration("lynchpin-towny-pacts");
+ if (integration == null || !integration.isEnabled()) {
+ return json;
+ }
+
+ JsonArray active = new JsonArray();
+ JsonArray pending = new JsonArray();
+
+ for (Pact pact : integration.getActivePacts(nation)) {
+ active.add(EndpointUtils.getPactObject(pact));
+ }
+
+ for (Pact pact : integration.getPendingPacts(nation)) {
+ pending.add(EndpointUtils.getPactObject(pact));
+ }
+
+ json.add("active", active);
+ json.add("pending", pending);
+
+ return json;
+ }
}
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/towny/PlayersEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/towny/PlayersEndpoint.java
index d3ff25a..9bc0098 100644
--- a/src/main/java/net/earthmc/emcapi/endpoint/towny/PlayersEndpoint.java
+++ b/src/main/java/net/earthmc/emcapi/endpoint/towny/PlayersEndpoint.java
@@ -9,6 +9,7 @@
import github.scarsz.discordsrv.DiscordSRV;
import io.javalin.http.BadRequestResponse;
import net.earthmc.emcapi.EMCAPI;
+import net.earthmc.emcapi.integration.Integrations;
import net.earthmc.emcapi.manager.KeyManager;
import net.earthmc.emcapi.object.endpoint.PostEndpoint;
import net.earthmc.emcapi.util.EndpointUtils;
@@ -110,14 +111,14 @@ private JsonArray getRankArray(List ranks) {
}
private String getDiscordId(UUID uuid) {
- if (!plugin.integrations().discordIntegration().isEnabled()) {
+ if (!Integrations.getIntegration("DiscordSRV").isEnabled()) {
return null;
}
return DiscordSRV.getPlugin().getAccountLinkManager().getDiscordId(uuid);
}
private UUID getUUIDFromDiscordId(String discordId) {
- if (!plugin.integrations().discordIntegration().isEnabled()) {
+ if (!Integrations.getIntegration("DiscordSRV").isEnabled()) {
return null;
}
if (!DISCORD_ID_PATTERN.matcher(discordId).find()) return null;
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/towny/TownsEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/towny/TownsEndpoint.java
index 98fe171..ef70202 100644
--- a/src/main/java/net/earthmc/emcapi/endpoint/towny/TownsEndpoint.java
+++ b/src/main/java/net/earthmc/emcapi/endpoint/towny/TownsEndpoint.java
@@ -10,11 +10,14 @@
import com.palmergames.bukkit.towny.permissions.TownyPerms;
import io.javalin.http.BadRequestResponse;
import net.earthmc.emcapi.EMCAPI;
+import net.earthmc.emcapi.integration.Integrations;
import net.earthmc.emcapi.integration.QuartersIntegration;
+import net.earthmc.emcapi.integration.WarpsIntegration;
import net.earthmc.emcapi.manager.TownMetadataManager;
import net.earthmc.emcapi.object.endpoint.PostEndpoint;
import net.earthmc.emcapi.util.EndpointUtils;
import net.earthmc.emcapi.util.JSONUtil;
+import net.earthmc.lynchpin.api.towny.warps.Warp;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
@@ -49,6 +52,7 @@ public JsonElement getJsonElement(Town town, @Nullable String key) {
townObject.addProperty("board", town.getBoard().isEmpty() ? null : town.getBoard());
townObject.addProperty("founder", town.getFounder());
townObject.addProperty("wiki", TownMetadataManager.getWikiURL(town));
+ townObject.addProperty("discord", TownMetadataManager.getDiscordURL(town));
townObject.add("mayor", EndpointUtils.getResidentJsonObject(town.getMayor()));
townObject.add("nation", EndpointUtils.getNationJsonObject(town.getNationOrNull()));
@@ -68,8 +72,10 @@ public JsonElement getJsonElement(Town town, @Nullable String key) {
statusObject.addProperty("isRuined", town.isRuined());
statusObject.addProperty("isForSale", town.isForSale());
statusObject.addProperty("hasNation", town.hasNation());
- statusObject.addProperty("hasOverclaimShield", TownMetadataManager.hasOverclaimShield(town));
statusObject.addProperty("canOutsidersSpawn", TownMetadataManager.getCanOutsidersSpawn(town));
+ statusObject.addProperty("canPassiveMobsSpawn", TownMetadataManager.getPassiveMobs(town));
+ statusObject.addProperty("hasSnowAccumulation", TownMetadataManager.getSnow(town));
+ statusObject.addProperty("hasFriendlyFire", TownMetadataManager.getFriendlyFire(town));
townObject.add("status", statusObject);
JsonObject statsObject = new JsonObject();
@@ -108,7 +114,7 @@ public JsonElement getJsonElement(Town town, @Nullable String key) {
townObject.add("trusted", EndpointUtils.getResidentArray(town.getTrustedResidents().stream().toList()));
townObject.add("outlaws", EndpointUtils.getResidentArray(town.getOutlaws().stream().toList()));
- final QuartersIntegration quartersIntegration = plugin.integrations().quartersIntegration();
+ final QuartersIntegration quartersIntegration = Integrations.getIntegration("Quarters");
JsonArray quartersArray = quartersIntegration.isEnabled() ? quartersIntegration.getQuartersArrayForTown(town) : new JsonArray();
townObject.add("quarters", quartersArray);
@@ -118,6 +124,22 @@ public JsonElement getJsonElement(Town town, @Nullable String key) {
}
townObject.add("ranks", ranksObject);
+ townObject.add("warps", getWarpsObject(town));
+
return townObject;
}
+
+ private JsonArray getWarpsObject(Town town) {
+ JsonArray json = new JsonArray();
+ WarpsIntegration integration = Integrations.getIntegration("lynchpin-towny-warps");
+ if (integration == null || !integration.isEnabled()) {
+ return json;
+ }
+
+ for (Warp warp : integration.getWarps(town)) {
+ json.add(EndpointUtils.getWarpObject(warp));
+ }
+
+ return json;
+ }
}
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/towny/list/NationsListEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/towny/list/NationsListEndpoint.java
index da99fa0..24851db 100644
--- a/src/main/java/net/earthmc/emcapi/endpoint/towny/list/NationsListEndpoint.java
+++ b/src/main/java/net/earthmc/emcapi/endpoint/towny/list/NationsListEndpoint.java
@@ -7,11 +7,6 @@
public class NationsListEndpoint extends GetEndpoint {
- @Override
- public String lookup() {
- return getJsonElement().toString();
- }
-
@Override
public JsonArray getJsonElement() {
return EndpointUtils.getNationArray(TownyAPI.getInstance().getNations());
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/towny/list/PlayersListEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/towny/list/PlayersListEndpoint.java
index 81cf5ad..f26aaf2 100644
--- a/src/main/java/net/earthmc/emcapi/endpoint/towny/list/PlayersListEndpoint.java
+++ b/src/main/java/net/earthmc/emcapi/endpoint/towny/list/PlayersListEndpoint.java
@@ -7,11 +7,6 @@
public class PlayersListEndpoint extends GetEndpoint {
- @Override
- public String lookup() {
- return getJsonElement().toString();
- }
-
@Override
public JsonArray getJsonElement() {
return EndpointUtils.getResidentArray(TownyAPI.getInstance().getResidents());
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/towny/list/QuartersListEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/towny/list/QuartersListEndpoint.java
index 7574f73..ca4ad45 100644
--- a/src/main/java/net/earthmc/emcapi/endpoint/towny/list/QuartersListEndpoint.java
+++ b/src/main/java/net/earthmc/emcapi/endpoint/towny/list/QuartersListEndpoint.java
@@ -11,11 +11,6 @@ public QuartersListEndpoint(QuartersIntegration quartersIntegration) {
this.quartersIntegration = quartersIntegration;
}
- @Override
- public String lookup() {
- return getJsonElement().toString();
- }
-
@Override
public JsonArray getJsonElement() {
return quartersIntegration.getAllQuartersArray();
diff --git a/src/main/java/net/earthmc/emcapi/endpoint/towny/list/TownsListEndpoint.java b/src/main/java/net/earthmc/emcapi/endpoint/towny/list/TownsListEndpoint.java
index 3850b1c..f6f3e96 100644
--- a/src/main/java/net/earthmc/emcapi/endpoint/towny/list/TownsListEndpoint.java
+++ b/src/main/java/net/earthmc/emcapi/endpoint/towny/list/TownsListEndpoint.java
@@ -7,11 +7,6 @@
public class TownsListEndpoint extends GetEndpoint {
- @Override
- public String lookup() {
- return getJsonElement().toString();
- }
-
@Override
public JsonArray getJsonElement() {
return EndpointUtils.getTownArray(TownyAPI.getInstance().getTowns());
diff --git a/src/main/java/net/earthmc/emcapi/integration/AdvancementsIntegration.java b/src/main/java/net/earthmc/emcapi/integration/AdvancementsIntegration.java
new file mode 100644
index 0000000..8e2d857
--- /dev/null
+++ b/src/main/java/net/earthmc/emcapi/integration/AdvancementsIntegration.java
@@ -0,0 +1,33 @@
+package net.earthmc.emcapi.integration;
+
+import net.earthmc.lynchpin.api.advancements.AdvancementEntry;
+import net.earthmc.lynchpin.api.advancements.AdvancementsProvider;
+
+import java.util.Set;
+
+public class AdvancementsIntegration extends Integration {
+ private AdvancementsProvider module;
+
+ public AdvancementsIntegration() {
+ super("Lynchpin");
+ try {
+ module = AdvancementsProvider.instance();
+ } catch (Throwable ignored) {
+ plugin.getSLF4JLogger().warn("Not loading advancements integration due to the module not being present/enabled");
+ }
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled() && module != null && module.isEnabled();
+ }
+
+ @Override
+ public void register() {
+ Integrations.addIntegration("lynchpin-advancements", this);
+ }
+
+ public Set getAdvancements() {
+ return module.getAdvancements();
+ }
+}
diff --git a/src/main/java/net/earthmc/emcapi/integration/EmbargoesIntegration.java b/src/main/java/net/earthmc/emcapi/integration/EmbargoesIntegration.java
new file mode 100644
index 0000000..89b83d9
--- /dev/null
+++ b/src/main/java/net/earthmc/emcapi/integration/EmbargoesIntegration.java
@@ -0,0 +1,35 @@
+package net.earthmc.emcapi.integration;
+
+import com.palmergames.bukkit.towny.object.Nation;
+import net.earthmc.lynchpin.api.towny.TownyProvider;
+import net.earthmc.lynchpin.api.towny.embargoes.Embargo;
+import net.earthmc.lynchpin.api.towny.embargoes.TownyEmbargo;
+
+import java.util.Set;
+
+public class EmbargoesIntegration extends Integration {
+ private TownyEmbargo module;
+
+ public EmbargoesIntegration() {
+ super("Lynchpin");
+ try {
+ this.module = TownyProvider.instance().embargoes();
+ } catch (Throwable ignored) {
+ plugin.getSLF4JLogger().warn("Not loading towny-embargoes integration due to the module not being present/enabled");
+ }
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled() && module != null && module.isEnabled();
+ }
+
+ @Override
+ public void register() {
+ Integrations.addIntegration("lynchpin-towny-embargoes", this);
+ }
+
+ public Set getEmbargoes(Nation nation) {
+ return module.getEmbargoes(nation);
+ }
+}
diff --git a/src/main/java/net/earthmc/emcapi/integration/Integration.java b/src/main/java/net/earthmc/emcapi/integration/Integration.java
index 97aea9d..b8493bb 100644
--- a/src/main/java/net/earthmc/emcapi/integration/Integration.java
+++ b/src/main/java/net/earthmc/emcapi/integration/Integration.java
@@ -1,5 +1,6 @@
package net.earthmc.emcapi.integration;
+import net.earthmc.emcapi.EMCAPI;
import net.earthmc.emcapi.util.HttpExceptions;
/**
@@ -8,11 +9,19 @@
public abstract class Integration {
private final String name;
private boolean enabled;
+ protected final EMCAPI plugin = EMCAPI.instance;
protected Integration(final String name) {
this.name = name;
}
+ /**
+ * Allow overriding to use a custom identifier. {@code name()} will still be used for plugin names.
+ */
+ public void register() {
+ Integrations.addIntegration(name(), this);
+ }
+
/**
* {@return the name of the plugin that this is integrating with}
*/
@@ -32,7 +41,7 @@ public void setEnabled(boolean enabled) {
}
public void throwIfDisabled() {
- if (!this.enabled) {
+ if (!isEnabled()) {
throw HttpExceptions.MISSING_PLUGIN;
}
}
diff --git a/src/main/java/net/earthmc/emcapi/integration/Integrations.java b/src/main/java/net/earthmc/emcapi/integration/Integrations.java
index 075ef3c..aea88cf 100644
--- a/src/main/java/net/earthmc/emcapi/integration/Integrations.java
+++ b/src/main/java/net/earthmc/emcapi/integration/Integrations.java
@@ -10,55 +10,24 @@
import java.util.concurrent.ConcurrentHashMap;
public class Integrations implements Listener {
- private final EMCAPI plugin;
+ private static final Map INTEGRATIONS = new ConcurrentHashMap<>();
+ private static final Map PLUGIN_INTEGRATION_MAP = new ConcurrentHashMap<>();
- private final Map integrations = new ConcurrentHashMap<>();
- private final DiscordIntegration discordIntegration;
- private final QuartersIntegration quartersIntegration;
- private final SuperbVoteIntegration superbVoteIntegration;
- private final MysteryMasterIntegration mysteryMasterIntegration;
- private final QuickShopIntegration quickShopIntegration;
-
- public Integrations(final EMCAPI plugin) {
- this.plugin = plugin;
-
- this.discordIntegration = addIntegration(new DiscordIntegration());
- this.quartersIntegration = addIntegration(new QuartersIntegration());
- this.superbVoteIntegration = addIntegration(new SuperbVoteIntegration());
- this.mysteryMasterIntegration = addIntegration(new MysteryMasterIntegration());
- this.quickShopIntegration = addIntegration(new QuickShopIntegration());
- }
-
- private T addIntegration(final T integration) {
- integrations.put(integration.name(), integration);
-
- integration.setEnabled(plugin.getServer().getPluginManager().isPluginEnabled(integration.name()));
- return integration;
- }
-
- public DiscordIntegration discordIntegration() {
- return this.discordIntegration;
- }
-
- public QuartersIntegration quartersIntegration() {
- return this.quartersIntegration;
+ @SuppressWarnings("unchecked")
+ public static T getIntegration(String name) {
+ return (T) INTEGRATIONS.get(name);
}
- public SuperbVoteIntegration superbVoteIntegration() {
- return this.superbVoteIntegration;
- }
-
- public MysteryMasterIntegration mysteryMasterIntegration() {
- return this.mysteryMasterIntegration;
- }
+ public static void addIntegration(String identifier, Integration integration) {
+ INTEGRATIONS.put(identifier, integration);
+ PLUGIN_INTEGRATION_MAP.put(integration.name(), integration);
- public QuickShopIntegration quickShopIntegration() {
- return quickShopIntegration;
+ integration.setEnabled(EMCAPI.instance.getServer().getPluginManager().isPluginEnabled(integration.name()));
}
@EventHandler
public void onPluginEnable(final PluginEnableEvent event) {
- final Integration integration = integrations.get(event.getPlugin().getName());
+ final Integration integration = PLUGIN_INTEGRATION_MAP.get(event.getPlugin().getName());
if (integration != null) {
integration.setEnabled(true);
}
@@ -66,7 +35,7 @@ public void onPluginEnable(final PluginEnableEvent event) {
@EventHandler
public void onPluginDisable(final PluginDisableEvent event) {
- final Integration integration = integrations.get(event.getPlugin().getName());
+ final Integration integration = PLUGIN_INTEGRATION_MAP.get(event.getPlugin().getName());
if (integration != null) {
integration.setEnabled(false);
}
diff --git a/src/main/java/net/earthmc/emcapi/integration/McMMOIntegration.java b/src/main/java/net/earthmc/emcapi/integration/McMMOIntegration.java
new file mode 100644
index 0000000..50e0e72
--- /dev/null
+++ b/src/main/java/net/earthmc/emcapi/integration/McMMOIntegration.java
@@ -0,0 +1,17 @@
+package net.earthmc.emcapi.integration;
+
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.mcMMO;
+
+import java.util.UUID;
+
+public class McMMOIntegration extends Integration {
+
+ public McMMOIntegration() {
+ super("mcMMO");
+ }
+
+ public PlayerProfile getPlayerProfile(UUID uuid) {
+ return mcMMO.getDatabaseManager().loadPlayerProfile(uuid);
+ }
+}
diff --git a/src/main/java/net/earthmc/emcapi/integration/PactsIntegration.java b/src/main/java/net/earthmc/emcapi/integration/PactsIntegration.java
new file mode 100644
index 0000000..63bf922
--- /dev/null
+++ b/src/main/java/net/earthmc/emcapi/integration/PactsIntegration.java
@@ -0,0 +1,39 @@
+package net.earthmc.emcapi.integration;
+
+import com.palmergames.bukkit.towny.object.Nation;
+import net.earthmc.lynchpin.api.towny.TownyProvider;
+import net.earthmc.lynchpin.api.towny.pacts.Pact;
+import net.earthmc.lynchpin.api.towny.pacts.TownyPacts;
+
+import java.util.List;
+
+public class PactsIntegration extends Integration {
+ private TownyPacts module;
+
+ public PactsIntegration() {
+ super("Lynchpin");
+ try {
+ this.module = TownyProvider.instance().pacts();
+ } catch (Throwable ignored) {
+ plugin.getSLF4JLogger().warn("Not loading towny-pacts integration due to the module not being present/enabled");
+ }
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled() && module != null && module.isEnabled();
+ }
+
+ @Override
+ public void register() {
+ Integrations.addIntegration("lynchpin-towny-pacts", this);
+ }
+
+ public List getActivePacts(Nation nation) {
+ return module.getActivePacts(nation);
+ }
+
+ public List getPendingPacts(Nation nation) {
+ return module.getPendingPacts(nation);
+ }
+}
diff --git a/src/main/java/net/earthmc/emcapi/integration/PursuitsIntegration.java b/src/main/java/net/earthmc/emcapi/integration/PursuitsIntegration.java
new file mode 100644
index 0000000..6fd88e3
--- /dev/null
+++ b/src/main/java/net/earthmc/emcapi/integration/PursuitsIntegration.java
@@ -0,0 +1,37 @@
+package net.earthmc.emcapi.integration;
+
+import net.earthmc.lynchpin.api.pursuits.Pursuit;
+import net.earthmc.lynchpin.api.pursuits.PursuitsProvider;
+
+import java.util.Map;
+
+public class PursuitsIntegration extends Integration {
+ private PursuitsProvider pursuits;
+
+ public PursuitsIntegration() {
+ super("Lynchpin");
+ try {
+ pursuits = PursuitsProvider.instance();
+ } catch (Throwable ignored) {
+ plugin.getSLF4JLogger().warn("Not loading pursuits integration due to the module not being present/enabled");
+ }
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled() && pursuits != null && pursuits.isEnabled();
+ }
+
+ @Override
+ public void register() {
+ Integrations.addIntegration("lynchpin-pursuits", this);
+ }
+
+ public Pursuit getPursuit(Pursuit.Type type) {
+ return pursuits.getPursuit(type);
+ }
+
+ public Map getPursuits() {
+ return pursuits.getPursuits();
+ }
+}
diff --git a/src/main/java/net/earthmc/emcapi/integration/WarpsIntegration.java b/src/main/java/net/earthmc/emcapi/integration/WarpsIntegration.java
new file mode 100644
index 0000000..3799ffd
--- /dev/null
+++ b/src/main/java/net/earthmc/emcapi/integration/WarpsIntegration.java
@@ -0,0 +1,35 @@
+package net.earthmc.emcapi.integration;
+
+import com.palmergames.bukkit.towny.object.Town;
+import net.earthmc.lynchpin.api.towny.TownyProvider;
+import net.earthmc.lynchpin.api.towny.warps.TownyWarps;
+import net.earthmc.lynchpin.api.towny.warps.Warp;
+
+import java.util.Set;
+
+public class WarpsIntegration extends Integration {
+ private TownyWarps module;
+
+ public WarpsIntegration() {
+ super("Lynchpin");
+ try {
+ this.module = TownyProvider.instance().warps();
+ } catch (Throwable ignored) {
+ plugin.getSLF4JLogger().warn("Not loading towny-warps integration due to the module not being present/enabled");
+ }
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return super.isEnabled() && module != null && module.isEnabled();
+ }
+
+ @Override
+ public void register() {
+ Integrations.addIntegration("lynchpin-towny-warps", this);
+ }
+
+ public Set getWarps(Town town) {
+ return module.getWarps(town);
+ }
+}
diff --git a/src/main/java/net/earthmc/emcapi/manager/EndpointManager.java b/src/main/java/net/earthmc/emcapi/manager/EndpointManager.java
index b69ea75..c56e71f 100644
--- a/src/main/java/net/earthmc/emcapi/manager/EndpointManager.java
+++ b/src/main/java/net/earthmc/emcapi/manager/EndpointManager.java
@@ -12,6 +12,10 @@
import net.earthmc.emcapi.endpoint.OnlineEndpoint;
import net.earthmc.emcapi.endpoint.ServerEndpoint;
import net.earthmc.emcapi.endpoint.ShopEndpoint;
+import net.earthmc.emcapi.endpoint.McMMOEndpoint;
+import net.earthmc.emcapi.endpoint.McMMOTopEndpoint;
+import net.earthmc.emcapi.endpoint.PursuitsEndpoint;
+import net.earthmc.emcapi.endpoint.AdvancementsEndpoint;
import net.earthmc.emcapi.endpoint.towny.NationsEndpoint;
import net.earthmc.emcapi.endpoint.towny.PlayersEndpoint;
import net.earthmc.emcapi.endpoint.towny.QuartersEndpoint;
@@ -20,9 +24,7 @@
import net.earthmc.emcapi.endpoint.towny.list.PlayersListEndpoint;
import net.earthmc.emcapi.endpoint.towny.list.QuartersListEndpoint;
import net.earthmc.emcapi.endpoint.towny.list.TownsListEndpoint;
-import net.earthmc.emcapi.integration.MysteryMasterIntegration;
-import net.earthmc.emcapi.integration.QuartersIntegration;
-import net.earthmc.emcapi.integration.QuickShopIntegration;
+import net.earthmc.emcapi.integration.*;
import net.earthmc.emcapi.util.JSONUtil;
import org.jetbrains.annotations.Nullable;
@@ -39,6 +41,8 @@ public EndpointManager(EMCAPI plugin) {
}
public void loadEndpoints() {
+ new SuperbVoteIntegration().register(); // Register integrations for usage in ServerEndpoint
+ new QuartersIntegration().register();
ServerEndpoint serverEndpoint = new ServerEndpoint(plugin);
javalin.get(URLPath, ctx -> ctx.json(serverEndpoint.lookup()));
@@ -51,6 +55,9 @@ public void loadEndpoints() {
loadOnlinePlayersEndpoint();
loadMysteryMasterEndpoint();
loadShopsEndpoint();
+ loadmcMMoEndpoint();
+ loadPursuitsEndpoint();
+ loadAdvancementsEndpoint();
}
private QueryBody parseBody(String body) {
@@ -76,6 +83,7 @@ private void loadPlayersEndpoint() {
PlayersListEndpoint ple = new PlayersListEndpoint();
javalin.get(URLPath + "/players", ctx -> ctx.json(ple.lookup()));
+ new DiscordIntegration().register(); // Load the discord integration to check if DiscordSRV is enabled - checked when including discord for player
PlayersEndpoint playersEndpoint = new PlayersEndpoint(plugin);
javalin.post(URLPath + "/players", ctx -> {
QueryBody parsedBody = parseBody(ctx.body());
@@ -87,6 +95,7 @@ private void loadTownsEndpoint() {
TownsListEndpoint tle = new TownsListEndpoint();
javalin.get(URLPath + "/towns", ctx -> ctx.json(tle.lookup()));
+ new WarpsIntegration().register();
TownsEndpoint townsEndpoint = new TownsEndpoint(plugin);
javalin.post(URLPath + "/towns", ctx -> {
QueryBody parsedBody = parseBody(ctx.body());
@@ -98,6 +107,8 @@ private void loadNationsEndpoint() {
NationsListEndpoint nle = new NationsListEndpoint();
javalin.get(URLPath + "/nations", ctx -> ctx.json(nle.lookup()));
+ new EmbargoesIntegration().register();
+ new PactsIntegration().register();
NationsEndpoint nationsEndpoint = new NationsEndpoint(plugin);
javalin.post(URLPath + "/nations", ctx -> {
QueryBody parsedBody = parseBody(ctx.body());
@@ -106,7 +117,7 @@ private void loadNationsEndpoint() {
}
private void loadQuartersEndpoint() {
- QuartersIntegration quartersIntegration = plugin.integrations().quartersIntegration();
+ QuartersIntegration quartersIntegration = Integrations.getIntegration("Quarters");
QuartersListEndpoint qle = new QuartersListEndpoint(quartersIntegration);
javalin.get(URLPath + "/quarters", ctx -> {
@@ -144,8 +155,9 @@ private void loadOnlinePlayersEndpoint() {
}
private void loadMysteryMasterEndpoint() {
+ MysteryMasterIntegration mysteryMasterIntegration = new MysteryMasterIntegration();
+ mysteryMasterIntegration.register();
MysteryMasterEndpoint mysteryMasterEndpoint = new MysteryMasterEndpoint(plugin);
- MysteryMasterIntegration mysteryMasterIntegration = plugin.integrations().mysteryMasterIntegration();
javalin.get(URLPath + "/mm", ctx -> {
mysteryMasterIntegration.throwIfDisabled();
@@ -154,8 +166,9 @@ private void loadMysteryMasterEndpoint() {
}
private void loadShopsEndpoint() {
+ QuickShopIntegration quickShopIntegration = new QuickShopIntegration();
+ quickShopIntegration.register();
ShopEndpoint shopEndpoint = new ShopEndpoint(plugin);
- QuickShopIntegration quickShopIntegration = plugin.integrations().quickShopIntegration();;
javalin.post(URLPath + "/shop", ctx -> {
quickShopIntegration.throwIfDisabled();
@@ -163,4 +176,47 @@ private void loadShopsEndpoint() {
ctx.json(shopEndpoint.lookup(parsedBody.query, parsedBody.template, parsedBody.key));
});
}
+
+ private void loadmcMMoEndpoint() {
+ McMMOIntegration mcMMOIntegration = new McMMOIntegration();
+ mcMMOIntegration.register();
+ McMMOEndpoint mcMMOEndpoint = new McMMOEndpoint(plugin);
+ javalin.post(URLPath + "/mcmmo", ctx -> {
+ mcMMOIntegration.throwIfDisabled();
+
+ QueryBody parsedBody = parseBody(ctx.body());
+ ctx.json(mcMMOEndpoint.lookup(parsedBody.query, parsedBody.template, parsedBody.key));
+ });
+
+ McMMOTopEndpoint mcMMOTopEndpoint = new McMMOTopEndpoint(plugin);
+ javalin.post(URLPath + "/mcmmo-top", ctx -> {
+ mcMMOIntegration.throwIfDisabled();
+
+ QueryBody parsedBody = parseBody(ctx.body());
+ ctx.json(mcMMOTopEndpoint.lookup(parsedBody.query, parsedBody.template, parsedBody.key));
+ });
+ }
+
+ private void loadPursuitsEndpoint() {
+ PursuitsIntegration pursuitsIntegration = new PursuitsIntegration();
+ pursuitsIntegration.register();
+ PursuitsEndpoint pursuitsEndpoint = new PursuitsEndpoint(plugin);
+ javalin.post(URLPath + "/pursuits", ctx -> {
+ pursuitsIntegration.throwIfDisabled();
+
+ QueryBody parsedBody = parseBody(ctx.body());
+ ctx.json(pursuitsEndpoint.lookup(parsedBody.query, parsedBody.template, parsedBody.key));
+ });
+ }
+
+ private void loadAdvancementsEndpoint() {
+ AdvancementsIntegration advancementsIntegration = new AdvancementsIntegration();
+ advancementsIntegration.register();
+ AdvancementsEndpoint advancementsEndpoint = new AdvancementsEndpoint();
+ javalin.get(URLPath + "/advancements", ctx -> {
+ advancementsIntegration.throwIfDisabled();
+
+ ctx.json(advancementsEndpoint.lookup());
+ });
+ }
}
diff --git a/src/main/java/net/earthmc/emcapi/manager/LegacyEndpointManager.java b/src/main/java/net/earthmc/emcapi/manager/LegacyEndpointManager.java
deleted file mode 100644
index c8c17a6..0000000
--- a/src/main/java/net/earthmc/emcapi/manager/LegacyEndpointManager.java
+++ /dev/null
@@ -1,168 +0,0 @@
-package net.earthmc.emcapi.manager;
-
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import io.javalin.Javalin;
-import io.javalin.http.BadRequestResponse;
-import kotlin.Pair;
-import net.earthmc.emcapi.EMCAPI;
-import net.earthmc.emcapi.endpoint.legacy.DiscordEndpoint;
-import net.earthmc.emcapi.endpoint.legacy.DocumentationEndpoint;
-import net.earthmc.emcapi.endpoint.LocationEndpoint;
-import net.earthmc.emcapi.endpoint.legacy.MudkipEndpoint;
-import net.earthmc.emcapi.endpoint.MysteryMasterEndpoint;
-import net.earthmc.emcapi.endpoint.NearbyEndpoint;
-import net.earthmc.emcapi.endpoint.OnlineEndpoint;
-import net.earthmc.emcapi.endpoint.ServerEndpoint;
-import net.earthmc.emcapi.endpoint.towny.NationsEndpoint;
-import net.earthmc.emcapi.endpoint.towny.PlayersEndpoint;
-import net.earthmc.emcapi.endpoint.towny.QuartersEndpoint;
-import net.earthmc.emcapi.endpoint.towny.TownsEndpoint;
-import net.earthmc.emcapi.endpoint.towny.list.NationsListEndpoint;
-import net.earthmc.emcapi.endpoint.towny.list.PlayersListEndpoint;
-import net.earthmc.emcapi.endpoint.towny.list.QuartersListEndpoint;
-import net.earthmc.emcapi.endpoint.towny.list.TownsListEndpoint;
-import net.earthmc.emcapi.integration.DiscordIntegration;
-import net.earthmc.emcapi.integration.QuartersIntegration;
-import net.earthmc.emcapi.util.JSONUtil;
-
-public class LegacyEndpointManager {
-
- private final EMCAPI plugin;
- private final Javalin javalin;
- private final String URLPath = "v3/aurora";
-
- public LegacyEndpointManager(EMCAPI plugin) {
- this.plugin = plugin;
- this.javalin = plugin.getJavalin();
- }
-
- public void loadEndpoints() {
- DocumentationEndpoint documentationEndpoint = new DocumentationEndpoint();
- javalin.get("/", ctx -> ctx.json(documentationEndpoint.lookup()));
-
- ServerEndpoint serverEndpoint = new ServerEndpoint(plugin);
- javalin.get(URLPath, ctx -> ctx.json(serverEndpoint.lookup()));
-
- MysteryMasterEndpoint mysteryMasterEndpoint = new MysteryMasterEndpoint(plugin);
- javalin.get(URLPath + "/mm", ctx -> {
- plugin.integrations().mysteryMasterIntegration().throwIfDisabled();
- ctx.json(mysteryMasterEndpoint.lookup());
- });
-
- MudkipEndpoint mudkipEndpoint = new MudkipEndpoint();
- javalin.get("/mudkip", ctx -> {
- ctx.contentType("text/plain; charset=UTF-8");
- ctx.result(mudkipEndpoint.lookup());
- });
-
- loadPlayersEndpoint();
- loadTownsEndpoint();
- loadNationsEndpoint();
- loadQuartersEndpoint();
- loadLocationEndpoint();
- loadNearbyEndpoint();
- loadDiscordEndpoint();
- loadOnlinePlayersEndpoint();
- }
-
- private Pair parseBody(String body) {
- JsonObject jsonObject = JSONUtil.getJsonObjectFromString(body);
-
- JsonElement queryElement = jsonObject.get("query");
- if (queryElement == null) throw new BadRequestResponse("No query array provided");
- if (!queryElement.isJsonArray()) throw new BadRequestResponse("Provided query is not an array");
- JsonArray queryArray = queryElement.getAsJsonArray();
-
- JsonElement templateElement = jsonObject.get("template");
- JsonObject templateObject = templateElement != null && templateElement.isJsonObject()
- ? templateElement.getAsJsonObject()
- : null;
-
- return new Pair<>(queryArray, templateObject);
- }
-
- private void loadPlayersEndpoint() {
- PlayersListEndpoint ple = new PlayersListEndpoint();
- javalin.get(URLPath + "/players", ctx -> ctx.json(ple.lookup()));
-
- PlayersEndpoint playersEndpoint = new PlayersEndpoint(plugin);
- javalin.post(URLPath + "/players", ctx -> {
- Pair parsedBody = parseBody(ctx.body());
- ctx.json(playersEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond(), null));
- });
- }
-
- private void loadTownsEndpoint() {
- TownsListEndpoint tle = new TownsListEndpoint();
- javalin.get(URLPath + "/towns", ctx -> ctx.json(tle.lookup()));
-
- TownsEndpoint townsEndpoint = new TownsEndpoint(plugin);
- javalin.post(URLPath + "/towns", ctx -> {
- Pair parsedBody = parseBody(ctx.body());
- ctx.json(townsEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond(), null));
- });
- }
-
- private void loadNationsEndpoint() {
- NationsListEndpoint nle = new NationsListEndpoint();
- javalin.get(URLPath + "/nations", ctx -> ctx.json(nle.lookup()));
-
- NationsEndpoint nationsEndpoint = new NationsEndpoint(plugin);
- javalin.post(URLPath + "/nations", ctx -> {
- Pair parsedBody = parseBody(ctx.body());
- ctx.json(nationsEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond(), null));
- });
- }
-
- private void loadQuartersEndpoint() {
- QuartersIntegration quartersIntegration = plugin.integrations().quartersIntegration();
- QuartersListEndpoint qle = new QuartersListEndpoint(quartersIntegration);
-
- javalin.get(URLPath + "/quarters", ctx -> {
- quartersIntegration.throwIfDisabled();
- ctx.json(qle.lookup());
- });
-
- QuartersEndpoint quartersEndpoint = new QuartersEndpoint(plugin);
- javalin.post(URLPath + "/quarters", ctx -> {
- quartersIntegration.throwIfDisabled();
- Pair parsedBody = parseBody(ctx.body());
- ctx.json(quartersEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond(), null));
- });
- }
-
- private void loadLocationEndpoint() {
- LocationEndpoint locationEndpoint = new LocationEndpoint(plugin);
- javalin.post(URLPath + "/location", ctx -> {
- Pair parsedBody = parseBody(ctx.body());
- ctx.json(locationEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond(), null));
- });
- }
-
- private void loadNearbyEndpoint() {
- NearbyEndpoint nearbyEndpoint = new NearbyEndpoint(plugin);
- javalin.post(URLPath + "/nearby", ctx -> {
- Pair parsedBody = parseBody(ctx.body());
- ctx.json(nearbyEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond(), null));
- });
- }
-
- private void loadDiscordEndpoint() {
- DiscordEndpoint discordEndpoint = new DiscordEndpoint(plugin);
- final DiscordIntegration discordIntegration = plugin.integrations().discordIntegration();
-
- javalin.post(URLPath + "/discord", ctx -> {
- discordIntegration.throwIfDisabled();
-
- Pair parsedBody = parseBody(ctx.body());
- ctx.json(discordEndpoint.lookup(parsedBody.getFirst(), parsedBody.getSecond(), null));
- });
- }
-
- private void loadOnlinePlayersEndpoint() {
- OnlineEndpoint onlineEndpoint = new OnlineEndpoint();
- javalin.get(URLPath + "/online", ctx -> ctx.json(onlineEndpoint.lookup()));
- }
-}
diff --git a/src/main/java/net/earthmc/emcapi/manager/NationMetadataManager.java b/src/main/java/net/earthmc/emcapi/manager/NationMetadataManager.java
index 97e3fe6..7037c40 100644
--- a/src/main/java/net/earthmc/emcapi/manager/NationMetadataManager.java
+++ b/src/main/java/net/earthmc/emcapi/manager/NationMetadataManager.java
@@ -7,6 +7,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.UUID;
public class NationMetadataManager {
@@ -14,35 +15,29 @@ public class NationMetadataManager {
private static final String DEFAULT_DYNMAP_COLOUR = "3FB4FF";
public static String getDynmapColour(Nation nation) {
- StringDataField sdf = (StringDataField) nation.getMetadata("dynmapColour");
- if (sdf == null) return DEFAULT_DYNMAP_COLOUR;
-
- return sdf.getValue().toUpperCase();
+ return Objects.requireNonNullElse(getStringField(nation, "dynmapColour"), DEFAULT_DYNMAP_COLOUR).toUpperCase();
}
public static String getDynmapOutline(Nation nation) {
- StringDataField sdf = (StringDataField) nation.getMetadata("dynmapOutline");
- if (sdf == null) return DEFAULT_DYNMAP_COLOUR;
-
- return sdf.getValue().toUpperCase();
+ return Objects.requireNonNullElse(getStringField(nation, "dynmapOutline"), DEFAULT_DYNMAP_COLOUR).toUpperCase();
}
public static String getWikiURL(Nation nation) {
- StringDataField sdf = (StringDataField) nation.getMetadata("wiki_url");
- if (sdf == null)
- return null;
+ return getStringField(nation, "wiki_url");
+ }
- return sdf.getValue();
+ public static String getDiscordURL(Nation nation) {
+ return getStringField(nation, "discord_url");
}
public static List getNationOutlaws(Nation nation) {
if (nation == null) return List.of();
- StringDataField sdf = (StringDataField) nation.getMetadata("townycommandaddons_nation_outlaws");
- if (sdf == null || sdf.getValue() == null || sdf.getValue().isEmpty()) return List.of();
+ String value = getStringField(nation, "townycommandaddons_nation_outlaws");
+ if (value == null || value.isEmpty()) return List.of();
List outlawedResidents = new ArrayList<>();
- for (String string : sdf.getValue().split(",")) {
+ for (String string : value.split(",")) {
Resident resident = TownyAPI.getInstance().getResident(UUID.fromString(string));
if (resident == null) continue;
@@ -50,4 +45,12 @@ public static List getNationOutlaws(Nation nation) {
}
return outlawedResidents;
}
+
+ private static String getStringField(Nation nation, String field) {
+ if (nation.getMetadata(field) instanceof StringDataField sdf) {
+ return sdf.getValue();
+ }
+
+ return null;
+ }
}
diff --git a/src/main/java/net/earthmc/emcapi/manager/TownMetadataManager.java b/src/main/java/net/earthmc/emcapi/manager/TownMetadataManager.java
index 2bd4f62..6e30a77 100644
--- a/src/main/java/net/earthmc/emcapi/manager/TownMetadataManager.java
+++ b/src/main/java/net/earthmc/emcapi/manager/TownMetadataManager.java
@@ -1,32 +1,50 @@
package net.earthmc.emcapi.manager;
import com.palmergames.bukkit.towny.object.Town;
-import com.palmergames.bukkit.towny.object.metadata.BooleanDataField;
-import com.palmergames.bukkit.towny.object.metadata.StringDataField;
+import com.palmergames.bukkit.towny.object.metadata.CustomDataField;
+
+import java.util.Objects;
public class TownMetadataManager {
public static boolean hasOverclaimShield(Town town) {
- BooleanDataField bdf = (BooleanDataField) town.getMetadata("os_hasShield");
- if (bdf == null)
- return false;
-
- return bdf.getValue();
+ return Objects.requireNonNullElse(getField(town, "os_hasShield", Boolean.class), false);
}
public static boolean getCanOutsidersSpawn(Town town) {
- BooleanDataField bdf = (BooleanDataField) town.getMetadata("bspawn_canoutsidersspawn");
- if (bdf == null)
- return false;
-
- return bdf.getValue();
+ return Objects.requireNonNullElse(getField(town, "bspawn_canoutsidersspawn", Boolean.class), false);
}
public static String getWikiURL(Town town) {
- StringDataField sdf = (StringDataField) town.getMetadata("wiki_url");
- if (sdf == null)
+ return getField(town, "wiki_url", String.class);
+ }
+
+ public static String getDiscordURL(Town town) {
+ return getField(town, "discord_url", String.class);
+ }
+
+ public static boolean getPassiveMobs(Town town) {
+ return Objects.requireNonNullElse(getField(town, "passive_mobs", Boolean.class), true);
+ }
+
+ public static boolean getSnow(Town town) {
+ return Objects.requireNonNullElse(getField(town, "accumulate_snow", Boolean.class), true);
+ }
+
+ public static boolean getFriendlyFire(Town town) {
+ return Objects.requireNonNullElse(getField(town, "friendly_fire", Boolean.class), false);
+ }
+
+ private static T getField(Town town, String field, Class type) {
+ CustomDataField> dataField = town.getMetadata(field);
+
+ if (dataField == null)
+ return null;
+
+ Object value = dataField.getValue();
+ if (!type.isInstance(value))
return null;
- return sdf.getValue();
+ return type.cast(value);
}
}
diff --git a/src/main/java/net/earthmc/emcapi/object/endpoint/GetEndpoint.java b/src/main/java/net/earthmc/emcapi/object/endpoint/GetEndpoint.java
index bb327ae..00959a5 100644
--- a/src/main/java/net/earthmc/emcapi/object/endpoint/GetEndpoint.java
+++ b/src/main/java/net/earthmc/emcapi/object/endpoint/GetEndpoint.java
@@ -4,7 +4,9 @@
public abstract class GetEndpoint {
- public abstract String lookup();
+ public final String lookup() {
+ return getJsonElement().toString();
+ }
public abstract JsonElement getJsonElement();
}
diff --git a/src/main/java/net/earthmc/emcapi/util/CooldownUtil.java b/src/main/java/net/earthmc/emcapi/util/CooldownUtil.java
new file mode 100644
index 0000000..b89a545
--- /dev/null
+++ b/src/main/java/net/earthmc/emcapi/util/CooldownUtil.java
@@ -0,0 +1,28 @@
+package net.earthmc.emcapi.util;
+
+import io.javalin.http.TooManyRequestsResponse;
+
+import java.time.Instant;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class CooldownUtil {
+ private static final Map> COOLDOWNS = new ConcurrentHashMap<>();
+
+ public static void refresh() {
+ long now = Instant.now().getEpochSecond();
+ COOLDOWNS.values().forEach((map) -> map.entrySet().removeIf(entry -> entry.getValue() < now));
+ }
+
+ public static void checkAndAddCooldownOrThrow(String type, String id, long seconds) {
+ long now = Instant.now().getEpochSecond();
+ Map cooldowns = COOLDOWNS.get(type);
+ if (cooldowns != null && cooldowns.containsKey(id)) {
+ long timeUntilNext = cooldowns.get(id) - now;
+ if (timeUntilNext > 0) {
+ throw new TooManyRequestsResponse("Too Many Requests. Try again in " + timeUntilNext + " second(s)");
+ }
+ }
+ COOLDOWNS.computeIfAbsent(type, k -> new ConcurrentHashMap<>()).put(id, now + seconds);
+ }
+}
diff --git a/src/main/java/net/earthmc/emcapi/util/EndpointUtils.java b/src/main/java/net/earthmc/emcapi/util/EndpointUtils.java
index b365f00..ab75584 100644
--- a/src/main/java/net/earthmc/emcapi/util/EndpointUtils.java
+++ b/src/main/java/net/earthmc/emcapi/util/EndpointUtils.java
@@ -8,6 +8,8 @@
import com.palmergames.bukkit.towny.object.Town;
import com.palmergames.bukkit.towny.object.TownyPermission;
import net.earthmc.emcapi.EMCAPI;
+import net.earthmc.lynchpin.api.towny.pacts.Pact;
+import net.earthmc.lynchpin.api.towny.warps.Warp;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
@@ -175,6 +177,7 @@ public static JsonObject generateNameUUIDJsonObject(String name, UUID uuid) {
public static JsonObject getShopObject(Shop shop) {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("id", shop.getShopId());
+ jsonObject.addProperty("owner", String.valueOf(shop.getOwner().getUniqueId()));
jsonObject.addProperty("item", shop.getItem().getType().name());
jsonObject.addProperty("price", shop.getPrice());
jsonObject.addProperty("amount", shop.getItem().getAmount());
@@ -190,4 +193,42 @@ public static JsonObject getShopObject(Shop shop) {
jsonObject.add("location", locationObject);
return jsonObject;
}
+
+ public static JsonObject getPactObject(Pact pact) {
+ JsonObject json = new JsonObject();
+
+ json.addProperty("sender", pact.getSenderNation().getName());
+ json.addProperty("receiver", pact.getReceivingNation().getName());
+ json.addProperty("status", pact.status().name());
+
+ JsonObject stats = new JsonObject();
+ stats.addProperty("createdAt", pact.getCreatedAt());
+ stats.addProperty("expiresAt", pact.getExpiresAt());
+ stats.addProperty("duration", pact.getDuration());
+ json.add("stats", stats);
+
+ return json;
+ }
+
+ public static JsonObject getWarpObject(Warp warp) {
+ JsonObject json = new JsonObject();
+
+ json.addProperty("name", warp.getName());
+ json.addProperty("uuid", warp.getUUID().toString());
+ json.addProperty("createdAt", warp.getCreatedAt());
+ json.addProperty("createdBy", warp.getCreatedBy());
+ json.addProperty("access", warp.getAccesslevel().name());
+
+ try {
+ Location location = warp.getLocation();
+ JsonObject locationObject = new JsonObject();
+ locationObject.addProperty("x", location.getBlockX());
+ locationObject.addProperty("y", location.getBlockY());
+ locationObject.addProperty("z", location.getBlockZ());
+
+ json.add("location", locationObject);
+ } catch (Throwable ignored) {} // getLocation() may throw an exception if the world couldn't be fetched
+
+ return json;
+ }
}
diff --git a/src/main/java/net/earthmc/emcapi/util/HttpExceptions.java b/src/main/java/net/earthmc/emcapi/util/HttpExceptions.java
index 8667cfb..4bb5fd1 100644
--- a/src/main/java/net/earthmc/emcapi/util/HttpExceptions.java
+++ b/src/main/java/net/earthmc/emcapi/util/HttpExceptions.java
@@ -1,9 +1,13 @@
package net.earthmc.emcapi.util;
import io.javalin.http.BadRequestResponse;
+import io.javalin.http.ForbiddenResponse;
import io.javalin.http.ServiceUnavailableResponse;
+import io.javalin.http.UnauthorizedResponse;
public class HttpExceptions {
public static final BadRequestResponse NOT_A_JSON_OBJECT = new BadRequestResponse("Your query contains a value that is not a JSON object");
public static final ServiceUnavailableResponse MISSING_PLUGIN = new ServiceUnavailableResponse("This endpoint is unavailable due to a missing plugin dependency.");
+ public static final UnauthorizedResponse MISSING_API_KEY = new UnauthorizedResponse("Could not find an owner for this API key");
+ public static final ForbiddenResponse FORBIDDEN = new ForbiddenResponse("This API key is not owned by the player queried");
}
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 1052d93..eabdfc0 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -3,7 +3,7 @@ version: "${version}"
main: net.earthmc.emcapi.EMCAPI
api-version: "1.20"
depend: [Towny]
-softdepend: [MysteryMaster, Quarters, DiscordSRV, SuperbVote, QuickShop-Hikari]
+softdepend: [MysteryMaster, Quarters, DiscordSRV, SuperbVote, QuickShop-Hikari, Lynchpin, mcMMO]
authors: [Fruitloopins]
contributors: [Warriorrr, Yoditi, Veyronity]
description: API for EarthMC using Javalin