diff --git a/src/main/java/com/shweit/serverapi/endpoints/RegisterEndpoints.java b/src/main/java/com/shweit/serverapi/endpoints/RegisterEndpoints.java index f8705f4..b5152cf 100644 --- a/src/main/java/com/shweit/serverapi/endpoints/RegisterEndpoints.java +++ b/src/main/java/com/shweit/serverapi/endpoints/RegisterEndpoints.java @@ -93,6 +93,9 @@ public void registerEndpoints() { server.addRoute(NanoHTTPD.Method.POST, "/v1/server/exec", serverAPI::execCommand); Logger.debug("Registered POST /v1/server/exec"); + server.addRoute(NanoHTTPD.Method.POST, "/v1/server/exec-multiple", serverAPI::execMultipleCommands); + Logger.debug("Registered POST /v1/server/exec-multiple"); + server.addRoute(NanoHTTPD.Method.POST, "/v1/server/reload", serverAPI::reload); Logger.debug("Registered POST /v1/server/reload"); diff --git a/src/main/java/com/shweit/serverapi/endpoints/v1/ServerAPI.java b/src/main/java/com/shweit/serverapi/endpoints/v1/ServerAPI.java index f04cfa8..01dee98 100644 --- a/src/main/java/com/shweit/serverapi/endpoints/v1/ServerAPI.java +++ b/src/main/java/com/shweit/serverapi/endpoints/v1/ServerAPI.java @@ -12,6 +12,7 @@ import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; +import org.json.JSONArray; import org.json.JSONObject; import java.io.File; @@ -24,6 +25,7 @@ import java.nio.file.FileSystems; import java.nio.file.Files; import java.util.Base64; +import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; @@ -249,6 +251,89 @@ public NanoHTTPD.Response execCommand(final Map params) { return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "application/json", jsonResponse.toString()); } + public NanoHTTPD.Response execMultipleCommands(final Map params) { + String commandsParam = params.get("commands"); + if (commandsParam == null) { + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "application/json", + "{\"error\":\"Missing 'commands' parameter\"}" + ); + } + + JSONArray commandsJsonArray; + try { + commandsJsonArray = new JSONArray(commandsParam); + } catch (Exception e) { + Logger.error("Error parsing 'commands' array: " + e.getMessage()); + return NanoHTTPD.newFixedLengthResponse( + NanoHTTPD.Response.Status.BAD_REQUEST, + "application/json", + "{\"error\":\"'commands' parameter must be a valid JSON array\"}" + ); + } + + JSONArray resultsArray = new JSONArray(); + + for (int i = 0; i < commandsJsonArray.length(); i++) { + String command = commandsJsonArray.optString(i); + if (command == null || command.trim().isEmpty()) { + JSONObject result = new JSONObject(); + result.put("command", JSONObject.NULL); + result.put("success", false); + result.put("output", "Empty command string provided."); + resultsArray.put(result); + continue; + } + + AtomicBoolean success = new AtomicBoolean(false); + CommandOutputCapture outputCapture = new CommandOutputCapture(); + JSONObject result = new JSONObject(); + result.put("command", command); + + try { + BukkitTask task = Bukkit.getScheduler().runTask(MinecraftServerAPI.getInstance(), () -> { + success.set(Bukkit.getServer().dispatchCommand(outputCapture, command)); + }); + + while (!task.isCancelled() && (Bukkit.getScheduler().isCurrentlyRunning(task.getTaskId()) || Bukkit.getScheduler().isQueued(task.getTaskId()))) { + try { + Thread.sleep(50); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + Logger.error("Command execution interrupted: " + command); + result.put("success", false); + result.put("output", "Command execution was interrupted."); + break; + } + } + + if (Thread.currentThread().isInterrupted()){ + Logger.error("Command execution interrupted: " + command); + result.put("success", false); + result.put("output", "Command execution was interrupted."); + } + + } catch (Exception e) { + Logger.error("Error executing command '" + command + "': " + e.getMessage()); + result.put("success", false); + result.put("output", "Error executing command: " + e.getMessage()); + } + + if (!result.has("success")) { + result.put("success", success.get()); + result.put("output", outputCapture.getOutputMessages()); + } + resultsArray.put(result); + } + + JSONObject responseJson = new JSONObject(); + responseJson.put("results", resultsArray); + + return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "application/json", responseJson.toString()); + } + + public NanoHTTPD.Response reload(final Map ignoredParams) { NanoHTTPD.Response response = NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, "application/json", "{}"); diff --git a/src/main/resources/api.yaml b/src/main/resources/api.yaml index 9877cb2..5307e03 100644 --- a/src/main/resources/api.yaml +++ b/src/main/resources/api.yaml @@ -3,7 +3,7 @@ openapi: 3.0.0 info: title: Minecraft Server API description: This API provides endpoints for interacting with and managing - various aspects of a Minecraft server. You can find a public list of available Endpoints [here](https://msa.shweit.com). + various aspects of a Minecraft server. The API specification can be found in the [api.yaml](api.yaml) file. version: 1.21.x contact: name: Dennis van den Brock diff --git a/src/test/java/ServerAPITest.java b/src/test/java/ServerAPITest.java index 3c89719..340cd54 100644 --- a/src/test/java/ServerAPITest.java +++ b/src/test/java/ServerAPITest.java @@ -1,13 +1,15 @@ import io.swagger.v3.core.util.Json; -import org.json.JSONObject; +import org.json.JSONArray; +import org.json.JSONObject; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.io.IOException; import java.net.HttpURLConnection; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertNotNull; public class ServerAPITest extends ApiTestHelper { @Test