From f053a8f96df56a97568fe08994dc4f1e899592bb Mon Sep 17 00:00:00 2001 From: M_Viper Date: Wed, 1 Apr 2026 10:15:05 +0000 Subject: [PATCH] Upload via Git Manager GUI - BroadcastModule.java --- .../modules/broadcast/BroadcastModule.java | 760 +++++++++--------- 1 file changed, 380 insertions(+), 380 deletions(-) diff --git a/src/main/java/net/viper/status/modules/broadcast/BroadcastModule.java b/src/main/java/net/viper/status/modules/broadcast/BroadcastModule.java index 03ce738..9228034 100644 --- a/src/main/java/net/viper/status/modules/broadcast/BroadcastModule.java +++ b/src/main/java/net/viper/status/modules/broadcast/BroadcastModule.java @@ -1,381 +1,381 @@ -package net.viper.status.modules.broadcast; - -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.plugin.Plugin; -import net.md_5.bungee.api.connection.ProxiedPlayer; -import net.md_5.bungee.api.chat.TextComponent; -import net.md_5.bungee.api.plugin.Listener; -import net.viper.status.module.Module; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; - -/** - * BroadcastModule - * - * Speichert geplante Broadcasts jetzt persistent in 'broadcasts.schedules'. - * Beim Neustart werden diese automatisch wieder geladen. - */ -public class BroadcastModule implements Module, Listener { - - private Plugin plugin; - private boolean enabled = true; - private String requiredApiKey = ""; - private String format = "%prefix% %message%"; - private String fallbackPrefix = "[Broadcast]"; - private String fallbackPrefixColor = "&c"; - private String fallbackBracketColor = "&8"; // Neu - private String fallbackMessageColor = "&f"; - - private final Map scheduledByClientId = new ConcurrentHashMap<>(); - private File schedulesFile; - private final SimpleDateFormat dateFormat; - - public BroadcastModule() { - dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); - dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); - } - - @Override - public String getName() { - return "BroadcastModule"; - } - - @Override - public void onEnable(Plugin plugin) { - this.plugin = plugin; - schedulesFile = new File(plugin.getDataFolder(), "broadcasts.schedules"); - loadConfig(); - - if (!enabled) { - plugin.getLogger().info("[BroadcastModule] deaktiviert via verify.properties (broadcast.enabled=false)"); - return; - } - - try { - plugin.getProxy().getPluginManager().registerListener(plugin, this); - } catch (Throwable ignored) {} - - plugin.getLogger().info("[BroadcastModule] aktiviert. Format: " + format); - loadSchedules(); - plugin.getProxy().getScheduler().schedule(plugin, this::processScheduled, 1, 1, TimeUnit.SECONDS); - } - - @Override - public void onDisable(Plugin plugin) { - plugin.getLogger().info("[BroadcastModule] deaktiviert."); - saveSchedules(); - scheduledByClientId.clear(); - } - - private void loadConfig() { - File file = new File(plugin.getDataFolder(), "verify.properties"); - if (!file.exists()) { - enabled = true; - requiredApiKey = ""; - format = "%prefix% %message%"; - fallbackPrefix = "[Broadcast]"; - fallbackPrefixColor = "&c"; - fallbackBracketColor = "&8"; // Neu - fallbackMessageColor = "&f"; - return; - } - - try (InputStream in = new FileInputStream(file)) { - Properties props = new Properties(); - props.load(new InputStreamReader(in, StandardCharsets.UTF_8)); - enabled = Boolean.parseBoolean(props.getProperty("broadcast.enabled", "true")); - requiredApiKey = props.getProperty("broadcast.api_key", "").trim(); - format = props.getProperty("broadcast.format", format).trim(); - if (format.isEmpty()) format = "%prefix% %message%"; - fallbackPrefix = props.getProperty("broadcast.prefix", fallbackPrefix).trim(); - fallbackPrefixColor = props.getProperty("broadcast.prefix-color", fallbackPrefixColor).trim(); - fallbackBracketColor = props.getProperty("broadcast.bracket-color", fallbackBracketColor).trim(); // Neu - fallbackMessageColor = props.getProperty("broadcast.message-color", fallbackMessageColor).trim(); - } catch (IOException e) { - plugin.getLogger().warning("[BroadcastModule] Fehler beim Laden von verify.properties: " + e.getMessage()); - } - } - - public boolean handleBroadcast(String sourceName, String message, String type, String apiKeyHeader, - String prefix, String prefixColor, String bracketColor, String messageColor) { - loadConfig(); - - if (!enabled) { - plugin.getLogger().info("[BroadcastModule] Broadcast abgelehnt: Modul ist deaktiviert."); - return false; - } - - if (requiredApiKey != null && !requiredApiKey.isEmpty()) { - if (apiKeyHeader == null || !requiredApiKey.equals(apiKeyHeader)) { - plugin.getLogger().warning("[BroadcastModule] Broadcast abgelehnt: API-Key fehlt oder inkorrekt."); - return false; - } - } - - if (message == null) message = ""; - if (sourceName == null || sourceName.isEmpty()) sourceName = "System"; - if (type == null) type = "global"; - - String usedPrefix = (prefix != null && !prefix.trim().isEmpty()) ? prefix.trim() : fallbackPrefix; - String usedPrefixColor = (prefixColor != null && !prefixColor.trim().isEmpty()) ? prefixColor.trim() : fallbackPrefixColor; - String usedBracketColor = (bracketColor != null && !bracketColor.trim().isEmpty()) ? bracketColor.trim() : fallbackBracketColor; // Neu - String usedMessageColor = (messageColor != null && !messageColor.trim().isEmpty()) ? messageColor.trim() : fallbackMessageColor; - - String prefixColorCode = normalizeColorCode(usedPrefixColor); - String bracketColorCode = normalizeColorCode(usedBracketColor); // Neu - String messageColorCode = normalizeColorCode(usedMessageColor); - - // --- KLAMMER LOGIK --- - String finalPrefix; - - // Wenn eine Klammerfarbe gesetzt ist, bauen wir den Prefix neu zusammen - // Format: [BracketColor][ [PrefixColor]Text [BracketColor]] - if (!bracketColorCode.isEmpty()) { - String textContent = usedPrefix; - // Entferne manuelle Klammern, falls der User [Broadcast] in das Textfeld geschrieben hat - if (textContent.startsWith("[")) textContent = textContent.substring(1); - if (textContent.endsWith("]")) textContent = textContent.substring(0, textContent.length() - 1); - - finalPrefix = bracketColorCode + "[" + prefixColorCode + textContent + bracketColorCode + "]" + ChatColor.RESET; - } else { - // Altes Verhalten: Ganzen String einfärben - finalPrefix = prefixColorCode + usedPrefix + ChatColor.RESET; - } - // --------------------- - - String coloredMessage = (messageColorCode.isEmpty() ? "" : messageColorCode) + message; - - String out = format.replace("%name%", sourceName) - .replace("%prefix%", finalPrefix) // Neu verwendete Variable - .replace("%prefixColored%", finalPrefix) // Fallback - .replace("%message%", message) - .replace("%messageColored%", coloredMessage) - .replace("%type%", type); - - if (!out.contains("%prefixColored%") && !out.contains("%messageColored%") && !out.contains("%prefix%") && !out.contains("%message%")) { - out = finalPrefix + " " + coloredMessage; - } - - TextComponent tc = new TextComponent(out); - int sent = 0; - for (ProxiedPlayer p : plugin.getProxy().getPlayers()) { - try { p.sendMessage(tc); sent++; } catch (Throwable ignored) {} - } - - plugin.getLogger().info("[BroadcastModule] Broadcast gesendet (Empfänger=" + sent + "): " + message); - return true; - } - - private String normalizeColorCode(String code) { - if (code == null) return ""; - code = code.trim(); - if (code.isEmpty()) return ""; - return code.contains("&") ? ChatColor.translateAlternateColorCodes('&', code) : code; - } - - private void saveSchedules() { - Properties props = new Properties(); - for (Map.Entry entry : scheduledByClientId.entrySet()) { - String id = entry.getKey(); - ScheduledBroadcast sb = entry.getValue(); - props.setProperty(id + ".nextRunMillis", String.valueOf(sb.nextRunMillis)); - props.setProperty(id + ".sourceName", sb.sourceName); - props.setProperty(id + ".message", sb.message); - props.setProperty(id + ".type", sb.type); - props.setProperty(id + ".prefix", sb.prefix); - props.setProperty(id + ".prefixColor", sb.prefixColor); - props.setProperty(id + ".bracketColor", sb.bracketColor); // Neu - props.setProperty(id + ".messageColor", sb.messageColor); - props.setProperty(id + ".recur", sb.recur); - } - - try (OutputStream out = new FileOutputStream(schedulesFile)) { - props.store(out, "PulseCast Scheduled Broadcasts"); - } catch (IOException e) { - plugin.getLogger().severe("[BroadcastModule] Konnte Schedules nicht speichern: " + e.getMessage()); - } - } - - private void loadSchedules() { - if (!schedulesFile.exists()) { - plugin.getLogger().info("[BroadcastModule] Keine bestehenden Schedules gefunden (Neustart)."); - return; - } - - Properties props = new Properties(); - try (InputStream in = new FileInputStream(schedulesFile)) { - props.load(in); - } catch (IOException e) { - plugin.getLogger().severe("[BroadcastModule] Konnte Schedules nicht laden: " + e.getMessage()); - return; - } - - Map loaded = new HashMap<>(); - for (String key : props.stringPropertyNames()) { - if (!key.contains(".")) continue; - String[] parts = key.split("\\."); - if (parts.length != 2) continue; - - String id = parts[0]; - String field = parts[1]; - String value = props.getProperty(key); - - ScheduledBroadcast sb = loaded.get(id); - if (sb == null) { - sb = new ScheduledBroadcast(id, 0, "", "", "", "", "", "", "", ""); // Ein leerer String mehr für Bracket - loaded.put(id, sb); - } - - switch (field) { - case "nextRunMillis": - try { sb.nextRunMillis = Long.parseLong(value); } catch (NumberFormatException ignored) {} - break; - case "sourceName": sb.sourceName = value; break; - case "message": sb.message = value; break; - case "type": sb.type = value; break; - case "prefix": sb.prefix = value; break; - case "prefixColor": sb.prefixColor = value; break; - case "bracketColor": sb.bracketColor = value; break; // Neu - case "messageColor": sb.messageColor = value; break; - case "recur": sb.recur = value; break; - } - } - scheduledByClientId.putAll(loaded); - plugin.getLogger().info("[BroadcastModule] " + loaded.size() + " geplante Broadcasts aus Datei wiederhergestellt."); - } - - public boolean scheduleBroadcast(long timestampMillis, String sourceName, String message, String type, - String apiKeyHeader, String prefix, String prefixColor, String bracketColor, String messageColor, - String recur, String clientScheduleId) { - loadConfig(); - - if (!enabled) { - plugin.getLogger().info("[BroadcastModule] schedule abgelehnt: Modul deaktiviert."); - return false; - } - - if (requiredApiKey != null && !requiredApiKey.isEmpty()) { - if (apiKeyHeader == null || !requiredApiKey.equals(apiKeyHeader)) { - plugin.getLogger().warning("[BroadcastModule] schedule abgelehnt: API-Key fehlt oder inkorrekt."); - return false; - } - } - - if (message == null) message = ""; - if (sourceName == null || sourceName.isEmpty()) sourceName = "System"; - if (type == null) type = "global"; - if (recur == null) recur = "none"; - - String id = (clientScheduleId != null && !clientScheduleId.trim().isEmpty()) ? clientScheduleId.trim() : UUID.randomUUID().toString(); - - long now = System.currentTimeMillis(); - String scheduledTimeStr = dateFormat.format(new Date(timestampMillis)); - - plugin.getLogger().info("[BroadcastModule] Neue geplante Nachricht registriert: " + id + " @ " + scheduledTimeStr); - - if (timestampMillis <= now) { - plugin.getLogger().warning("[BroadcastModule] Geplante Zeit liegt in der Vergangenheit -> sende sofort!"); - return handleBroadcast(sourceName, message, type, apiKeyHeader, prefix, prefixColor, bracketColor, messageColor); - } - - ScheduledBroadcast sb = new ScheduledBroadcast(id, timestampMillis, sourceName, message, type, prefix, prefixColor, bracketColor, messageColor, recur); - scheduledByClientId.put(id, sb); - saveSchedules(); - return true; - } - - public boolean cancelScheduled(String clientScheduleId) { - if (clientScheduleId == null || clientScheduleId.trim().isEmpty()) return false; - ScheduledBroadcast removed = scheduledByClientId.remove(clientScheduleId); - if (removed != null) { - plugin.getLogger().info("[BroadcastModule] Geplante Nachricht abgebrochen: id=" + clientScheduleId); - saveSchedules(); - return true; - } - return false; - } - - private void processScheduled() { - if (scheduledByClientId.isEmpty()) return; - - long now = System.currentTimeMillis(); - List toRemove = new ArrayList<>(); - boolean changed = false; - - for (Map.Entry entry : scheduledByClientId.entrySet()) { - ScheduledBroadcast sb = entry.getValue(); - - if (sb.nextRunMillis <= now) { - String timeStr = dateFormat.format(new Date(sb.nextRunMillis)); - plugin.getLogger().info("[BroadcastModule] ⏰ Sende geplante Nachricht (ID: " + entry.getKey() + ", Zeit: " + timeStr + ")"); - - handleBroadcast(sb.sourceName, sb.message, sb.type, "", sb.prefix, sb.prefixColor, sb.bracketColor, sb.messageColor); - - if (!"none".equalsIgnoreCase(sb.recur)) { - long next = computeNextMillis(sb.nextRunMillis, sb.recur); - if (next > 0) { - sb.nextRunMillis = next; - String nextTimeStr = dateFormat.format(new Date(next)); - plugin.getLogger().info("[BroadcastModule] Nächste Wiederholung (" + sb.recur + "): " + nextTimeStr); - changed = true; - } else { - toRemove.add(entry.getKey()); - changed = true; - } - } else { - toRemove.add(entry.getKey()); - changed = true; - } - } - } - - if (changed || !toRemove.isEmpty()) { - for (String k : toRemove) { - scheduledByClientId.remove(k); - plugin.getLogger().info("[BroadcastModule] Schedule entfernt: " + k); - } - saveSchedules(); - } - } - - private long computeNextMillis(long currentMillis, String recur) { - switch (recur.toLowerCase(Locale.ROOT)) { - case "hourly": return currentMillis + TimeUnit.HOURS.toMillis(1); - case "daily": return currentMillis + TimeUnit.DAYS.toMillis(1); - case "weekly": return currentMillis + TimeUnit.DAYS.toMillis(7); - default: return -1L; - } - } - - private static class ScheduledBroadcast { - final String clientId; - long nextRunMillis; - String sourceName; - String message; - String type; - String prefix; - String prefixColor; - String bracketColor; // Neu - String messageColor; - String recur; - - ScheduledBroadcast(String clientId, long nextRunMillis, String sourceName, String message, String type, - String prefix, String prefixColor, String bracketColor, String messageColor, String recur) { - this.clientId = clientId; - this.nextRunMillis = nextRunMillis; - this.sourceName = sourceName; - this.message = message; - this.type = type; - this.prefix = prefix; - this.prefixColor = prefixColor; - this.bracketColor = bracketColor; // Neu - this.messageColor = messageColor; - this.recur = (recur == null ? "none" : recur); - } - } +package net.viper.status.modules.broadcast; + +import net.md_5.bungee.api.ChatColor; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.plugin.Listener; +import net.viper.status.module.Module; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +/** + * BroadcastModule + * + * Speichert geplante Broadcasts jetzt persistent in 'broadcasts.schedules'. + * Beim Neustart werden diese automatisch wieder geladen. + */ +public class BroadcastModule implements Module, Listener { + + private Plugin plugin; + private boolean enabled = true; + private String requiredApiKey = ""; + private String format = "%prefix% %message%"; + private String fallbackPrefix = "[Broadcast]"; + private String fallbackPrefixColor = "&c"; + private String fallbackBracketColor = "&8"; // Neu + private String fallbackMessageColor = "&f"; + + private final Map scheduledByClientId = new ConcurrentHashMap<>(); + private File schedulesFile; + private final SimpleDateFormat dateFormat; + + public BroadcastModule() { + dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + } + + @Override + public String getName() { + return "BroadcastModule"; + } + + @Override + public void onEnable(Plugin plugin) { + this.plugin = plugin; + schedulesFile = new File(plugin.getDataFolder(), "broadcasts.schedules"); + loadConfig(); + + if (!enabled) { + plugin.getLogger().info("[BroadcastModule] deaktiviert via verify.properties (broadcast.enabled=false)"); + return; + } + + try { + plugin.getProxy().getPluginManager().registerListener(plugin, this); + } catch (Throwable ignored) {} + + plugin.getLogger().info("[BroadcastModule] aktiviert. Format: " + format); + loadSchedules(); + plugin.getProxy().getScheduler().schedule(plugin, this::processScheduled, 1, 1, TimeUnit.SECONDS); + } + + @Override + public void onDisable(Plugin plugin) { + plugin.getLogger().info("[BroadcastModule] deaktiviert."); + saveSchedules(); + scheduledByClientId.clear(); + } + + private void loadConfig() { + File file = new File(plugin.getDataFolder(), "verify.properties"); + if (!file.exists()) { + enabled = true; + requiredApiKey = ""; + format = "%prefix% %message%"; + fallbackPrefix = "[Broadcast]"; + fallbackPrefixColor = "&c"; + fallbackBracketColor = "&8"; // Neu + fallbackMessageColor = "&f"; + return; + } + + try (InputStream in = new FileInputStream(file)) { + Properties props = new Properties(); + props.load(new InputStreamReader(in, StandardCharsets.UTF_8)); + enabled = Boolean.parseBoolean(props.getProperty("broadcast.enabled", "true")); + requiredApiKey = props.getProperty("broadcast.api_key", "").trim(); + format = props.getProperty("broadcast.format", format).trim(); + if (format.isEmpty()) format = "%prefix% %message%"; + fallbackPrefix = props.getProperty("broadcast.prefix", fallbackPrefix).trim(); + fallbackPrefixColor = props.getProperty("broadcast.prefix-color", fallbackPrefixColor).trim(); + fallbackBracketColor = props.getProperty("broadcast.bracket-color", fallbackBracketColor).trim(); // Neu + fallbackMessageColor = props.getProperty("broadcast.message-color", fallbackMessageColor).trim(); + } catch (IOException e) { + plugin.getLogger().warning("[BroadcastModule] Fehler beim Laden von verify.properties: " + e.getMessage()); + } + } + + public boolean handleBroadcast(String sourceName, String message, String type, String apiKeyHeader, + String prefix, String prefixColor, String bracketColor, String messageColor) { + loadConfig(); + + if (!enabled) { + plugin.getLogger().info("[BroadcastModule] Broadcast abgelehnt: Modul ist deaktiviert."); + return false; + } + + if (requiredApiKey != null && !requiredApiKey.isEmpty()) { + if (apiKeyHeader == null || !requiredApiKey.equals(apiKeyHeader)) { + plugin.getLogger().warning("[BroadcastModule] Broadcast abgelehnt: API-Key fehlt oder inkorrekt."); + return false; + } + } + + if (message == null) message = ""; + if (sourceName == null || sourceName.isEmpty()) sourceName = "System"; + if (type == null) type = "global"; + + String usedPrefix = (prefix != null && !prefix.trim().isEmpty()) ? prefix.trim() : fallbackPrefix; + String usedPrefixColor = (prefixColor != null && !prefixColor.trim().isEmpty()) ? prefixColor.trim() : fallbackPrefixColor; + String usedBracketColor = (bracketColor != null && !bracketColor.trim().isEmpty()) ? bracketColor.trim() : fallbackBracketColor; // Neu + String usedMessageColor = (messageColor != null && !messageColor.trim().isEmpty()) ? messageColor.trim() : fallbackMessageColor; + + String prefixColorCode = normalizeColorCode(usedPrefixColor); + String bracketColorCode = normalizeColorCode(usedBracketColor); // Neu + String messageColorCode = normalizeColorCode(usedMessageColor); + + // --- KLAMMER LOGIK --- + String finalPrefix; + + // Wenn eine Klammerfarbe gesetzt ist, bauen wir den Prefix neu zusammen + // Format: [BracketColor][ [PrefixColor]Text [BracketColor]] + if (!bracketColorCode.isEmpty()) { + String textContent = usedPrefix; + // Entferne manuelle Klammern, falls der User [Broadcast] in das Textfeld geschrieben hat + if (textContent.startsWith("[")) textContent = textContent.substring(1); + if (textContent.endsWith("]")) textContent = textContent.substring(0, textContent.length() - 1); + + finalPrefix = bracketColorCode + "[" + prefixColorCode + textContent + bracketColorCode + "]" + ChatColor.RESET; + } else { + // Altes Verhalten: Ganzen String einfärben + finalPrefix = prefixColorCode + usedPrefix + ChatColor.RESET; + } + // --------------------- + + String coloredMessage = (messageColorCode.isEmpty() ? "" : messageColorCode) + message; + + String out = format.replace("%name%", sourceName) + .replace("%prefix%", finalPrefix) // Neu verwendete Variable + .replace("%prefixColored%", finalPrefix) // Fallback + .replace("%message%", message) + .replace("%messageColored%", coloredMessage) + .replace("%type%", type); + + if (!out.contains("%prefixColored%") && !out.contains("%messageColored%") && !out.contains("%prefix%") && !out.contains("%message%")) { + out = finalPrefix + " " + coloredMessage; + } + + TextComponent tc = new TextComponent(out); + int sent = 0; + for (ProxiedPlayer p : plugin.getProxy().getPlayers()) { + try { p.sendMessage(tc); sent++; } catch (Throwable ignored) {} + } + + plugin.getLogger().info("[BroadcastModule] Broadcast gesendet (Empfänger=" + sent + "): " + message); + return true; + } + + private String normalizeColorCode(String code) { + if (code == null) return ""; + code = code.trim(); + if (code.isEmpty()) return ""; + return code.contains("&") ? ChatColor.translateAlternateColorCodes('&', code) : code; + } + + private void saveSchedules() { + Properties props = new Properties(); + for (Map.Entry entry : scheduledByClientId.entrySet()) { + String id = entry.getKey(); + ScheduledBroadcast sb = entry.getValue(); + props.setProperty(id + ".nextRunMillis", String.valueOf(sb.nextRunMillis)); + props.setProperty(id + ".sourceName", sb.sourceName); + props.setProperty(id + ".message", sb.message); + props.setProperty(id + ".type", sb.type); + props.setProperty(id + ".prefix", sb.prefix); + props.setProperty(id + ".prefixColor", sb.prefixColor); + props.setProperty(id + ".bracketColor", sb.bracketColor); // Neu + props.setProperty(id + ".messageColor", sb.messageColor); + props.setProperty(id + ".recur", sb.recur); + } + + try (OutputStream out = new FileOutputStream(schedulesFile)) { + props.store(out, "PulseCast Scheduled Broadcasts"); + } catch (IOException e) { + plugin.getLogger().severe("[BroadcastModule] Konnte Schedules nicht speichern: " + e.getMessage()); + } + } + + private void loadSchedules() { + if (!schedulesFile.exists()) { + plugin.getLogger().info("[BroadcastModule] Keine bestehenden Schedules gefunden (Neustart)."); + return; + } + + Properties props = new Properties(); + try (InputStream in = new FileInputStream(schedulesFile)) { + props.load(in); + } catch (IOException e) { + plugin.getLogger().severe("[BroadcastModule] Konnte Schedules nicht laden: " + e.getMessage()); + return; + } + + Map loaded = new HashMap<>(); + for (String key : props.stringPropertyNames()) { + if (!key.contains(".")) continue; + String[] parts = key.split("\\."); + if (parts.length != 2) continue; + + String id = parts[0]; + String field = parts[1]; + String value = props.getProperty(key); + + ScheduledBroadcast sb = loaded.get(id); + if (sb == null) { + sb = new ScheduledBroadcast(id, 0, "", "", "", "", "", "", "", ""); // Ein leerer String mehr für Bracket + loaded.put(id, sb); + } + + switch (field) { + case "nextRunMillis": + try { sb.nextRunMillis = Long.parseLong(value); } catch (NumberFormatException ignored) {} + break; + case "sourceName": sb.sourceName = value; break; + case "message": sb.message = value; break; + case "type": sb.type = value; break; + case "prefix": sb.prefix = value; break; + case "prefixColor": sb.prefixColor = value; break; + case "bracketColor": sb.bracketColor = value; break; // Neu + case "messageColor": sb.messageColor = value; break; + case "recur": sb.recur = value; break; + } + } + scheduledByClientId.putAll(loaded); + plugin.getLogger().info("[BroadcastModule] " + loaded.size() + " geplante Broadcasts aus Datei wiederhergestellt."); + } + + public boolean scheduleBroadcast(long timestampMillis, String sourceName, String message, String type, + String apiKeyHeader, String prefix, String prefixColor, String bracketColor, String messageColor, + String recur, String clientScheduleId) { + loadConfig(); + + if (!enabled) { + plugin.getLogger().info("[BroadcastModule] schedule abgelehnt: Modul deaktiviert."); + return false; + } + + if (requiredApiKey != null && !requiredApiKey.isEmpty()) { + if (apiKeyHeader == null || !requiredApiKey.equals(apiKeyHeader)) { + plugin.getLogger().warning("[BroadcastModule] schedule abgelehnt: API-Key fehlt oder inkorrekt."); + return false; + } + } + + if (message == null) message = ""; + if (sourceName == null || sourceName.isEmpty()) sourceName = "System"; + if (type == null) type = "global"; + if (recur == null) recur = "none"; + + String id = (clientScheduleId != null && !clientScheduleId.trim().isEmpty()) ? clientScheduleId.trim() : UUID.randomUUID().toString(); + + long now = System.currentTimeMillis(); + String scheduledTimeStr = dateFormat.format(new Date(timestampMillis)); + + plugin.getLogger().info("[BroadcastModule] Neue geplante Nachricht registriert: " + id + " @ " + scheduledTimeStr); + + if (timestampMillis <= now) { + plugin.getLogger().warning("[BroadcastModule] Geplante Zeit liegt in der Vergangenheit -> sende sofort!"); + return handleBroadcast(sourceName, message, type, apiKeyHeader, prefix, prefixColor, bracketColor, messageColor); + } + + ScheduledBroadcast sb = new ScheduledBroadcast(id, timestampMillis, sourceName, message, type, prefix, prefixColor, bracketColor, messageColor, recur); + scheduledByClientId.put(id, sb); + saveSchedules(); + return true; + } + + public boolean cancelScheduled(String clientScheduleId) { + if (clientScheduleId == null || clientScheduleId.trim().isEmpty()) return false; + ScheduledBroadcast removed = scheduledByClientId.remove(clientScheduleId); + if (removed != null) { + plugin.getLogger().info("[BroadcastModule] Geplante Nachricht abgebrochen: id=" + clientScheduleId); + saveSchedules(); + return true; + } + return false; + } + + private void processScheduled() { + if (scheduledByClientId.isEmpty()) return; + + long now = System.currentTimeMillis(); + List toRemove = new ArrayList<>(); + boolean changed = false; + + for (Map.Entry entry : scheduledByClientId.entrySet()) { + ScheduledBroadcast sb = entry.getValue(); + + if (sb.nextRunMillis <= now) { + String timeStr = dateFormat.format(new Date(sb.nextRunMillis)); + plugin.getLogger().info("[BroadcastModule] ⏰ Sende geplante Nachricht (ID: " + entry.getKey() + ", Zeit: " + timeStr + ")"); + + handleBroadcast(sb.sourceName, sb.message, sb.type, "", sb.prefix, sb.prefixColor, sb.bracketColor, sb.messageColor); + + if (!"none".equalsIgnoreCase(sb.recur)) { + long next = computeNextMillis(sb.nextRunMillis, sb.recur); + if (next > 0) { + sb.nextRunMillis = next; + String nextTimeStr = dateFormat.format(new Date(next)); + plugin.getLogger().info("[BroadcastModule] Nächste Wiederholung (" + sb.recur + "): " + nextTimeStr); + changed = true; + } else { + toRemove.add(entry.getKey()); + changed = true; + } + } else { + toRemove.add(entry.getKey()); + changed = true; + } + } + } + + if (changed || !toRemove.isEmpty()) { + for (String k : toRemove) { + scheduledByClientId.remove(k); + plugin.getLogger().info("[BroadcastModule] Schedule entfernt: " + k); + } + saveSchedules(); + } + } + + private long computeNextMillis(long currentMillis, String recur) { + switch (recur.toLowerCase(Locale.ROOT)) { + case "hourly": return currentMillis + TimeUnit.HOURS.toMillis(1); + case "daily": return currentMillis + TimeUnit.DAYS.toMillis(1); + case "weekly": return currentMillis + TimeUnit.DAYS.toMillis(7); + default: return -1L; + } + } + + private static class ScheduledBroadcast { + final String clientId; + long nextRunMillis; + String sourceName; + String message; + String type; + String prefix; + String prefixColor; + String bracketColor; // Neu + String messageColor; + String recur; + + ScheduledBroadcast(String clientId, long nextRunMillis, String sourceName, String message, String type, + String prefix, String prefixColor, String bracketColor, String messageColor, String recur) { + this.clientId = clientId; + this.nextRunMillis = nextRunMillis; + this.sourceName = sourceName; + this.message = message; + this.type = type; + this.prefix = prefix; + this.prefixColor = prefixColor; + this.bracketColor = bracketColor; // Neu + this.messageColor = messageColor; + this.recur = (recur == null ? "none" : recur); + } + } } \ No newline at end of file