Delete src/main/java/net/viper/status/modules/forum/ForumBridgeModule.java via Git Manager GUI

This commit is contained in:
2026-05-24 19:43:51 +00:00
parent 6e49d3b226
commit f47a5a0729

View File

@@ -1,349 +0,0 @@
package net.viper.status.modules.forum;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.ChatColor;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.CommandSender;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.ProxyServer;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.ClickEvent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.HoverEvent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.chat.TextComponent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.event.PostLoginEvent;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Command;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Listener;
import net.viper.status.StatusAPI;
import net.md_5.bungee.api.plugin.Plugin;
import net.viper.status.StatusAPI;
import net.md_5.bungee.event.EventHandler;
import net.viper.status.module.Module;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
/**
* ForumBridgeModule: Verbindet das WP Business Forum mit dem Minecraft-Server.
*
* Fix #13: extractJsonString() gibt jetzt immer einen leeren String statt null zurück.
* Alle Aufrufer müssen nicht mehr auf null prüfen, was NullPointerExceptions verhindert.
*/
public class ForumBridgeModule implements Module, Listener {
private Plugin plugin;
private ForumNotifStorage storage;
private boolean enabled = true;
private String wpBaseUrl = "";
private String apiSecret = "";
private int loginDelaySeconds = 3;
@Override
public String getName() { return "ForumBridgeModule"; }
@Override
public void onEnable(Plugin plugin) {
this.plugin = plugin;
loadConfig(plugin);
if (!enabled) { StatusAPI.debugLog(plugin, "ForumBridgeModule ist deaktiviert."); return; }
storage = new ForumNotifStorage(plugin.getDataFolder(), plugin.getLogger());
storage.load();
plugin.getProxy().getPluginManager().registerListener(plugin, this);
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new ForumLinkCommand());
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new ForumCommand());
plugin.getProxy().getScheduler().schedule(plugin, () -> {
try { storage.save(); } catch (Exception e) { plugin.getLogger().warning("ForumBridge Auto-Save Fehler: " + e.getMessage()); }
}, 10, 10, TimeUnit.MINUTES);
plugin.getProxy().getScheduler().schedule(plugin, () -> storage.purgeOld(30), 1, 24, TimeUnit.HOURS);
plugin.getLogger().fine("ForumBridgeModule aktiviert.");
}
@Override
public void onDisable(Plugin plugin) {
if (storage != null) { storage.save(); StatusAPI.debugLog(plugin, "Forum-Benachrichtigungen gespeichert."); }
}
private void loadConfig(Plugin plugin) {
try {
Properties props = new Properties();
File configFile = new File(plugin.getDataFolder(), "verify.properties");
if (configFile.exists()) {
try (FileInputStream fis = new FileInputStream(configFile)) { props.load(fis); }
}
this.enabled = !"false".equalsIgnoreCase(props.getProperty("forum.enabled", "true"));
this.wpBaseUrl = props.getProperty("forum.wp_url", props.getProperty("wp_verify_url", ""));
this.apiSecret = props.getProperty("forum.api_secret", "");
this.loginDelaySeconds = parseInt(props.getProperty("forum.login_delay_seconds", "3"), 3);
} catch (Exception e) {
plugin.getLogger().warning("Fehler beim Laden der ForumBridge-Config: " + e.getMessage());
}
}
private int parseInt(String s, int def) { try { return Integer.parseInt(s); } catch (Exception e) { return def; } }
// ===== HTTP HANDLER =====
public String handleNotify(String body, String apiKeyHeader) {
if (!apiSecret.isEmpty() && !apiSecret.equals(apiKeyHeader)) {
return "{\"success\":false,\"error\":\"unauthorized\"}";
}
// FIX #13: extractJsonString gibt "" statt null → kein NullPointerException möglich
String playerUuid = extractJsonString(body, "player_uuid");
String type = extractJsonString(body, "type");
String title = extractJsonString(body, "title");
String author = extractJsonString(body, "author");
String url = extractJsonString(body, "url");
if (playerUuid.isEmpty()) return "{\"success\":false,\"error\":\"missing_player_uuid\"}";
java.util.UUID uuid;
try { uuid = java.util.UUID.fromString(playerUuid); }
catch (Exception e) { return "{\"success\":false,\"error\":\"invalid_uuid\"}"; }
if ("thread".equalsIgnoreCase(type) && title.toLowerCase().contains("umfrage")) type = "poll";
if (type.isEmpty()) type = "reply";
// Alle Werte sind garantiert nicht null (extractJsonString gibt "" zurück)
ForumNotification notification = new ForumNotification(uuid, type, title, author, url);
ProxiedPlayer online = ProxyServer.getInstance().getPlayer(uuid);
if (online != null && online.isConnected()) {
deliverNotification(online, notification);
notification.setDelivered(true);
return "{\"success\":true,\"delivered\":true}";
}
storage.add(notification);
return "{\"success\":true,\"delivered\":false}";
}
public String handleUnlink(String body, String apiKeyHeader) {
if (!apiSecret.isEmpty() && !apiSecret.equals(apiKeyHeader)) return "{\"success\":false,\"error\":\"unauthorized\"}";
return "{\"success\":true}";
}
public String handleStatus() {
String version = "unknown";
try { if (plugin.getDescription() != null) version = plugin.getDescription().getVersion(); } catch (Exception ignored) {}
return "{\"success\":true,\"module\":\"ForumBridgeModule\",\"version\":\"" + version + "\"}";
}
// ===== NOTIFICATION =====
private void deliverNotification(ProxiedPlayer player, ForumNotification notif) {
String color = notif.getTypeColor();
String label = notif.getTypeLabel();
player.sendMessage(new TextComponent("§8§m "));
player.sendMessage(new TextComponent("§6§l✉ Forum §8» " + color + label));
if (!notif.getTitle().isEmpty()) player.sendMessage(new TextComponent("§7 " + notif.getTitle()));
if (!notif.getAuthor().isEmpty()) player.sendMessage(new TextComponent("§7 von §f" + notif.getAuthor()));
if (!notif.getUrl().isEmpty()) {
TextComponent link = new TextComponent("§a ➜ Im Forum ansehen");
link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, notif.getUrl()));
link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
new ComponentBuilder("§7Klicke um den Beitrag im Forum zu öffnen").create()));
player.sendMessage(link);
}
player.sendMessage(new TextComponent("§8§m "));
}
private void deliverPending(ProxiedPlayer player) {
List<ForumNotification> pending = storage.getPending(player.getUniqueId());
if (pending.isEmpty()) return;
int count = pending.size();
if (count > 3) {
player.sendMessage(new TextComponent("§8§m "));
player.sendMessage(new TextComponent("§6§l✉ Forum §8» §fDu hast §e" + count + " §fneue Benachrichtigungen!"));
player.sendMessage(new TextComponent("§7 Tippe §e/forum §7um sie anzuzeigen."));
player.sendMessage(new TextComponent("§8§m "));
} else {
for (ForumNotification n : pending) deliverNotification(player, n);
}
storage.markAllDelivered(player.getUniqueId());
storage.clearDelivered(player.getUniqueId());
}
@EventHandler
public void onJoin(PostLoginEvent e) {
ProxiedPlayer player = e.getPlayer();
plugin.getProxy().getScheduler().schedule(plugin, () -> {
if (player.isConnected()) deliverPending(player);
}, loginDelaySeconds, TimeUnit.SECONDS);
}
// ===== COMMANDS =====
private class ForumLinkCommand extends Command {
public ForumLinkCommand() { super("forumlink", null, "fl"); }
@Override
public void execute(CommandSender sender, String[] args) {
if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(new TextComponent("§cNur Spieler können diesen Befehl benutzen.")); return; }
ProxiedPlayer p = (ProxiedPlayer) sender;
if (args.length != 1) {
p.sendMessage(new TextComponent("§eBenutzung: §f/forumlink <token>"));
p.sendMessage(new TextComponent("§7Den Token erhältst du in deinem Forum-Profil unter §fMinecraft-Verknüpfung§7."));
return;
}
String token = args[0].trim().toUpperCase();
if (wpBaseUrl.isEmpty()) { p.sendMessage(new TextComponent("§cForum-Verknüpfung ist nicht konfiguriert.")); return; }
p.sendMessage(new TextComponent("§7Überprüfe Token..."));
plugin.getProxy().getScheduler().runAsync(plugin, () -> {
try {
String endpoint = wpBaseUrl + "/wp-json/mc-bridge/v1/verify-link";
String payload = "{\"token\":\"" + escapeJson(token) + "\","
+ "\"mc_uuid\":\"" + p.getUniqueId() + "\","
+ "\"mc_name\":\"" + escapeJson(p.getName()) + "\"}";
HttpURLConnection conn = (HttpURLConnection) new URL(endpoint).openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setConnectTimeout(5000);
conn.setReadTimeout(7000);
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
if (!apiSecret.isEmpty()) conn.setRequestProperty("X-Api-Key", apiSecret);
Charset utf8 = Charset.forName("UTF-8");
try (OutputStream os = conn.getOutputStream()) { os.write(payload.getBytes(utf8)); }
int code = conn.getResponseCode();
String resp = code >= 200 && code < 300
? streamToString(conn.getInputStream(), utf8)
: streamToString(conn.getErrorStream(), utf8);
if (resp != null && resp.contains("\"success\":true")) {
String displayName = extractJsonString(resp, "display_name");
String username = extractJsonString(resp, "username");
String show = !displayName.isEmpty() ? displayName : username;
p.sendMessage(new TextComponent("§8§m "));
p.sendMessage(new TextComponent("§a§l✓ §fForum-Account erfolgreich verknüpft!"));
if (!show.isEmpty()) p.sendMessage(new TextComponent("§7 Forum-User: §f" + show));
p.sendMessage(new TextComponent("§7 Du erhältst jetzt Ingame-Benachrichtigungen."));
p.sendMessage(new TextComponent("§8§m "));
} else {
String error = extractJsonString(resp, "error");
String message = extractJsonString(resp, "message");
if ("token_expired".equals(error)) p.sendMessage(new TextComponent("§c✗ Der Token ist abgelaufen."));
else if ("uuid_already_linked".equals(error)) p.sendMessage(new TextComponent("§c✗ " + (!message.isEmpty() ? message : "Diese UUID ist bereits verknüpft.")));
else if ("invalid_token".equals(error)) p.sendMessage(new TextComponent("§c✗ Ungültiger Token."));
else p.sendMessage(new TextComponent("§c✗ Verknüpfung fehlgeschlagen: " + (!error.isEmpty() ? error : "Unbekannter Fehler")));
}
} catch (Exception ex) {
p.sendMessage(new TextComponent("§c✗ Fehler bei der Verbindung zum Forum."));
plugin.getLogger().warning("ForumLink Fehler: " + ex.getMessage());
}
});
}
}
private class ForumCommand extends Command {
public ForumCommand() { super("forum"); }
@Override
public void execute(CommandSender sender, String[] args) {
if (!(sender instanceof ProxiedPlayer)) { sender.sendMessage(new TextComponent("§cNur Spieler können diesen Befehl benutzen.")); return; }
ProxiedPlayer p = (ProxiedPlayer) sender;
List<ForumNotification> pending = storage.getPending(p.getUniqueId());
if (pending.isEmpty()) {
p.sendMessage(new TextComponent("§7Keine neuen Forum-Benachrichtigungen."));
if (!wpBaseUrl.isEmpty()) {
TextComponent link = new TextComponent("§a➜ Forum öffnen");
link.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, wpBaseUrl));
link.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("§7Klicke um das Forum zu öffnen").create()));
p.sendMessage(link);
}
return;
}
p.sendMessage(new TextComponent("§8§m "));
p.sendMessage(new TextComponent("§6§l✉ Forum-Benachrichtigungen §8(§f" + pending.size() + "§8)"));
p.sendMessage(new TextComponent(""));
int shown = 0;
for (ForumNotification n : pending) {
if (shown >= 10) { p.sendMessage(new TextComponent("§7 ... und " + (pending.size() - 10) + " weitere")); break; }
String color = n.getTypeColor();
TextComponent line = new TextComponent(color + "" + n.getTypeLabel() + "§7: ");
TextComponent detail = new TextComponent(!n.getTitle().isEmpty() ? "§f" + n.getTitle() : "§fvon " + n.getAuthor());
if (!n.getUrl().isEmpty()) {
detail.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, n.getUrl()));
detail.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder("§7Klicke zum Öffnen").create()));
}
line.addExtra(detail);
p.sendMessage(line);
shown++;
}
p.sendMessage(new TextComponent(""));
p.sendMessage(new TextComponent("§8§m "));
storage.markAllDelivered(p.getUniqueId());
storage.clearDelivered(p.getUniqueId());
}
}
// ===== HELPER =====
public ForumNotifStorage getStorage() { return storage; }
/**
* FIX #13: Gibt immer einen leeren String zurück, niemals null.
* Verhindert NullPointerExceptions in allen Aufrufern.
*/
private static String extractJsonString(String json, String key) {
if (json == null || key == null) return "";
String search = "\"" + key + "\"";
int idx = json.indexOf(search);
if (idx < 0) return "";
int colon = json.indexOf(':', idx + search.length());
if (colon < 0) return "";
int i = colon + 1;
while (i < json.length() && Character.isWhitespace(json.charAt(i))) i++;
if (i >= json.length()) return "";
char c = json.charAt(i);
if (c == '"') {
i++;
StringBuilder sb = new StringBuilder();
boolean escape = false;
while (i < json.length()) {
char ch = json.charAt(i++);
if (escape) { sb.append(ch); escape = false; }
else { if (ch == '\\') escape = true; else if (ch == '"') break; else sb.append(ch); }
}
return sb.toString();
}
return "";
}
private static String escapeJson(String s) {
if (s == null) return "";
return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");
}
private static String streamToString(InputStream in, Charset charset) throws IOException {
if (in == null) return "";
try (BufferedReader br = new BufferedReader(new InputStreamReader(in, charset))) {
StringBuilder sb = new StringBuilder(); String line;
while ((line = br.readLine()) != null) sb.append(line);
return sb.toString();
}
}
}