From f2a6249af1f8091f5e9275a3f6f2232f27ec11e3 Mon Sep 17 00:00:00 2001 From: Etzelia Date: Tue, 16 Mar 2021 01:54:15 +0000 Subject: [PATCH] schedule (#1) Schedule kick Signed-off-by: Etzelia Chat API (#7) Chat API Signed-off-by: Etzelia Reviewed-on: https://git.etztech.xyz/Minecraft/ServerAPI/pulls/7 Reviewed-by: ZeroHD Add token auth and POST endpoints (#6) Add token auth and POST endpoints Signed-off-by: Etzelia Reviewed-on: https://git.etztech.xyz/Minecraft/ServerAPI/pulls/6 Reviewed-by: ZeroHD Change QueryAPI to PingAPI and add PluginAPI (#4) typo Fix null fields Signed-off-by: Etzelia Change QueryAPI to PingAPI and add PluginAPI Signed-off-by: Etzelia Reviewed-on: https://git.etztech.xyz/Minecraft/ServerAPI/pulls/4 Reviewed-by: ZeroHD Fix conflicting name (#3) Fix conflicting name Signed-off-by: Etzelia Reviewed-on: https://git.etztech.xyz/Minecraft/ServerAPI/pulls/3 Reviewed-on: https://git.birbmc.com/BirbMC/serverapi/pulls/1 Co-Authored-By: Etzelia Co-Committed-By: Etzelia --- pom.xml | 2 +- .../java/xyz/etztech/serverapi/ServerAPI.java | 80 ++++++++++++++-- .../listeners/AsyncPlayerChatListener.java | 31 ++++++ .../xyz/etztech/serverapi/token/Token.java | 27 ++++++ .../etztech/serverapi/token/TokenList.java | 49 ++++++++++ .../etztech/serverapi/token/TokenScope.java | 24 +++++ .../xyz/etztech/serverapi/web/GraphQL.java | 16 +++- .../xyz/etztech/serverapi/web/IProvider.java | 14 ++- .../java/xyz/etztech/serverapi/web/README.md | 22 +++++ .../java/xyz/etztech/serverapi/web/Web.java | 94 ++++++++++++++++--- .../xyz/etztech/serverapi/web/api/BanAPI.java | 11 ++- .../serverapi/web/api/BroadcastAPI.java | 25 +++++ .../etztech/serverapi/web/api/ChatAPI.java | 26 +++++ .../etztech/serverapi/web/api/CustomAPI.java | 32 +++++++ .../web/api/{QueryAPI.java => PingAPI.java} | 12 +-- .../etztech/serverapi/web/api/PluginAPI.java | 54 +++++++++++ .../etztech/serverapi/web/api/WorldAPI.java | 1 - src/main/resources/config.yml | 17 +++- .../xyz/etztech/serverapi/MockProvider.java | 38 +++++++- .../xyz/etztech/serverapi/ServerRunner.java | 21 ++++- 20 files changed, 555 insertions(+), 41 deletions(-) create mode 100644 src/main/java/xyz/etztech/serverapi/listeners/AsyncPlayerChatListener.java create mode 100644 src/main/java/xyz/etztech/serverapi/token/Token.java create mode 100644 src/main/java/xyz/etztech/serverapi/token/TokenList.java create mode 100644 src/main/java/xyz/etztech/serverapi/token/TokenScope.java create mode 100644 src/main/java/xyz/etztech/serverapi/web/README.md create mode 100644 src/main/java/xyz/etztech/serverapi/web/api/BroadcastAPI.java create mode 100644 src/main/java/xyz/etztech/serverapi/web/api/ChatAPI.java create mode 100644 src/main/java/xyz/etztech/serverapi/web/api/CustomAPI.java rename src/main/java/xyz/etztech/serverapi/web/api/{QueryAPI.java => PingAPI.java} (83%) create mode 100644 src/main/java/xyz/etztech/serverapi/web/api/PluginAPI.java diff --git a/pom.xml b/pom.xml index 4796725..59d84f9 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ xyz.etztech ServerAPI - 0.0.1 + 0.0.3 jar diff --git a/src/main/java/xyz/etztech/serverapi/ServerAPI.java b/src/main/java/xyz/etztech/serverapi/ServerAPI.java index 4d871e2..0978d73 100644 --- a/src/main/java/xyz/etztech/serverapi/ServerAPI.java +++ b/src/main/java/xyz/etztech/serverapi/ServerAPI.java @@ -3,20 +3,20 @@ package xyz.etztech.serverapi; import org.bukkit.BanEntry; import org.bukkit.BanList; -import org.bukkit.OfflinePlayer; +import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; import xyz.etztech.serverapi.commands.MainCommand; +import xyz.etztech.serverapi.listeners.AsyncPlayerChatListener; +import xyz.etztech.serverapi.token.TokenList; import xyz.etztech.serverapi.tps.TPS; import xyz.etztech.serverapi.web.IProvider; import xyz.etztech.serverapi.web.Web; import xyz.etztech.serverapi.web.api.*; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import java.util.logging.Logger; public class ServerAPI extends JavaPlugin implements IProvider { @@ -26,6 +26,8 @@ public class ServerAPI extends JavaPlugin implements IProvider { private Web web = new Web(this); private final Logger log = Logger.getLogger( "Minecraft" ); + private static final List chat = new ArrayList<>(); + @Override public void onEnable() { instance = this; @@ -34,6 +36,7 @@ public class ServerAPI extends JavaPlugin implements IProvider { reloadConfig(); if (isEnabled()) { + new AsyncPlayerChatListener(this); new MainCommand(this); tps = new TPS(); @@ -52,7 +55,8 @@ public class ServerAPI extends JavaPlugin implements IProvider { web.stop(); web.start( getConfig().getInt("port", 8080), - getConfig().getString("password", "") + new TokenList(getConfig().getConfigurationSection("auth")), + getConfig().getStringList("custom") ); } @@ -68,6 +72,10 @@ public class ServerAPI extends JavaPlugin implements IProvider { return tps; } + public static List getChat() { + return chat; + } + @Override public TPSAPI TPS() { return new TPSAPI(tps.getHistory()); @@ -111,8 +119,64 @@ public class ServerAPI extends JavaPlugin implements IProvider { } @Override - public QueryAPI query() { - return QueryAPI.fromMinecraft(getServer()); + public List chat() { + return chat; + } + + @Override + public void kick(BanAPI kick) { + Player player = Bukkit.getPlayerExact(kick.getTarget()); + if (player != null) { + getServer().getScheduler().runTask(this, () -> { + player.kickPlayer("You have been kicked: " + kick.getReason()); + }); + } + } + + @Override + public void ban(BanAPI ban) { + Date expires = null; + if (ban.getExpiration() != 0) { + expires = new Date(ban.getExpiration()); + } + Bukkit.getBanList(BanList.Type.NAME).addBan(ban.getTarget(), ban.getReason(), expires, "ServerAPI"); + Player player = Bukkit.getPlayerExact(ban.getTarget()); + if (player != null) { + getServer().getScheduler().runTask(this, () -> { + player.kickPlayer("You have been banned: " + ban.getReason()); + }); + } + } + + @Override + public void unban(BanAPI ban) { + Bukkit.getBanList(BanList.Type.NAME).pardon(ban.getTarget()); + } + + @Override + public void broadcast(BroadcastAPI broadcast) { + Bukkit.broadcastMessage(String.format("%s > %s", broadcast.getFrom(), broadcast.getMessage())); + } + + @Override + public void custom(CustomAPI custom) { + getServer().getScheduler().runTask(this, () -> { + getServer().dispatchCommand(getServer().getConsoleSender(), custom.build()); + }); + } + + @Override + public PingAPI ping() { + return PingAPI.fromMinecraft(getServer()); + } + + @Override + public List plugins() { + List plugins = new ArrayList<>(); + for (Plugin plugin : getServer().getPluginManager().getPlugins()) { + plugins.add(PluginAPI.fromMinecraft(plugin)); + } + return plugins; } } diff --git a/src/main/java/xyz/etztech/serverapi/listeners/AsyncPlayerChatListener.java b/src/main/java/xyz/etztech/serverapi/listeners/AsyncPlayerChatListener.java new file mode 100644 index 0000000..6206e2e --- /dev/null +++ b/src/main/java/xyz/etztech/serverapi/listeners/AsyncPlayerChatListener.java @@ -0,0 +1,31 @@ +package xyz.etztech.serverapi.listeners; + +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; +import xyz.etztech.serverapi.ServerAPI; +import xyz.etztech.serverapi.web.api.ChatAPI; + +import java.util.Date; + +public class AsyncPlayerChatListener implements Listener { + private final ServerAPI plugin; + + public AsyncPlayerChatListener(ServerAPI plugin) { + this.plugin = plugin; + plugin.getServer().getPluginManager().registerEvents(this, plugin); + } + + @EventHandler(priority= EventPriority.MONITOR, ignoreCancelled=true) + public void onChat(AsyncPlayerChatEvent event) { + int chatLimit = plugin.getConfig().getInt("chat", 100); + if (ServerAPI.getChat().size() >= chatLimit) { + ServerAPI.getChat().remove(0); + } + ServerAPI.getChat().add(new ChatAPI( + String.format(event.getFormat(), event.getPlayer().getDisplayName(), event.getMessage()), + new Date().getTime() + )); + } +} diff --git a/src/main/java/xyz/etztech/serverapi/token/Token.java b/src/main/java/xyz/etztech/serverapi/token/Token.java new file mode 100644 index 0000000..ba1bf78 --- /dev/null +++ b/src/main/java/xyz/etztech/serverapi/token/Token.java @@ -0,0 +1,27 @@ +package xyz.etztech.serverapi.token; + +public class Token { + private final String token; + private final TokenScope scope; + + public Token(String token, TokenScope scope) { + this.token = token; + this.scope = scope; + } + + public String getToken() { + return token; + } + + public TokenScope getScope() { + return scope; + } + + public boolean canGET() { + return scope == TokenScope.GET || scope == TokenScope.ALL; + } + + public boolean canPOST() { + return scope == TokenScope.POST || scope == TokenScope.ALL; + } +} \ No newline at end of file diff --git a/src/main/java/xyz/etztech/serverapi/token/TokenList.java b/src/main/java/xyz/etztech/serverapi/token/TokenList.java new file mode 100644 index 0000000..80119e4 --- /dev/null +++ b/src/main/java/xyz/etztech/serverapi/token/TokenList.java @@ -0,0 +1,49 @@ +package xyz.etztech.serverapi.token; + +import org.bukkit.configuration.ConfigurationSection; + +import java.util.ArrayList; +import java.util.List; + +public class TokenList { + private final boolean protectGET; + private final boolean protectPOST; + private final List tokens; + + public TokenList(boolean protectGET, boolean protectPOST, List tokens) { + this.protectGET = protectGET; + this.protectPOST = protectPOST; + this.tokens = tokens; + } + + public TokenList(ConfigurationSection auth) { + if (auth == null) { + this.protectGET = false; + this.protectPOST = false; + this.tokens = null; + return; + } + this.protectGET = auth.getBoolean("get", true); + this.protectPOST = auth.getBoolean("post", true); + this.tokens = new ArrayList<>(); + ConfigurationSection tokenSection = auth.getConfigurationSection("tokens"); + if (tokenSection == null) { + return; + } + for (String token : tokenSection.getKeys(false)) { + this.tokens.add(new Token(token, TokenScope.parseScope(tokenSection.getString(token, "none")))); + } + } + + public boolean isProtectGET() { + return protectGET; + } + + public boolean isProtectPOST() { + return protectPOST; + } + + public List getTokens() { + return tokens; + } +} diff --git a/src/main/java/xyz/etztech/serverapi/token/TokenScope.java b/src/main/java/xyz/etztech/serverapi/token/TokenScope.java new file mode 100644 index 0000000..17b6573 --- /dev/null +++ b/src/main/java/xyz/etztech/serverapi/token/TokenScope.java @@ -0,0 +1,24 @@ +package xyz.etztech.serverapi.token; + +public enum TokenScope { + NONE, + GET, + POST, + ALL; + + public static TokenScope parseScope(String scope) { + if (scope == null) { + return NONE; + } + switch (scope.toLowerCase()) { + case "get": + return GET; + case "post": + return POST; + case "all": + return ALL; + default: + return NONE; + } + } +} diff --git a/src/main/java/xyz/etztech/serverapi/web/GraphQL.java b/src/main/java/xyz/etztech/serverapi/web/GraphQL.java index 42f5ca7..81f62be 100644 --- a/src/main/java/xyz/etztech/serverapi/web/GraphQL.java +++ b/src/main/java/xyz/etztech/serverapi/web/GraphQL.java @@ -21,9 +21,14 @@ public class GraphQL implements QueryGraphql { return provider.players().toArray(new PlayerAPI[0]); } - @GraphQLName("query") - public QueryAPI getQuery() { - return provider.query(); + @GraphQLName("ping") + public PingAPI getPing() { + return provider.ping(); + } + + @GraphQLName("plugins") + public PluginAPI[] getPlugins() { + return provider.plugins().toArray(new PluginAPI[0]); } @GraphQLName("world") @@ -40,4 +45,9 @@ public class GraphQL implements QueryGraphql { public TPSAPI getTps() { return provider.TPS(); } + + @GraphQLName("chat") + public ChatAPI[] getChat() { + return provider.chat().toArray(new ChatAPI[0]); + } } diff --git a/src/main/java/xyz/etztech/serverapi/web/IProvider.java b/src/main/java/xyz/etztech/serverapi/web/IProvider.java index cc353d1..17a4636 100644 --- a/src/main/java/xyz/etztech/serverapi/web/IProvider.java +++ b/src/main/java/xyz/etztech/serverapi/web/IProvider.java @@ -6,12 +6,24 @@ import java.util.List; import java.util.Set; public interface IProvider { + + // GET Set bans(); Set players(); - QueryAPI query(); + PingAPI ping(); TPSAPI TPS(); List worlds(); WorldAPI world(String name); + List plugins(); + List chat(); + // POST + void kick(BanAPI kick); + void ban(BanAPI ban); + void unban(BanAPI ban); + void broadcast(BroadcastAPI broadcast); + void custom(CustomAPI custom); + + // MISC void log(String message); } diff --git a/src/main/java/xyz/etztech/serverapi/web/README.md b/src/main/java/xyz/etztech/serverapi/web/README.md new file mode 100644 index 0000000..13fc2f2 --- /dev/null +++ b/src/main/java/xyz/etztech/serverapi/web/README.md @@ -0,0 +1,22 @@ +# Adding a new endpoint + +1. Create a new `API` class in [api](api). + * Make sure to correctly add the appropriate JSON and GraphQL annotations. + Look at other classes for examples. + * **NOTE:** If anything returned could be null, make sure to instead provide an appropriate zero-value property, + otherwise GraphQL will choke. + See [PluginAPI::new](api/PluginAPI.java) for an example. +2. Add a method to return the needed data to [IProvider](IProvider.java). +3. Add a new REST endpoint to the [Web::start](Web.java) method. +4. Add a new GraphQL method to [GraphQL](GraphQL.java) + * Make sure to correctly add the appropriate GraphQL annotation. +5. Modify both [ServerAPI](../ServerAPI.java) +and [MockProvider](../../../../../../test/java/xyz/etztech/serverapi/MockProvider.java) +to fulfill the [IProvider](IProvider.java) interface. + +# Testing + +[ServerRunner](../../../../../../test/java/xyz/etztech/serverapi/ServerRunner.java) should start up the API using +[MockProvider](../../../../../../test/java/xyz/etztech/serverapi/MockProvider.java). + +If possible, a real test on a running Minecraft server would ideal! \ No newline at end of file diff --git a/src/main/java/xyz/etztech/serverapi/web/Web.java b/src/main/java/xyz/etztech/serverapi/web/Web.java index a1d8cc4..395f4fd 100644 --- a/src/main/java/xyz/etztech/serverapi/web/Web.java +++ b/src/main/java/xyz/etztech/serverapi/web/Web.java @@ -4,6 +4,7 @@ import io.javalin.Javalin; import io.javalin.core.event.EventListener; import io.javalin.core.plugin.Plugin; import io.javalin.core.util.RouteOverviewPlugin; +import io.javalin.http.BadRequestResponse; import io.javalin.http.Context; import io.javalin.http.UnauthorizedResponse; import io.javalin.plugin.graphql.GraphQLOptions; @@ -12,18 +13,25 @@ import io.javalin.plugin.json.JavalinJson; import org.apache.commons.lang.exception.ExceptionUtils; import org.slf4j.helpers.NOPLogger; import xyz.etztech.serverapi.ServerAPI; +import xyz.etztech.serverapi.token.TokenList; +import xyz.etztech.serverapi.web.api.BanAPI; +import xyz.etztech.serverapi.web.api.BroadcastAPI; +import xyz.etztech.serverapi.web.api.CustomAPI; import xyz.etztech.serverapi.web.api.ErrorAPI; +import java.util.List; + public class Web { private final IProvider provider; - private String password; + private TokenList tokens; + private List custom; private Javalin app; public Web(IProvider provider) { this.provider = provider; } - public void start(int port, String password) { + public void start(int port, TokenList tokens, List custom) { Javalin.log = NOPLogger.NOP_LOGGER; app = Javalin.create(config -> { config.registerPlugin(graphql()); @@ -31,17 +39,33 @@ public class Web { config.enableCorsForAllOrigins(); }).events(this::events).exception(Exception.class, this::exception); - this.password = password; - if (!"".equals(password)) { + this.custom = custom; + + this.tokens = tokens; + if (tokens != null) { + provider.log(String.format("Loaded %d tokens", tokens.getTokens().size())); + if (!tokens.isProtectPOST()) { + provider.log("WARNING: You have disabled POST protection, which can enable users to arbitrarily run actions against your server."); + } app.before(this::access); } + // REST Endpoints + // For GraphQL endpoints, see GraphQL.java app.get("/bans", this::bans); app.get("/players", this::players); - app.get("/query", this::query); + app.get("/plugins", this::plugins); + app.get("/ping", this::ping); app.get("/tps", this::tps); app.get("/worlds", this::worlds); app.get("/worlds/:name", this::world); + app.get("/chat", this::chat); + + app.post("/kick", this::kick); + app.post("/ban", this::ban); + app.post("/unban", this::unban); + app.post("/broadcast", this::broadcast); + app.post("/custom", this::custom); // What in the actual fuck... https://github.com/tipsy/javalin/issues/358 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); @@ -74,13 +98,22 @@ public class Web { } public void access(Context ctx) { - String pw = ctx.header("X-ServerAPI-Password"); - if (pw == null) { - pw = ctx.queryParam("password"); + String token = ctx.header("X-ServerAPI-Token"); + if (token == null) { + token = ctx.queryParam("token"); } + boolean isGET = "GET".equalsIgnoreCase(ctx.method()); + boolean isPOST = "POST".equalsIgnoreCase(ctx.method()); - if (!password.equals(pw)) { - throw new UnauthorizedResponse(JavalinJson.toJson(new ErrorAPI(401, "Unauthorized"))); + if (isGET && !tokens.isProtectGET()) return; + if (isPOST && !tokens.isProtectPOST()) return; + + String finalToken = token; + boolean pass = tokens.getTokens().stream().anyMatch(t -> t.getToken().equals(finalToken) && + ((isGET && t.canGET()) || (isPOST && t.canPOST()))); + + if (!pass) { + throw new UnauthorizedResponse(); } } @@ -103,15 +136,52 @@ public class Web { ctx.json(provider.world(ctx.pathParam("name"))); } + public void chat(Context ctx) { + ctx.json(provider.chat()); + } + public void players(Context ctx) { ctx.json(provider.players()); } + public void plugins(Context ctx) { + ctx.json(provider.plugins()); + } + public void bans(Context ctx) { ctx.json(provider.bans()); } - public void query(Context ctx) { - ctx.json(provider.query()); + public void ping(Context ctx) { + ctx.json(provider.ping()); + } + + public void kick(Context ctx) { + provider.kick(JavalinJson.fromJson(ctx.body(), BanAPI.class)); + ctx.status(200); + } + + public void ban(Context ctx) { + provider.ban(JavalinJson.fromJson(ctx.body(), BanAPI.class)); + ctx.status(200); + } + + public void unban(Context ctx) { + provider.unban(JavalinJson.fromJson(ctx.body(), BanAPI.class)); + ctx.status(200); + } + + public void broadcast(Context ctx) { + provider.broadcast(JavalinJson.fromJson(ctx.body(), BroadcastAPI.class)); + ctx.status(200); + } + + public void custom(Context ctx) { + CustomAPI capi = JavalinJson.fromJson(ctx.body(), CustomAPI.class); + if (custom.stream().noneMatch(c -> c.equalsIgnoreCase(capi.getCommand()))) { + throw new BadRequestResponse(); + } + provider.custom(capi); + ctx.status(200); } } diff --git a/src/main/java/xyz/etztech/serverapi/web/api/BanAPI.java b/src/main/java/xyz/etztech/serverapi/web/api/BanAPI.java index c1736c3..c70b2c9 100644 --- a/src/main/java/xyz/etztech/serverapi/web/api/BanAPI.java +++ b/src/main/java/xyz/etztech/serverapi/web/api/BanAPI.java @@ -1,12 +1,9 @@ package xyz.etztech.serverapi.web.api; import com.expediagroup.graphql.annotations.GraphQLDescription; -import com.expediagroup.graphql.annotations.GraphQLDirective; import com.expediagroup.graphql.annotations.GraphQLName; import org.bukkit.BanEntry; -import java.util.Date; - @GraphQLName("Ban") @GraphQLDescription("Ban GraphQL") public class BanAPI { @@ -16,6 +13,14 @@ public class BanAPI { private final long created; private final long expiration; + public BanAPI() { + this.target = ""; + this.source = ""; + this.reason = ""; + this.created = 0; + this.expiration = 0; + } + public BanAPI(String target, String source, String reason, long created, long expiration) { this.target = target; this.source = source; diff --git a/src/main/java/xyz/etztech/serverapi/web/api/BroadcastAPI.java b/src/main/java/xyz/etztech/serverapi/web/api/BroadcastAPI.java new file mode 100644 index 0000000..8746d11 --- /dev/null +++ b/src/main/java/xyz/etztech/serverapi/web/api/BroadcastAPI.java @@ -0,0 +1,25 @@ +package xyz.etztech.serverapi.web.api; + +public class BroadcastAPI { + private final String from; + private final String message; + + public BroadcastAPI() { + this.from = ""; + this.message = ""; + } + + public BroadcastAPI(String from, String message) { + this.from = from; + this.message = message; + } + + public String getFrom() { + return from; + } + + public String getMessage() { + return message; + } + +} diff --git a/src/main/java/xyz/etztech/serverapi/web/api/ChatAPI.java b/src/main/java/xyz/etztech/serverapi/web/api/ChatAPI.java new file mode 100644 index 0000000..9cb1623 --- /dev/null +++ b/src/main/java/xyz/etztech/serverapi/web/api/ChatAPI.java @@ -0,0 +1,26 @@ +package xyz.etztech.serverapi.web.api; + +import com.expediagroup.graphql.annotations.GraphQLDescription; +import com.expediagroup.graphql.annotations.GraphQLName; + +@GraphQLName("Chat") +@GraphQLDescription("Chat GraphQL") +public class ChatAPI { + private final String message; + private final long timestamp; + + public ChatAPI(String message, long timestamp) { + this.message = message; + this.timestamp = timestamp; + } + + @GraphQLName("message") + public String getMessage() { + return message; + } + + @GraphQLName("timestamp") + public long getTimestamp() { + return timestamp; + } +} diff --git a/src/main/java/xyz/etztech/serverapi/web/api/CustomAPI.java b/src/main/java/xyz/etztech/serverapi/web/api/CustomAPI.java new file mode 100644 index 0000000..faeaa67 --- /dev/null +++ b/src/main/java/xyz/etztech/serverapi/web/api/CustomAPI.java @@ -0,0 +1,32 @@ +package xyz.etztech.serverapi.web.api; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class CustomAPI { + private final String command; + private final List args; + + public CustomAPI() { + this.command = ""; + this.args = new ArrayList<>(); + } + + public CustomAPI(String command, List args) { + this.command = command; + this.args = args; + } + + public String getCommand() { + return command; + } + + public List getArgs() { + return args; + } + + public String build() { + return command + " " + String.join(" ", args); + } +} diff --git a/src/main/java/xyz/etztech/serverapi/web/api/QueryAPI.java b/src/main/java/xyz/etztech/serverapi/web/api/PingAPI.java similarity index 83% rename from src/main/java/xyz/etztech/serverapi/web/api/QueryAPI.java rename to src/main/java/xyz/etztech/serverapi/web/api/PingAPI.java index 348215e..4b0aeee 100644 --- a/src/main/java/xyz/etztech/serverapi/web/api/QueryAPI.java +++ b/src/main/java/xyz/etztech/serverapi/web/api/PingAPI.java @@ -5,9 +5,9 @@ import com.expediagroup.graphql.annotations.GraphQLName; import com.fasterxml.jackson.annotation.JsonProperty; import org.bukkit.Server; -@GraphQLName("Query") -@GraphQLDescription("Query GraphQL") -public class QueryAPI { +@GraphQLName("Ping") +@GraphQLDescription("Ping GraphQL") +public class PingAPI { private final String type; private final String version; private final String motd; @@ -16,7 +16,7 @@ public class QueryAPI { @JsonProperty("max_players") private final int maxPlayers; - public QueryAPI(String type, String version, String motd, int currentPlayers, int maxPlayers) { + public PingAPI(String type, String version, String motd, int currentPlayers, int maxPlayers) { this.type = type; this.version = version; this.motd = motd; @@ -49,8 +49,8 @@ public class QueryAPI { return maxPlayers; } - public static QueryAPI fromMinecraft(Server server) { - return new QueryAPI( + public static PingAPI fromMinecraft(Server server) { + return new PingAPI( server.getName(), server.getBukkitVersion().split("-")[0], // 1.x.x-R0.1-SNAPSHOT server.getMotd(), diff --git a/src/main/java/xyz/etztech/serverapi/web/api/PluginAPI.java b/src/main/java/xyz/etztech/serverapi/web/api/PluginAPI.java new file mode 100644 index 0000000..f185700 --- /dev/null +++ b/src/main/java/xyz/etztech/serverapi/web/api/PluginAPI.java @@ -0,0 +1,54 @@ +package xyz.etztech.serverapi.web.api; + +import com.expediagroup.graphql.annotations.GraphQLDescription; +import com.expediagroup.graphql.annotations.GraphQLName; +import org.bukkit.plugin.Plugin; + +import java.util.ArrayList; +import java.util.List; + +@GraphQLName("Plugin") +@GraphQLDescription("Plugin GraphQL") +public class PluginAPI { + private final String name; + private final String version; + private final List authors; + private final String website; + + + public PluginAPI(String name, String version, List authors, String website) { + this.name = name; + this.version = version; + this.authors = authors != null ? authors : new ArrayList<>(); + this.website = website != null ? website : ""; + } + + @GraphQLName("name") + public String getName() { + return name; + } + + @GraphQLName("version") + public String getVersion() { + return version; + } + + @GraphQLName("authors") + public List getAuthors() { + return authors; + } + + @GraphQLName("website") + public String getWebsite() { + return website; + } + + public static PluginAPI fromMinecraft(Plugin plugin) { + return new PluginAPI( + plugin.getName(), + plugin.getDescription().getVersion(), + plugin.getDescription().getAuthors(), + plugin.getDescription().getWebsite() + ); + } +} diff --git a/src/main/java/xyz/etztech/serverapi/web/api/WorldAPI.java b/src/main/java/xyz/etztech/serverapi/web/api/WorldAPI.java index 813315c..098d7d5 100644 --- a/src/main/java/xyz/etztech/serverapi/web/api/WorldAPI.java +++ b/src/main/java/xyz/etztech/serverapi/web/api/WorldAPI.java @@ -3,7 +3,6 @@ package xyz.etztech.serverapi.web.api; import com.expediagroup.graphql.annotations.GraphQLDescription; import com.expediagroup.graphql.annotations.GraphQLName; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonNaming; import org.bukkit.World; @GraphQLName("World") diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 32a3443..9cd5c8c 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,5 +1,18 @@ # The port to run on port: 8080 -# (Optional) password -password: '' \ No newline at end of file +# Number of chat logs to retain +chat: 100 + +# Authentication +auth: + # Protect GET routes. If false, GET routes are public. + get: true + # Protect POST routes. If false, POST routes are public. + post: true + tokens: + token: access + +# Custom commands (POST) +custom: + - say \ No newline at end of file diff --git a/src/test/java/xyz/etztech/serverapi/MockProvider.java b/src/test/java/xyz/etztech/serverapi/MockProvider.java index 5051acc..c733e85 100644 --- a/src/test/java/xyz/etztech/serverapi/MockProvider.java +++ b/src/test/java/xyz/etztech/serverapi/MockProvider.java @@ -46,6 +46,21 @@ public class MockProvider implements IProvider { )); } + @Override + public void kick(BanAPI kick) {} + + @Override + public void ban(BanAPI ban) {} + + @Override + public void unban(BanAPI ban) {} + + @Override + public void broadcast(BroadcastAPI broadcast) {} + + @Override + public void custom(CustomAPI custom) {} + @Override public Set players() { return new HashSet<>(Arrays.asList( @@ -56,8 +71,27 @@ public class MockProvider implements IProvider { } @Override - public QueryAPI query() { - return new QueryAPI("Mock", "0.0.1", "Hello, world!", 0, 100); + public PingAPI ping() { + return new PingAPI("Mock", "0.0.1", "Hello, world!", 0, 100); + } + + @Override + public List plugins() { + return Arrays.asList( + new PluginAPI("ServerAPI", "0.0.1", Collections.singletonList("Etzelia"), "https://git.etztech.xyz"), + new PluginAPI("dynmap", "0.1.0", null, "https://www.spigotmc.org/resources/dynmap.274/"), + new PluginAPI("CoreProtect", "1.0.0", Collections.singletonList("Intelli"), null) + ); + } + + @Override + public List chat() { + long now = new Date().getTime(); + return Arrays.asList( + new ChatAPI("message 1", now-2), + new ChatAPI("message 2", now-1), + new ChatAPI("message 3", now) + ); } @Override diff --git a/src/test/java/xyz/etztech/serverapi/ServerRunner.java b/src/test/java/xyz/etztech/serverapi/ServerRunner.java index e34587f..821723b 100644 --- a/src/test/java/xyz/etztech/serverapi/ServerRunner.java +++ b/src/test/java/xyz/etztech/serverapi/ServerRunner.java @@ -1,12 +1,29 @@ package xyz.etztech.serverapi; +import xyz.etztech.serverapi.token.Token; +import xyz.etztech.serverapi.token.TokenList; +import xyz.etztech.serverapi.token.TokenScope; import xyz.etztech.serverapi.web.Web; +import java.util.Arrays; +import java.util.List; + public class ServerRunner { + static TokenList tokens = new TokenList(true, true, Arrays.asList( + new Token("testGET", TokenScope.GET), + new Token("testPOST", TokenScope.POST), + new Token("testAll", TokenScope.ALL), + new Token("testNone", TokenScope.NONE) + )); + + static List custom = Arrays.asList( + "test", + "say" + ); + public static void main(String[] args) { Web web = new Web(new MockProvider()); - - web.start(8080, ""); + web.start(8080, tokens, custom); } }