Soft-delete copy _trash/2026-05-07T19-39-34-009Z/_trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/chat/ReportManager.java
This commit is contained in:
@@ -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 <ID> 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<String, ChatReport> 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<ChatReport> getOpenReports() {
|
||||||
|
List<ChatReport> 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<ChatReport> getAllReports() {
|
||||||
|
List<ChatReport> 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("\\\\", "\\");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user