Dateien nach "src/main/java/net/viper/status/modules/network" hochladen
This commit is contained in:
@@ -0,0 +1,687 @@
|
||||
package net.viper.status.modules.network;
|
||||
|
||||
import com.sun.management.OperatingSystemMXBean;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.viper.status.module.Module;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Liefert erweiterte Proxy- und Systeminformationen für API und Ingame-Debug.
|
||||
*/
|
||||
public class NetworkInfoModule implements Module {
|
||||
|
||||
private static final String CONFIG_FILE_NAME = "network-guard.properties";
|
||||
|
||||
private Plugin plugin;
|
||||
private long startedAtMillis;
|
||||
|
||||
private boolean enabled = true;
|
||||
private boolean commandEnabled = true;
|
||||
private boolean includePlayerNames = false;
|
||||
|
||||
private boolean webhookEnabled = false;
|
||||
private String webhookUrl = "";
|
||||
private String webhookUsername = "StatusAPI";
|
||||
private String webhookThumbnailUrl = "";
|
||||
private boolean webhookNotifyStartStop = true;
|
||||
private String webhookEmbedMode = "detailed";
|
||||
private int webhookCheckSeconds = 30;
|
||||
private int alertMemoryPercent = 90;
|
||||
private int alertPlayerPercent = 95;
|
||||
private int alertCooldownSeconds = 300;
|
||||
private boolean attackNotificationsEnabled = true;
|
||||
private String attackApiKey = "";
|
||||
private String attackDefaultSource = "BetterBungee";
|
||||
private long lastMemoryAlertAt = 0L;
|
||||
private long lastPlayerAlertAt = 0L;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "NetworkInfoModule";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.startedAtMillis = System.currentTimeMillis();
|
||||
ensureModuleConfigExists();
|
||||
loadConfig();
|
||||
|
||||
if (!enabled) {
|
||||
plugin.getLogger().info("[NetworkInfoModule] deaktiviert via " + CONFIG_FILE_NAME + " (networkinfo.enabled=false)");
|
||||
return;
|
||||
}
|
||||
|
||||
if (commandEnabled) {
|
||||
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new NetInfoCommand());
|
||||
}
|
||||
|
||||
if (webhookEnabled && !webhookUrl.isEmpty()) {
|
||||
if (webhookNotifyStartStop) {
|
||||
if (isCompactEmbedMode()) {
|
||||
sendWebhookEmbed(
|
||||
webhookUrl,
|
||||
"✅ NetworkInfo gestartet",
|
||||
"Proxy: **" + ProxyServer.getInstance().getName() + "**\nÜberwachung und Webhook-Alerts sind jetzt aktiv.",
|
||||
0x2ECC71
|
||||
);
|
||||
} else {
|
||||
StringBuilder fields = new StringBuilder();
|
||||
appendEmbedField(fields, "Proxy", ProxyServer.getInstance().getName(), true);
|
||||
appendEmbedField(fields, "Modus", "Detailed", true);
|
||||
appendEmbedField(fields, "Check-Intervall", Math.max(10, webhookCheckSeconds) + "s", true);
|
||||
sendWebhookEmbed(
|
||||
webhookUrl,
|
||||
"✅ NetworkInfo gestartet",
|
||||
"Überwachung und Webhook-Alerts sind jetzt aktiv.",
|
||||
0x2ECC71,
|
||||
fields.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
int interval = Math.max(10, webhookCheckSeconds);
|
||||
ProxyServer.getInstance().getScheduler().schedule(plugin, this::evaluateAndSendAlerts, interval, interval, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
plugin.getLogger().info("[NetworkInfoModule] aktiviert. commandEnabled=" + commandEnabled + ", includePlayerNames=" + includePlayerNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable(Plugin plugin) {
|
||||
if (enabled && webhookEnabled && webhookNotifyStartStop && webhookUrl != null && !webhookUrl.isEmpty()) {
|
||||
if (isCompactEmbedMode()) {
|
||||
sendWebhookEmbed(
|
||||
webhookUrl,
|
||||
"🛑 NetworkInfo gestoppt",
|
||||
"Die NetworkInfo-Überwachung wurde gestoppt.\nKeine weiteren Auto-Alerts bis zum nächsten Start.",
|
||||
0xE74C3C
|
||||
);
|
||||
} else {
|
||||
StringBuilder fields = new StringBuilder();
|
||||
appendEmbedField(fields, "Proxy", ProxyServer.getInstance().getName(), true);
|
||||
appendEmbedField(fields, "Modus", "Detailed", true);
|
||||
appendEmbedField(fields, "Status", "Monitoring pausiert", false);
|
||||
sendWebhookEmbed(
|
||||
webhookUrl,
|
||||
"🛑 NetworkInfo gestoppt",
|
||||
"Die NetworkInfo-Überwachung wurde gestoppt.",
|
||||
0xE74C3C,
|
||||
fields.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public boolean isAttackNotificationsEnabled() {
|
||||
return enabled && attackNotificationsEnabled;
|
||||
}
|
||||
|
||||
public boolean isAttackApiKeyValid(String providedKey) {
|
||||
if (attackApiKey == null || attackApiKey.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return providedKey != null && attackApiKey.equals(providedKey.trim());
|
||||
}
|
||||
|
||||
public boolean sendAttackNotification(String eventType,
|
||||
Integer connectionsPerSecond,
|
||||
Integer blockedIps,
|
||||
Long blockedConnections,
|
||||
String source) {
|
||||
if (!isAttackNotificationsEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String usedSource = (source == null || source.trim().isEmpty()) ? attackDefaultSource : source.trim();
|
||||
String type = eventType == null ? "detected" : eventType.trim().toLowerCase(Locale.ROOT);
|
||||
|
||||
String title;
|
||||
String shortText;
|
||||
int color;
|
||||
if ("stopped".equals(type) || "mitigated".equals(type)) {
|
||||
title = "✅ Attack Stopped";
|
||||
shortText = "Traffic hat sich normalisiert.";
|
||||
color = 0x2ECC71;
|
||||
} else {
|
||||
title = "🚨 Attack Detected";
|
||||
shortText = "Ungewöhnlich hoher Verbindungs-Traffic erkannt.";
|
||||
color = 0xE74C3C;
|
||||
}
|
||||
|
||||
StringBuilder fields = new StringBuilder();
|
||||
appendEmbedField(fields, "Source", usedSource, true);
|
||||
appendEmbedField(fields, "Event", type.toUpperCase(Locale.ROOT), true);
|
||||
|
||||
if (connectionsPerSecond != null && connectionsPerSecond >= 0) {
|
||||
appendEmbedField(fields, "Connections / Second", String.valueOf(connectionsPerSecond), true);
|
||||
}
|
||||
if (blockedIps != null && blockedIps >= 0) {
|
||||
appendEmbedField(fields, "Blocked IPs", String.valueOf(blockedIps), true);
|
||||
}
|
||||
if (blockedConnections != null && blockedConnections >= 0L) {
|
||||
appendEmbedField(fields, "Blocked Connections", String.valueOf(blockedConnections), true);
|
||||
}
|
||||
|
||||
sendWebhookAttackEmbed(webhookUrl, title, shortText, color, fields.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
public Map<String, Object> buildSnapshot() {
|
||||
Map<String, Object> out = new LinkedHashMap<String, Object>();
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long uptimeMs = Math.max(0L, now - startedAtMillis);
|
||||
|
||||
Runtime rt = Runtime.getRuntime();
|
||||
long maxMemory = rt.maxMemory();
|
||||
long totalMemory = rt.totalMemory();
|
||||
long freeMemory = rt.freeMemory();
|
||||
long usedMemory = totalMemory - freeMemory;
|
||||
|
||||
Map<String, Object> memory = new LinkedHashMap<String, Object>();
|
||||
memory.put("used_mb", bytesToMb(usedMemory));
|
||||
memory.put("free_mb", bytesToMb(freeMemory));
|
||||
memory.put("total_mb", bytesToMb(totalMemory));
|
||||
memory.put("max_mb", bytesToMb(maxMemory));
|
||||
memory.put("usage_percent", percent(usedMemory, Math.max(1L, maxMemory)));
|
||||
|
||||
int onlinePlayers = ProxyServer.getInstance().getPlayers().size();
|
||||
int maxPlayers = ProxyServer.getInstance().getConfig().getPlayerLimit();
|
||||
|
||||
Map<String, Object> ping = buildPingSummary(ProxyServer.getInstance().getPlayers());
|
||||
|
||||
Map<String, Object> players = new LinkedHashMap<String, Object>();
|
||||
players.put("online", onlinePlayers);
|
||||
players.put("max", maxPlayers);
|
||||
players.put("occupancy_percent", percent(onlinePlayers, Math.max(1, maxPlayers)));
|
||||
players.put("bedrock_online", countBedrockPlayers());
|
||||
players.put("ping", ping);
|
||||
|
||||
List<Map<String, Object>> backend = buildBackendDistribution();
|
||||
|
||||
Map<String, Object> system = new LinkedHashMap<String, Object>();
|
||||
system.put("java_version", System.getProperty("java.version"));
|
||||
system.put("java_vendor", System.getProperty("java.vendor"));
|
||||
system.put("os_name", System.getProperty("os.name"));
|
||||
system.put("os_arch", System.getProperty("os.arch"));
|
||||
system.put("available_processors", Runtime.getRuntime().availableProcessors());
|
||||
system.put("system_load_percent", getSystemLoadPercent());
|
||||
|
||||
out.put("enabled", true);
|
||||
out.put("timestamp_unix", now / 1000L);
|
||||
out.put("uptime_seconds", uptimeMs / 1000L);
|
||||
out.put("uptime_human", formatDuration(uptimeMs));
|
||||
out.put("players", players);
|
||||
out.put("backend_servers", backend);
|
||||
out.put("memory", memory);
|
||||
out.put("system", system);
|
||||
out.put("features", buildFeatureSummary());
|
||||
|
||||
if (includePlayerNames) {
|
||||
List<String> names = new ArrayList<String>();
|
||||
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
||||
names.add(p.getName());
|
||||
}
|
||||
out.put("player_names", names);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildPingSummary(Collection<ProxiedPlayer> players) {
|
||||
Map<String, Object> ping = new LinkedHashMap<String, Object>();
|
||||
if (players.isEmpty()) {
|
||||
ping.put("avg_ms", 0);
|
||||
ping.put("min_ms", 0);
|
||||
ping.put("max_ms", 0);
|
||||
return ping;
|
||||
}
|
||||
|
||||
long sum = 0L;
|
||||
int min = Integer.MAX_VALUE;
|
||||
int max = Integer.MIN_VALUE;
|
||||
|
||||
for (ProxiedPlayer p : players) {
|
||||
int ms = Math.max(0, p.getPing());
|
||||
sum += ms;
|
||||
if (ms < min) min = ms;
|
||||
if (ms > max) max = ms;
|
||||
}
|
||||
|
||||
ping.put("avg_ms", Math.round((double) sum / (double) players.size()));
|
||||
ping.put("min_ms", min == Integer.MAX_VALUE ? 0 : min);
|
||||
ping.put("max_ms", max == Integer.MIN_VALUE ? 0 : max);
|
||||
return ping;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> buildBackendDistribution() {
|
||||
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
|
||||
for (Map.Entry<String, ServerInfo> entry : ProxyServer.getInstance().getServers().entrySet()) {
|
||||
ServerInfo info = entry.getValue();
|
||||
|
||||
Map<String, Object> row = new LinkedHashMap<String, Object>();
|
||||
row.put("name", entry.getKey());
|
||||
row.put("online_players", info.getPlayers().size());
|
||||
row.put("address", String.valueOf(info.getAddress()));
|
||||
list.add(row);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private Map<String, Object> buildFeatureSummary() {
|
||||
Map<String, Object> features = new LinkedHashMap<String, Object>();
|
||||
features.put("luckperms", ProxyServer.getInstance().getPluginManager().getPlugin("LuckPerms") != null);
|
||||
features.put("floodgate", isFloodgateAvailable());
|
||||
return features;
|
||||
}
|
||||
|
||||
private boolean isFloodgateAvailable() {
|
||||
try {
|
||||
Class.forName("org.geysermc.floodgate.api.FloodgateApi");
|
||||
return true;
|
||||
} catch (Throwable ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int countBedrockPlayers() {
|
||||
try {
|
||||
Class<?> apiClass = Class.forName("org.geysermc.floodgate.api.FloodgateApi");
|
||||
Object api = apiClass.getMethod("getInstance").invoke(null);
|
||||
int count = 0;
|
||||
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
||||
Boolean isBedrock = (Boolean) api.getClass().getMethod("isBedrockPlayer", UUID.class).invoke(api, p.getUniqueId());
|
||||
if (Boolean.TRUE.equals(isBedrock)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
} catch (Throwable ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private Integer getSystemLoadPercent() {
|
||||
try {
|
||||
java.lang.management.OperatingSystemMXBean bean = ManagementFactory.getOperatingSystemMXBean();
|
||||
if (bean instanceof OperatingSystemMXBean) {
|
||||
double load = ((OperatingSystemMXBean) bean).getSystemCpuLoad();
|
||||
if (load >= 0D) {
|
||||
return (int) Math.round(load * 100D);
|
||||
}
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private int bytesToMb(long bytes) {
|
||||
return (int) (bytes / (1024L * 1024L));
|
||||
}
|
||||
|
||||
private int percent(long value, long max) {
|
||||
if (max <= 0L) return 0;
|
||||
return (int) Math.min(100L, Math.round((value * 100.0D) / max));
|
||||
}
|
||||
|
||||
private String formatDuration(long ms) {
|
||||
long totalSeconds = ms / 1000L;
|
||||
long days = totalSeconds / 86400L;
|
||||
long hours = (totalSeconds % 86400L) / 3600L;
|
||||
long minutes = (totalSeconds % 3600L) / 60L;
|
||||
long seconds = totalSeconds % 60L;
|
||||
return String.format(Locale.ROOT, "%dd %02dh %02dm %02ds", days, hours, minutes, seconds);
|
||||
}
|
||||
|
||||
private void loadConfig() {
|
||||
File file = new File(plugin.getDataFolder(), CONFIG_FILE_NAME);
|
||||
if (!file.exists()) {
|
||||
enabled = true;
|
||||
commandEnabled = true;
|
||||
includePlayerNames = false;
|
||||
webhookEnabled = false;
|
||||
webhookUrl = "";
|
||||
webhookUsername = "StatusAPI";
|
||||
webhookThumbnailUrl = "";
|
||||
webhookNotifyStartStop = true;
|
||||
webhookEmbedMode = "detailed";
|
||||
webhookCheckSeconds = 30;
|
||||
alertMemoryPercent = 90;
|
||||
alertPlayerPercent = 95;
|
||||
alertCooldownSeconds = 300;
|
||||
attackNotificationsEnabled = true;
|
||||
attackApiKey = "";
|
||||
attackDefaultSource = "BetterBungee";
|
||||
return;
|
||||
}
|
||||
|
||||
Properties props = new Properties();
|
||||
try (FileInputStream in = new FileInputStream(file)) {
|
||||
props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||
enabled = Boolean.parseBoolean(props.getProperty("networkinfo.enabled", "true"));
|
||||
commandEnabled = Boolean.parseBoolean(props.getProperty("networkinfo.command.enabled", "true"));
|
||||
includePlayerNames = Boolean.parseBoolean(props.getProperty("networkinfo.include_player_names", "false"));
|
||||
|
||||
webhookEnabled = Boolean.parseBoolean(props.getProperty("networkinfo.webhook.enabled", "false"));
|
||||
webhookUrl = props.getProperty("networkinfo.webhook.url", "").trim();
|
||||
webhookUsername = props.getProperty("networkinfo.webhook.username", "StatusAPI").trim();
|
||||
webhookThumbnailUrl = props.getProperty("networkinfo.webhook.thumbnail_url", "").trim();
|
||||
webhookNotifyStartStop = Boolean.parseBoolean(props.getProperty("networkinfo.webhook.notify_start_stop", "true"));
|
||||
webhookEmbedMode = props.getProperty("networkinfo.webhook.embed_mode", "detailed").trim();
|
||||
webhookCheckSeconds = parseInt(props.getProperty("networkinfo.webhook.check_seconds", "30"), 30);
|
||||
alertMemoryPercent = parseInt(props.getProperty("networkinfo.alert.memory_percent", "90"), 90);
|
||||
alertPlayerPercent = parseInt(props.getProperty("networkinfo.alert.player_percent", "95"), 95);
|
||||
alertCooldownSeconds = parseInt(props.getProperty("networkinfo.alert.cooldown_seconds", "300"), 300);
|
||||
attackNotificationsEnabled = Boolean.parseBoolean(props.getProperty("networkinfo.attack.enabled", "true"));
|
||||
attackApiKey = props.getProperty("networkinfo.attack.api_key", "").trim();
|
||||
attackDefaultSource = props.getProperty("networkinfo.attack.source", "BetterBungee").trim();
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[NetworkInfoModule] Fehler beim Laden von " + CONFIG_FILE_NAME + ": " + e.getMessage());
|
||||
enabled = true;
|
||||
commandEnabled = true;
|
||||
includePlayerNames = false;
|
||||
webhookEnabled = false;
|
||||
webhookUrl = "";
|
||||
webhookUsername = "StatusAPI";
|
||||
webhookThumbnailUrl = "";
|
||||
webhookNotifyStartStop = true;
|
||||
webhookEmbedMode = "detailed";
|
||||
webhookCheckSeconds = 30;
|
||||
alertMemoryPercent = 90;
|
||||
alertPlayerPercent = 95;
|
||||
alertCooldownSeconds = 300;
|
||||
attackNotificationsEnabled = true;
|
||||
attackApiKey = "";
|
||||
attackDefaultSource = "BetterBungee";
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureModuleConfigExists() {
|
||||
File target = new File(plugin.getDataFolder(), CONFIG_FILE_NAME);
|
||||
if (target.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!plugin.getDataFolder().exists()) {
|
||||
plugin.getDataFolder().mkdirs();
|
||||
}
|
||||
|
||||
try (InputStream in = plugin.getResourceAsStream(CONFIG_FILE_NAME);
|
||||
OutputStream out = new FileOutputStream(target)) {
|
||||
if (in == null) {
|
||||
plugin.getLogger().warning("[NetworkInfoModule] Standarddatei " + CONFIG_FILE_NAME + " nicht im JAR gefunden.");
|
||||
return;
|
||||
}
|
||||
byte[] buffer = new byte[4096];
|
||||
int read;
|
||||
while ((read = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, read);
|
||||
}
|
||||
plugin.getLogger().info("[NetworkInfoModule] " + CONFIG_FILE_NAME + " wurde erstellt.");
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[NetworkInfoModule] Konnte " + CONFIG_FILE_NAME + " nicht erstellen: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void evaluateAndSendAlerts() {
|
||||
if (!enabled || !webhookEnabled || webhookUrl == null || webhookUrl.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
Map<String, Object> snapshot = buildSnapshot();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> memory = (Map<String, Object>) snapshot.get("memory");
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> players = (Map<String, Object>) snapshot.get("players");
|
||||
|
||||
int memoryPercent = toInt(memory.get("usage_percent"));
|
||||
int playerPercent = toInt(players.get("occupancy_percent"));
|
||||
|
||||
if (memoryPercent >= Math.max(1, alertMemoryPercent) && canSend(lastMemoryAlertAt, now)) {
|
||||
lastMemoryAlertAt = now;
|
||||
if (isCompactEmbedMode()) {
|
||||
sendWebhookEmbed(
|
||||
webhookUrl,
|
||||
"⚠️ Hohe RAM-Auslastung",
|
||||
"Aktuell: **" + memoryPercent + "%**\nVerbrauch: **" + memory.get("used_mb") + " MB / " + memory.get("max_mb") + " MB**\nSchwelle: **" + alertMemoryPercent + "%**",
|
||||
0xF39C12
|
||||
);
|
||||
} else {
|
||||
StringBuilder fields = new StringBuilder();
|
||||
appendEmbedField(fields, "RAM-Nutzung", memoryPercent + "%", true);
|
||||
appendEmbedField(fields, "Schwelle", alertMemoryPercent + "%", true);
|
||||
appendEmbedField(fields, "Verbrauch", memory.get("used_mb") + " MB / " + memory.get("max_mb") + " MB", false);
|
||||
sendWebhookEmbed(
|
||||
webhookUrl,
|
||||
"⚠️ Hohe RAM-Auslastung",
|
||||
"Ein Schwellwert wurde überschritten.",
|
||||
0xF39C12,
|
||||
fields.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (playerPercent >= Math.max(1, alertPlayerPercent) && canSend(lastPlayerAlertAt, now)) {
|
||||
lastPlayerAlertAt = now;
|
||||
if (isCompactEmbedMode()) {
|
||||
sendWebhookEmbed(
|
||||
webhookUrl,
|
||||
"📈 Hohe Spieler-Auslastung",
|
||||
"Auslastung: **" + playerPercent + "%**\nSpieler: **" + players.get("online") + "/" + players.get("max") + "**\nSchwelle: **" + alertPlayerPercent + "%**",
|
||||
0x3498DB
|
||||
);
|
||||
} else {
|
||||
StringBuilder fields = new StringBuilder();
|
||||
appendEmbedField(fields, "Auslastung", playerPercent + "%", true);
|
||||
appendEmbedField(fields, "Schwelle", alertPlayerPercent + "%", true);
|
||||
appendEmbedField(fields, "Spieler", players.get("online") + "/" + players.get("max"), true);
|
||||
sendWebhookEmbed(
|
||||
webhookUrl,
|
||||
"📈 Hohe Spieler-Auslastung",
|
||||
"Die Spielerlast ist aktuell sehr hoch.",
|
||||
0x3498DB,
|
||||
fields.toString()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isCompactEmbedMode() {
|
||||
return "compact".equalsIgnoreCase(webhookEmbedMode == null ? "" : webhookEmbedMode.trim());
|
||||
}
|
||||
|
||||
private boolean canSend(long lastSentAt, long now) {
|
||||
long cooldownMs = Math.max(10, alertCooldownSeconds) * 1000L;
|
||||
return (now - lastSentAt) >= cooldownMs;
|
||||
}
|
||||
|
||||
private int parseInt(String s, int fallback) {
|
||||
try {
|
||||
return Integer.parseInt(s == null ? "" : s.trim());
|
||||
} catch (Exception ignored) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
private int toInt(Object o) {
|
||||
if (o instanceof Number) {
|
||||
return ((Number) o).intValue();
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(String.valueOf(o));
|
||||
} catch (Exception ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendWebhookEmbed(String targetWebhookUrl, String title, String description, int color) {
|
||||
sendWebhookEmbed(targetWebhookUrl, title, description, color, null);
|
||||
}
|
||||
|
||||
private void sendWebhookEmbed(String targetWebhookUrl, String title, String description, int color, String fieldsJson) {
|
||||
if (targetWebhookUrl == null || targetWebhookUrl.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder embed = new StringBuilder();
|
||||
embed.append("{\"username\":\"").append(escapeJson(webhookUsername)).append("\",")
|
||||
.append("\"embeds\":[{\"title\":\"").append(escapeJson(title)).append("\",")
|
||||
.append("\"description\":\"").append(escapeJson(description)).append("\",")
|
||||
.append("\"color\":").append(color).append(",");
|
||||
|
||||
if (fieldsJson != null && !fieldsJson.trim().isEmpty()) {
|
||||
embed.append("\"fields\":[").append(fieldsJson).append("],");
|
||||
}
|
||||
|
||||
embed.append("\"footer\":{\"text\":\"StatusPulse • NetworkInfo\"},")
|
||||
.append("\"timestamp\":\"").append(Instant.now().toString()).append("\"");
|
||||
|
||||
if (webhookThumbnailUrl != null && !webhookThumbnailUrl.isEmpty()) {
|
||||
embed.append(",\"thumbnail\":{\"url\":\"").append(escapeJson(webhookThumbnailUrl)).append("\"}");
|
||||
}
|
||||
|
||||
embed.append("}]}");
|
||||
postWebhookPayload(targetWebhookUrl, embed.toString());
|
||||
}
|
||||
|
||||
private void sendWebhookAttackEmbed(String targetWebhookUrl,
|
||||
String title,
|
||||
String description,
|
||||
int color,
|
||||
String fieldsJson) {
|
||||
if (targetWebhookUrl == null || targetWebhookUrl.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder embed = new StringBuilder();
|
||||
embed.append("{\"username\":\"").append(escapeJson(webhookUsername)).append("\",")
|
||||
.append("\"embeds\":[{\"title\":\"").append(escapeJson(title)).append("\",")
|
||||
.append("\"description\":\"").append(escapeJson(description)).append("\",")
|
||||
.append("\"color\":").append(color).append(",")
|
||||
.append("\"fields\":[").append(fieldsJson).append("],")
|
||||
.append("\"footer\":{\"text\":\"StatusPulse • Network Guard\"},")
|
||||
.append("\"timestamp\":\"").append(Instant.now().toString()).append("\"");
|
||||
|
||||
if (webhookThumbnailUrl != null && !webhookThumbnailUrl.isEmpty()) {
|
||||
embed.append(",\"thumbnail\":{\"url\":\"").append(escapeJson(webhookThumbnailUrl)).append("\"}");
|
||||
}
|
||||
|
||||
embed.append("}]}");
|
||||
postWebhookPayload(targetWebhookUrl, embed.toString());
|
||||
}
|
||||
|
||||
private void appendEmbedField(StringBuilder out, String name, String value, boolean inline) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (out.length() > 0) {
|
||||
out.append(",");
|
||||
}
|
||||
out.append("{\"name\":\"").append(escapeJson(name)).append("\",")
|
||||
.append("\"value\":\"").append(escapeJson(value.trim())).append("\",")
|
||||
.append("\"inline\":").append(inline ? "true" : "false")
|
||||
.append("}");
|
||||
}
|
||||
|
||||
private void postWebhookPayload(String targetWebhookUrl, String payload) {
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> {
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
byte[] bytes = payload.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
conn = (HttpURLConnection) new URL(targetWebhookUrl).openConnection();
|
||||
conn.setRequestMethod("POST");
|
||||
conn.setConnectTimeout(5000);
|
||||
conn.setReadTimeout(8000);
|
||||
conn.setDoOutput(true);
|
||||
conn.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
|
||||
conn.setRequestProperty("Content-Length", String.valueOf(bytes.length));
|
||||
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write(bytes);
|
||||
}
|
||||
|
||||
int code = conn.getResponseCode();
|
||||
if (code >= 400) {
|
||||
plugin.getLogger().warning("[NetworkInfoModule] Discord Webhook HTTP " + code);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLogger().warning("[NetworkInfoModule] Discord Webhook Fehler: " + e.getMessage());
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private String escapeJson(String s) {
|
||||
if (s == null) return "";
|
||||
return s.replace("\\", "\\\\")
|
||||
.replace("\"", "\\\"")
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r");
|
||||
}
|
||||
|
||||
private class NetInfoCommand extends Command {
|
||||
|
||||
NetInfoCommand() {
|
||||
super("netinfo", "statusapi.netinfo");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (!enabled) {
|
||||
sender.sendMessage(ChatColor.RED + "NetworkInfoModule ist deaktiviert.");
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> snapshot = buildSnapshot();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> players = (Map<String, Object>) snapshot.get("players");
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> memory = (Map<String, Object>) snapshot.get("memory");
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> ping = (Map<String, Object>) players.get("ping");
|
||||
|
||||
sender.sendMessage(ChatColor.GOLD + "----- StatusAPI NetworkInfo -----");
|
||||
sender.sendMessage(ChatColor.YELLOW + "Uptime: " + ChatColor.WHITE + snapshot.get("uptime_human"));
|
||||
sender.sendMessage(ChatColor.YELLOW + "Spieler: " + ChatColor.WHITE + players.get("online") + "/" + players.get("max") + ChatColor.GRAY + " (Bedrock: " + players.get("bedrock_online") + ")");
|
||||
sender.sendMessage(ChatColor.YELLOW + "Ping: " + ChatColor.WHITE + "avg " + ping.get("avg_ms") + "ms, min " + ping.get("min_ms") + "ms, max " + ping.get("max_ms") + "ms");
|
||||
sender.sendMessage(ChatColor.YELLOW + "RAM: " + ChatColor.WHITE + memory.get("used_mb") + "MB / " + memory.get("max_mb") + "MB" + ChatColor.GRAY + " (" + memory.get("usage_percent") + "%)");
|
||||
sender.sendMessage(ChatColor.YELLOW + "Backends: " + ChatColor.WHITE + ((List<?>) snapshot.get("backend_servers")).size());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user