From bd088c83a26d13c50f251b09d2f4eb2196efcc3a Mon Sep 17 00:00:00 2001 From: Veyronity Date: Fri, 22 May 2026 19:30:04 +0300 Subject: [PATCH 01/10] 1. General updates CooldownUtil, removal of legacy endpoints, cleaning up GetEndpoint#lookup overrides --- build.gradle.kts | 11 ++ gradle/libs.versions.toml | 6 + src/main/java/net/earthmc/emcapi/EMCAPI.java | 17 +- .../endpoint/MysteryMasterEndpoint.java | 5 - .../emcapi/endpoint/OnlineEndpoint.java | 5 - .../emcapi/endpoint/ServerEndpoint.java | 10 +- .../earthmc/emcapi/endpoint/ShopEndpoint.java | 31 +--- .../legacy/DocumentationEndpoint.java | 33 ---- .../endpoint/towny/PlayersEndpoint.java | 5 +- .../towny/list/NationsListEndpoint.java | 5 - .../towny/list/PlayersListEndpoint.java | 5 - .../towny/list/QuartersListEndpoint.java | 5 - .../towny/list/TownsListEndpoint.java | 5 - .../emcapi/integration/Integration.java | 12 +- .../emcapi/integration/Integrations.java | 51 +----- .../emcapi/manager/EndpointManager.java | 66 +++++-- .../emcapi/manager/LegacyEndpointManager.java | 168 ------------------ .../emcapi/object/endpoint/GetEndpoint.java | 4 +- .../net/earthmc/emcapi/util/CooldownUtil.java | 28 +++ .../earthmc/emcapi/util/HttpExceptions.java | 4 + src/main/resources/plugin.yml | 2 +- 21 files changed, 147 insertions(+), 331 deletions(-) delete mode 100644 src/main/java/net/earthmc/emcapi/endpoint/legacy/DocumentationEndpoint.java delete mode 100644 src/main/java/net/earthmc/emcapi/manager/LegacyEndpointManager.java create mode 100644 src/main/java/net/earthmc/emcapi/util/CooldownUtil.java 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/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/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/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/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/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/Integration.java b/src/main/java/net/earthmc/emcapi/integration/Integration.java index 97aea9d..cb187aa 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,9 +9,18 @@ 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; + register(); + } + + /** + * Allow overriding to use a custom identifier. {@code name()} will still be used for plugin names. + */ + public void register() { + Integrations.addIntegration(name(), this); } /** @@ -32,7 +42,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..c1c599c 100644 --- a/src/main/java/net/earthmc/emcapi/integration/Integrations.java +++ b/src/main/java/net/earthmc/emcapi/integration/Integrations.java @@ -10,55 +10,22 @@ import java.util.concurrent.ConcurrentHashMap; public class Integrations implements Listener { - private final EMCAPI plugin; + private static final Map INTEGRATIONS = 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); - 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 = INTEGRATIONS.get(event.getPlugin().getName()); if (integration != null) { integration.setEnabled(true); } @@ -66,7 +33,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 = INTEGRATIONS.get(event.getPlugin().getName()); if (integration != null) { integration.setEnabled(false); } diff --git a/src/main/java/net/earthmc/emcapi/manager/EndpointManager.java b/src/main/java/net/earthmc/emcapi/manager/EndpointManager.java index b69ea75..73568ef 100644 --- a/src/main/java/net/earthmc/emcapi/manager/EndpointManager.java +++ b/src/main/java/net/earthmc/emcapi/manager/EndpointManager.java @@ -6,12 +6,7 @@ import io.javalin.Javalin; import io.javalin.http.BadRequestResponse; import net.earthmc.emcapi.EMCAPI; -import net.earthmc.emcapi.endpoint.LocationEndpoint; -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.ShopEndpoint; +import net.earthmc.emcapi.endpoint.*; import net.earthmc.emcapi.endpoint.towny.NationsEndpoint; import net.earthmc.emcapi.endpoint.towny.PlayersEndpoint; import net.earthmc.emcapi.endpoint.towny.QuartersEndpoint; @@ -20,9 +15,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 +32,8 @@ public EndpointManager(EMCAPI plugin) { } public void loadEndpoints() { + new SuperbVoteIntegration(); // Register integrations for usage in ServerEndpoint + new QuartersIntegration(); ServerEndpoint serverEndpoint = new ServerEndpoint(plugin); javalin.get(URLPath, ctx -> ctx.json(serverEndpoint.lookup())); @@ -51,6 +46,9 @@ public void loadEndpoints() { loadOnlinePlayersEndpoint(); loadMysteryMasterEndpoint(); loadShopsEndpoint(); + loadMcMMoEndpoint(); + loadPursuitsEndpoint(); + loadAdvancementsEndpoint(); } private QueryBody parseBody(String body) { @@ -76,6 +74,7 @@ private void loadPlayersEndpoint() { PlayersListEndpoint ple = new PlayersListEndpoint(); javalin.get(URLPath + "/players", ctx -> ctx.json(ple.lookup())); + new DiscordIntegration(); // 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 +86,7 @@ private void loadTownsEndpoint() { TownsListEndpoint tle = new TownsListEndpoint(); javalin.get(URLPath + "/towns", ctx -> ctx.json(tle.lookup())); + new WarpsIntegration(); TownsEndpoint townsEndpoint = new TownsEndpoint(plugin); javalin.post(URLPath + "/towns", ctx -> { QueryBody parsedBody = parseBody(ctx.body()); @@ -98,6 +98,8 @@ private void loadNationsEndpoint() { NationsListEndpoint nle = new NationsListEndpoint(); javalin.get(URLPath + "/nations", ctx -> ctx.json(nle.lookup())); + new EmbargoesIntegration(); + new PactsIntegration(); NationsEndpoint nationsEndpoint = new NationsEndpoint(plugin); javalin.post(URLPath + "/nations", ctx -> { QueryBody parsedBody = parseBody(ctx.body()); @@ -106,7 +108,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 +146,8 @@ private void loadOnlinePlayersEndpoint() { } private void loadMysteryMasterEndpoint() { + MysteryMasterIntegration mysteryMasterIntegration = new MysteryMasterIntegration(); MysteryMasterEndpoint mysteryMasterEndpoint = new MysteryMasterEndpoint(plugin); - MysteryMasterIntegration mysteryMasterIntegration = plugin.integrations().mysteryMasterIntegration(); javalin.get(URLPath + "/mm", ctx -> { mysteryMasterIntegration.throwIfDisabled(); @@ -154,8 +156,8 @@ private void loadMysteryMasterEndpoint() { } private void loadShopsEndpoint() { + QuickShopIntegration quickShopIntegration = new QuickShopIntegration(); ShopEndpoint shopEndpoint = new ShopEndpoint(plugin); - QuickShopIntegration quickShopIntegration = plugin.integrations().quickShopIntegration();; javalin.post(URLPath + "/shop", ctx -> { quickShopIntegration.throwIfDisabled(); @@ -163,4 +165,44 @@ private void loadShopsEndpoint() { ctx.json(shopEndpoint.lookup(parsedBody.query, parsedBody.template, parsedBody.key)); }); } + + private void loadMcMMoEndpoint() { + McMMOIntegration mcMMOIntegration = new McMMOIntegration(); + 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(); + 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(); + 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/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/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..cef4a1c 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] authors: [Fruitloopins] contributors: [Warriorrr, Yoditi, Veyronity] description: API for EarthMC using Javalin From ca5ad2cd5f9972969b9bdd140a8d5e1261b16790 Mon Sep 17 00:00:00 2001 From: Veyronity Date: Fri, 22 May 2026 19:30:39 +0300 Subject: [PATCH 02/10] 2. Update town & nation endpoints --- .../endpoint/towny/NationsEndpoint.java | 60 +++++++++++++++++++ .../emcapi/endpoint/towny/TownsEndpoint.java | 26 +++++++- .../integration/EmbargoesIntegration.java | 35 +++++++++++ .../emcapi/integration/PactsIntegration.java | 39 ++++++++++++ .../emcapi/integration/WarpsIntegration.java | 35 +++++++++++ .../emcapi/manager/NationMetadataManager.java | 33 +++++----- .../emcapi/manager/TownMetadataManager.java | 48 ++++++++++----- .../earthmc/emcapi/util/EndpointUtils.java | 41 +++++++++++++ 8 files changed, 285 insertions(+), 32 deletions(-) create mode 100644 src/main/java/net/earthmc/emcapi/integration/EmbargoesIntegration.java create mode 100644 src/main/java/net/earthmc/emcapi/integration/PactsIntegration.java create mode 100644 src/main/java/net/earthmc/emcapi/integration/WarpsIntegration.java 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..8814ba1 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,59 @@ 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; + } + + JsonObject active = new JsonObject(); + JsonObject pending = new JsonObject(); + + for (Pact pact : integration.getActivePacts(nation)) { + Nation other = nation.equals(pact.getSenderNation()) ? pact.getReceivingNation() : pact.getSenderNation(); + active.add(other.getName(), EndpointUtils.getPactObject(pact)); + } + + for (Pact pact : integration.getPendingPacts(nation)) { + Nation other = nation.equals(pact.getSenderNation()) ? pact.getReceivingNation() : pact.getSenderNation(); + pending.add(other.getName(), EndpointUtils.getPactObject(pact)); + } + + json.add("active", active); + json.add("pending", pending); + + return json; + } } 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..f34245c 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 JsonObject getWarpsObject(Town town) { + JsonObject json = new JsonObject(); + WarpsIntegration integration = Integrations.getIntegration("lynchpin-towny-warps"); + if (integration == null || !integration.isEnabled()) { + return json; + } + + for (Warp warp : integration.getWarps(town)) { + json.add(warp.getName(), EndpointUtils.getWarpObject(warp)); + } + + return json; + } } 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..03c3161 --- /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 (Exception ignored) { + plugin.getLogger().warning("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/PactsIntegration.java b/src/main/java/net/earthmc/emcapi/integration/PactsIntegration.java new file mode 100644 index 0000000..3ed158d --- /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 (Exception ignored) { + plugin.getLogger().warning("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/WarpsIntegration.java b/src/main/java/net/earthmc/emcapi/integration/WarpsIntegration.java new file mode 100644 index 0000000..e8047f6 --- /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 (Exception ignored) { + plugin.getLogger().warning("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/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/util/EndpointUtils.java b/src/main/java/net/earthmc/emcapi/util/EndpointUtils.java index f6ac66d..d950cf6 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; @@ -174,6 +176,7 @@ public static JsonObject generateNameUUIDJsonObject(String name, UUID uuid) { public static JsonObject getShopObject(Shop shop) { JsonObject jsonObject = new JsonObject(); + 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()); @@ -189,4 +192,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; + } } From 796017d5cb2adda69bff971b1f0a57e7ffa9e2a1 Mon Sep 17 00:00:00 2001 From: Veyronity Date: Fri, 22 May 2026 19:31:00 +0300 Subject: [PATCH 03/10] 3. Add advancements endpoint --- .../emcapi/endpoint/AdvancementsEndpoint.java | 33 +++++++++++++++++++ .../integration/AdvancementsIntegration.java | 33 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/main/java/net/earthmc/emcapi/endpoint/AdvancementsEndpoint.java create mode 100644 src/main/java/net/earthmc/emcapi/integration/AdvancementsIntegration.java 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/integration/AdvancementsIntegration.java b/src/main/java/net/earthmc/emcapi/integration/AdvancementsIntegration.java new file mode 100644 index 0000000..a7f68a2 --- /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 (Exception ignored) { + plugin.getLogger().warning("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(); + } +} From 26e3448f5f55e7a5e23e223801f33642e4ef08ea Mon Sep 17 00:00:00 2001 From: Veyronity Date: Fri, 22 May 2026 19:31:10 +0300 Subject: [PATCH 04/10] 4. Add Pursuits endpoint --- .../emcapi/endpoint/PursuitsEndpoint.java | 104 ++++++++++++++++++ .../integration/PursuitsIntegration.java | 37 +++++++ 2 files changed, 141 insertions(+) create mode 100644 src/main/java/net/earthmc/emcapi/endpoint/PursuitsEndpoint.java create mode 100644 src/main/java/net/earthmc/emcapi/integration/PursuitsIntegration.java 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/integration/PursuitsIntegration.java b/src/main/java/net/earthmc/emcapi/integration/PursuitsIntegration.java new file mode 100644 index 0000000..95a9002 --- /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 (Exception e) { + plugin.getLogger().warning("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(); + } +} From 0a5defa2e03c033f584bd07603fa09f4e8f5591b Mon Sep 17 00:00:00 2001 From: Veyronity Date: Fri, 22 May 2026 19:31:17 +0300 Subject: [PATCH 05/10] 5. Add mcMMO endpoint --- .../emcapi/endpoint/McMMOEndpoint.java | 63 +++++++++++ .../emcapi/endpoint/McMMOTopEndpoint.java | 107 ++++++++++++++++++ .../emcapi/integration/McMMOIntegration.java | 17 +++ 3 files changed, 187 insertions(+) create mode 100644 src/main/java/net/earthmc/emcapi/endpoint/McMMOEndpoint.java create mode 100644 src/main/java/net/earthmc/emcapi/endpoint/McMMOTopEndpoint.java create mode 100644 src/main/java/net/earthmc/emcapi/integration/McMMOIntegration.java 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/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); + } +} From e2c3a06f2e367cd3d679f04b14331bb55b37376c Mon Sep 17 00:00:00 2001 From: Veyronity Date: Fri, 22 May 2026 21:03:28 +0300 Subject: [PATCH 06/10] Use JsonArray for warps & Pacts --- .../earthmc/emcapi/endpoint/towny/NationsEndpoint.java | 10 ++++------ .../earthmc/emcapi/endpoint/towny/TownsEndpoint.java | 6 +++--- 2 files changed, 7 insertions(+), 9 deletions(-) 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 8814ba1..ce58280 100644 --- a/src/main/java/net/earthmc/emcapi/endpoint/towny/NationsEndpoint.java +++ b/src/main/java/net/earthmc/emcapi/endpoint/towny/NationsEndpoint.java @@ -134,17 +134,15 @@ private JsonObject getPactsObject(Nation nation) { return json; } - JsonObject active = new JsonObject(); - JsonObject pending = new JsonObject(); + JsonArray active = new JsonArray(); + JsonArray pending = new JsonArray(); for (Pact pact : integration.getActivePacts(nation)) { - Nation other = nation.equals(pact.getSenderNation()) ? pact.getReceivingNation() : pact.getSenderNation(); - active.add(other.getName(), EndpointUtils.getPactObject(pact)); + active.add(EndpointUtils.getPactObject(pact)); } for (Pact pact : integration.getPendingPacts(nation)) { - Nation other = nation.equals(pact.getSenderNation()) ? pact.getReceivingNation() : pact.getSenderNation(); - pending.add(other.getName(), EndpointUtils.getPactObject(pact)); + pending.add(EndpointUtils.getPactObject(pact)); } json.add("active", active); 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 f34245c..ef70202 100644 --- a/src/main/java/net/earthmc/emcapi/endpoint/towny/TownsEndpoint.java +++ b/src/main/java/net/earthmc/emcapi/endpoint/towny/TownsEndpoint.java @@ -129,15 +129,15 @@ public JsonElement getJsonElement(Town town, @Nullable String key) { return townObject; } - private JsonObject getWarpsObject(Town town) { - JsonObject json = new JsonObject(); + 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(warp.getName(), EndpointUtils.getWarpObject(warp)); + json.add(EndpointUtils.getWarpObject(warp)); } return json; From 9210a663fb14414209c48eae99e53b93106cf53b Mon Sep 17 00:00:00 2001 From: Veyronity Date: Sat, 23 May 2026 10:45:58 +0300 Subject: [PATCH 07/10] Fix mismatch between plugin name & integration identifier --- .../java/net/earthmc/emcapi/integration/Integrations.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/earthmc/emcapi/integration/Integrations.java b/src/main/java/net/earthmc/emcapi/integration/Integrations.java index c1c599c..aea88cf 100644 --- a/src/main/java/net/earthmc/emcapi/integration/Integrations.java +++ b/src/main/java/net/earthmc/emcapi/integration/Integrations.java @@ -11,6 +11,7 @@ public class Integrations implements Listener { private static final Map INTEGRATIONS = new ConcurrentHashMap<>(); + private static final Map PLUGIN_INTEGRATION_MAP = new ConcurrentHashMap<>(); @SuppressWarnings("unchecked") public static T getIntegration(String name) { @@ -19,13 +20,14 @@ public static T getIntegration(String name) { public static void addIntegration(String identifier, Integration integration) { INTEGRATIONS.put(identifier, integration); + PLUGIN_INTEGRATION_MAP.put(integration.name(), integration); 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); } @@ -33,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); } From d5cdf194b10115d655298ffcd75865d6a0afa06d Mon Sep 17 00:00:00 2001 From: Veyronity Date: Sun, 24 May 2026 14:11:51 +0300 Subject: [PATCH 08/10] Update docs --- docs/advancements.md | 42 ++++++++++++++++++++++ docs/discord.md | 40 --------------------- docs/keys.md | 3 +- docs/mcmmo.md | 84 ++++++++++++++++++++++++++++++++++++++++++++ docs/nations.md | 24 +++++++++++++ docs/pursuits.md | 60 +++++++++++++++++++++++++++++++ docs/shop.md | 2 ++ docs/towns.md | 17 ++++++++- 8 files changed, 230 insertions(+), 42 deletions(-) create mode 100644 docs/advancements.md delete mode 100644 docs/discord.md create mode 100644 docs/mcmmo.md create mode 100644 docs/pursuits.md 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..85edf52 100644 --- a/docs/nations.md +++ b/docs/nations.md @@ -185,6 +185,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 7a41557..50f85eb 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: - `item` - The Material/name of the item being traded - `price` - The price of one transaction diff --git a/docs/towns.md b/docs/towns.md index 7fe7455..62ec782 100644 --- a/docs/towns.md +++ b/docs/towns.md @@ -270,7 +270,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 + } + } + ] + } } ] ``` From 19df939a655d3f7f0fee516cceae17fc0562b680 Mon Sep 17 00:00:00 2001 From: Veyronity Date: Sun, 24 May 2026 14:17:14 +0300 Subject: [PATCH 09/10] Update nation & town docs --- docs/nations.md | 1 + docs/towns.md | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/nations.md b/docs/nations.md index 85edf52..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" diff --git a/docs/towns.md b/docs/towns.md index 62ec782..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 From b6f704508c0f0f2789e01c3e841327ab0cbb2b0e Mon Sep 17 00:00:00 2001 From: Veyronity Date: Tue, 26 May 2026 08:38:44 +0300 Subject: [PATCH 10/10] Implement requested changes and fix Errors for missing dependencies --- .../integration/AdvancementsIntegration.java | 4 +-- .../integration/EmbargoesIntegration.java | 4 +-- .../emcapi/integration/Integration.java | 1 - .../emcapi/integration/PactsIntegration.java | 4 +-- .../integration/PursuitsIntegration.java | 4 +-- .../emcapi/integration/WarpsIntegration.java | 4 +-- .../emcapi/manager/EndpointManager.java | 32 +++++++++++++------ src/main/resources/plugin.yml | 2 +- 8 files changed, 34 insertions(+), 21 deletions(-) diff --git a/src/main/java/net/earthmc/emcapi/integration/AdvancementsIntegration.java b/src/main/java/net/earthmc/emcapi/integration/AdvancementsIntegration.java index a7f68a2..8e2d857 100644 --- a/src/main/java/net/earthmc/emcapi/integration/AdvancementsIntegration.java +++ b/src/main/java/net/earthmc/emcapi/integration/AdvancementsIntegration.java @@ -12,8 +12,8 @@ public AdvancementsIntegration() { super("Lynchpin"); try { module = AdvancementsProvider.instance(); - } catch (Exception ignored) { - plugin.getLogger().warning("Not loading advancements integration due to the module not being present/enabled"); + } catch (Throwable ignored) { + plugin.getSLF4JLogger().warn("Not loading advancements integration due to the module not being present/enabled"); } } diff --git a/src/main/java/net/earthmc/emcapi/integration/EmbargoesIntegration.java b/src/main/java/net/earthmc/emcapi/integration/EmbargoesIntegration.java index 03c3161..89b83d9 100644 --- a/src/main/java/net/earthmc/emcapi/integration/EmbargoesIntegration.java +++ b/src/main/java/net/earthmc/emcapi/integration/EmbargoesIntegration.java @@ -14,8 +14,8 @@ public EmbargoesIntegration() { super("Lynchpin"); try { this.module = TownyProvider.instance().embargoes(); - } catch (Exception ignored) { - plugin.getLogger().warning("Not loading towny-embargoes integration due to the module not being present/enabled"); + } catch (Throwable ignored) { + plugin.getSLF4JLogger().warn("Not loading towny-embargoes integration due to the module not being present/enabled"); } } diff --git a/src/main/java/net/earthmc/emcapi/integration/Integration.java b/src/main/java/net/earthmc/emcapi/integration/Integration.java index cb187aa..b8493bb 100644 --- a/src/main/java/net/earthmc/emcapi/integration/Integration.java +++ b/src/main/java/net/earthmc/emcapi/integration/Integration.java @@ -13,7 +13,6 @@ public abstract class Integration { protected Integration(final String name) { this.name = name; - register(); } /** diff --git a/src/main/java/net/earthmc/emcapi/integration/PactsIntegration.java b/src/main/java/net/earthmc/emcapi/integration/PactsIntegration.java index 3ed158d..63bf922 100644 --- a/src/main/java/net/earthmc/emcapi/integration/PactsIntegration.java +++ b/src/main/java/net/earthmc/emcapi/integration/PactsIntegration.java @@ -14,8 +14,8 @@ public PactsIntegration() { super("Lynchpin"); try { this.module = TownyProvider.instance().pacts(); - } catch (Exception ignored) { - plugin.getLogger().warning("Not loading towny-pacts integration due to the module not being present/enabled"); + } catch (Throwable ignored) { + plugin.getSLF4JLogger().warn("Not loading towny-pacts integration due to the module not being present/enabled"); } } diff --git a/src/main/java/net/earthmc/emcapi/integration/PursuitsIntegration.java b/src/main/java/net/earthmc/emcapi/integration/PursuitsIntegration.java index 95a9002..6fd88e3 100644 --- a/src/main/java/net/earthmc/emcapi/integration/PursuitsIntegration.java +++ b/src/main/java/net/earthmc/emcapi/integration/PursuitsIntegration.java @@ -12,8 +12,8 @@ public PursuitsIntegration() { super("Lynchpin"); try { pursuits = PursuitsProvider.instance(); - } catch (Exception e) { - plugin.getLogger().warning("Not loading pursuits integration due to the module not being present/enabled"); + } catch (Throwable ignored) { + plugin.getSLF4JLogger().warn("Not loading pursuits integration due to the module not being present/enabled"); } } diff --git a/src/main/java/net/earthmc/emcapi/integration/WarpsIntegration.java b/src/main/java/net/earthmc/emcapi/integration/WarpsIntegration.java index e8047f6..3799ffd 100644 --- a/src/main/java/net/earthmc/emcapi/integration/WarpsIntegration.java +++ b/src/main/java/net/earthmc/emcapi/integration/WarpsIntegration.java @@ -14,8 +14,8 @@ public WarpsIntegration() { super("Lynchpin"); try { this.module = TownyProvider.instance().warps(); - } catch (Exception ignored) { - plugin.getLogger().warning("Not loading towny-warps integration due to the module not being present/enabled"); + } catch (Throwable ignored) { + plugin.getSLF4JLogger().warn("Not loading towny-warps integration due to the module not being present/enabled"); } } diff --git a/src/main/java/net/earthmc/emcapi/manager/EndpointManager.java b/src/main/java/net/earthmc/emcapi/manager/EndpointManager.java index 73568ef..c56e71f 100644 --- a/src/main/java/net/earthmc/emcapi/manager/EndpointManager.java +++ b/src/main/java/net/earthmc/emcapi/manager/EndpointManager.java @@ -6,7 +6,16 @@ import io.javalin.Javalin; import io.javalin.http.BadRequestResponse; import net.earthmc.emcapi.EMCAPI; -import net.earthmc.emcapi.endpoint.*; +import net.earthmc.emcapi.endpoint.LocationEndpoint; +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.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; @@ -32,8 +41,8 @@ public EndpointManager(EMCAPI plugin) { } public void loadEndpoints() { - new SuperbVoteIntegration(); // Register integrations for usage in ServerEndpoint - new QuartersIntegration(); + 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())); @@ -46,7 +55,7 @@ public void loadEndpoints() { loadOnlinePlayersEndpoint(); loadMysteryMasterEndpoint(); loadShopsEndpoint(); - loadMcMMoEndpoint(); + loadmcMMoEndpoint(); loadPursuitsEndpoint(); loadAdvancementsEndpoint(); } @@ -74,7 +83,7 @@ private void loadPlayersEndpoint() { PlayersListEndpoint ple = new PlayersListEndpoint(); javalin.get(URLPath + "/players", ctx -> ctx.json(ple.lookup())); - new DiscordIntegration(); // Load the discord integration to check if DiscordSRV is enabled - checked when including discord for player + 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()); @@ -86,7 +95,7 @@ private void loadTownsEndpoint() { TownsListEndpoint tle = new TownsListEndpoint(); javalin.get(URLPath + "/towns", ctx -> ctx.json(tle.lookup())); - new WarpsIntegration(); + new WarpsIntegration().register(); TownsEndpoint townsEndpoint = new TownsEndpoint(plugin); javalin.post(URLPath + "/towns", ctx -> { QueryBody parsedBody = parseBody(ctx.body()); @@ -98,8 +107,8 @@ private void loadNationsEndpoint() { NationsListEndpoint nle = new NationsListEndpoint(); javalin.get(URLPath + "/nations", ctx -> ctx.json(nle.lookup())); - new EmbargoesIntegration(); - new PactsIntegration(); + new EmbargoesIntegration().register(); + new PactsIntegration().register(); NationsEndpoint nationsEndpoint = new NationsEndpoint(plugin); javalin.post(URLPath + "/nations", ctx -> { QueryBody parsedBody = parseBody(ctx.body()); @@ -147,6 +156,7 @@ private void loadOnlinePlayersEndpoint() { private void loadMysteryMasterEndpoint() { MysteryMasterIntegration mysteryMasterIntegration = new MysteryMasterIntegration(); + mysteryMasterIntegration.register(); MysteryMasterEndpoint mysteryMasterEndpoint = new MysteryMasterEndpoint(plugin); javalin.get(URLPath + "/mm", ctx -> { mysteryMasterIntegration.throwIfDisabled(); @@ -157,6 +167,7 @@ private void loadMysteryMasterEndpoint() { private void loadShopsEndpoint() { QuickShopIntegration quickShopIntegration = new QuickShopIntegration(); + quickShopIntegration.register(); ShopEndpoint shopEndpoint = new ShopEndpoint(plugin); javalin.post(URLPath + "/shop", ctx -> { quickShopIntegration.throwIfDisabled(); @@ -166,8 +177,9 @@ private void loadShopsEndpoint() { }); } - private void loadMcMMoEndpoint() { + private void loadmcMMoEndpoint() { McMMOIntegration mcMMOIntegration = new McMMOIntegration(); + mcMMOIntegration.register(); McMMOEndpoint mcMMOEndpoint = new McMMOEndpoint(plugin); javalin.post(URLPath + "/mcmmo", ctx -> { mcMMOIntegration.throwIfDisabled(); @@ -187,6 +199,7 @@ private void loadMcMMoEndpoint() { private void loadPursuitsEndpoint() { PursuitsIntegration pursuitsIntegration = new PursuitsIntegration(); + pursuitsIntegration.register(); PursuitsEndpoint pursuitsEndpoint = new PursuitsEndpoint(plugin); javalin.post(URLPath + "/pursuits", ctx -> { pursuitsIntegration.throwIfDisabled(); @@ -198,6 +211,7 @@ private void loadPursuitsEndpoint() { private void loadAdvancementsEndpoint() { AdvancementsIntegration advancementsIntegration = new AdvancementsIntegration(); + advancementsIntegration.register(); AdvancementsEndpoint advancementsEndpoint = new AdvancementsEndpoint(); javalin.get(URLPath + "/advancements", ctx -> { advancementsIntegration.throwIfDisabled(); diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index cef4a1c..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, Lynchpin] +softdepend: [MysteryMaster, Quarters, DiscordSRV, SuperbVote, QuickShop-Hikari, Lynchpin, mcMMO] authors: [Fruitloopins] contributors: [Warriorrr, Yoditi, Veyronity] description: API for EarthMC using Javalin