8 Commits
1.0 ... 1.1

6 changed files with 702 additions and 420 deletions

18
LICENSE
View File

@@ -1,18 +0,0 @@
MIT License
Copyright (c) 2025 M_Viper
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

144
pom.xml
View File

@@ -1,64 +1,80 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"> http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>de.viper</groupId> <groupId>de.viper</groupId>
<artifactId>globalchat</artifactId> <artifactId>globalchat</artifactId>
<version>1.3-SNAPSHOT</version> <version>1.1</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>GlobalChat</name> <name>GlobalChat</name>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
</properties> </properties>
<dependencies> <repositories>
<!-- BungeeCord API für Minecraft 1.21.x --> <!-- LuckPerms Repository -->
<dependency> <repository>
<groupId>net.md-5</groupId> <id>luck-repo</id>
<artifactId>bungeecord-api</artifactId> <url>https://repo.lucko.me/</url>
<version>1.21-R0.1-SNAPSHOT</version> </repository>
<scope>provided</scope> </repositories>
</dependency>
</dependencies> <dependencies>
<!-- BungeeCord API -->
<build> <dependency>
<plugins> <groupId>net.md-5</groupId>
<!-- Compiler Plugin --> <artifactId>bungeecord-api</artifactId>
<plugin> <version>1.21-R0.1-SNAPSHOT</version>
<groupId>org.apache.maven.plugins</groupId> <scope>provided</scope>
<artifactId>maven-compiler-plugin</artifactId> </dependency>
<version>3.8.1</version>
<configuration> <!-- LuckPerms API -->
<source>1.8</source> <dependency>
<target>1.8</target> <groupId>net.luckperms</groupId>
</configuration> <artifactId>api</artifactId>
</plugin> <version>5.4</version>
<scope>provided</scope>
<!-- Shade Plugin für fertiges JAR --> </dependency>
<plugin> </dependencies>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <build>
<version>3.2.4</version> <plugins>
<executions> <!-- Compiler Plugin -->
<execution> <plugin>
<phase>package</phase> <groupId>org.apache.maven.plugins</groupId>
<goals> <artifactId>maven-compiler-plugin</artifactId>
<goal>shade</goal> <version>3.8.1</version>
</goals> <configuration>
<configuration> <source>1.8</source>
<createDependencyReducedPom>false</createDependencyReducedPom> <target>1.8</target>
</configuration> </configuration>
</execution> </plugin>
</executions>
</plugin> <!-- Shade Plugin -->
</plugins> <plugin>
</build> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
</project> <version>3.2.4</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -1,299 +1,575 @@
package de.viper.globalchat; package de.viper.globalchat;
import net.md_5.bungee.api.CommandSender; import net.luckperms.api.LuckPerms;
import net.md_5.bungee.api.chat.ComponentBuilder; import net.luckperms.api.LuckPermsProvider;
import net.md_5.bungee.api.chat.HoverEvent; import net.luckperms.api.model.user.User;
import net.md_5.bungee.api.chat.TextComponent; import net.luckperms.api.cacheddata.CachedMetaData;
import net.md_5.bungee.api.chat.HoverEvent.Action;
import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.event.ChatEvent; import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.plugin.Plugin; import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.api.chat.HoverEvent.Action;
import net.md_5.bungee.api.config.ServerInfo;
import java.io.*; import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.nio.file.*; import net.md_5.bungee.api.event.ChatEvent;
import java.text.SimpleDateFormat; import net.md_5.bungee.api.event.ServerConnectEvent;
import java.util.*; import net.md_5.bungee.api.event.ServerSwitchEvent;
import java.util.concurrent.ConcurrentHashMap; import net.md_5.bungee.api.plugin.Command;
import java.util.regex.Pattern; import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
public class GlobalChat extends Plugin implements Listener { import net.md_5.bungee.event.EventHandler;
private List<String> badWords = new ArrayList<>(); import java.io.*;
private File logFolder; import java.nio.file.*;
private boolean chatMuted = false; // Globaler Mute-Status import java.text.SimpleDateFormat;
import java.util.*;
// Spieler-UUID -> isOp (vom Spigot gemeldet) import java.util.concurrent.ConcurrentHashMap;
private final Map<UUID, Boolean> playerIsOp = new ConcurrentHashMap<>(); import java.util.regex.Pattern;
// Letzte Support-Kontakte für /reply /**
private final Map<UUID, UUID> lastSupportContact = new ConcurrentHashMap<>(); * GlobalChat - Bungee Plugin
* - Filter (filter.yml)
@Override * - Logs (plugins/GlobalChat/logs/YYYY-MM-DD.log)
public void onEnable() { * - /support, /reply, /info
getProxy().getPluginManager().registerListener(this, this); * - Prefix/Suffix via LuckPerms (Proxy-side)
* - ServerSwitch announcement (sends message to chat when a player switches from one server to another)
// Filter laden * - Attempts to suppress Spigot join/quit messages during server switch
loadFilter(); */
public class GlobalChat extends Plugin implements Listener {
// Log-Ordner erstellen
logFolder = new File(getDataFolder(), "logs"); private static final String CHANNEL_CONTROL = "global:control";
if (!logFolder.exists()) logFolder.mkdirs();
cleanupOldLogs(); private List<String> badWords = new ArrayList<>();
private File logFolder;
// Befehle registrieren (Support & Info sind für alle verfügbar) private boolean chatMuted = false;
getProxy().getPluginManager().registerCommand(this, new ReloadCommand());
getProxy().getPluginManager().registerCommand(this, new MuteCommand()); // Optional: falls Spigot-Server OP-Info per PluginMessage sendet
getProxy().getPluginManager().registerCommand(this, new SupportCommand()); private final Map<UUID, Boolean> playerIsOp = new ConcurrentHashMap<>();
getProxy().getPluginManager().registerCommand(this, new ReplyCommand()); // Letzte Support-Kontakte (staff UUID -> target UUID)
getProxy().getPluginManager().registerCommand(this, new InfoCommand()); private final Map<UUID, UUID> lastSupportContact = new ConcurrentHashMap<>();
// Tracking für unterdrückte Join-/Quit-Nachrichten
getLogger().info("§aGlobalChat mit Zensur, Logs, Reload, Mute, Support, Reply & Info aktiviert!"); private final Set<UUID> suppressJoinQuit = ConcurrentHashMap.newKeySet();
}
// ===========================
@Override // Neu: Welcome-Nachrichten
public void onDisable() { // ===========================
getLogger().info("§cGlobalChat deaktiviert!"); private List<String> welcomeMessages = new ArrayList<>();
}
private void loadWelcomeMessages() {
// =========================== File file = new File(getDataFolder(), "welcome.yml");
// Chatfilter & Logging if (!file.exists()) {
// =========================== try {
@EventHandler getDataFolder().mkdirs();
public void onChat(ChatEvent e) { file.createNewFile();
if (!(e.getSender() instanceof ProxiedPlayer)) return; try (PrintWriter out = new PrintWriter(file)) {
if (e.isCommand()) return; out.println("welcome-messages:");
out.println(" - \"&aWillkommen, %player%! Viel Spaß auf unserem Server!\"");
ProxiedPlayer player = (ProxiedPlayer) e.getSender(); out.println(" - \"&aHey %player%, schön dich hier zu sehen! Los geht's!\"");
String originalMsg = e.getMessage(); out.println(" - \"&a%player%, dein Abenteuer beginnt jetzt! Viel Spaß!\"");
String censoredMsg = originalMsg; out.println(" - \"&aWillkommen an Bord, %player%! Entdecke den Server!\"");
out.println(" - \"&a%player%, herzlich willkommen! Lass uns loslegen!\"");
// Globaler Mute check }
if (chatMuted && !player.hasPermission("globalchat.bypass")) { } catch (IOException e) {
player.sendMessage(new TextComponent("§cDer globale Chat ist derzeit deaktiviert!")); e.printStackTrace();
e.setCancelled(true); }
return; }
}
try {
// Badword-Zensur List<String> lines = Files.readAllLines(file.toPath());
for (String bad : badWords) { welcomeMessages.clear();
if (bad == null || bad.trim().isEmpty()) continue; for (String line : lines) {
censoredMsg = censoredMsg.replaceAll("(?i)" + Pattern.quote(bad), repeat("*", bad.length())); line = line.trim();
} if (line.startsWith("-")) welcomeMessages.add(line.substring(1).trim());
}
e.setCancelled(true); getLogger().info("§eGeladene Welcome-Nachrichten: " + welcomeMessages.size());
} catch (IOException e) {
String serverName = player.getServer().getInfo().getName(); e.printStackTrace();
String chatOut = "§7[" + serverName + "] §b" + player.getName() + "§f: " + censoredMsg; }
}
for (ProxiedPlayer p : getProxy().getPlayers()) {
p.sendMessage(new TextComponent(chatOut)); private void sendRandomWelcomeMessage(ProxiedPlayer player) {
} if (welcomeMessages.isEmpty()) return;
logMessage("[" + serverName + "] " + player.getName() + ": " + originalMsg); Random rand = new Random();
} String message = welcomeMessages.get(rand.nextInt(welcomeMessages.size()));
message = message.replace("%player%", player.getName());
private String repeat(String str, int count) { message = ChatColor.translateAlternateColorCodes('&', message);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) sb.append(str); player.sendMessage(new TextComponent(message));
return sb.toString(); }
}
@Override
// =========================== public void onEnable() {
// Filter laden // Plugin channel registrieren (für Steuer-Nachrichten an Spigot)
// =========================== try {
private void loadFilter() { getProxy().registerChannel(CHANNEL_CONTROL);
File file = new File(getDataFolder(), "filter.yml"); } catch (Throwable ignored) {
if (!file.exists()) { getLogger().warning("Konnte Kanal " + CHANNEL_CONTROL + " nicht registrieren.");
try { }
getDataFolder().mkdirs();
file.createNewFile(); getProxy().getPluginManager().registerListener(this, this);
try (PrintWriter out = new PrintWriter(file)) {
out.println("badwords:"); loadFilter();
out.println(" - arsch"); loadWelcomeMessages();
out.println(" - hurensohn");
out.println(" - scheiße"); logFolder = new File(getDataFolder(), "logs");
} if (!logFolder.exists()) logFolder.mkdirs();
} catch (IOException e) { cleanupOldLogs();
e.printStackTrace();
} // Befehle registrieren
} getProxy().getPluginManager().registerCommand(this, new ReloadCommand());
getProxy().getPluginManager().registerCommand(this, new MuteCommand());
try { getProxy().getPluginManager().registerCommand(this, new SupportCommand());
List<String> lines = Files.readAllLines(file.toPath()); getProxy().getPluginManager().registerCommand(this, new ReplyCommand());
badWords.clear(); getProxy().getPluginManager().registerCommand(this, new InfoCommand());
for (String line : lines) {
line = line.trim(); getLogger().info("§aGlobalChat aktiviert (Zensur, Logs, Reload, Mute, Support, Reply & Info)!");
if (line.startsWith("-")) badWords.add(line.substring(1).trim()); }
}
getLogger().info("§eGeladene Filter-Wörter: " + badWords.size()); @Override
} catch (IOException e) { public void onDisable() {
e.printStackTrace(); getLogger().info("§cGlobalChat deaktiviert!");
} try {
} getProxy().unregisterChannel(CHANNEL_CONTROL);
} catch (Throwable ignored) {
private void cleanupOldLogs() { getLogger().warning("Konnte Kanal " + CHANNEL_CONTROL + " nicht deregistrieren.");
File[] files = logFolder.listFiles(); }
if (files == null) return; }
long now = System.currentTimeMillis(); // ===========================
long sevenDays = 1000L * 60 * 60 * 24 * 7; // Chatfilter & Global-Chat
// ===========================
for (File f : files) { @EventHandler
if (now - f.lastModified() > sevenDays) { public void onChat(ChatEvent e) {
f.delete(); if (!(e.getSender() instanceof ProxiedPlayer)) return;
} if (e.isCommand()) return;
}
} ProxiedPlayer player = (ProxiedPlayer) e.getSender();
String originalMsg = e.getMessage();
private void logMessage(String message) {
String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date()); // Debugging: Logge alle Chat-Nachrichten, um zu prüfen, ob Join-/Quit-Nachrichten ankommen
File logFile = new File(logFolder, date + ".log"); getLogger().info("ChatEvent: Spieler=" + player.getName() + ", Nachricht=" + originalMsg);
try (BufferedWriter bw = new BufferedWriter(new FileWriter(logFile, true))) { // Versuche, Join-/Quit-Nachrichten zu filtern
String time = new SimpleDateFormat("HH:mm:ss").format(new Date()); if (suppressJoinQuit.contains(player.getUniqueId()) &&
bw.write("[" + time + "] " + message); (originalMsg.contains("joined the Game") || originalMsg.contains("left the Game"))) {
bw.newLine(); getLogger().info("Unterdrücke Join-/Quit-Nachricht für " + player.getName() + ": " + originalMsg);
} catch (IOException e) { e.setCancelled(true);
e.printStackTrace(); return;
} }
}
// Globaler Mute
// =========================== if (chatMuted && !player.hasPermission("globalchat.bypass")) {
// Staff-Check player.sendMessage(new TextComponent("§cDer globale Chat ist derzeit deaktiviert!"));
// =========================== e.setCancelled(true);
private boolean isStaff(ProxiedPlayer p) { return;
if (p == null) return false; }
Boolean reportedOp = playerIsOp.get(p.getUniqueId());
if (reportedOp != null && reportedOp) return true; // Badword-Zensur (nur für Chat-Ausgabe; Log bleibt unzensiert)
String censoredMsg = originalMsg;
if (p.hasPermission("team")) return true; for (String bad : badWords) {
if (p.hasPermission("bungeecord.admin")) return true; if (bad == null || bad.trim().isEmpty()) continue;
if (p.hasPermission("globalchat.op")) return true; censoredMsg = censoredMsg.replaceAll("(?i)" + Pattern.quote(bad), repeat("*", bad.length()));
if (p.hasPermission("*")) return true; }
if (p.hasPermission("bungeecord.command.alert")) return true; e.setCancelled(true);
if (p.hasPermission("bungeecord.command.reload")) return true;
if (p.hasPermission("bungeecord.command.kick")) return true; String serverName = player.getServer().getInfo().getName();
if (p.hasPermission("bungeecord.command.send")) return true;
if (p.hasPermission("bungeecord.command.perms")) return true; // Prefix/Suffix holen (LuckPerms Proxy-side)
String[] ps = getPrefixSuffix(player);
return false; String prefix = ps[0] == null ? "" : ps[0].trim();
} String suffix = ps[1] == null ? "" : ps[1].trim();
// =========================== // Entscheide: zeige entweder Prefix (falls vorhanden) oder, falls kein Prefix, das Suffix.
// Commands String displayTag = "";
// =========================== if (!prefix.isEmpty()) {
public class ReloadCommand extends Command { displayTag = prefix;
public ReloadCommand() { super("globalreload", "globalchat.reload"); } } else if (!suffix.isEmpty()) {
@Override displayTag = suffix;
public void execute(CommandSender sender, String[] args) { }
loadFilter();
sender.sendMessage(new TextComponent("§aFilter wurde neu geladen!")); // saubere Leerzeichen um displayTag
} if (!displayTag.isEmpty() && !displayTag.endsWith(" ")) displayTag = displayTag + " ";
}
// Format: [Server] <tag>Name: Nachricht
public class MuteCommand extends Command { StringBuilder out = new StringBuilder();
public MuteCommand() { super("globalmute", "globalchat.mute"); } out.append("§7[").append(serverName).append("] ");
@Override if (!displayTag.isEmpty()) out.append(displayTag);
public void execute(CommandSender sender, String[] args) { out.append(player.getName());
chatMuted = !chatMuted; out.append("§f: ").append(censoredMsg);
String status = chatMuted ? "§caktiviert" : "§aaufgehoben";
for (ProxiedPlayer p : getProxy().getPlayers()) { String chatOut = out.toString();
p.sendMessage(new TextComponent("§7[GlobalChat] §eDer globale Chat Mute wurde " + status + "§e!"));
} for (ProxiedPlayer p : getProxy().getPlayers()) {
getLogger().info("GlobalMute wurde " + (chatMuted ? "aktiviert" : "deaktiviert") + "."); p.sendMessage(new TextComponent(chatOut));
} }
}
// Log unzensiert (ohne color codes)
public class SupportCommand extends Command { String logEntry = "[" + serverName + "] " +
// kein Permission-Parameter -> für alle verfügbar (displayTag.isEmpty() ? "" : stripColor(displayTag) + " ") +
public SupportCommand() { super("support"); } player.getName() +
@Override ": " + originalMsg;
public void execute(CommandSender sender, String[] args) { logMessage(logEntry);
if (!(sender instanceof ProxiedPlayer)) { }
sender.sendMessage(new TextComponent("§cNur Spieler können Support-Nachrichten senden."));
return; // ===========================
} // Server connect (pre-switch suppression)
// ===========================
ProxiedPlayer player = (ProxiedPlayer) sender; @EventHandler
if (args.length == 0) { public void onServerConnect(ServerConnectEvent e) {
player.sendMessage(new TextComponent("§cBitte eine Nachricht angeben: /support <Nachricht>")); if (e.isCancelled()) return;
return;
} ProxiedPlayer player = e.getPlayer();
ServerInfo target = e.getTarget();
String msg = String.join(" ", args); ServerInfo from = player.getServer() != null ? player.getServer().getInfo() : null;
String serverName = player.getServer().getInfo().getName();
if (from == null || from.equals(target)) return;
TextComponent supportMsg = new TextComponent("§7[Support] §b" + player.getName() +
" §7vom Server §e" + serverName + " §7: §f" + msg); // Markiere Spieler für Unterdrückung
suppressJoinQuit.add(player.getUniqueId());
// Hover-Text getLogger().info("Markiert " + player.getName() + " für Join-/Quit-Unterdrückung");
supportMsg.setHoverEvent(new HoverEvent(Action.SHOW_TEXT,
new ComponentBuilder("Klicke, um /reply " + player.getName() + " zu schreiben").create())); // Suppress quit on old server
try {
// Klick-Event (füllt die Chatbox mit /reply <Name> ) sendSuppressJoinQuit(from, player.getUniqueId());
supportMsg.setClickEvent(new net.md_5.bungee.api.chat.ClickEvent( getLogger().info("Sent suppress quit message for " + player.getName() + " to server " + from.getName());
net.md_5.bungee.api.chat.ClickEvent.Action.SUGGEST_COMMAND, } catch (Throwable ex) {
"/reply " + player.getName() + " " getLogger().warning("Fehler beim Senden der Quit-Unterdrückung an " + from.getName() + ": " + ex.getMessage());
)); }
for (ProxiedPlayer p : getProxy().getPlayers()) { // Suppress join on new server
if (isStaff(p)) { try {
p.sendMessage(supportMsg); sendSuppressJoinQuit(target, player.getUniqueId());
lastSupportContact.put(p.getUniqueId(), player.getUniqueId()); getLogger().info("Sent suppress join message for " + player.getName() + " to server " + target.getName());
} } catch (Throwable ex) {
} getLogger().warning("Fehler beim Senden der Join-Unterdrückung an " + target.getName() + ": " + ex.getMessage());
}
player.sendMessage(new TextComponent("§aDeine Support-Nachricht wurde gesendet."));
logMessage("[Support][" + serverName + "] " + player.getName() + ": " + msg); // Entferne Unterdrückung nach kurzer Zeit (2 Sekunden)
} getProxy().getScheduler().schedule(this, () -> {
} suppressJoinQuit.remove(player.getUniqueId());
getLogger().info("Entfernte Unterdrückung für " + player.getName());
public class ReplyCommand extends Command { }, 2, java.util.concurrent.TimeUnit.SECONDS);
public ReplyCommand() { super("reply"); } }
@Override
public void execute(CommandSender sender, String[] args) { // ===========================
if (!(sender instanceof ProxiedPlayer)) return; // Server switch announcement
// ===========================
ProxiedPlayer staff = (ProxiedPlayer) sender; @EventHandler
UUID targetId = lastSupportContact.get(staff.getUniqueId()); public void onServerSwitch(ServerSwitchEvent e) {
if (targetId == null) { ProxiedPlayer player = e.getPlayer();
staff.sendMessage(new TextComponent("§cKein Spieler zum Antworten gefunden.")); ServerInfo from = e.getFrom(); // kann null sein beim ersten Join
return; ServerInfo to = player.getServer() != null ? player.getServer().getInfo() : null;
}
if (to == null) return;
if (args.length == 0) {
staff.sendMessage(new TextComponent("§cBitte eine Nachricht angeben.")); // Wenn from == null -> erster Join, keine Nachricht senden
return; if (from == null) return;
}
// Nur senden, wenn Server wirklich gewechselt wurde
ProxiedPlayer target = getProxy().getPlayer(targetId); if (from.getName().equalsIgnoreCase(to.getName())) return;
if (target == null) {
staff.sendMessage(new TextComponent("§cSpieler ist nicht online.")); String fromName = from.getName();
return; String toName = to.getName();
}
// Prefix/Suffix für Anzeige (show either prefix or suffix)
String msg = String.join(" ", args); String[] ps = getPrefixSuffix(player);
target.sendMessage(new TextComponent("§7[Reply von §b" + staff.getName() + "§7]: §f" + msg)); String prefix = ps[0] == null ? "" : ps[0].trim();
staff.sendMessage(new TextComponent("§aDeine Nachricht wurde an §b" + target.getName() + "§a gesendet.")); String suffix = ps[1] == null ? "" : ps[1].trim();
}
} String displayTag = "";
if (!prefix.isEmpty()) displayTag = prefix;
public class InfoCommand extends Command { else if (!suffix.isEmpty()) displayTag = suffix;
// kein Permission-Parameter -> für alle verfügbar
public InfoCommand() { super("info"); } if (!displayTag.isEmpty() && !displayTag.endsWith(" ")) displayTag = displayTag + " ";
@Override StringBuilder msg = new StringBuilder();
public void execute(CommandSender sender, String[] args) { msg.append("§7[").append(toName).append("] ");
sender.sendMessage(new TextComponent("§8§m------------------------------")); if (!displayTag.isEmpty()) msg.append(displayTag);
sender.sendMessage(new TextComponent("§6§lGlobalChat Info")); msg.append(player.getName());
sender.sendMessage(new TextComponent("§ePlugin-Name: §b" + getDescription().getName())); msg.append(" §7hat den Server gewechselt: §e")
sender.sendMessage(new TextComponent("§eVersion: §b" + getDescription().getVersion())); .append(fromName).append(" §7→ §e").append(toName).append("§7.");
sender.sendMessage(new TextComponent("§eErsteller: §bM_Viper"));
sender.sendMessage(new TextComponent("§8§m------------------------------")); String finalMsg = msg.toString();
}
} // An alle Spieler senden (sichtbar im Chat)
} for (ProxiedPlayer p : getProxy().getPlayers()) {
p.sendMessage(new TextComponent(finalMsg));
}
// Log (ohne Farb-Codes)
String logEntry = "[" + toName + "] " +
(displayTag.isEmpty() ? "" : stripColor(displayTag) + " ") +
player.getName() + " hat den Server gewechselt: " + fromName + " -> " + toName + ".";
logMessage(logEntry);
}
/**
* Sendet an den Ziel-Server eine PluginMessage mit dem Befehl, Join/Quit für den Spieler zu unterdrücken.
* Format: writeUTF("suppress"); writeUTF(playerUUID)
*/
private void sendSuppressJoinQuit(ServerInfo server, UUID playerId) {
if (server == null) return;
try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos)) {
out.writeUTF("suppress");
out.writeUTF(playerId.toString());
server.sendData(CHANNEL_CONTROL, baos.toByteArray());
} catch (IOException ex) {
getLogger().warning("Fehler beim Senden der suppress-Nachricht an " + server.getName() + ": " + ex.getMessage());
}
}
// Java8-kompatible repeat
private String repeat(String str, int count) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) sb.append(str);
return sb.toString();
}
// ===========================
// Prefix/Suffix via LuckPerms (Proxy-side)
// ===========================
private String[] getPrefixSuffix(ProxiedPlayer player) {
String prefix = "";
String suffix = "";
try {
LuckPerms lp = LuckPermsProvider.get();
if (lp != null) {
// Versuche schnell aus dem Cache zu holen
User user = lp.getUserManager().getUser(player.getUniqueId());
// Falls nicht im Cache: synchrones Laden (Fallback) — kann blockieren, aber stellt Prefix sicher
if (user == null) {
try {
user = lp.getUserManager().loadUser(player.getUniqueId()).join();
} catch (Exception ignored) {
user = null;
}
}
if (user != null) {
CachedMetaData meta = user.getCachedData().getMetaData();
if (meta != null) {
String p = meta.getPrefix();
String s = meta.getSuffix();
if (p != null) prefix = p;
if (s != null) suffix = s;
}
}
}
} catch (Throwable ignored) {
// LuckPerms nicht vorhanden oder andere Fehler -> kein Prefix/Suffix
}
// Farbcodes übersetzen (& -> §)
if (prefix != null && !prefix.isEmpty()) prefix = ChatColor.translateAlternateColorCodes('&', prefix);
if (suffix != null && !suffix.isEmpty()) suffix = ChatColor.translateAlternateColorCodes('&', suffix);
if (prefix == null) prefix = "";
if (suffix == null) suffix = "";
return new String[]{prefix, suffix};
}
// Entfernt Bungee-Farbcodes aus Strings (für saubere Logs)
private String stripColor(String s) {
if (s == null) return "";
return ChatColor.stripColor(s);
}
// ===========================
// Filter einlesen
// ===========================
private void loadFilter() {
File file = new File(getDataFolder(), "filter.yml");
if (!file.exists()) {
try {
getDataFolder().mkdirs();
file.createNewFile();
try (PrintWriter out = new PrintWriter(file)) {
out.println("badwords:");
out.println(" - arsch");
out.println(" - hurensohn");
out.println(" - scheiße");
}
} catch (IOException e) {
e.printStackTrace();
}
}
try {
List<String> lines = Files.readAllLines(file.toPath());
badWords.clear();
for (String line : lines) {
line = line.trim();
if (line.startsWith("-")) badWords.add(line.substring(1).trim());
}
getLogger().info("§eGeladene Filter-Wörter: " + badWords.size());
} catch (IOException e) {
e.printStackTrace();
}
}
// ===========================
// Logs aufräumen / schreiben
// ===========================
private void cleanupOldLogs() {
File[] files = logFolder.listFiles();
if (files == null) return;
long now = System.currentTimeMillis();
long sevenDays = 1000L * 60 * 60 * 24 * 7;
for (File f : files) {
if (now - f.lastModified() > sevenDays) {
f.delete();
}
}
}
private void logMessage(String message) {
String date = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
File logFile = new File(logFolder, date + ".log");
try (BufferedWriter bw = new BufferedWriter(new FileWriter(logFile, true))) {
String time = new SimpleDateFormat("HH:mm:ss").format(new Date());
bw.write("[" + time + "] " + message);
bw.newLine();
} catch (IOException e) {
e.printStackTrace();
}
}
// ===========================
// Staff-Check (Op / team perms)
// ===========================
private boolean isStaff(ProxiedPlayer p) {
if (p == null) return false;
Boolean reportedOp = playerIsOp.get(p.getUniqueId());
if (reportedOp != null && reportedOp) return true;
if (p.hasPermission("team")) return true;
if (p.hasPermission("bungeecord.admin")) return true;
if (p.hasPermission("globalchat.op")) return true;
if (p.hasPermission("*")) return true;
if (p.hasPermission("bungeecord.command.alert")) return true;
if (p.hasPermission("bungeecord.command.reload")) return true;
if (p.hasPermission("bungeecord.command.kick")) return true;
if (p.hasPermission("bungeecord.command.send")) return true;
if (p.hasPermission("bungeecord.command.perms")) return true;
return false;
}
// ===========================
// Commands
// ===========================
public class ReloadCommand extends Command {
public ReloadCommand() { super("globalreload", "globalchat.reload"); }
@Override
public void execute(CommandSender sender, String[] args) {
loadFilter();
sender.sendMessage(new TextComponent("§aFilter wurde neu geladen!"));
}
}
public class MuteCommand extends Command {
public MuteCommand() { super("globalmute", "globalchat.mute"); }
@Override
public void execute(CommandSender sender, String[] args) {
chatMuted = !chatMuted;
String status = chatMuted ? "§caktiviert" : "§aaufgehoben";
for (ProxiedPlayer p : getProxy().getPlayers()) {
p.sendMessage(new TextComponent("§7[GlobalChat] §eDer globale Chat Mute wurde " + status + "§e!"));
}
getLogger().info("GlobalMute wurde " + (chatMuted ? "aktiviert" : "deaktiviert") + ".");
}
}
// /support <msg>
public class SupportCommand extends Command {
public SupportCommand() { super("support"); }
@Override
public void execute(CommandSender sender, String[] args) {
if (!(sender instanceof ProxiedPlayer)) {
sender.sendMessage(new TextComponent("§cNur Spieler können Support-Nachrichten senden."));
return;
}
ProxiedPlayer player = (ProxiedPlayer) sender;
if (args.length == 0) {
player.sendMessage(new TextComponent("§cBitte eine Nachricht angeben: /support <Nachricht>"));
return;
}
String msg = String.join(" ", args);
String serverName = player.getServer().getInfo().getName();
TextComponent supportMsg = new TextComponent("§7[Support] §b" + player.getName() + " §7vom Server §e" + serverName + " §7: §f" + msg);
supportMsg.setHoverEvent(new HoverEvent(Action.SHOW_TEXT, new ComponentBuilder("Klicke, um /reply " + player.getName() + " zu schreiben").create()));
supportMsg.setClickEvent(new net.md_5.bungee.api.chat.ClickEvent(net.md_5.bungee.api.chat.ClickEvent.Action.SUGGEST_COMMAND, "/reply " + player.getName() + " "));
// an alle Staff
for (ProxiedPlayer p : getProxy().getPlayers()) {
if (isStaff(p)) {
p.sendMessage(supportMsg);
lastSupportContact.put(p.getUniqueId(), player.getUniqueId());
}
}
player.sendMessage(new TextComponent("§aDeine Support-Nachricht wurde gesendet."));
logMessage("[Support][" + serverName + "] " + player.getName() + ": " + msg);
}
}
// /reply <msg>
public class ReplyCommand extends Command {
public ReplyCommand() { super("reply"); }
@Override
public void execute(CommandSender sender, String[] args) {
if (!(sender instanceof ProxiedPlayer)) return;
ProxiedPlayer staff = (ProxiedPlayer) sender;
UUID targetId = lastSupportContact.get(staff.getUniqueId());
if (targetId == null) {
staff.sendMessage(new TextComponent("§cKein Spieler zum Antworten gefunden."));
return;
}
if (args.length == 0) {
staff.sendMessage(new TextComponent("§cBitte eine Nachricht angeben."));
return;
}
ProxiedPlayer target = getProxy().getPlayer(targetId);
if (target == null) {
staff.sendMessage(new TextComponent("§cSpieler ist nicht online."));
return;
}
String msg = String.join(" ", args);
target.sendMessage(new TextComponent("§7[Reply von §b" + staff.getName() + "§7]: §f" + msg));
staff.sendMessage(new TextComponent("§aDeine Nachricht wurde an §b" + target.getName() + "§a gesendet."));
}
}
// /info
public class InfoCommand extends Command {
public InfoCommand() { super("info"); }
@Override
public void execute(CommandSender sender, String[] args) {
sender.sendMessage(new TextComponent("§8§m------------------------------"));
sender.sendMessage(new TextComponent("§6§lGlobalChat Info"));
sender.sendMessage(new TextComponent("§ePlugin-Name: §b" + getDescription().getName()));
sender.sendMessage(new TextComponent("§eVersion: §b" + getDescription().getVersion()));
sender.sendMessage(new TextComponent("§eErsteller: §bM_Viper"));
sender.sendMessage(new TextComponent("§8§m------------------------------"));
}
}
}

View File

@@ -1,33 +1,35 @@
name: GlobalChat name: GlobalChat
main: de.viper.globalchat.GlobalChat main: de.viper.globalchat.GlobalChat
version: 1.0 version: 1.1
author: M_Viper author: M_Viper
commands: depend: [LuckPerms]
globalreload:
description: Lädt den Filter neu commands:
usage: /globalreload globalreload:
permission: globalchat.reload description: Lädt den Filter neu
globalmute: usage: /globalreload
description: Schaltet den globalen Chat an/aus permission: globalchat.reload
usage: /globalmute globalmute:
permission: globalchat.mute description: Schaltet den globalen Chat an/aus
support: usage: /globalmute
description: Sendet eine Support-Nachricht an das Team permission: globalchat.mute
usage: /support <Nachricht> support:
reply: description: Sendet eine Support-Nachricht an das Team
description: Antwortet auf eine Support-Nachricht usage: /support <Nachricht>
usage: /reply <Nachricht> reply:
info: description: Antwortet auf eine Support-Nachricht
description: Zeigt Plugin-Informationen an usage: /reply <Nachricht>
usage: /info info:
description: Zeigt Plugin-Informationen an
permissions: usage: /info
globalchat.reload:
description: Erlaubt das Neuladen des Filters permissions:
default: op globalchat.reload:
globalchat.mute: description: Erlaubt das Neuladen des Filters
description: Erlaubt das Aktivieren/Deaktivieren des globalen Mutes default: op
default: op globalchat.mute:
globalchat.bypass: description: Erlaubt das Aktivieren/Deaktivieren des globalen Mutes
description: Umgeht den globalen Mute default: op
default: op globalchat.bypass:
description: Umgeht den globalen Mute
default: op

View File

@@ -1,6 +1,6 @@
name: GlobalChatSpigot name: GlobalChatSpigot
main: de.viper.globalchat.GlobalChatSpigot main: de.viper.globalchat.GlobalChatSpigot
version: 1.0 version: 1.1
api-version: 1.13 api-version: 1.13
description: Spigot helper for GlobalChat Bungee (sends coords, handles teleport requests) description: Spigot helper for GlobalChat Bungee (sends coords, handles teleport requests)
authors: [M_Viper] authors: [M_Viper]

View File

@@ -0,0 +1,6 @@
welcome-messages:
- "&aWillkommen, %player%! Viel Spaß auf unserem Server!"
- "&aHey %player%, schön dich hier zu sehen! Los geht's!"
- "&a%player%, dein Abenteuer beginnt jetzt! Viel Spaß!"
- "&aWillkommen an Bord, %player%! Entdecke den Server!"
- "&a%player%, herzlich willkommen! Lass uns loslegen!"