Skip to content
Open
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
7 changes: 7 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ repositories {
maven("https://repo.earthmc.net/public/") {
mavenContent { includeGroupAndSubgroups("net.earthmc") }
}

maven("https://repo.codemc.io/repository/maven-public/") {
mavenContent { includeGroup("org.maxgamer") }
}
}

dependencies {
Expand All @@ -33,6 +37,9 @@ dependencies {
compileOnly(libs.discordsrv)
compileOnly(libs.superbvote)
compileOnly(libs.mysterymaster.api)
compileOnly(libs.quickshop) {
exclude("*")
}
}

tasks {
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group = net.earthmc.emcapi
version = 3.0.0-SNAPSHOT
version = 4.0.0-SNAPSHOT
description = EMCAPI

org.gradle.configuration-cache=true
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ towny = "0.102.0.0"
mysterymaster-api = "1.0.0"
superbvote = "0.6.0"
conventions = "1.0.8"
quickshop = "5.1.2.5-SNAPSHOT"

[libraries]
discordsrv = { group = "com.discordsrv", name = "discordsrv", version.ref = "discordsrv" }
Expand All @@ -16,6 +17,7 @@ javalin = { group = "io.javalin", name = "javalin", version.ref = "javalin" }
paper = { group = "io.papermc.paper", name = "paper-api", version.ref = "paper" }
mysterymaster-api = { group = "net.earthmc.mysterymaster", name = "mysterymaster-api", version.ref = "mysterymaster-api" }
superbvote = { group = "net.earthmc.superbvote", name = "SuperbVote", version.ref = "superbvote" }
quickshop = { group = "org.maxgamer", name = "QuickShop", version.ref = "quickshop" }

[plugins]
conventions-java = { id = "net.earthmc.conventions.java", version.ref = "conventions" }
42 changes: 38 additions & 4 deletions src/main/java/net/earthmc/emcapi/EMCAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@
import jakarta.servlet.http.HttpServletResponse;
import net.earthmc.emcapi.integration.Integrations;
import net.earthmc.emcapi.manager.EndpointManager;
import net.earthmc.emcapi.manager.LegacyEndpointManager;
import net.earthmc.emcapi.sse.SSEManager;
import net.earthmc.emcapi.sse.listeners.ShopSSEListener;
import net.earthmc.emcapi.sse.listeners.TownySSEListener;
import net.earthmc.emcapi.util.EndpointUtils;
import net.earthmc.emcapi.command.OptOutCommand;
import net.earthmc.emcapi.command.ApiCommand;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConnectionFactory;
Expand All @@ -25,6 +30,7 @@ public final class EMCAPI extends JavaPlugin {
public static EMCAPI instance;
private Javalin javalin;
private Integrations pluginIntegrations;
private SSEManager sseManager;

@Override
public void onLoad() {
Expand All @@ -42,14 +48,16 @@ public void onEnable() {
this.pluginIntegrations = new Integrations(this);
getServer().getPluginManager().registerEvents(this.pluginIntegrations, this);

EndpointManager endpointManager = new EndpointManager(this);
endpointManager.loadEndpoints();
if (getConfig().getBoolean("behavior.load_legacy")) {
new LegacyEndpointManager(this).loadEndpoints(); // Load retired endpoints and still serve current endpoints at /v3/aurora/
}
new EndpointManager(this).loadEndpoints();

PluginCommand apiCommand = getCommand("api");
if (apiCommand == null) {
getLogger().warning("API command not found.");
} else {
OptOutCommand cmd = new OptOutCommand();
ApiCommand cmd = new ApiCommand();
apiCommand.setExecutor(cmd);
apiCommand.setTabCompleter(cmd);
}
Expand All @@ -58,6 +66,21 @@ public void onEnable() {
} catch (IOException e) {
getLogger().warning("IOException while loading opted-out players: " + e);
}

sseManager = new SSEManager(this);
sseManager.loadSSE();
PluginManager pm = getServer().getPluginManager();
if (pm.isPluginEnabled("Towny")) {
pm.registerEvents(new TownySSEListener(sseManager), this);
}
if (pm.isPluginEnabled("QuickShop")) {
pm.registerEvents(new ShopSSEListener(sseManager), this);
}
try {
EndpointUtils.loadApiKeys(getDataFolder().toPath());
} catch (IOException e) {
getSLF4JLogger().warn("IOException while loading API keys: ", e);
}
}

@Override
Expand All @@ -68,6 +91,12 @@ public void onDisable() {
} catch (IOException e) {
getLogger().warning("IOException while saving opted-out players: " + e);
}
sseManager.shutdown();
try {
EndpointUtils.saveApiKeys(getDataFolder().toPath());
} catch (IOException e) {
getSLF4JLogger().warn("IOException while saving API keys: ", e);
}
}

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

public String getURLPath() {
String version = getConfig().getString("networking.api_version", "3");
return "v" + version + "/" + getConfig().getString("networking.url_path");
}
}
121 changes: 121 additions & 0 deletions src/main/java/net/earthmc/emcapi/command/ApiCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package net.earthmc.emcapi.command;

import net.earthmc.emcapi.sse.SSEManager;
import net.earthmc.emcapi.util.EndpointUtils;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabExecutor;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.List;
import java.util.UUID;
import java.util.stream.Stream;

public class ApiCommand implements TabExecutor {
private static final Component infoMessage = Component.text()
.append(Component.text("- The API provides real-time information about players, towns, and nations. The API can be accessed ", NamedTextColor.AQUA))
.append(Component.text("here", NamedTextColor.AQUA, TextDecoration.UNDERLINED).clickEvent(ClickEvent.openUrl("https://api.earthmc.net/")))
.appendNewline()
.append(Component.text("- Read the docs ", NamedTextColor.GREEN))
.append(Component.text("here", NamedTextColor.GREEN, TextDecoration.UNDERLINED).clickEvent(ClickEvent.openUrl("https://earthmc.net/docs/api")))
.appendNewline()
.append(Component.text("- If you'd like to connect to the API's Server-Sent-Events endpoint, please create an API key using /api key create", NamedTextColor.GREEN))
.appendNewline()
.append(Component.text("- If you'd like to opt out of your information being public on the API, you can use /api opt-out", NamedTextColor.RED))
.build();

@Override
public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] args) {
if (!(commandSender instanceof Player player)) {
commandSender.sendMessage(Component.text("Only players may use this command.", NamedTextColor.RED));
return true;
}

if (args.length < 1) {
player.sendMessage(infoMessage);
return true;
}
String action = args[0].toLowerCase();
switch (action) {
case "opt-in" -> {
player.sendMessage(Component.text("You have opted back in for your information being public on the API.", NamedTextColor.GREEN));
EndpointUtils.setOptedOut(player.getUniqueId(), false);
}
case "opt-out" -> {
player.sendMessage(Component.text("You have opted out of your information being public on the API", NamedTextColor.RED));
EndpointUtils.setOptedOut(player.getUniqueId(), true);
}
case "key" -> handleKey(player, args);
default -> player.sendMessage(Component.text("Usage: /api [opt-in|opt-out|key]", NamedTextColor.RED));
}

return true;
}

@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender commandSender, @NotNull Command command, @NotNull String s, @NotNull String[] args) {
if (args.length == 1) {
return Stream.of("opt-in", "opt-out", "key").filter(str -> str.toLowerCase().startsWith(args[0].toLowerCase())).toList();
}
if (args.length == 2 && args[0].equalsIgnoreCase("key")) {
return Stream.of("create", "delete", "copy").filter(str -> str.toLowerCase().startsWith(args[1].toLowerCase())).toList();
}

return List.of();
}

private void handleKey(Player player, String[] args) {
if (!player.hasPermission("emcapi.key")) {
player.sendMessage(Component.text("You do not have permission to use this command", NamedTextColor.RED));
return;
}

UUID playerID = player.getUniqueId();
UUID key;
if (args.length == 1) {
key = EndpointUtils.getPlayerKey(playerID);
if (key != null) {
player.sendMessage(Component.text("Click to copy your API key.", NamedTextColor.GREEN).clickEvent(ClickEvent.copyToClipboard(key.toString())));
} else {
player.sendMessage(Component.text("You do not have an API key. Use /api key create to create one.", NamedTextColor.RED));
}
return;
}
String action = args[1].toLowerCase();
switch (action) {
case "create" -> {
if (EndpointUtils.getPlayerKey(playerID) != null) {
player.sendMessage(Component.text("You already have an API key! Use /api key to get it.", NamedTextColor.RED));
} else {
key = EndpointUtils.createApiKey(playerID);
player.sendMessage(Component.text("Key created! Click to copy.", NamedTextColor.GREEN).clickEvent(ClickEvent.copyToClipboard(key.toString())));
}
}
case "delete" -> {
key = EndpointUtils.getPlayerKey(playerID);
if (key != null) {
SSEManager.deleteKey(key);
EndpointUtils.deletePlayerKey(playerID);
player.sendMessage(Component.text("Successfully deleted your API key", NamedTextColor.GREEN));
} else {
player.sendMessage(Component.text("You do not have an API key.", NamedTextColor.RED));
}
}
case "copy" -> {
key = EndpointUtils.getPlayerKey(playerID);
if (key != null) {
player.sendMessage(Component.text("Click to copy your API key.", NamedTextColor.GREEN).clickEvent(ClickEvent.copyToClipboard(key.toString())));
} else {
player.sendMessage(Component.text("You do not have an API key. Use /api key create to create one.", NamedTextColor.RED));
}
}
default -> player.sendMessage(Component.text("Usage: /api key <create|delete|copy>", NamedTextColor.RED));
}
}
}
59 changes: 0 additions & 59 deletions src/main/java/net/earthmc/emcapi/command/OptOutCommand.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
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.util.UUID;
import java.util.regex.Matcher;
Expand All @@ -21,7 +22,7 @@ public class DiscordEndpoint extends PostEndpoint<DiscordContext> {
private static final BadRequestResponse INVALID_TYPE_TARGET = new BadRequestResponse("Your JSON query has an invalid type or target");

@Override
public DiscordContext getObjectOrNull(JsonElement element) {
public DiscordContext getObjectOrNull(JsonElement element, @Nullable UUID key) {
JsonObject jsonObject = JSONUtil.getJsonElementAsJsonObjectOrNull(element);
if (jsonObject == null) {
throw HttpExceptions.NOT_A_JSON_OBJECT;
Expand Down Expand Up @@ -60,7 +61,7 @@ public DiscordContext getObjectOrNull(JsonElement element) {
}

@Override
public JsonElement getJsonElement(DiscordContext context) {
public JsonElement getJsonElement(DiscordContext context, @Nullable UUID key) {
DiscordType type = context.getType();
String target = context.getTarget();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
import net.earthmc.emcapi.util.JSONUtil;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.jetbrains.annotations.Nullable;

import java.util.UUID;

public class LocationEndpoint extends PostEndpoint<Pair<Integer, Integer>> {

@Override
public Pair<Integer, Integer> getObjectOrNull(JsonElement element) {
public Pair<Integer, Integer> getObjectOrNull(JsonElement element, @Nullable UUID 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 @@ -40,7 +43,7 @@ public Pair<Integer, Integer> getObjectOrNull(JsonElement element) {
}

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

Expand Down
6 changes: 4 additions & 2 deletions src/main/java/net/earthmc/emcapi/endpoint/NearbyEndpoint.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@
import net.earthmc.emcapi.util.JSONUtil;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class NearbyEndpoint extends PostEndpoint<NearbyContext> {

@Override
public NearbyContext getObjectOrNull(JsonElement element) {
public NearbyContext getObjectOrNull(JsonElement element, @Nullable UUID key) {
JsonObject jsonObject = JSONUtil.getJsonElementAsJsonObjectOrNull(element);
if (jsonObject == null) throw new BadRequestResponse("Your query contains a value that is not a JSON object");

Expand Down Expand Up @@ -64,7 +66,7 @@ public NearbyContext getObjectOrNull(JsonElement element) {
}

@Override
public JsonElement getJsonElement(NearbyContext context) {
public JsonElement getJsonElement(NearbyContext context, @Nullable UUID key) {
NearbyType targetType = context.getTargetType();
int radius = context.getRadius();
switch (targetType) {
Expand Down
Loading