From d71f15086d0cb815aeacf3709f65382f18b54394 Mon Sep 17 00:00:00 2001 From: Git Manager GUI Date: Tue, 5 May 2026 21:08:18 +0200 Subject: [PATCH] Upload folder via GUI - src --- .../de/mviper/spigot/IngameShopSpigot.java | 381 +++++++++++++++++- .../src/main/resources/plugin.yml | 9 +- 2 files changed, 388 insertions(+), 2 deletions(-) diff --git a/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java b/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java index b1dc9ba..af703e6 100644 --- a/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java +++ b/IngameShopSpigot/src/main/java/de/mviper/spigot/IngameShopSpigot.java @@ -97,6 +97,9 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { // URL für Geschenk-Endpoint und Rückerstattungs-Endpoint private String wpUrlGiftAccept; private String wpUrlGiftDecline; + private String wpUrlItemAboStatus; + private String wpUrlItemAboCancel; + private String wpUrlTriggerAboDelivery; private FlyManager flyManager; private FlyCodeManager flyCodeManager; @@ -142,6 +145,9 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { wpUrlSell = wpBase + "/sell_item"; wpUrlGiftAccept = wpBase + "/gift_accept"; wpUrlGiftDecline = wpBase + "/gift_decline"; + wpUrlItemAboStatus = wpBase + "/item_abo_status"; + wpUrlItemAboCancel = wpBase + "/item_abo_cancel"; + wpUrlTriggerAboDelivery = wpBase + "/trigger_abo_delivery"; targetServer = getConfig().getString("server-name", "survival").toLowerCase(); currency = getConfig().getString("currency-name", "Coins"); @@ -208,6 +214,7 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { getCommand("plotabogive").setExecutor(new PlotAboGiveCommand()); getCommand("plotslotsgive").setExecutor(new PlotSlotsGiveCommand()); getCommand("abo").setExecutor(new AboOverviewCommand()); + getCommand("itemabocancel").setExecutor(new ItemAboCancelCommand()); getCommand("sell").setExecutor(new SellCommand()); getCommand("wpis").setExecutor(new ReloadCommand()); @@ -340,6 +347,56 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { && !order.get("gift_recipient").isJsonNull()) ? order.get("gift_recipient").getAsString().trim() : ""; + // ── Item-Abo Tageslieferung: automatisch ausliefern (kein Confirm-GUI) ── + boolean isAboDelivery = false; + try { + JsonObject respObj = JsonParser.parseString(jsonResponse).getAsJsonObject(); + isAboDelivery = respObj.has("abo_delivery") + && respObj.get("abo_delivery").getAsBoolean(); + } catch (Exception ignored) {} + + if (isAboDelivery) { + // Direkt ausliefern ohne GUI + final int deliveryId = id; + final String deliveryTitle = itemTitle; + final String deliveryJson = jsonResponse; + synchronized (activeOrderIds) { + if (activeOrderIds.containsKey(p.getUniqueId())) { + if (debug) getLogger().info( + "Abo-Delivery #" + id + " für " + p.getName() + + " verschoben (aktive Order)."); + return; + } + OrderData delivData = new OrderData( + deliveryId, "item_abo_delivery", deliveryTitle, + 0.0, 1, deliveryJson, ""); + orderCache.put(deliveryId, delivData); + activeOrderIds.put(p.getUniqueId(), deliveryId); + } + // execute_order sofort markieren, dann Items geben + new BukkitRunnable() { + @Override public void run() { + try { + HttpURLConnection conn = openAuthConnection(wpUrlExecute, "POST"); + writeJson(conn, "{\"id\":\"" + deliveryId + "\"}"); + conn.getResponseCode(); + } catch (Exception ex) { + if (debug) getLogger().log(Level.WARNING, + "[AboDelivery] Execute Fehler", ex); + } + // Direkt auf Main-Thread Items geben + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { + OrderData dd = orderCache.get(deliveryId); + if (dd != null) { + activeOrderIds.remove(p.getUniqueId()); + deliverAboItems(p, deliveryId, deliveryTitle, deliveryJson); + } + }); + } + }.runTaskAsynchronously(IngameShopSpigot.this); + return; // Kein Confirm-GUI öffnen + } + // Spieler A bestätigt IMMER zuerst selbst (verhindert Missbrauch). // giftRecipient wird in OrderData gespeichert und erst nach // Bestätigung + Geld-Abbuchung an Spieler B weitergeleitet. @@ -597,9 +654,12 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { // Polling für diesen Spieler sperren bis der Join-Fetch durch ist joinFetchCooldown.put(p.getUniqueId(), System.currentTimeMillis() + 10_000L); - // Offline-Bestellungen abholen + // Offline-Bestellungen abholen + Item-Abo Lieferung triggern new BukkitRunnable() { @Override public void run() { + // Zuerst Item-Abo prüfen → WP legt ggf. pending-Order an + triggerItemAboDelivery(p); + // Dann abholen (findet die gerade angelegte Order) fetchPendingOrders(p, wpUrlPendingOffline); // Sperre aufheben sobald Join-Fetch fertig joinFetchCooldown.remove(p.getUniqueId()); @@ -817,6 +877,27 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { ItemStack test = parseItem(id); if (test != null) return test.getType(); } + // Wenn nur Commands vorhanden: Icon je nach Typ wählen + if (root.isJsonObject()) { + JsonObject obj2 = root.getAsJsonObject(); + if (obj2.has("commands")) { + JsonArray cmds = obj2.getAsJsonArray("commands"); + if (cmds.size() > 0) { + String cmdType = cmds.get(0).getAsJsonObject().has("type") + ? cmds.get(0).getAsJsonObject().get("type").getAsString() : "generic"; + switch (cmdType) { + case "fly": return Material.FEATHER; + case "rank": return Material.GOLDEN_HELMET; + case "fly_abo": return Material.FEATHER; + case "plot_slots": return Material.OAK_SIGN; + case "plot_abo": return Material.OAK_SIGN; + case "custom_cmd": return Material.COMMAND_BLOCK; + case "item_abo": return Material.CHEST; + default: return Material.FEATHER; + } + } + } + } } catch (Exception e) { if (debug) getLogger().log(Level.WARNING, "getFirstItemMaterial Fehler", e); } @@ -1300,6 +1381,77 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { plotSlotManager.grantAbo(player, slots, label, monthlyPrice); anyCode = true; + } else if ("custom_cmd".equals(type)) { + // Custom Command Item – beliebiger Konsolenbefehl mit Platzhaltern + String rawCommand = cmdObj.has("command") + ? cmdObj.get("command").getAsString() : ""; + String label = cmdObj.has("label") + ? cmdObj.get("label").getAsString() : "Custom Item"; + + if (!rawCommand.isEmpty()) { + // Mehrere Commands per Zeilenumbruch möglich + String[] lines = rawCommand.split("\\n"); + for (String line : lines) { + String trimmed = line.trim(); + if (trimmed.isEmpty()) continue; + // Führenden / entfernen falls vorhanden + if (trimmed.startsWith("/")) trimmed = trimmed.substring(1); + String finalCmd = trimmed + .replace("{player}", player.getName()) + .replace("{amount}", "1") + .replace("{server}", targetServer); + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), finalCmd); + if (debug) getLogger().info("[CustomCmd] Ausgeführt: " + finalCmd); + } + player.sendMessage(""); + player.sendMessage(ChatColor.GOLD + "⚙ ════════════════════════════"); + player.sendMessage(ChatColor.YELLOW + " " + label + " erhalten!"); + player.sendMessage(ChatColor.GOLD + " ════════════════════════════"); + player.sendMessage(""); + player.playSound(player.getLocation(), + Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F); + } else { + getLogger().warning("[CustomCmd] Kein Command konfiguriert für: " + label); + player.sendMessage(ChatColor.RED + "❌ Kein Command konfiguriert – Admin kontaktieren."); + } + anyCode = true; + + } else if ("item_abo".equals(type)) { + // Item-Abo: Abo wurde auf WP-Seite registriert, + // erste Tageslieferung sofort direkt geben (wie fly_abo) + String aboItemId = cmdObj.has("item_id") + ? cmdObj.get("item_id").getAsString() : "?"; + int dailyQty = cmdObj.has("daily_qty") + ? cmdObj.get("daily_qty").getAsInt() : 1; + int durationDays = cmdObj.has("duration_days") + ? cmdObj.get("duration_days").getAsInt() : 30; + String label = cmdObj.has("label") + ? cmdObj.get("label").getAsString() : "Item-Abo"; + + // Items direkt ins Inventar geben + Material mat = Material.matchMaterial(aboItemId); + if (mat != null && mat != Material.AIR) { + ItemStack stack = new ItemStack(mat, dailyQty); + giveItems(player, stack, dailyQty); + player.sendMessage(""); + player.sendMessage(ChatColor.GREEN + "📦 ════════════════════════════"); + player.sendMessage(ChatColor.YELLOW + " " + label + " aktiviert!"); + player.sendMessage(ChatColor.GRAY + " Item: " + ChatColor.WHITE + aboItemId); + player.sendMessage(ChatColor.GRAY + " Täglich: " + ChatColor.AQUA + dailyQty + "x"); + player.sendMessage(ChatColor.GRAY + " Läuft: " + ChatColor.WHITE + durationDays + " Tage"); + player.sendMessage(ChatColor.GREEN + " ✔ Erste Lieferung soeben erhalten!"); + player.sendMessage(ChatColor.GRAY + " Du erhältst die Items täglich beim Login."); + player.sendMessage(ChatColor.GREEN + " ════════════════════════════"); + player.sendMessage(""); + player.playSound(player.getLocation(), + Sound.ENTITY_PLAYER_LEVELUP, 1.0F, 1.0F); + } else { + getLogger().warning("[ItemAbo] Unbekanntes Item: " + aboItemId); + player.sendMessage(ChatColor.RED + "⚠ Item-ID unbekannt: " + aboItemId + + " – Admin kontaktieren."); + } + anyCode = true; + } else if ("generic".equals(type) && cmdObj.has("cmd")) { String cmd = cmdObj.get("cmd").getAsString() .replace("{player}", player.getName()); @@ -1343,6 +1495,49 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } } + /** + * Liefert Item-Abo Tagesitems direkt aus ohne Confirm-GUI und ohne Vault-Abbuchung. + * Wird nur für Lieferungen mit abo_delivery=true aufgerufen. + */ + private void deliverAboItems(Player player, int orderId, String itemTitle, String jsonPayload) { + try { + JsonElement root = gson.fromJson(jsonPayload, JsonElement.class); + JsonArray items = null; + if (root != null && root.isJsonObject()) { + items = root.getAsJsonObject().getAsJsonArray("items"); + } + int delivered = 0; + if (items != null) { + for (JsonElement e : items) { + JsonObject itemObj = e.getAsJsonObject(); + String itemId = itemObj.get("id").getAsString(); + int amount = itemObj.get("amount").getAsInt(); + ItemStack stack = parseItem(itemId); + if (stack != null) { + giveItems(player, stack, amount); + delivered++; + } else { + getLogger().warning("[AboDelivery] Unbekanntes Item: " + itemId); + } + } + } + if (delivered > 0) { + player.sendMessage(""); + player.sendMessage(ChatColor.GREEN + "📦 Abo-Lieferung erhalten!"); + player.sendMessage(ChatColor.GRAY + " " + itemTitle); + player.sendMessage(""); + player.playSound(player.getLocation(), Sound.ENTITY_ITEM_PICKUP, 0.8F, 1.0F); + } + orderCache.remove(orderId); + markOrderCompleted(orderId); + if (debug) getLogger().info("[AboDelivery] " + player.getName() + + " – " + itemTitle + " (Order #" + orderId + ")"); + } catch (Exception ex) { + getLogger().log(Level.WARNING, "[AboDelivery] Fehler bei Lieferung", ex); + orderCache.remove(orderId); + } + } + private void markOrderCompleted(int orderId) { new BukkitRunnable() { @Override public void run() { @@ -1703,6 +1898,7 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { int plotExtra = plotSlotManager.getExtraSlots(p.getName()); int plotAboSlots= plotSlotManager.getAboSlots(p.getName()); int plotTotal = plotBase + plotExtra + plotAboSlots; + List itemAbos = fetchItemAbos(p.getName()); Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { String line = ChatColor.GOLD + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; @@ -1772,6 +1968,33 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } } + // ── Item-Abos (PetFutter, etc.) ─────────────────────────── + for (int i = 0; i < itemAbos.size(); i++) { + JsonObject ia = itemAbos.get(i); + hasAny = true; + int aboId = ia.has("id") ? ia.get("id").getAsInt() : -1; + String iaLabel = ia.has("label") ? ia.get("label").getAsString() : "Item-Abo"; + String iaItem = ia.has("item_id") ? ia.get("item_id").getAsString() : "?"; + int iaQty = ia.has("daily_qty") ? ia.get("daily_qty").getAsInt() : 1; + String iaExpires = ia.has("expires_at")? ia.get("expires_at").getAsString(): "?"; + boolean iaCancelled = ia.has("cancelled") && ia.get("cancelled").getAsBoolean(); + int nr = i + 1; + + p.sendMessage(""); + p.sendMessage(ChatColor.AQUA + "" + ChatColor.BOLD + " 📦 Item-Abo #" + nr + ": " + iaLabel); + p.sendMessage(ChatColor.GRAY + " ┌ Item: " + ChatColor.WHITE + iaItem); + p.sendMessage(ChatColor.GRAY + " ├ Täglich: " + ChatColor.AQUA + iaQty + "x"); + + if (iaCancelled) { + p.sendMessage(ChatColor.GRAY + " ├ Status: " + ChatColor.RED + "⚠ Gekündigt"); + p.sendMessage(ChatColor.GRAY + " └ Endet am: " + ChatColor.YELLOW + iaExpires); + } else { + p.sendMessage(ChatColor.GRAY + " ├ Status: " + ChatColor.GREEN + "✔ Aktiv"); + p.sendMessage(ChatColor.GRAY + " ├ Läuft bis: " + ChatColor.WHITE + iaExpires); + p.sendMessage(ChatColor.GRAY + " └ Kündigen: " + ChatColor.WHITE + "/itemabocancel " + nr); + } + } + // ── Plot-Slots Übersicht (immer anzeigen) ───────────── p.sendMessage(""); p.sendMessage(ChatColor.AQUA + "" + ChatColor.BOLD + " 📦 Plot-Slots"); @@ -2142,6 +2365,159 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { } } + // =========================================================== + // ITEM-ABO HELPER + COMMAND + // =========================================================== + + /** + * Holt alle aktiven Item-Abos des Spielers vom WordPress-Backend. + * Muss auf einem Async-Thread aufgerufen werden. + */ + private List fetchItemAbos(String playerName) { + List list = new ArrayList<>(); + try { + HttpURLConnection conn = openAuthConnection( + wpUrlItemAboStatus + "?player=" + playerName, "GET"); + if (conn.getResponseCode() == 200) { + JsonObject resp = JsonParser.parseString(readResponse(conn)).getAsJsonObject(); + JsonArray abos = resp.has("abos") ? resp.getAsJsonArray("abos") : new JsonArray(); + for (JsonElement el : abos) + list.add(el.getAsJsonObject()); + } + } catch (Exception e) { + if (debug) getLogger().log(Level.WARNING, "[ItemAbo] fetchItemAbos Fehler", e); + } + return list; + } + + /** + * Prüft beim Login ob heute noch keine Item-Abo-Lieferung erfolgt ist. + * Falls nicht, legt WP eine pending-Order an, die dann vom normalen + * fetchPendingOrders (wpUrlPendingOffline) direkt im selben Join-Runnable abgeholt wird. + * Muss auf einem Async-Thread aufgerufen werden – VOR fetchPendingOrders. + */ + private void triggerItemAboDelivery(Player player) { + try { + HttpURLConnection conn = openAuthConnection(wpUrlTriggerAboDelivery, "POST"); + writeJson(conn, "{\"player\":\"" + player.getName() + + "\",\"server\":\"" + targetServer + "\"}"); + int code = conn.getResponseCode(); + if (code == 200 && debug) { + String body = ""; + try { body = readResponse(conn); } catch (Exception ignored) {} + JsonObject resp = JsonParser.parseString(body).getAsJsonObject(); + int delivered = resp.has("delivered") ? resp.get("delivered").getAsInt() : 0; + if (delivered > 0) + getLogger().info("[ItemAbo] " + delivered + + " Lieferung(en) für " + player.getName() + " vorbereitet."); + } + } catch (Exception e) { + if (debug) getLogger().log(Level.WARNING, "[ItemAbo] triggerDelivery Fehler", e); + } + } + + /** /itemabocancel [confirm] – kündigt genau das Item-Abo mit der angezeigten Nummer */ + private class ItemAboCancelCommand implements CommandExecutor { + @Override + public boolean onCommand(CommandSender sender, Command command, + String label, String[] args) { + if (!(sender instanceof Player)) { sender.sendMessage("Nur für Spieler."); return true; } + Player p = (Player) sender; + + // Verwendung ohne Argumente → Übersicht zeigen + if (args.length == 0) { + p.sendMessage(ChatColor.YELLOW + "Verwendung: " + ChatColor.WHITE + "/itemabocancel "); + p.sendMessage(ChatColor.GRAY + " Die Nummer siehst du in " + ChatColor.WHITE + "/abo"); + p.sendMessage(ChatColor.GRAY + " Beispiel: " + ChatColor.WHITE + "/itemabocancel 1"); + return true; + } + + // Nummer parsen + int nr; + try { + nr = Integer.parseInt(args[0]); + if (nr <= 0) throw new NumberFormatException(); + } catch (NumberFormatException e) { + p.sendMessage(ChatColor.RED + "✗ Bitte eine gültige Abo-Nummer angeben (z.B. /itemabocancel 1)"); + return true; + } + + // Bestätigung erforderlich: /itemabocancel confirm + if (args.length < 2 || !args[1].equalsIgnoreCase("confirm")) { + p.sendMessage(ChatColor.YELLOW + "⚠ Item-Abo #" + nr + " wirklich kündigen?"); + p.sendMessage(ChatColor.GRAY + " Das Abo läuft bis zum Laufzeitende weiter,"); + p.sendMessage(ChatColor.GRAY + " du erhältst weiterhin täglich deine Items bis dahin."); + p.sendMessage(ChatColor.WHITE + " Bestätigen: " + + ChatColor.RED + "/itemabocancel " + nr + " confirm"); + return true; + } + + final int selectedNr = nr; + + // Abos laden, dann die richtige ID heraussuchen + new BukkitRunnable() { + @Override public void run() { + List abos = fetchItemAbos(p.getName()); + + if (abos.isEmpty()) { + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> + p.sendMessage(ChatColor.RED + "✗ Du hast keine aktiven Item-Abos.")); + return; + } + if (selectedNr > abos.size()) { + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> + p.sendMessage(ChatColor.RED + "✗ Abo #" + selectedNr + + " existiert nicht. Du hast " + abos.size() + + " Item-Abo(s). Sieh dir /abo an.")); + return; + } + + JsonObject target = abos.get(selectedNr - 1); + if (!target.has("id") || target.get("id").getAsInt() <= 0) { + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> + p.sendMessage(ChatColor.RED + "✗ Fehler: Abo-ID nicht gefunden. Bitte Admin kontaktieren.")); + return; + } + + int aboId = target.get("id").getAsInt(); + String aboLabel = target.has("label") ? target.get("label").getAsString() : "Abo #" + selectedNr; + + // Kündigung an WP senden + try { + HttpURLConnection conn = openAuthConnection(wpUrlItemAboCancel, "POST"); + writeJson(conn, "{\"player\":\"" + p.getName() + + "\",\"abo_id\":" + aboId + "}"); + int code = conn.getResponseCode(); + String body = ""; + try { body = readResponse(conn); } catch (Exception ignored) {} + final String finalBody = body; + final String finalLabel = aboLabel; + + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> { + if (code == 200) { + p.sendMessage(ChatColor.YELLOW + "📦 Item-Abo »" + finalLabel + "« wurde gekündigt."); + p.sendMessage(ChatColor.GRAY + " Du erhältst deine Items bis zum Laufzeitende weiterhin täglich."); + p.playSound(p.getLocation(), Sound.BLOCK_NOTE_BLOCK_BASS, 1.0F, 0.8F); + } else if (code == 403) { + p.sendMessage(ChatColor.RED + "✗ Dieses Abo gehört dir nicht."); + } else if (code == 404) { + p.sendMessage(ChatColor.RED + "✗ Abo nicht gefunden oder bereits gekündigt."); + } else { + p.sendMessage(ChatColor.RED + "✗ Fehler beim Kündigen (HTTP " + code + ")."); + if (debug) getLogger().warning("[ItemAbo] Cancel HTTP " + code + " – " + finalBody); + } + }); + } catch (Exception e) { + getLogger().log(Level.WARNING, "[ItemAbo] Cancel Fehler", e); + Bukkit.getScheduler().runTask(IngameShopSpigot.this, () -> + p.sendMessage(ChatColor.RED + "✗ Verbindungsfehler zum Shop-Server.")); + } + } + }.runTaskAsynchronously(IngameShopSpigot.this); + return true; + } + } + // =========================================================== // HTTP HILFSMETHODEN // =========================================================== @@ -3600,6 +3976,9 @@ public class IngameShopSpigot extends JavaPlugin implements Listener { wpUrlSell = wpBase + "/sell_item"; wpUrlGiftAccept = wpBase + "/gift_accept"; wpUrlGiftDecline = wpBase + "/gift_decline"; + wpUrlItemAboStatus = wpBase + "/item_abo_status"; + wpUrlItemAboCancel = wpBase + "/item_abo_cancel"; + wpUrlTriggerAboDelivery = wpBase + "/trigger_abo_delivery"; targetServer = getConfig().getString("server-name", "survival").toLowerCase(); currency = getConfig().getString("currency-name", "Coins"); diff --git a/IngameShopSpigot/src/main/resources/plugin.yml b/IngameShopSpigot/src/main/resources/plugin.yml index e2f1e54..4fb133f 100644 --- a/IngameShopSpigot/src/main/resources/plugin.yml +++ b/IngameShopSpigot/src/main/resources/plugin.yml @@ -79,6 +79,10 @@ commands: description: Gibt einem Spieler permanente Plot-Slots (Admin) usage: /plotslotsgive [Label] permission: ingameshop.plotslotsgive + itemabocancel: + description: Kündigt ein bestimmtes Item-Abo anhand der Nummer aus /abo + usage: /itemabocancel [confirm] + permission: ingameshop.itemabocancel permissions: ingameshop.orders: @@ -134,4 +138,7 @@ permissions: default: op ingameshop.plotslotsgive: description: Kann Spielern permanente Plot-Slots vergeben (Admin) - default: op \ No newline at end of file + default: op + ingameshop.itemabocancel: + description: Kann eigenes Item-Abo kündigen + default: true \ No newline at end of file