From de97f3dea7254c56fd313099401c4e0845eacbc8 Mon Sep 17 00:00:00 2001 From: M_Viper Date: Tue, 17 Feb 2026 17:58:45 +0100 Subject: [PATCH] Update from Git Manager GUI --- .../de/mviper/spigot/IngameShopSpigot.java | 682 +++++++++++------- .../src/main/resources/config.yml | 28 +- .../src/main/resources/plugin.yml | 27 +- 3 files changed, 444 insertions(+), 293 deletions(-) diff --git a/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java b/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java index f34aa84..27cc4a8 100644 --- a/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java +++ b/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java @@ -4,10 +4,14 @@ import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.Sound; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -24,6 +28,7 @@ import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.UUID; @@ -38,21 +43,29 @@ import com.google.gson.JsonParser; public class IngameShopSpigot extends JavaPlugin implements Listener { private static Economy econ = null; - private String wpBase; + private String wpBase; private String wpUrlPending; + private String wpUrlPendingOffline; // NEU: Offline-Queue private String wpUrlExecute; private String wpUrlComplete; - private String wpUrlCancel; // NEU - + private String wpUrlCancel; + private Gson gson = new Gson(); private boolean debug = false; private BukkitTask task; - private String currency = "Coins"; + private String currency = "Coins"; private String targetServer = "survival"; - private Map orderCache = new HashMap<>(); - private Map activeOrderIds = new HashMap<>(); + // NEU: API-Key für gesicherte Endpunkte + private String apiKey = ""; + + private Map orderCache = new HashMap<>(); + private Map activeOrderIds = new HashMap<>(); + + // =========================================================== + // LIFECYCLE + // =========================================================== @Override public void onEnable() { @@ -64,32 +77,48 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { saveDefaultConfig(); reloadConfig(); - - String domain = getConfig().getString("wordpress-url", "http://localhost/Windelgeschichten.org"); - if (domain.endsWith("/")) { - domain = domain.substring(0, domain.length() - 1); - } + + String domain = getConfig().getString("wordpress-url", "http://localhost"); + if (domain.endsWith("/")) domain = domain.substring(0, domain.length() - 1); wpBase = domain + "/wp-json/wis/v1"; - - wpUrlPending = wpBase + "/pending_orders"; - wpUrlExecute = wpBase + "/execute_order"; - wpUrlComplete = wpBase + "/complete_order"; - wpUrlCancel = wpBase + "/cancel_order"; // NEU - - this.targetServer = getConfig().getString("server-name", "survival").toLowerCase(); - this.currency = getConfig().getString("currency-name", "Coins"); - + + wpUrlPending = wpBase + "/pending_orders"; + wpUrlPendingOffline = wpBase + "/pending_offline"; // NEU + wpUrlExecute = wpBase + "/execute_order"; + wpUrlComplete = wpBase + "/complete_order"; + wpUrlCancel = wpBase + "/cancel_order"; + + targetServer = getConfig().getString("server-name", "survival").toLowerCase(); + currency = getConfig().getString("currency-name", "Coins"); + apiKey = getConfig().getString("api-key", ""); // NEU + debug = getConfig().getBoolean("debug-mode", false); + + if (apiKey.isEmpty()) { + getLogger().warning("⚠️ Kein api-key in config.yml gesetzt! Geschützte Endpunkte sind nicht erreichbar."); + getLogger().warning(" Trage den Key aus den WordPress-Einstellungen (Ingame Shop → Einstellungen → 🔑 API-Key) ein."); + } + int intervalSeconds = getConfig().getInt("check-interval", 10); - long pollInterval = intervalSeconds * 20L; - debug = getConfig().getBoolean("debug-mode", false); - + long pollInterval = intervalSeconds * 20L; + getServer().getPluginManager().registerEvents(this, this); + + // /orders Befehl registrieren + getCommand("orders").setExecutor(new OrdersCommand()); + startPolling(pollInterval); - - getLogger().info("=== IngameShopSpigot v6.2 (Cancel Logic) CONFIG ==="); - getLogger().info("Domain: " + domain); - getLogger().info("Target Server: " + this.targetServer); - getLogger().info("Currency: " + this.currency); + + getLogger().info("=== IngameShopSpigot v6.3 (API-Key + Offline-Queue) ==="); + getLogger().info("Domain: " + domain); + getLogger().info("Target Server: " + targetServer); + getLogger().info("Currency: " + currency); + getLogger().info("API-Key: " + (apiKey.isEmpty() ? "❌ NICHT GESETZT" : "✅ gesetzt")); + } + + @Override + public void onDisable() { + if (task != null) task.cancel(); + getLogger().info("IngameShopSpigot gestoppt"); } private boolean setupEconomy() { @@ -99,6 +128,10 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { return econ != null; } + // =========================================================== + // POLLING + // =========================================================== + private void startPolling(long intervalTicks) { this.task = new BukkitRunnable() { @Override @@ -107,61 +140,13 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { @Override public void run() { for (Player p : Bukkit.getOnlinePlayers()) { - try { - String urlString = wpUrlPending + "?player=" + p.getName(); - URL url = new URL(urlString); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("GET"); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setConnectTimeout(5000); - conn.setReadTimeout(5000); - - if (conn.getResponseCode() == 200) { - BufferedReader reader = new BufferedReader( - new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8) - ); - StringBuilder response = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - response.append(line); - } - reader.close(); - - JsonObject json = JsonParser.parseString(response.toString()).getAsJsonObject(); - JsonArray orders = json.getAsJsonArray("orders"); - - if (orders != null && orders.size() > 0) { - for (int i = 0; i < orders.size(); i++) { - JsonObject order = orders.get(i).getAsJsonObject(); - - int id = order.get("id").getAsInt(); - String orderServer = order.has("server") ? order.get("server").getAsString().toLowerCase() : ""; - - if (!orderServer.equals(targetServer)) { - if (debug) getLogger().info("Order #" + id + " ist für Server '" + orderServer + "'. Ignoriere."); - continue; - } - - String jsonResponse = order.has("response") ? order.get("response").getAsString() : "[]"; - String itemTitle = order.get("item_title").getAsString(); - double price = order.get("price").getAsDouble(); - String status = order.get("status").getAsString(); - - if ("pending".equals(status)) { - OrderData data = new OrderData(id, "multi_item", itemTitle, price, 1, jsonResponse); - orderCache.put(id, data); - activeOrderIds.put(p.getUniqueId(), id); - - Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { - openConfirmGUI(p, id, itemTitle, price); - }); - } - } - } - } - } catch (Exception e) { - if (debug) Bukkit.getLogger().log(Level.WARNING, "Polling Fehler", e); + // RACE CONDITION FIX: Spieler der bereits eine aktive Order im GUI hat + // bekommt keine neue Order angezeigt bis die vorherige abgeschlossen ist + if (activeOrderIds.containsKey(p.getUniqueId())) { + if (debug) getLogger().info("Spieler " + p.getName() + " hat bereits aktive Order – überspringe Poll."); + continue; } + fetchPendingOrders(p, wpUrlPending); } } }.runTaskAsynchronously(IngameShopSpigot.this); @@ -169,25 +154,92 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { }.runTaskTimer(this, 20L, intervalTicks); } - private void openConfirmGUI(Player player, int orderId, String itemTitle, double price) { - Inventory gui = Bukkit.createInventory(null, 27, "§eKauf bestätigen?"); + private void fetchPendingOrders(Player p, String endpointUrl) { + try { + String urlString = endpointUrl + "?player=" + p.getName(); + HttpURLConnection conn = openAuthConnection(urlString, "GET"); - ItemStack info = new ItemStack(Material.WRITTEN_BOOK); + if (conn.getResponseCode() == 200) { + String body = readResponse(conn); + JsonObject json = JsonParser.parseString(body).getAsJsonObject(); + JsonArray orders = json.getAsJsonArray("orders"); + + if (orders != null && orders.size() > 0) { + JsonObject order = orders.get(0).getAsJsonObject(); // immer nur erste Order zeigen + + int id = order.get("id").getAsInt(); + String orderServer = order.has("server") ? order.get("server").getAsString().toLowerCase() : ""; + + if (!orderServer.equals(targetServer)) { + if (debug) getLogger().info("Order #" + id + " ist für Server '" + orderServer + "'. Ignoriere."); + return; + } + + String jsonResponse = order.has("response") ? order.get("response").getAsString() : "[]"; + String itemTitle = order.get("item_title").getAsString(); + double price = order.get("price").getAsDouble(); + + OrderData data = new OrderData(id, "multi_item", itemTitle, price, 1, jsonResponse); + orderCache.put(id, data); + activeOrderIds.put(p.getUniqueId(), id); + + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> openConfirmGUI(p, id, itemTitle, price, jsonResponse)); + } + } else if (conn.getResponseCode() == 401) { + getLogger().warning("❌ API-Key ungültig oder nicht gesetzt! HTTP 401 von " + endpointUrl); + } + } catch (Exception e) { + if (debug) getLogger().log(Level.WARNING, "Polling Fehler für " + p.getName(), e); + } + } + + // =========================================================== + // OFFLINE-QUEUE: beim Login ausstehende Orders liefern + // =========================================================== + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player p = event.getPlayer(); + + new BukkitRunnable() { + @Override + public void run() { + fetchPendingOrders(p, wpUrlPendingOffline); + } + }.runTaskAsynchronously(this); + } + + // =========================================================== + // GUI: zeigt echtes Item-Icon aus dem Warenkorb-Payload + // =========================================================== + + private void openConfirmGUI(Player player, int orderId, String itemTitle, double price, String jsonPayload) { + Inventory gui = Bukkit.createInventory(null, 27, ChatColor.YELLOW + "Kauf bestätigen?"); + + // NEU: erstes Item aus dem Payload als Icon verwenden + Material iconMaterial = getFirstItemMaterial(jsonPayload); + ItemStack info = new ItemStack(iconMaterial); ItemMeta infoMeta = info.getItemMeta(); - infoMeta.setDisplayName(ChatColor.GOLD + itemTitle); - infoMeta.setLore(java.util.Arrays.asList( - ChatColor.WHITE + "Preis: " + price + " " + currency + infoMeta.setDisplayName(ChatColor.GOLD + "" + ChatColor.BOLD + itemTitle); + infoMeta.setLore(Arrays.asList( + ChatColor.GRAY + "──────────────────", + ChatColor.WHITE + "Preis: " + ChatColor.YELLOW + price + " " + currency, + ChatColor.GRAY + "──────────────────", + ChatColor.GRAY + "Klicke Grün zum Bestätigen", + ChatColor.GRAY + "Klicke Rot zum Abbrechen" )); info.setItemMeta(infoMeta); ItemStack yes = new ItemStack(Material.LIME_WOOL); ItemMeta yesMeta = yes.getItemMeta(); - yesMeta.setDisplayName(ChatColor.GREEN + "§lJA, kaufen!"); + yesMeta.setDisplayName(ChatColor.GREEN + "" + ChatColor.BOLD + "✔ JA, kaufen!"); + yesMeta.setLore(Arrays.asList(ChatColor.GRAY + "" + price + " " + currency + " werden abgezogen")); yes.setItemMeta(yesMeta); - + ItemStack no = new ItemStack(Material.RED_WOOL); ItemMeta noMeta = no.getItemMeta(); - noMeta.setDisplayName(ChatColor.RED + "§lNEIN, abbrechen"); + noMeta.setDisplayName(ChatColor.RED + "" + ChatColor.BOLD + "✘ NEIN, abbrechen"); + noMeta.setLore(Arrays.asList(ChatColor.GRAY + "Bestellung wird storniert")); no.setItemMeta(noMeta); ItemStack pane = new ItemStack(Material.BLACK_STAINED_GLASS_PANE); @@ -203,15 +255,43 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { player.openInventory(gui); } + /** + * NEU: Liest das erste Item aus dem JSON-Payload und gibt das passende Material zurück. + * Fällt auf CHEST zurück wenn nichts parsebar ist. + */ + private Material getFirstItemMaterial(String jsonPayload) { + try { + JsonElement root = gson.fromJson(jsonPayload, JsonElement.class); + JsonArray items; + if (root.isJsonObject()) { + items = root.getAsJsonObject().getAsJsonArray("items"); + } else { + items = root.getAsJsonArray(); + } + if (items != null && items.size() > 0) { + String itemId = items.get(0).getAsJsonObject().get("id").getAsString(); + ItemStack test = parseItem(itemId); + if (test != null) return test.getType(); + } + } catch (Exception e) { + if (debug) getLogger().log(Level.WARNING, "getFirstItemMaterial Fehler", e); + } + return Material.CHEST; // sicherer Fallback + } + + // =========================================================== + // INVENTORY CLICK + // =========================================================== + @EventHandler public void onInventoryClick(InventoryClickEvent event) { if (!event.getView().getTitle().contains("Kauf bestätigen?")) return; event.setCancelled(true); if (!(event.getWhoClicked() instanceof Player)) return; - - Player p = (Player) event.getWhoClicked(); - int slot = event.getRawSlot(); - + + Player p = (Player) event.getWhoClicked(); + int slot = event.getRawSlot(); + if (slot == 11) { // JA Integer orderId = activeOrderIds.get(p.getUniqueId()); if (orderId != null) { @@ -225,13 +305,17 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } else if (slot == 15) { // NEIN Integer orderId = activeOrderIds.get(p.getUniqueId()); if (orderId != null) { - cancelOrder(p, orderId); // NEU + cancelOrder(p, orderId); p.closeInventory(); activeOrderIds.remove(p.getUniqueId()); } } } + // =========================================================== + // ORDER PROCESSING + // =========================================================== + private void processOrder(Player player, int orderId) { OrderData data = orderCache.get(orderId); if (data == null) { @@ -243,76 +327,54 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { @Override public void run() { try { - String jsonInputString = "{\"id\":\"" + orderId + "\"}"; - URL url = new URL(wpUrlExecute); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setDoOutput(true); - - try(OutputStream os = conn.getOutputStream()) { - byte[] input = jsonInputString.getBytes("utf-8"); - os.write(input, 0, input.length); - } - - int responseCode = conn.getResponseCode(); - if (responseCode == 200) { + HttpURLConnection conn = openAuthConnection(wpUrlExecute, "POST"); + writeJson(conn, "{\"id\":\"" + orderId + "\"}"); + + int code = conn.getResponseCode(); + if (code == 200) { Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> executeShopLogic(player, data, orderId)); + } else if (code == 401) { + getLogger().warning("❌ API-Key ungültig bei /execute_order"); + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> + player.sendMessage(ChatColor.RED + "❌ Server-Konfigurationsfehler (Auth).")); } else { - if (debug) getLogger().warning("Execute Order API Code: " + responseCode); - Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { - player.sendMessage(ChatColor.RED + "❌ Server-Fehler beim Starten des Kaufs."); - }); + if (debug) getLogger().warning("Execute Order HTTP " + code); + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> + player.sendMessage(ChatColor.RED + "❌ Server-Fehler beim Starten des Kaufs.")); } } catch (Exception e) { getLogger().log(Level.SEVERE, "Fehler bei /execute_order", e); - Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { - player.sendMessage(ChatColor.RED + "❌ Interner Fehler beim Kauf."); - }); + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> + player.sendMessage(ChatColor.RED + "❌ Interner Fehler beim Kauf.")); } } }.runTaskAsynchronously(this); } - // =========================================================== - // NEU: ABBRECHEN LOGIC - // =========================================================== private void cancelOrder(Player player, int orderId) { new BukkitRunnable() { @Override public void run() { try { - String jsonInputString = "{\"id\":\"" + orderId + "\"}"; - URL url = new URL(wpUrlCancel); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setDoOutput(true); - - try(OutputStream os = conn.getOutputStream()) { - byte[] input = jsonInputString.getBytes("utf-8"); - os.write(input, 0, input.length); - } - - int responseCode = conn.getResponseCode(); - - if (responseCode == 200) { - orderCache.remove(orderId); + HttpURLConnection conn = openAuthConnection(wpUrlCancel, "POST"); + writeJson(conn, "{\"id\":\"" + orderId + "\"}"); + + int code = conn.getResponseCode(); + orderCache.remove(orderId); + + if (code == 200) { player.sendMessage(ChatColor.YELLOW + "❌ Kauf abgebrochen."); - if (debug) getLogger().info("✅ Order #" + orderId + " successfully cancelled"); + if (debug) getLogger().info("✅ Order #" + orderId + " cancelled"); } else { - if (debug) getLogger().warning("⚠️ Cancel Order API returned code: " + responseCode); - player.sendMessage(ChatColor.RED + "❌ Fehler beim Abbrechen des Kaufs."); - // Fallback: Cache leeren damit es nicht als Loop erscheint - orderCache.remove(orderId); + if (debug) getLogger().warning("⚠️ Cancel HTTP " + code); + player.sendMessage(ChatColor.RED + "❌ Fehler beim Abbrechen – Order wird lokal verworfen."); activeOrderIds.remove(player.getUniqueId()); } } catch (Exception e) { getLogger().log(Level.WARNING, "Fehler bei /cancel_order", e); - // Fallback: Cache leeren orderCache.remove(orderId); activeOrderIds.remove(player.getUniqueId()); - player.sendMessage(ChatColor.YELLOW + "❌ Kauf abgebrochen (lokale Bestätigung)."); + player.sendMessage(ChatColor.YELLOW + "❌ Kauf abgebrochen (lokal)."); } } }.runTaskAsynchronously(this); @@ -321,70 +383,68 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { private void executeShopLogic(Player player, OrderData data, int orderId) { try { if (econ.getBalance(player) < data.price) { - player.sendMessage(ChatColor.RED + "❌ Du hast nicht genug Geld! (Benötigt: " + data.price + " " + currency + ")"); - // Wenn der Kauf fehlschagt (Geld), sollte die Bestellung abgebrochen werden + player.sendMessage(ChatColor.RED + "❌ Nicht genug " + currency + "! (Benötigt: " + data.price + ")"); cancelOrder(player, orderId); return; } - + econ.withdrawPlayer(player, data.price); player.sendMessage(ChatColor.GREEN + "💰 " + data.price + " " + currency + " abgezogen."); - - try { - JsonElement root = gson.fromJson(data.jsonPayload, JsonElement.class); - JsonArray items; - - if (root.isJsonObject()) { - items = root.getAsJsonObject().getAsJsonArray("items"); - } else { - items = root.getAsJsonArray(); - } - - int totalItemsGiven = 0; - - for (JsonElement e : items) { - JsonObject itemObj = e.getAsJsonObject(); - String itemId = itemObj.get("id").getAsString(); - int amount = itemObj.get("amount").getAsInt(); - - ItemStack item = parseItem(itemId); - if (item != null) { - int remaining = amount; - while (remaining > 0) { - int stackSize = Math.min(remaining, item.getMaxStackSize()); - ItemStack stack = item.clone(); - stack.setAmount(stackSize); - - if (player.getInventory().firstEmpty() == -1) { - player.getWorld().dropItemNaturally(player.getLocation(), stack); - } else { - player.getInventory().addItem(stack); - } - remaining -= stackSize; - } - totalItemsGiven++; - } else { - player.sendMessage(ChatColor.RED + "❌ Item '" + itemId + "' konnte nicht gefunden werden."); - } - } - - if (totalItemsGiven > 0) { - player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F); - player.sendMessage(ChatColor.GREEN + "✅ Kauf erfolgreich abgeschlossen!"); - } - - markOrderCompleted(orderId); - orderCache.remove(orderId); - - } catch (Exception e) { - getLogger().log(Level.SEVERE, "Fehler beim Verarbeiten der JSON-Items", e); - player.sendMessage(ChatColor.RED + "❌ Fehler beim Verteilen der Items. Bitte Admin kontaktieren."); - markOrderCompleted(orderId); + + JsonElement root = gson.fromJson(data.jsonPayload, JsonElement.class); + JsonArray items; + if (root.isJsonObject()) { + items = root.getAsJsonObject().getAsJsonArray("items"); + } else { + items = root.getAsJsonArray(); } - + + int totalGiven = 0; + for (JsonElement e : items) { + JsonObject itemObj = e.getAsJsonObject(); + String itemId = itemObj.get("id").getAsString(); + int amount = itemObj.get("amount").getAsInt(); + + ItemStack item = parseItem(itemId); + if (item != null) { + giveItems(player, item, amount); + totalGiven++; + } else { + player.sendMessage(ChatColor.RED + "❌ Item '" + itemId + "' nicht gefunden."); + } + } + + if (totalGiven > 0) { + player.playSound(player.getLocation(), Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F); + player.sendMessage(ChatColor.GREEN + "✅ Kauf erfolgreich abgeschlossen!"); + } + + markOrderCompleted(orderId); + orderCache.remove(orderId); + } catch (Exception e) { getLogger().log(Level.SEVERE, "Fehler bei Shop-Logik", e); - player.sendMessage(ChatColor.RED + "❌ Interner Fehler."); + player.sendMessage(ChatColor.RED + "❌ Interner Fehler beim Verteilen. Admin kontaktieren."); + } + } + + /** + * NEU: Gibt Items an den Spieler – volles Inventar → natürlicher Drop + * (bestehende Logik unverändert, aber in eigene Methode ausgelagert) + */ + private void giveItems(Player player, ItemStack template, int amount) { + int remaining = amount; + while (remaining > 0) { + int stackSize = Math.min(remaining, template.getMaxStackSize()); + ItemStack stack = template.clone(); + stack.setAmount(stackSize); + if (player.getInventory().firstEmpty() == -1) { + player.getWorld().dropItemNaturally(player.getLocation(), stack); + player.sendMessage(ChatColor.YELLOW + "⚠ Inventar voll – Items wurden gedroppt."); + } else { + player.getInventory().addItem(stack); + } + remaining -= stackSize; } } @@ -393,103 +453,195 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { @Override public void run() { try { - String jsonInputString = "{\"id\":\"" + orderId + "\"}"; - - if (debug) getLogger().info("🔄 Marking order #" + orderId + " as completed..."); - - URL url = new URL(wpUrlComplete); - HttpURLConnection conn = (HttpURLConnection) url.openConnection(); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Content-Type", "application/json"); - conn.setDoOutput(true); - - try(OutputStream os = conn.getOutputStream()) { - byte[] input = jsonInputString.getBytes("utf-8"); - os.write(input, 0, input.length); - } - - int responseCode = conn.getResponseCode(); - - if (debug) { - if (responseCode == 200) { - getLogger().info("✅ Order #" + orderId + " successfully marked as completed"); - } else { - getLogger().warning("⚠️ Complete Order API returned code: " + responseCode); - } - } + HttpURLConnection conn = openAuthConnection(wpUrlComplete, "POST"); + writeJson(conn, "{\"id\":\"" + orderId + "\"}"); + int code = conn.getResponseCode(); + if (debug) getLogger().info("Complete Order #" + orderId + " → HTTP " + code); } catch (Exception e) { - getLogger().log(Level.WARNING, "Complete Order Error for #" + orderId, e); + getLogger().log(Level.WARNING, "Complete Order Error #" + orderId, e); } } }.runTaskAsynchronously(this); } + // =========================================================== + // /orders BEFEHL – Bestellhistorie im Chat + // =========================================================== + + private class OrdersCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (!(sender instanceof Player)) { + sender.sendMessage("Nur für Spieler verfügbar."); + return true; + } + Player p = (Player) sender; + p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + p.sendMessage(ChatColor.YELLOW + "📦 Deine letzten Bestellungen:"); + p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + + new BukkitRunnable() { + @Override + public void run() { + try { + String urlString = wpBase + "/orders_history?player=" + p.getName(); + HttpURLConnection conn = openAuthConnection(urlString, "GET"); + int code = conn.getResponseCode(); + + if (code == 200) { + String body = readResponse(conn); + JsonObject json = JsonParser.parseString(body).getAsJsonObject(); + JsonArray orders = json.getAsJsonArray("orders"); + + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { + if (orders == null || orders.size() == 0) { + p.sendMessage(ChatColor.GRAY + "Noch keine Bestellungen vorhanden."); + } else { + for (int i = 0; i < Math.min(orders.size(), 10); i++) { + JsonObject o = orders.get(i).getAsJsonObject(); + String title = o.get("item_title").getAsString(); + double price = o.get("price").getAsDouble(); + String status = o.get("status").getAsString(); + String date = o.has("created_at") ? o.get("created_at").getAsString().substring(0,10) : "?"; + + String statusColor; + switch (status) { + case "completed": statusColor = ChatColor.GREEN + "✔ Geliefert"; break; + case "cancelled": statusColor = ChatColor.RED + "✘ Storniert"; break; + case "processing": statusColor = ChatColor.AQUA + "⟳ In Arbeit"; break; + default: statusColor = ChatColor.YELLOW + "⌛ Ausstehend"; break; + } + + // Titel kürzen wenn zu lang + String display = title.length() > 35 ? title.substring(0, 32) + "…" : title; + p.sendMessage( + ChatColor.WHITE + " #" + o.get("id").getAsInt() + + " " + ChatColor.AQUA + display + + ChatColor.GRAY + " | " + ChatColor.YELLOW + price + " " + currency + + ChatColor.GRAY + " | " + statusColor + + ChatColor.GRAY + " (" + date + ")" + ); + } + } + p.sendMessage(ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); + }); + } else if (code == 404) { + // Endpunkt noch nicht implementiert + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> + p.sendMessage(ChatColor.GRAY + "Bestellhistorie nicht verfügbar (WP-Plugin zu alt?).")); + } else { + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> + p.sendMessage(ChatColor.RED + "Fehler beim Laden der Bestellungen (HTTP " + code + ").")); + } + } catch (Exception e) { + getLogger().log(Level.WARNING, "/orders Fehler", e); + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> + p.sendMessage(ChatColor.RED + "Verbindungsfehler zum Shop-Server.")); + } + } + }.runTaskAsynchronously(IngameShopSpigot.this); + return true; + } + } + + // =========================================================== + // HTTP HILFSMETHODEN + // =========================================================== + + /** + * Öffnet eine HTTP-Verbindung mit gesetztem API-Key Header. + */ + private HttpURLConnection openAuthConnection(String urlString, String method) throws Exception { + URL url = new URL(urlString); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod(method); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("X-WIS-Key", apiKey); // NEU: API-Key bei jedem Request + conn.setConnectTimeout(5000); + conn.setReadTimeout(5000); + if ("POST".equals(method)) conn.setDoOutput(true); + return conn; + } + + private void writeJson(HttpURLConnection conn, String json) throws Exception { + try (OutputStream os = conn.getOutputStream()) { + os.write(json.getBytes(StandardCharsets.UTF_8)); + } + } + + private String readResponse(HttpURLConnection conn) throws Exception { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) { + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) sb.append(line); + return sb.toString(); + } + } + + // =========================================================== + // ITEM PARSING + // =========================================================== + private ItemStack parseItem(String itemId) { try { - String cleanId = itemId.toUpperCase().trim().replace("MINECRAFT:", "").replace("MC:", ""); - String materialName = numericIdToMaterial(cleanId); - if (materialName == null) materialName = cleanId; - - if (materialName.equalsIgnoreCase("VIP") || materialName.startsWith("VIP")) { - return null; - } - + String clean = itemId.toUpperCase().trim() + .replace("MINECRAFT:", "").replace("MC:", ""); + String materialName = numericIdToMaterial(clean); + if (materialName == null) materialName = clean; + + if (materialName.equalsIgnoreCase("VIP") || materialName.startsWith("VIP")) return null; + Material material = Material.matchMaterial(materialName); - if (material != null && material.isItem()) { - return new ItemStack(material,1); - } + if (material != null && material.isItem()) return new ItemStack(material, 1); return null; } catch (Exception e) { getLogger().log(Level.WARNING, "parseItem Fehler: " + itemId, e); return null; } } - + private String numericIdToMaterial(String id) { if (!id.matches("\\d+")) return null; switch (id) { case "352": return "BONE_MEAL"; - case "264": return "EMERALD"; - case "388": return "EMERALD_BLOCK"; - case "357": return "GOLDEN_APPLE"; + case "264": return "DIAMOND"; + case "388": return "EMERALD"; + case "357": return "COOKIE"; case "322": return "GOLDEN_CARROT"; case "348": return "GOLD_INGOT"; - case "133": return "EMERALD_ORE"; - case "41": return "GOLD_ORE"; - case "14": return "GOLD_BLOCK"; - case "289": return "COOKIE"; - case "1": return "STONE"; - case "5": return "PLANKS"; - case "17": return "OAK_LOG"; - case "260": return "BONE_BLOCK"; - case "287": return "TOTEM_OF_UNDYING"; - default: return null; + case "133": return "EMERALD_BLOCK"; + case "41": return "GOLD_BLOCK"; + case "14": return "GOLD_ORE"; + case "289": return "GUNPOWDER"; + case "1": return "STONE"; + case "5": return "OAK_PLANKS"; + case "17": return "OAK_LOG"; + case "260": return "APPLE"; + case "287": return "STRING"; + default: return null; } } + // =========================================================== + // DATA CLASS + // =========================================================== + private static class OrderData { - int id; + int id; String itemId; String itemTitle; double price; - int quantity; + int quantity; String jsonPayload; - + OrderData(int id, String itemId, String itemTitle, double price, int quantity, String jsonPayload) { - this.id = id; - this.itemId = itemId; - this.itemTitle = itemTitle; - this.price = price; - this.quantity = quantity; + this.id = id; + this.itemId = itemId; + this.itemTitle = itemTitle; + this.price = price; + this.quantity = quantity; this.jsonPayload = jsonPayload; } } - - @Override - public void onDisable() { - if (task != null) { - task.cancel(); - } - getLogger().info("IngameShopSpigot gestoppt"); - } } \ No newline at end of file diff --git a/IngameShopSpigot/src/main/resources/config.yml b/IngameShopSpigot/src/main/resources/config.yml index d3030ed..518b6b8 100644 --- a/IngameShopSpigot/src/main/resources/config.yml +++ b/IngameShopSpigot/src/main/resources/config.yml @@ -1,15 +1,21 @@ -# Hier nur die Domain eingeben (ohne /wp-json am Ende) -wordpress-url: "" +# IngameShopSpigot v6.3 Konfiguration +# ========================================== -# WICHTIG: Der Name des Servers, wie er in WordPress hinterlegt ist (Slug). -# Damit wird verhindert, dass ein Spieler auf Lobby etwas für Survival kauft und es dort erhält. -server-name: "Lobby" +# Deine WordPress-URL (kein abschließendes /) +wordpress-url: "https://deine-domain.de" -# Name der Währung (ersetze Coins durch deinen Namen, z.B. Dollar, Points, etc.) -currency-name: "Euro" +# API-Key aus den WordPress-Einstellungen: +# Ingame Shop → Einstellungen → 🔑 Spigot API-Key → Kopieren +api-key: "HIER_DEN_KEY_AUS_WORDPRESS_EINTRAGEN" -# Alle wie viel Sekunden (ticks) nach neuen Bestellungen gesucht werden soll (Standard: 10 Sekunden) -check-interval: 5 +# Name dieses Servers (muss mit dem Server-Slug in WordPress übereinstimmen) +server-name: "survival" -# Debug Modus (true/false) -debug-mode: true \ No newline at end of file +# Währungsname (muss mit WordPress-Einstellung übereinstimmen) +currency-name: "Coins" + +# Wie oft (in Sekunden) nach ausstehenden Bestellungen gesucht wird +check-interval: 10 + +# Debug-Modus (ausführliche Logs in der Konsole) +debug-mode: false \ No newline at end of file diff --git a/IngameShopSpigot/src/main/resources/plugin.yml b/IngameShopSpigot/src/main/resources/plugin.yml index 5145307..3642e8d 100644 --- a/IngameShopSpigot/src/main/resources/plugin.yml +++ b/IngameShopSpigot/src/main/resources/plugin.yml @@ -1,25 +1,18 @@ name: IngameShopSpigot +version: 6.3 main: de.mviper.spigot.IngameShopSpigot -version: 1.0 -api-version: "1.20" +api-version: 1.19 +depend: [Vault] +description: Verbindet den WP Ingame Shop Pro mit dem Spigot-Server author: M_Viper -description: Ingame-Shop Plugin (Spigot) empfPlugin-Messages vom Bungee und vergibt Items / zieht Geld via Vault. -# Vault ist optional, daher softdepend -softdepend: - - Vault - -# Beispiel-Command (optional, kann in Java implementiert werden) commands: - ingameshop: - description: oder testet den IngameShop (Admin/Debug) - usage: /ingameshop + orders: + description: Zeigt deine letzten Bestellungen im Shop an + usage: /orders + permission: ingameshop.orders -# Beispiel-Permissions (anpassbar) permissions: - ingameshop.admin: - description: Zugriff auf Admin-Funktionen des IngameShops - default: op - ingameshop.use: - description: Basisrecht, um Shop-Features zu nutzen + ingameshop.orders: + description: Kann eigene Bestellhistorie einsehen default: true \ No newline at end of file