Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 25 additions & 11 deletions build.gradle.kts
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@
plugins {
alias(libs.plugins.conventions.java)
alias(libs.plugins.shadow)
alias(libs.plugins.blossom)
}

repositories {
Expand DownExpand Up@@ -61,6 +62,10 @@ dependencies {
compileOnly(libs.lynchpin.towny)
compileOnly(libs.lynchpin.advancements)
implementation(libs.inventories)
compileOnly(libs.javalin.openapi)
compileOnly(libs.javalin.swagger)
compileOnly(libs.javalin.swagger.webjar)
annotationProcessor(libs.javalin.openapi.annotation)
}

tasks {
Expand All@@ -71,20 +76,29 @@ tasks {
relocate("dev.warriorrr.inventories", "net.earthmc.emcapi.libs.inventories")
}

processResources {
val shortCommitId = providers.exec { commandLine("git", "rev-parse", "--short", "HEAD") }.standardOutput.asText.get().trim()
val commitId = providers.exec { commandLine("git", "rev-parse", "HEAD") }.standardOutput.asText.get().trim()

expand(
"version" to shortCommitId,
"commit" to commitId,
"javalin_version" to libs.versions.javalin.get()
)
}

jar {
manifest {
attributes["paperweight-mappings-namespace"] = "mojang"
}
}
}

sourceSets.main {
blossom {
javaSources {
property("swaggerVersion", libs.versions.swagger.webjar)
}

resources {
trimNewlines = false

val shortCommitId: Provider<String> = providers.exec { commandLine("git", "rev-parse", "--short", "HEAD") }.standardOutput.asText.map { it.trim() }
val commitId: Provider<String> = providers.exec { commandLine("git", "rev-parse", "HEAD") }.standardOutput.asText.map { it.trim() }

property("version", shortCommitId)
property("commit", commitId)
property("javalin_version", libs.versions.javalin)
property("swagger_version", libs.versions.swagger.webjar)
}
}
}
2 changes: 1 addition & 1 deletion docs/shop.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,7 @@ Example **POST** request
"key": "API_KEY"
}
```
The player UUID must match the API key's owner, otherwise an empty list is returned.
The target player must have their data public, authorize the API key's owner, or be the owner themselves. Otherwise, an empty list is returned.

Example **POST** response
```json5
Expand Down
6 changes: 6 additions & 0 deletions gradle/libs.versions.toml
Original file line numberDiff line numberDiff line change
Expand Up@@ -11,6 +11,7 @@ hikaricp = "7.0.2"
mcmmo = "2.2.040"
lynchpin-api = "1.0-SNAPSHOT"
inventories = "1.1.3"
swagger-webjar = "5.32.8"

# plugins
conventions = "1.1.0"
Expand All@@ -32,7 +33,12 @@ lynchpin-pursuits = { group = "net.earthmc.lynchpin.api", name = "pursuits", ver
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"}
inventories = { group = "dev.warriorrr.inventories", name = "inventories", version.ref = "inventories" }
javalin-openapi = { group = "io.javalin.community.openapi", name = "javalin-openapi-plugin", version.ref = "javalin" }
javalin-openapi-annotation = { group = "io.javalin.community.openapi", name = "openapi-annotation-processor", version.ref = "javalin" }
javalin-swagger = { group = "io.javalin.community.openapi", name = "javalin-swagger-plugin", version.ref = "javalin" }
javalin-swagger-webjar = { group = "org.webjars", name = "swagger-ui", version.ref = "swagger-webjar" }

[plugins]
conventions-java = { id = "net.earthmc.conventions.java", version.ref = "conventions" }
shadow = { id = "com.gradleup.shadow", version.ref = "shadow" }
blossom = { id = "net.kyori.blossom", version = "2.2.0" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
package net.earthmc.emcapi;

class BuildConstants {
public static final String SWAGGER_VERSION = "{{ swaggerVersion }}";
}
29 changes: 27 additions & 2 deletions src/main/java/net/earthmc/emcapi/EMCAPI.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,8 @@
import dev.warriorrr.inventories.Inventories;
import io.javalin.Javalin;
import io.javalin.http.TooManyRequestsResponse;
import io.javalin.openapi.plugin.OpenApiPlugin;
import io.javalin.openapi.plugin.swagger.SwaggerPlugin;
import io.javalin.util.JavalinLogger;
import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;
import net.earthmc.emcapi.database.APIDatabase;
Expand DownExpand Up@@ -96,6 +98,26 @@ private void initialiseJavalin() {
javalin = Javalin.start(config -> {
config.jetty.modifyServer(this::disableServerVersionHeader);

config.registerPlugin(new OpenApiPlugin(openApi -> {
openApi.withDocumentationPath("/docs");
openApi.resourceClassLoader = this.getClassLoader();
openApi.withDefinitionConfiguration((string, builder) -> {
builder.info(info -> {
info.title("EarthMC API");
info.description("The official API for EarthMC");
info.contact("EarthMC", "https://earthmc.net/");
info.version(getApiVersion());
});
});
}));
config.registerPlugin(new SwaggerPlugin(swagger -> {
swagger.documentationPath = "/docs";
swagger.uiPath = "/ui";
swagger.title = "EarthMC API Docs";
swagger.version = BuildConstants.SWAGGER_VERSION; // Needed to properly access webjar
swagger.resourceClassLoader = this.getClassLoader();
}));

config.routes.exception(TooManyRequestsResponse.class, (e, ctx) -> {
final String retryAfter = e.getDetails().get("retry");
if (retryAfter != null) {
Expand DownExpand Up@@ -164,8 +186,11 @@ public Javalin getJavalin() {
}

public String getURLPath() {
String version = getConfig().getString("networking.api_version", "3");
return "v" + version;
return "v" + getApiVersion();
}

public String getApiVersion() {
return getConfig().getString("networking.api_version");
}

public APIDatabase getDatabase() {
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,13 +2,42 @@

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.javalin.openapi.ContentType;
import io.javalin.openapi.HttpMethod;
import io.javalin.openapi.OpenApi;
import io.javalin.openapi.OpenApiContent;
import io.javalin.openapi.OpenApiResponse;
import net.earthmc.emcapi.integration.AdvancementsIntegration;
import net.earthmc.emcapi.integration.Integrations;
import net.earthmc.emcapi.object.endpoint.GetEndpoint;
import net.earthmc.emcapi.util.ContentTypes;
import net.earthmc.lynchpin.api.advancements.AdvancementEntry;

import java.text.SimpleDateFormat;

@OpenApi(
path = "/v4/advancements",
methods = HttpMethod.GET,
summary = "Get a list of advancements completed on the server, along with the date it was completed on and the player that did it",
responses = {
@OpenApiResponse(
status = "200",
content = {
@OpenApiContent(
from = ContentTypes.Advancements.class,
mimeType = ContentType.JSON,
example = """
{minecraft:adventure/totem_of_undying":{"player":"77b181f2-61da-49cb-93a7-17e0febf0511","date":"2026-04-18"}}
"""
)
}
),
@OpenApiResponse(
status = "503",
description = "If the advancements service is currently unavailable or disabled"
)
}
)
public class AdvancementsEndpoint extends GetEndpoint {
private final AdvancementsIntegration integration;
private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
Expand Down
62 changes: 59 additions & 3 deletions src/main/java/net/earthmc/emcapi/endpoint/LocationEndpoint.java
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,23 +6,79 @@
import com.palmergames.bukkit.towny.TownyAPI;
import com.palmergames.bukkit.towny.object.Town;
import io.javalin.http.BadRequestResponse;
import io.javalin.openapi.ContentType;
import io.javalin.openapi.HttpMethod;
import io.javalin.openapi.OpenApi;
import io.javalin.openapi.OpenApiContent;
import io.javalin.openapi.OpenApiRequestBody;
import io.javalin.openapi.OpenApiResponse;
import kotlin.Pair;
import net.earthmc.emcapi.EMCAPI;
import net.earthmc.emcapi.object.endpoint.PostEndpoint;
import net.earthmc.emcapi.util.ContentTypes;
import net.earthmc.emcapi.util.EndpointUtils;
import net.earthmc.emcapi.util.JSONUtil;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@OpenApi(
path = "/v4/location",
methods = HttpMethod.POST,
summary = "Query location data",
requestBody = @OpenApiRequestBody(
description = "Specify a pair of x & z coordinates",
required = true,
content = {
@OpenApiContent(
from = int[].class,
mimeType = ContentType.JSON,
example = "[0, 0]"
)
}
),
responses = {
@OpenApiResponse(
status = "200",
content = {
@OpenApiContent(
from = ContentTypes.Location[].class,
mimeType = ContentType.JSON,
example = """
[{
"location": {
"x":0,
"z":0
},
"isWilderness":false,
"town": {
"name":"Jyväskylä",
"uuid":"0b69c00d-c112-4ca0-a16c-ce551120e464"
},
"nation": {
"name":"Finland",
"uuid":"ae16c3c0-f8ab-4715-8553-019168008c49"
}
}]
"""
)
}
),
@OpenApiResponse(
status = "400",
description = "BadRequestResponse is thrown if your query is invalid. One 'pair' (array) of coordinates (E.g. [0, 0]) or an array of pairs (E.g. [[0, 0], [100, 100] is expected."
)
}
)
public class LocationEndpoint extends PostEndpoint<Pair<Integer, Integer>> {

public LocationEndpoint(final EMCAPI plugin) {
super(plugin);
}

@Override
public Pair<Integer, Integer> getObjectOrNull(JsonElement element, @Nullable String key) {
public Pair<Integer, Integer> getObjectOrNull(@NotNull JsonElement element, @Nullable String key) {
JsonArray jsonArray = JSONUtil.getJsonElementAsJsonArrayOrNull(element);
if (jsonArray == null) throw new BadRequestResponse("Your query contains a value that is not a JSON array");

Expand All@@ -46,11 +102,11 @@ public Pair<Integer, Integer> getObjectOrNull(JsonElement element, @Nullable Str
}

@Override
public JsonElement getJsonElement(Pair<Integer, Integer> pair, @Nullable String key) {
public JsonElement getJsonElement(@NotNull Pair<Integer, Integer> pair, @Nullable String key) {
int x = pair.getFirst();
int z = pair.getSecond();

Location location = new Location(Bukkit.getWorlds().get(0), x, 0, z);
Location location = new Location(Bukkit.getWorlds().getFirst(), x, 0, z);
TownyAPI townyAPI = TownyAPI.getInstance();
Town town = townyAPI.getTown(location);

Expand Down
Loading
Loading