Files
TicketSystem/src/main/java/de/ticketsystem/commands/TicketCommand.java
2026-02-21 16:00:03 +01:00

788 lines
44 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package de.ticketsystem.commands;
import de.ticketsystem.TicketPlugin;
import de.ticketsystem.model.Ticket;
import de.ticketsystem.manager.CategoryManager;
import de.ticketsystem.model.ConfigCategory;
import de.ticketsystem.model.TicketComment;
import de.ticketsystem.model.TicketPriority;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
public class TicketCommand implements CommandExecutor, TabCompleter {
private final TicketPlugin plugin;
public TicketCommand(TicketPlugin plugin) { this.plugin = plugin; }
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!(sender instanceof Player player)) {
sender.sendMessage("Dieser Befehl kann nur von Spielern ausgeführt werden.");
return true;
}
if (args.length == 0) { plugin.getTicketManager().sendHelpMessage(player); return true; }
switch (args[0].toLowerCase()) {
case "create" -> handleCreate(player, args);
case "list" -> handleList(player);
case "claim" -> handleClaim(player, args);
case "close" -> handleClose(player, args);
case "forward" -> handleForward(player, args);
case "reload" -> handleReload(player);
case "migrate" -> handleMigrate(player, args);
case "export" -> handleExport(player, args);
case "import" -> handleImport(player, args);
case "stats" -> handleStats(player);
case "archive" -> handleArchive(player);
case "comment" -> handleComment(player, args);
case "blacklist" -> handleBlacklist(player, args);
case "rate" -> handleRate(player, args);
case "setpriority" -> handleSetPriority(player, args);
default -> plugin.getTicketManager().sendHelpMessage(player);
}
return true;
}
// ─────────────────────────── /ticket create ────────────────────────────
private void handleCreate(Player player, String[] args) {
if (!player.hasPermission("ticket.create")) {
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
}
// Blacklist-Check
if (plugin.getDatabaseManager().isBlacklisted(player.getUniqueId())) {
player.sendMessage(plugin.color("&cDu wurdest vom Ticket-System gesperrt und kannst keine Tickets erstellen."));
return;
}
if (args.length < 2) {
player.sendMessage(plugin.color("&cBenutzung: /ticket create [Kategorie] [Priorität] <Beschreibung>"));
if (plugin.getConfig().getBoolean("categories-enabled", true)) {
player.sendMessage(plugin.color("&7Kategorien: &ebug&7, &efrage&7, &ebeschwerde&7, &esonstiges&7, &eallgemein"));
}
if (plugin.getConfig().getBoolean("priorities-enabled", true)) {
player.sendMessage(plugin.color("&7Prioritäten: &alow&7, &enormal&7, &6high&7, &curgent"));
}
return;
}
if (plugin.getTicketManager().hasCooldown(player.getUniqueId())) {
long remaining = plugin.getTicketManager().getRemainingCooldown(player.getUniqueId());
player.sendMessage(plugin.formatMessage("messages.cooldown").replace("{seconds}", String.valueOf(remaining)));
return;
}
if (plugin.getTicketManager().hasReachedTicketLimit(player.getUniqueId())) {
int max = plugin.getConfig().getInt("max-open-tickets-per-player", 2);
player.sendMessage(plugin.color("&cDu hast bereits &e" + max + " &coffene Ticket(s). Bitte warte, bis dein Ticket bearbeitet wurde."));
return;
}
// Kategorie und Priorität optional parsen
CategoryManager cm = plugin.getCategoryManager();
ConfigCategory category = cm.getDefault();
TicketPriority priority = TicketPriority.NORMAL;
int messageStartIndex = 1;
boolean categoriesOn = plugin.getConfig().getBoolean("categories-enabled", true);
boolean prioritiesOn = plugin.getConfig().getBoolean("priorities-enabled", true);
if (args.length >= 3) {
if (categoriesOn) {
ConfigCategory parsedCat = cm.resolve(args[1]);
if (parsedCat != null) {
category = parsedCat;
messageStartIndex = 2;
if (prioritiesOn && args.length >= 4) {
TicketPriority parsedPrio = parsePriority(args[2]);
if (parsedPrio != null) {
priority = parsedPrio;
messageStartIndex = 3;
}
}
} else {
if (prioritiesOn) {
TicketPriority parsedPrio = parsePriority(args[1]);
if (parsedPrio != null) {
priority = parsedPrio;
messageStartIndex = 2;
}
}
}
} else if (prioritiesOn) {
TicketPriority parsedPrio = parsePriority(args[1]);
if (parsedPrio != null) {
priority = parsedPrio;
messageStartIndex = 2;
}
}
}
String message = String.join(" ", Arrays.copyOfRange(args, messageStartIndex, args.length));
int maxLen = plugin.getConfig().getInt("max-description-length", 100);
if (message.isEmpty()) {
player.sendMessage(plugin.color("&cBitte gib eine Beschreibung für dein Ticket an."));
return;
}
if (message.length() > maxLen) {
player.sendMessage(plugin.color("&cDeine Beschreibung ist zu lang! Maximal " + maxLen + " Zeichen."));
return;
}
final ConfigCategory finalCategory = category;
final TicketPriority finalPriority = priority;
Ticket ticket = new Ticket(player.getUniqueId(), player.getName(), message, player.getLocation());
ticket.setCategoryKey(finalCategory.getKey());
ticket.setPriority(finalPriority);
// BungeeCord: Server-Name des erstellenden Servers speichern
ticket.setServerName(plugin.getServerName());
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
int id = plugin.getDatabaseManager().createTicket(ticket);
if (id == -1) { player.sendMessage(plugin.color("&cFehler beim Erstellen des Tickets!")); return; }
ticket.setId(id);
plugin.getTicketManager().setCooldown(player.getUniqueId());
Bukkit.getScheduler().runTask(plugin, () -> {
String catInfo = plugin.getConfig().getBoolean("categories-enabled", true)
? " §7[" + finalCategory.getColored() + "§7]" : "";
String prioInfo = plugin.getConfig().getBoolean("priorities-enabled", true)
? " §7[" + finalPriority.getColored() + "§7]" : "";
player.sendMessage(plugin.formatMessage("messages.ticket-created").replace("{id}", String.valueOf(id)) + catInfo + prioInfo);
plugin.getTicketManager().notifyTeam(ticket);
});
});
}
// ─────────────────────────── /ticket list ──────────────────────────────
private void handleList(Player player) {
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin")) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
Bukkit.getScheduler().runTask(plugin, () -> plugin.getTicketGUI().openGUI(player)));
} else {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
Bukkit.getScheduler().runTask(plugin, () -> plugin.getTicketGUI().openPlayerGUI(player)));
}
}
// ─────────────────────────── /ticket claim ─────────────────────────────
private void handleClaim(Player player, String[] args) {
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
}
if (args.length < 2) { player.sendMessage(plugin.color("&cBenutzung: /ticket claim <ID>")); return; }
int id;
try { id = Integer.parseInt(args[1]); }
catch (NumberFormatException e) { player.sendMessage(plugin.color("&cUngültige ID!")); return; }
final int ticketId = id;
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
boolean success = plugin.getDatabaseManager().claimTicket(ticketId, player.getUniqueId(), player.getName());
Bukkit.getScheduler().runTask(plugin, () -> {
if (!success) { player.sendMessage(plugin.formatMessage("messages.already-claimed")); return; }
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
if (ticket == null) return;
player.sendMessage(plugin.formatMessage("messages.ticket-claimed")
.replace("{id}", String.valueOf(ticketId))
.replace("{player}", ticket.getCreatorName()));
plugin.getTicketManager().notifyCreatorClaimed(ticket);
// ── BUG FIX #1: Teleportation bei aktivem BungeeCord komplett sperren ──
// Wenn BungeeCord aktiv ist, kann das Ticket von einem anderen Server stammen.
// getLocation() würde null liefern (World existiert lokal nicht) oder den
// Supporter auf dem falschen Server teleportieren.
// Lösung: Bei aktivem BungeeCord generell keinen Teleport durchführen.
if (plugin.isBungeeCordEnabled()) {
// Hinweis: Server anzeigen wenn bekannt, damit Supporter weiß wo das Ticket ist
String serverHint = !"unknown".equals(ticket.getServerName())
? " &7(Server: &b" + ticket.getServerName() + "&7)"
: "";
player.sendMessage(plugin.color("&7Teleportation deaktiviert BungeeCord-Netzwerk aktiv." + serverHint));
} else {
// Standalone-Modus: Normal teleportieren
if (ticket.getLocation() != null) {
player.teleport(ticket.getLocation());
} else {
player.sendMessage(plugin.color("&7Teleportation nicht möglich World nicht gefunden."));
}
}
});
});
}
// ─────────────────────────── /ticket close ─────────────────────────────
private void handleClose(Player player, String[] args) {
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
}
if (args.length < 2) { player.sendMessage(plugin.color("&cBenutzung: /ticket close <ID> [Kommentar]")); return; }
int id;
try { id = Integer.parseInt(args[1]); }
catch (NumberFormatException e) { player.sendMessage(plugin.color("&cUngültige ID!")); return; }
String closeComment = args.length > 2 ? String.join(" ", Arrays.copyOfRange(args, 2, args.length)) : "";
final int ticketId = id;
final String comment = closeComment;
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
boolean success = plugin.getDatabaseManager().closeTicket(ticketId, comment);
if (success) {
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
// Ticket in persistente Stats-Tabelle eintragen (bleibt auch nach Löschung erhalten).
// player.getName() = der Admin der /ticket close ausgeführt hat nicht zwingend der Claimer.
if (ticket != null) plugin.getDatabaseManager().recordClosedTicket(ticket, player.getName());
Bukkit.getScheduler().runTask(plugin, () -> {
player.sendMessage(plugin.formatMessage("messages.ticket-closed").replace("{id}", String.valueOf(ticketId)));
if (ticket != null) {
ticket.setCloseComment(comment);
plugin.getTicketManager().notifyCreatorClosed(ticket, player.getName());
}
});
} else {
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.formatMessage("messages.ticket-not-found")));
}
});
}
// ─────────────────────────── /ticket forward ───────────────────────────
private void handleForward(Player player, String[] args) {
if (!player.hasPermission("ticket.admin")) {
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
}
if (args.length < 3) { player.sendMessage(plugin.color("&cBenutzung: /ticket forward <ID> <Spieler>")); return; }
int id;
try { id = Integer.parseInt(args[1]); }
catch (NumberFormatException e) { player.sendMessage(plugin.color("&cUngültige ID!")); return; }
// BungeeCord: Ziel-Spieler lokal suchen
Player localTarget = Bukkit.getPlayer(args[2]);
if (localTarget == null) {
if (plugin.isBungeeCordEnabled()) {
player.sendMessage(plugin.color("&7[BungeeCord] Spieler &e" + args[2]
+ " &7ist auf diesem Server nicht online."));
player.sendMessage(plugin.color("&7Tipp: Forwarden geht nur zu Spielern auf &bdemselben Server&7."));
} else {
player.sendMessage(plugin.color("&cSpieler nicht gefunden!"));
}
return;
}
final int ticketId = id;
final String fromName = player.getName();
final Player t = localTarget;
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
boolean success = plugin.getDatabaseManager().forwardTicket(ticketId, t.getUniqueId(), t.getName());
Bukkit.getScheduler().runTask(plugin, () -> {
if (!success) { player.sendMessage(plugin.formatMessage("messages.ticket-not-found")); return; }
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
if (ticket == null) return;
player.sendMessage(plugin.color("&aTicket &e#" + ticketId + " &awurde an &e" + t.getName() + " &aweitergeleitet."));
plugin.getTicketManager().notifyForwardedTo(ticket, fromName);
plugin.getTicketManager().notifyCreatorForwarded(ticket);
});
});
}
// ─────────────────────────── /ticket comment ───────────────────────────
private void handleComment(Player player, String[] args) {
if (!player.hasPermission("ticket.create") && !player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
}
if (args.length < 3) { player.sendMessage(plugin.color("&cBenutzung: /ticket comment <ID> <Nachricht>")); return; }
int id;
try { id = Integer.parseInt(args[1]); }
catch (NumberFormatException e) { player.sendMessage(plugin.color("&cUngültige ID!")); return; }
String msg = String.join(" ", Arrays.copyOfRange(args, 2, args.length));
if (msg.length() > 500) { player.sendMessage(plugin.color("&cNachricht zu lang! Maximal 500 Zeichen.")); return; }
final int ticketId = id;
final String message = msg;
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
if (ticket == null) {
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.formatMessage("messages.ticket-not-found")));
return;
}
boolean isOwner = ticket.getCreatorUUID().equals(player.getUniqueId());
boolean isStaff = player.hasPermission("ticket.support") || player.hasPermission("ticket.admin");
if (!isOwner && !isStaff) {
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.color("&cDu kannst nur deine eigenen Tickets kommentieren.")));
return;
}
TicketComment comment = new TicketComment(ticketId, player.getUniqueId(), player.getName(), message);
boolean success = plugin.getDatabaseManager().addComment(comment);
Bukkit.getScheduler().runTask(plugin, () -> {
if (success) {
player.sendMessage(plugin.color("&aDein Kommentar zu Ticket &e#" + ticketId + " &awurde gespeichert."));
notifyCommentReceivers(player, ticket, message);
} else {
player.sendMessage(plugin.color("&cFehler beim Speichern des Kommentars."));
}
});
});
}
/**
* Benachrichtigt alle relevanten Empfänger über einen neuen Kommentar.
*
* ── BUG FIX #2 ──────────────────────────────────────────────────────────
* Vorher: broadcastTeamNotification() wurde am Ende ZUSÄTZLICH aufgerufen
* obwohl alle lokalen Supporter bereits einzeln per Schleife
* benachrichtigt wurden. Das führte zu:
* a) Doppelter Nachricht für lokale Supporter
* b) broadcastTeamNotification() sendet intern ebenfalls lokal →
* lokale Supporter sahen die Nachricht dreifach
* c) Das Forward-Paket an andere Server war korrekt, aber die
* Empfänger auf anderen Servern sahen auch Duplikate da
* broadcastTeamNotification() wiederum lokal sendet
*
* Fix: broadcastTeamNotification() ERSETZT die lokale Supporter-Schleife
* komplett. Die Methode sendet bereits lokal direkt und forwardet
* gleichzeitig an alle anderen BungeeCord-Server.
* Im Standalone-Modus bleibt die lokale Schleife erhalten.
* ────────────────────────────────────────────────────────────────────────
*/
private void notifyCommentReceivers(Player author, Ticket ticket, String message) {
String onlineMsg = plugin.color("&e[Ticket #" + ticket.getId() + "] &f" + author.getName() + " &7hat kommentiert: &f" + message);
String offlineMsg = "&e[Ticket #" + ticket.getId() + "] &f" + author.getName() + " &7hat kommentiert (während du offline warst): &f" + message;
// ── 1. Ticket-Ersteller benachrichtigen (wenn nicht der Autor selbst) ──
if (!ticket.getCreatorUUID().equals(author.getUniqueId())) {
Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
if (creator != null && creator.isOnline()) {
creator.sendMessage(onlineMsg);
} else if (plugin.isBungeeCordEnabled()) {
// BungeeCord: Zustellung via Plugin-Messaging, kein Pending-Eintrag
// (PlayerJoinListener übernimmt Offline-Fallback via close_notified-Logik)
plugin.getBungeeMessenger().sendMessageToPlayer(
ticket.getCreatorUUID(), ticket.getCreatorName(), onlineMsg);
} else {
// Standalone: Offline → für nächsten Login speichern
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
plugin.getDatabaseManager().addPendingNotification(ticket.getCreatorUUID(), offlineMsg));
}
}
// ── 2. Supporter/Admin benachrichtigen (wenn Kommentar vom Spieler kommt) ──
if (!author.hasPermission("ticket.support") && !author.hasPermission("ticket.admin")) {
if (plugin.isBungeeCordEnabled()) {
// BungeeCord-Modus: broadcastTeamNotification() übernimmt ALLES
// lokal direkt + Forward an alle anderen Server in einem Paket.
// KEINE zusätzliche lokale Schleife, da das zu Duplikaten führt.
plugin.getBungeeMessenger().broadcastTeamNotification(onlineMsg);
} else {
// Standalone-Modus: Claimer gezielt benachrichtigen
UUID claimerUUID = ticket.getClaimerUUID();
if (claimerUUID != null && !claimerUUID.equals(author.getUniqueId())) {
Player claimer = Bukkit.getPlayer(claimerUUID);
if (claimer != null && claimer.isOnline()) {
claimer.sendMessage(onlineMsg);
} else {
String claimerOffline = "&e[Ticket #" + ticket.getId() + "] &f" + author.getName()
+ " &7hat auf dein bearbeitetes Ticket kommentiert (offline): &f" + message;
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
plugin.getDatabaseManager().addPendingNotification(claimerUUID, claimerOffline));
}
}
// Alle anderen Online-Supporter auf diesem Server informieren
for (Player p : Bukkit.getOnlinePlayers()) {
if (p.getUniqueId().equals(author.getUniqueId())) continue;
if (claimerUUID != null && p.getUniqueId().equals(claimerUUID)) continue;
if (p.hasPermission("ticket.support") || p.hasPermission("ticket.admin")) {
p.sendMessage(onlineMsg);
}
}
}
}
}
// ─────────────────────────── /ticket rate ──────────────────────────────
private void handleRate(Player player, String[] args) {
if (!plugin.getConfig().getBoolean("rating-enabled", true)) {
player.sendMessage(plugin.color("&cBewertungen sind deaktiviert.")); return;
}
if (args.length < 3) { player.sendMessage(plugin.color("&cBenutzung: /ticket rate <ID> <good|bad>")); return; }
int id;
try { id = Integer.parseInt(args[1]); }
catch (NumberFormatException e) { player.sendMessage(plugin.color("&cUngültige ID!")); return; }
String ratingArg = args[2].toLowerCase();
String rating;
if (ratingArg.equals("good") || ratingArg.equals("gut") || ratingArg.equals("thumbsup")) {
rating = "THUMBS_UP";
} else if (ratingArg.equals("bad") || ratingArg.equals("schlecht") || ratingArg.equals("thumbsdown")) {
rating = "THUMBS_DOWN";
} else {
player.sendMessage(plugin.color("&cUngültige Bewertung! Benutze &egood &coder &ebad&c."));
return;
}
final int ticketId = id;
final String finalRating = rating;
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
Ticket ticket = plugin.getDatabaseManager().getTicketById(ticketId);
if (ticket == null) {
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.formatMessage("messages.ticket-not-found")));
return;
}
if (!ticket.getCreatorUUID().equals(player.getUniqueId())) {
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.color("&cDu kannst nur deine eigenen Tickets bewerten.")));
return;
}
if (ticket.hasRating()) {
Bukkit.getScheduler().runTask(plugin, () -> player.sendMessage(plugin.color("&cDu hast dieses Ticket bereits bewertet.")));
return;
}
boolean success = plugin.getDatabaseManager().rateTicket(ticketId, finalRating);
Bukkit.getScheduler().runTask(plugin, () -> {
if (success) {
String emoji = "THUMBS_UP".equals(finalRating) ? "§a👍 Positiv" : "§c👎 Negativ";
player.sendMessage(plugin.color("&aDanke für deine Bewertung! (" + emoji + "§a)"));
} else {
player.sendMessage(plugin.color("&cBewertung konnte nicht gespeichert werden. Ticket geschlossen?"));
}
});
});
}
// ─────────────────────────── /ticket blacklist ─────────────────────────
private void handleBlacklist(Player player, String[] args) {
if (!player.hasPermission("ticket.admin")) {
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
}
if (args.length < 2) {
player.sendMessage(plugin.color("&cBenutzung: /ticket blacklist <add|remove|list> [Spieler] [Grund]"));
return;
}
switch (args[1].toLowerCase()) {
case "add" -> {
if (args.length < 3) { player.sendMessage(plugin.color("&cBenutzung: /ticket blacklist add <Spieler> [Grund]")); return; }
String targetName = args[2];
String reason = args.length > 3 ? String.join(" ", Arrays.copyOfRange(args, 3, args.length)) : "Missbrauch";
@SuppressWarnings("deprecation")
OfflinePlayer target = Bukkit.getOfflinePlayer(targetName);
if (target.getUniqueId() == null) { player.sendMessage(plugin.color("&cSpieler nicht gefunden.")); return; }
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
boolean success = plugin.getDatabaseManager().addBlacklist(
target.getUniqueId(), targetName, reason, player.getName());
Bukkit.getScheduler().runTask(plugin, () -> {
if (success)
player.sendMessage(plugin.color("&a" + targetName + " &awurde zur Ticket-Blacklist hinzugefügt. &7Grund: &e" + reason));
else
player.sendMessage(plugin.color("&cSpieler ist bereits auf der Blacklist."));
});
});
}
case "remove" -> {
if (args.length < 3) { player.sendMessage(plugin.color("&cBenutzung: /ticket blacklist remove <Spieler>")); return; }
String targetName = args[2];
@SuppressWarnings("deprecation")
OfflinePlayer target = Bukkit.getOfflinePlayer(targetName);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
boolean success = plugin.getDatabaseManager().removeBlacklist(target.getUniqueId());
Bukkit.getScheduler().runTask(plugin, () -> {
if (success) player.sendMessage(plugin.color("&a" + targetName + " &awurde von der Blacklist entfernt."));
else player.sendMessage(plugin.color("&cSpieler war nicht auf der Blacklist."));
});
});
}
case "list" -> {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
var list = plugin.getDatabaseManager().getBlacklist();
Bukkit.getScheduler().runTask(plugin, () -> {
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&6Ticket-Blacklist &7(" + list.size() + " Einträge)"));
player.sendMessage(plugin.color("&8&m "));
if (list.isEmpty()) {
player.sendMessage(plugin.color("&7Keine gesperrten Spieler."));
} else {
for (String[] entry : list) {
player.sendMessage(plugin.color("&e" + entry[1] + " &7 &f" + entry[2]
+ " &7(gesperrt von &e" + entry[3] + "&7)"));
}
}
player.sendMessage(plugin.color("&8&m "));
});
});
}
default -> player.sendMessage(plugin.color("&cBenutzung: /ticket blacklist <add|remove|list> [Spieler] [Grund]"));
}
}
// ─────────────────────────── /ticket reload ────────────────────────────
private void handleReload(Player player) {
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
plugin.reloadConfig();
plugin.getCategoryManager().reload();
player.sendMessage(plugin.color("&aKonfiguration wurde neu geladen. &7(inkl. Kategorien)"));
if (plugin.isBungeeCordEnabled()) {
player.sendMessage(plugin.color("&8[BungeeCord] &7Server: &b" + plugin.getServerName()));
}
}
// ─────────────────────────── /ticket archive ───────────────────────────
private void handleArchive(Player player) {
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
int count = plugin.getDatabaseManager().archiveClosedTickets();
Bukkit.getScheduler().runTask(plugin, () -> {
if (count > 0) player.sendMessage(plugin.formatMessage("messages.archive-success").replace("{count}", String.valueOf(count)));
else player.sendMessage(plugin.formatMessage("messages.archive-fail"));
});
});
}
// ─────────────────────────── /ticket stats ─────────────────────────────
private void handleStats(Player player) {
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
var stats = plugin.getDatabaseManager().getTicketStats();
var staffRatings = plugin.getDatabaseManager().getStaffRatings();
Bukkit.getScheduler().runTask(plugin, () -> {
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&6Ticket Statistik"));
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&eGesamt: &a" + stats.total));
player.sendMessage(plugin.color("&eOffen: &a" + stats.open));
player.sendMessage(plugin.color("&eGeschlossen: &a" + stats.closed + " &7(historisch)"));
player.sendMessage(plugin.color("&eWeitergeleitet: &a" + stats.forwarded));
if (plugin.getConfig().getBoolean("rating-enabled", true)) {
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&6Support-Bewertungen &7(gesamt, historisch)"));
player.sendMessage(plugin.color("&a👍 Positiv: &f" + stats.thumbsUp
+ " &c👎 Negativ: &f" + stats.thumbsDown));
int totalRated = stats.thumbsUp + stats.thumbsDown;
if (totalRated > 0) {
int percent = (int) Math.round(stats.thumbsUp * 100.0 / totalRated);
player.sendMessage(plugin.color("&7Zufriedenheit: &e" + percent + "%"));
}
// Bewertungen pro Support-Mitarbeiter
if (!staffRatings.isEmpty()) {
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&6Bewertungen nach Support-Mitarbeiter:"));
player.sendMessage(plugin.color("&7 Name 👍 👎 Tickets Zufrieden"));
for (String[] row : staffRatings) {
// row: [name, up, down, totalClosed, percent]
String name = String.format("%-16s", row[0]);
String up = String.format("%-5s", row[1]);
String down = String.format("%-5s", row[2]);
String total = String.format("%-8s", row[3]);
String percent = row[4];
player.sendMessage(plugin.color(
"&e " + name + " &a" + up + " &c" + down + " &7" + total + " &e" + percent));
}
}
}
// BungeeCord: Tickets pro Server anzeigen
if (plugin.isBungeeCordEnabled() && !stats.byServer.isEmpty()) {
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&6Tickets nach Server:"));
stats.byServer.entrySet().stream()
.sorted((a, b) -> b.getValue() - a.getValue())
.forEach(e -> player.sendMessage(plugin.color("&b " + e.getKey() + ": &a" + e.getValue())));
}
player.sendMessage(plugin.color("&8&m "));
player.sendMessage(plugin.color("&6Top Ersteller:"));
stats.byPlayer.entrySet().stream()
.sorted((a, b) -> b.getValue() - a.getValue())
.limit(5)
.forEach(e -> player.sendMessage(plugin.color("&e " + e.getKey() + ": &a" + e.getValue())));
player.sendMessage(plugin.color("&8&m "));
});
});
}
// ─────────────────────────── /ticket migrate ───────────────────────────
private void handleMigrate(Player player, String[] args) {
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
if (args.length < 2) { player.sendMessage(plugin.color("&cBenutzung: /ticket migrate <tomysql|tofile>")); return; }
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
int migrated = 0;
String mode = args[1].toLowerCase();
if (mode.equals("tomysql")) migrated = plugin.getDatabaseManager().migrateToMySQL();
else if (mode.equals("tofile")) migrated = plugin.getDatabaseManager().migrateToFile();
else { player.sendMessage(plugin.formatMessage("messages.unknown-mode")); return; }
int f = migrated;
Bukkit.getScheduler().runTask(plugin, () -> {
if (f > 0) player.sendMessage(plugin.formatMessage("messages.migration-success").replace("{count}", String.valueOf(f)));
else player.sendMessage(plugin.formatMessage("messages.migration-fail"));
});
});
}
// ─────────────────────────── /ticket export ────────────────────────────
private void handleExport(Player player, String[] args) {
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
if (args.length < 2) { player.sendMessage(plugin.color("&cBenutzung: /ticket export <Dateiname>")); return; }
String filename = args[1];
File exportFile = new File(plugin.getDataFolder(), filename);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
int count = plugin.getDatabaseManager().exportTickets(exportFile);
Bukkit.getScheduler().runTask(plugin, () -> {
if (count > 0) player.sendMessage(plugin.formatMessage("messages.export-success").replace("{count}", String.valueOf(count)).replace("{file}", filename));
else player.sendMessage(plugin.formatMessage("messages.export-fail"));
});
});
}
// ─────────────────────────── /ticket import ────────────────────────────
private void handleImport(Player player, String[] args) {
if (!player.hasPermission("ticket.admin")) { player.sendMessage(plugin.formatMessage("messages.no-permission")); return; }
if (args.length < 2) { player.sendMessage(plugin.color("&cBenutzung: /ticket import <Dateiname>")); return; }
String filename = args[1];
File importFile = new File(plugin.getDataFolder(), filename);
if (!importFile.exists()) { player.sendMessage(plugin.formatMessage("messages.file-not-found").replace("{file}", filename)); return; }
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
int count = plugin.getDatabaseManager().importTickets(importFile);
Bukkit.getScheduler().runTask(plugin, () -> {
if (count > 0) player.sendMessage(plugin.formatMessage("messages.import-success").replace("{count}", String.valueOf(count)));
else player.sendMessage(plugin.formatMessage("messages.import-fail"));
});
});
}
// ─────────────────────────── /ticket setpriority ──────────────────────
private void handleSetPriority(Player player, String[] args) {
if (!player.hasPermission("ticket.support") && !player.hasPermission("ticket.admin")) {
player.sendMessage(plugin.formatMessage("messages.no-permission")); return;
}
if (!plugin.getConfig().getBoolean("priorities-enabled", true)) {
player.sendMessage(plugin.color("&cDas Prioritäten-System ist deaktiviert.")); return;
}
if (args.length < 3) {
player.sendMessage(plugin.color("&cBenutzung: /ticket setpriority <ID> <low|normal|high|urgent>")); return;
}
int ticketId;
try { ticketId = Integer.parseInt(args[1]); }
catch (NumberFormatException e) {
player.sendMessage(plugin.color("&cUngültige Ticket-ID: &e" + args[1])); return;
}
TicketPriority priority = parsePriority(args[2]);
if (priority == null) {
player.sendMessage(plugin.color("&cUngültige Priorität! Gültig: &alow&7, &enormal&7, &6high&7, &curgent")); return;
}
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
boolean success = plugin.getDatabaseManager().setTicketPriority(ticketId, priority);
Bukkit.getScheduler().runTask(plugin, () -> {
if (success) {
player.sendMessage(plugin.color("&aPriorität von Ticket &e#" + ticketId
+ " &awurde auf " + priority.getColored() + " &agesetzt."));
} else {
player.sendMessage(plugin.color("&cTicket &e#" + ticketId + " &cwurde nicht gefunden."));
}
});
});
}
/** Parst Benutzer-Eingaben zu TicketPriority. Gibt null zurück wenn keine Übereinstimmung. */
private TicketPriority parsePriority(String input) {
if (input == null) return null;
return switch (input.toLowerCase()) {
case "low", "niedrig" -> TicketPriority.LOW;
case "normal" -> TicketPriority.NORMAL;
case "high", "hoch" -> TicketPriority.HIGH;
case "urgent", "dringend" -> TicketPriority.URGENT;
default -> null;
};
}
// ─────────────────────────── Tab-Completion ────────────────────────────
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
List<String> completions = new ArrayList<>();
if (!(sender instanceof Player player)) return completions;
if (args.length == 1) {
List<String> subs = new ArrayList<>(List.of("create", "list", "comment"));
if (player.hasPermission("ticket.support") || player.hasPermission("ticket.admin"))
subs.addAll(List.of("claim", "close"));
if (plugin.getConfig().getBoolean("rating-enabled", true)) subs.add("rate");
if (player.hasPermission("ticket.admin"))
subs.addAll(List.of("forward", "reload", "stats", "archive", "migrate", "export", "import", "blacklist"));
if ((player.hasPermission("ticket.support") || player.hasPermission("ticket.admin"))
&& plugin.getConfig().getBoolean("priorities-enabled", true))
subs.add("setpriority");
for (String s : subs) if (s.startsWith(args[0].toLowerCase())) completions.add(s);
} else if (args.length == 2 && args[0].equalsIgnoreCase("create")
&& plugin.getConfig().getBoolean("categories-enabled", true)) {
for (ConfigCategory c : plugin.getCategoryManager().getAll())
if (c.getKey().startsWith(args[1].toLowerCase())) completions.add(c.getKey());
if (plugin.getConfig().getBoolean("priorities-enabled", true))
for (String p : List.of("low", "normal", "high", "urgent"))
if (p.startsWith(args[1].toLowerCase())) completions.add(p);
} else if (args.length == 3 && args[0].equalsIgnoreCase("create")
&& plugin.getConfig().getBoolean("priorities-enabled", true)) {
for (String p : List.of("low", "normal", "high", "urgent"))
if (p.startsWith(args[2].toLowerCase())) completions.add(p);
} else if (args.length == 3 && args[0].equalsIgnoreCase("setpriority")) {
for (String p : List.of("low", "normal", "high", "urgent"))
if (p.startsWith(args[2].toLowerCase())) completions.add(p);
} else if (args.length == 3 && args[0].equalsIgnoreCase("forward")) {
// BungeeCord: Nur lokal online Spieler als Tab-Completion
for (Player p : Bukkit.getOnlinePlayers())
if (p.getName().toLowerCase().startsWith(args[2].toLowerCase())) completions.add(p.getName());
} else if (args.length == 2 && args[0].equalsIgnoreCase("blacklist")) {
completions.addAll(List.of("add", "remove", "list"));
} else if (args.length == 3 && args[0].equalsIgnoreCase("blacklist")
&& (args[1].equalsIgnoreCase("add") || args[1].equalsIgnoreCase("remove"))) {
for (Player p : Bukkit.getOnlinePlayers())
if (p.getName().toLowerCase().startsWith(args[2].toLowerCase())) completions.add(p.getName());
} else if (args.length == 3 && args[0].equalsIgnoreCase("rate")) {
completions.addAll(List.of("good", "bad"));
}
return completions;
}
}