From de728366198a6b04c497cb6cfd50ce95c2a6cd61 Mon Sep 17 00:00:00 2001 From: M_Viper Date: Thu, 7 May 2026 19:39:34 +0000 Subject: [PATCH] Soft-delete copy _trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/chat/ReportManager.java --- .../status/modules/chat/ReportManager.java | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 _trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/chat/ReportManager.java diff --git a/_trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/chat/ReportManager.java b/_trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/chat/ReportManager.java new file mode 100644 index 0000000..27a52a1 --- /dev/null +++ b/_trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/chat/ReportManager.java @@ -0,0 +1,227 @@ +package net.viper.status.modules.chat; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Logger; + +/** + * Verwaltet Spieler-Reports (/report). + * + * Reports werden mit einer eindeutigen ID (z.B. RPT-0001) gespeichert und + * bleiben offen, bis ein Admin sie explizit mit /reportclose schließt. + * + * Online-Admins werden sofort benachrichtigt. + * Offline-Admins erhalten eine verzögerte Benachrichtigung beim nächsten Login + * (gesteuert von außen via getPendingNotificationFor()). + * + * Speicherformat (chat_reports.dat): + * id|reporter|reporterUUID|reported|server|messageContext|reason|timestamp|closed|closedBy + */ +public class ReportManager { + + private final File file; + private final Logger logger; + + /** Alle Reports (offen und geschlossen). */ + private final ConcurrentHashMap reports = new ConcurrentHashMap<>(); + + /** Zähler für Report-IDs. Wird beim Laden synchronisiert. */ + private final AtomicInteger idCounter = new AtomicInteger(0); + + private static final SimpleDateFormat DATE_FMT = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss"); + + // ===== Report-Datenklasse ===== + + public static class ChatReport { + public String id; + public String reporterName; + public UUID reporterUUID; + public String reportedName; + public String server; + public String messageContext; // letzte bekannte Chatnachricht des Gemeldeten + public String reason; + public long timestamp; + public boolean closed; + public String closedBy; // Name des schließenden Admins (oder leer) + + public String getFormattedTime() { + return DATE_FMT.format(new Date(timestamp)); + } + } + + // ===== Konstruktor ===== + + public ReportManager(File dataFolder, Logger logger) { + this.file = new File(dataFolder, "chat_reports.dat"); + this.logger = logger; + } + + // ===== Report-Logik ===== + + /** + * Erstellt einen neuen Report. + * + * @param reporterName Name des meldenden Spielers + * @param reporterUUID UUID des meldenden Spielers + * @param reportedName Name des gemeldeten Spielers + * @param server Server, auf dem sich der Reporter befand + * @param messageContext Letzte bekannte Nachricht des Gemeldeten (für Kontext) + * @param reason Freitext-Begründung + * @return die neue Report-ID (z.B. RPT-0001) + */ + public String createReport(String reporterName, UUID reporterUUID, + String reportedName, String server, + String messageContext, String reason) { + String id = String.format("RPT-%04d", idCounter.incrementAndGet()); + + ChatReport report = new ChatReport(); + report.id = id; + report.reporterName = reporterName; + report.reporterUUID = reporterUUID; + report.reportedName = reportedName; + report.server = server; + report.messageContext = messageContext != null ? messageContext : ""; + report.reason = reason; + report.timestamp = System.currentTimeMillis(); + report.closed = false; + report.closedBy = ""; + + reports.put(id, report); + save(); + return id; + } + + /** + * Schließt einen Report. + * + * @param id Report-ID (z.B. RPT-0001, case-insensitiv) + * @param adminName Name des Admins, der den Report schließt + * @return true wenn erfolgreich geschlossen, false wenn nicht gefunden / bereits geschlossen + */ + public boolean closeReport(String id, String adminName) { + ChatReport report = getReport(id); + if (report == null || report.closed) return false; + report.closed = true; + report.closedBy = adminName; + save(); + return true; + } + + /** Gibt einen Report nach ID zurück (case-insensitiv). */ + public ChatReport getReport(String id) { + if (id == null) return null; + return reports.get(id.toUpperCase()); + } + + /** Gibt alle offenen Reports chronologisch (älteste zuerst) zurück. */ + public List getOpenReports() { + List list = new ArrayList<>(); + for (ChatReport r : reports.values()) { + if (!r.closed) list.add(r); + } + list.sort(Comparator.comparingLong(r -> r.timestamp)); + return list; + } + + /** Gibt alle Reports chronologisch zurück (auch geschlossene). */ + public List getAllReports() { + List list = new ArrayList<>(reports.values()); + list.sort(Comparator.comparingLong(r -> r.timestamp)); + return list; + } + + /** Anzahl offener Reports. */ + public int getOpenCount() { + int count = 0; + for (ChatReport r : reports.values()) if (!r.closed) count++; + return count; + } + + // ===== Persistenz ===== + + public void save() { + try (BufferedWriter bw = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(file), "UTF-8"))) { + for (ChatReport r : reports.values()) { + bw.write( + esc(r.id) + "|" + + esc(r.reporterName) + "|" + + r.reporterUUID + "|" + + esc(r.reportedName) + "|" + + esc(r.server) + "|" + + esc(r.messageContext) + "|" + + esc(r.reason) + "|" + + r.timestamp + "|" + + r.closed + "|" + + esc(r.closedBy != null ? r.closedBy : "") + ); + bw.newLine(); + } + } catch (IOException e) { + logger.warning("[ChatModule] Fehler beim Speichern der Reports: " + e.getMessage()); + } + } + + public void load() { + reports.clear(); + if (!file.exists()) return; + + int maxNum = 0; + try (BufferedReader br = new BufferedReader( + new InputStreamReader(new FileInputStream(file), "UTF-8"))) { + String line; + while ((line = br.readLine()) != null) { + line = line.trim(); + if (line.isEmpty()) continue; + String[] p = line.split("\\|", -1); + if (p.length < 10) continue; + try { + ChatReport r = new ChatReport(); + r.id = unesc(p[0]); + r.reporterName = unesc(p[1]); + r.reporterUUID = UUID.fromString(p[2]); + r.reportedName = unesc(p[3]); + r.server = unesc(p[4]); + r.messageContext = unesc(p[5]); + r.reason = unesc(p[6]); + r.timestamp = Long.parseLong(p[7]); + r.closed = Boolean.parseBoolean(p[8]); + r.closedBy = unesc(p[9]); + reports.put(r.id.toUpperCase(), r); + + // Zähler auf höchste bekannte Nummer synchronisieren + if (r.id.toUpperCase().startsWith("RPT-")) { + try { + int num = Integer.parseInt(r.id.substring(4)); + if (num > maxNum) maxNum = num; + } catch (NumberFormatException ignored) {} + } + } catch (Exception ignored) {} + } + } catch (IOException e) { + logger.warning("[ChatModule] Fehler beim Laden der Reports: " + e.getMessage()); + } + idCounter.set(maxNum); + + } + + // ===== Escape-Helfer (Pipe-Zeichen und Zeilenumbrüche escapen) ===== + + private static String esc(String s) { + if (s == null) return ""; + return s.replace("\\", "\\\\") + .replace("|", "\\p") + .replace("\n", "\\n") + .replace("\r", ""); + } + + private static String unesc(String s) { + if (s == null) return ""; + return s.replace("\\n", "\n") + .replace("\\p", "|") + .replace("\\\\", "\\"); + } +} \ No newline at end of file