From 2be1865263633cf4884912141ac38776fc457fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Sun, 10 Dec 2023 21:34:23 +0100 Subject: [PATCH] Added HTTP API and Event Item Rewards --- build.gradle | 23 +++++--- .../java/eu/mhsl/craftattack/spawn/Main.java | 5 ++ .../craftattack/spawn/api/HttpServer.java | 53 ++++++++++++++++++ .../spawn/appliance/Appliance.java | 9 ++- .../spawn/appliances/event/Event.java | 55 ++++++++++++++++++- .../listener/ApplyPendingRewardsListener.java | 13 +++++ .../spawn/appliances/whitelist/Whitelist.java | 3 + 7 files changed, 152 insertions(+), 9 deletions(-) create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/api/HttpServer.java create mode 100644 src/main/java/eu/mhsl/craftattack/spawn/appliances/event/listener/ApplyPendingRewardsListener.java diff --git a/build.gradle b/build.gradle index a41a8e9..146bf4c 100644 --- a/build.gradle +++ b/build.gradle @@ -22,9 +22,10 @@ repositories { } dependencies { - compileOnly "io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT" + compileOnly 'io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT' compileOnly 'org.geysermc.floodgate:api:2.2.2-SNAPSHOT' - compileOnly 'org.apache.httpcomponents:httpclient:4.5.14' + implementation 'org.apache.httpcomponents:httpclient:4.5.14' + implementation 'com.sparkjava:spark-core:2.9.4' } def targetJavaVersion = 17 @@ -43,9 +44,17 @@ tasks.withType(JavaCompile).configureEach { } } -tasks.register('copyJarToServer', Exec) { - dependsOn jar - mustRunAfter jar - - commandLine 'scp', 'build/libs/spawn-1.0.jar', 'root@10.20.6.1:/root/server/plugins' +configurations { + shadowImplementation.extendsFrom implementation +} + +shadowJar { + configurations = [project.configurations.shadowImplementation] +} + +tasks.register('copyJarToServer', Exec) { + dependsOn shadowJar + mustRunAfter shadowJar + + commandLine 'scp', 'build/libs/spawn-1.0-all.jar', 'root@10.20.6.1:/root/server/plugins' } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/Main.java b/src/main/java/eu/mhsl/craftattack/spawn/Main.java index b9bf48a..3a03a1f 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/Main.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/Main.java @@ -1,5 +1,6 @@ package eu.mhsl.craftattack.spawn; +import eu.mhsl.craftattack.spawn.api.HttpServer; import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliances.adminMarker.AdminMarker; import eu.mhsl.craftattack.spawn.appliances.kick.Kick; @@ -28,6 +29,7 @@ public final class Main extends JavaPlugin { private static Main instance; private List appliances; + private HttpServer httpApi; @Override public void onEnable() { instance = this; @@ -59,6 +61,9 @@ public final class Main extends JavaPlugin { }); Bukkit.getLogger().info("Loaded " + appliances.size() + " appliances!"); + Bukkit.getLogger().info("Starting HTTP API"); + this.httpApi = new HttpServer(); + getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); } diff --git a/src/main/java/eu/mhsl/craftattack/spawn/api/HttpServer.java b/src/main/java/eu/mhsl/craftattack/spawn/api/HttpServer.java new file mode 100644 index 0000000..4fadd3e --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/api/HttpServer.java @@ -0,0 +1,53 @@ +package eu.mhsl.craftattack.spawn.api; + +import com.google.gson.Gson; +import eu.mhsl.craftattack.spawn.Main; +import eu.mhsl.craftattack.spawn.appliance.Appliance; +import org.bukkit.configuration.ConfigurationSection; +import spark.Request; +import spark.Spark; + +import java.util.function.Function; + +public class HttpServer { + private final ConfigurationSection apiConf = Main.instance().getConfig().getConfigurationSection("api"); + protected final Gson gson = new Gson(); + + public static Object nothing = ""; + + public HttpServer() { + Spark.port(8080); + + Spark.get("/ping", (request, response) -> System.currentTimeMillis()); + + Main.instance().getAppliances().forEach(appliance -> appliance.httpApi(new ApiBuilder(appliance))); + } + + public class ApiBuilder { + @FunctionalInterface + public interface RequestProvider { + TResponse apply(TParsed parsed, TOriginal original); + } + + private final String applianceName; + private ApiBuilder(Appliance appliance) { + this.applianceName = appliance.getClass().getSimpleName().toLowerCase(); + } + + public void get(String path, Function onCall) { + Spark.get(this.buildRoute(path), (req, resp) -> HttpServer.this.gson.toJson(onCall.apply(req))); + } + + public void post(String path, Class clazz, RequestProvider onCall) { + Spark.post(this.buildRoute(path), (req, resp) -> { + TRequest parsed = new Gson().fromJson(req.body(), clazz); + Object response = onCall.apply(parsed, req); + return HttpServer.this.gson.toJson(response); + }); + } + + public String buildRoute(String path) { + return String.format("/api/%s/%s", this.applianceName, path); + } + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliance/Appliance.java b/src/main/java/eu/mhsl/craftattack/spawn/appliance/Appliance.java index ed66a31..b2fed1c 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliance/Appliance.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliance/Appliance.java @@ -1,5 +1,6 @@ package eu.mhsl.craftattack.spawn.appliance; +import eu.mhsl.craftattack.spawn.api.HttpServer; import eu.mhsl.craftattack.spawn.config.Configuration; import org.bukkit.Bukkit; import org.bukkit.command.PluginCommand; @@ -8,7 +9,6 @@ import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.plugin.java.JavaPlugin; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; @@ -50,6 +50,13 @@ public abstract class Appliance { return new ArrayList<>(); } + /** + * Called on initialization to add all needed API Routes. + * The routeBuilder can be used to get the correct Path prefixes + * @param apiBuilder holds data for needed route prefixes. + */ + public void httpApi(HttpServer.ApiBuilder apiBuilder) {} + /** * Provides a localized config section. Path can be set in appliance constructor. * @return Section of configuration for your appliance diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/event/Event.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/event/Event.java index 72c8c11..0a9cdcb 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/event/Event.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/event/Event.java @@ -1,21 +1,27 @@ package eu.mhsl.craftattack.spawn.appliances.event; import com.google.gson.Gson; +import eu.mhsl.craftattack.spawn.Main; +import eu.mhsl.craftattack.spawn.api.HttpServer; import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.appliance.ApplianceCommand; import eu.mhsl.craftattack.spawn.appliances.event.command.EventCommand; import eu.mhsl.craftattack.spawn.appliances.event.command.EventEndSessionCommand; import eu.mhsl.craftattack.spawn.appliances.event.command.EventOpenSessionCommand; import eu.mhsl.craftattack.spawn.appliances.event.command.MoveEventVillagerCommand; +import eu.mhsl.craftattack.spawn.appliances.event.listener.ApplyPendingRewardsListener; import eu.mhsl.craftattack.spawn.util.entity.DisplayVillager; import eu.mhsl.craftattack.spawn.util.server.PluginMessage; import eu.mhsl.craftattack.spawn.util.listener.DismissInventoryOpenFromHolder; import eu.mhsl.craftattack.spawn.util.listener.PlayerInteractAtEntityEventListener; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.entity.Villager; import org.bukkit.event.Listener; +import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; import java.io.IOException; @@ -24,15 +30,20 @@ import java.net.URISyntaxException; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.UUID; public class Event extends Appliance { public DisplayVillager.ConfigBound villager; private boolean isOpen = false; private UUID roomId; - private HttpClient eventServerClient = HttpClient.newHttpClient(); + private final HttpClient eventServerClient = HttpClient.newHttpClient(); + private final List pendingRewards = new ArrayList<>(); + record RewardConfiguration(String material, Map rewards) {} + record Reward(UUID playerUuid, Material material, int amount) {} public Event() { super("event"); @@ -103,6 +114,47 @@ public class Event extends Appliance { isOpen = false; } + private void rewardPlayers(RewardConfiguration rewardConfiguration) { + rewardConfiguration.rewards.forEach((uuid, amount) -> { + Reward reward = new Reward(uuid, Material.matchMaterial(rewardConfiguration.material), amount); + if(reward.material == null) throw new RuntimeException("Material not found"); + + if(Bukkit.getPlayer(uuid) == null) { + pendingRewards.add(reward); + return; + } + + giveReward(reward); + }); + } + + private void giveReward(Reward reward) { + Player player = Bukkit.getPlayer(reward.playerUuid); + if(player == null) throw new RuntimeException("Cannot reward offline playerUuid!"); + + ItemStack rewardStack = new ItemStack(reward.material, reward.amount); + Map remaining = player.getInventory().addItem(rewardStack); + Bukkit.getScheduler().runTask( + Main.instance(), + () -> remaining.values().forEach(remainingStack -> player.getWorld().dropItem(player.getLocation(), remainingStack)) + ); + } + + public void applyPendingRewards(Player player) { + if(this.pendingRewards.isEmpty()) return; + this.pendingRewards.stream() + .filter(reward -> reward.playerUuid.equals(player.getUniqueId())) + .forEach(this::giveReward); + } + + @Override + public void httpApi(HttpServer.ApiBuilder apiBuilder) { + apiBuilder.post("reward", RewardConfiguration.class, (rewardConfiguration, request) -> { + this.rewardPlayers(rewardConfiguration); + return HttpServer.nothing; + }); + } + @Override protected @NotNull List> commands() { return List.of( @@ -116,6 +168,7 @@ public class Event extends Appliance { @Override protected @NotNull List eventHandlers() { return List.of( + new ApplyPendingRewardsListener(), new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), playerInteractAtEntityEvent -> joinEvent(playerInteractAtEntityEvent.getPlayer())), new DismissInventoryOpenFromHolder(this.villager.getUniqueId()) ); diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/event/listener/ApplyPendingRewardsListener.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/event/listener/ApplyPendingRewardsListener.java new file mode 100644 index 0000000..539af1d --- /dev/null +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/event/listener/ApplyPendingRewardsListener.java @@ -0,0 +1,13 @@ +package eu.mhsl.craftattack.spawn.appliances.event.listener; + +import eu.mhsl.craftattack.spawn.appliance.ApplianceListener; +import eu.mhsl.craftattack.spawn.appliances.event.Event; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerJoinEvent; + +public class ApplyPendingRewardsListener extends ApplianceListener { + @EventHandler + public void onJoin(PlayerJoinEvent event) { + getAppliance().applyPendingRewards(event.getPlayer()); + } +} diff --git a/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java b/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java index 99f20c6..fbb7dd9 100644 --- a/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java +++ b/src/main/java/eu/mhsl/craftattack/spawn/appliances/whitelist/Whitelist.java @@ -1,6 +1,7 @@ package eu.mhsl.craftattack.spawn.appliances.whitelist; import com.google.gson.Gson; +import eu.mhsl.craftattack.spawn.Main; import eu.mhsl.craftattack.spawn.appliance.Appliance; import eu.mhsl.craftattack.spawn.util.server.Floodgate; import eu.mhsl.craftattack.spawn.util.text.DisconnectInfo; @@ -18,6 +19,7 @@ import java.net.http.HttpResponse; import java.util.List; import java.util.Objects; import java.util.UUID; +import java.util.logging.Level; public class Whitelist extends Appliance { private record UserData(UUID uuid, String username, String firstname, String lastname) {} @@ -50,6 +52,7 @@ public class Whitelist extends Appliance { } catch (DisconnectInfo.Throwable e) { throw e; } catch (Exception e) { + Main.instance().getLogger().log(Level.SEVERE, e, e::getMessage); throw new DisconnectInfo.Throwable( "Interner Serverfehler", "Deine Zugangsdaten konnten nicht abgerufen werden.",