21 Commits
1.0 ... main

Author SHA1 Message Date
1de5301f71 Dateien nach "GlobalChat" hochladen 2025-10-04 07:25:01 +00:00
4d8b7e16ca Dateien nach "GlobalChat/src/main/resources" hochladen 2025-10-04 07:23:35 +00:00
71b9d85f3c Dateien nach "GlobalChat/src/main/java/de/viper/globalchat" hochladen 2025-10-04 07:23:08 +00:00
85014a68b7 src/main/resources/bungee.yml gelöscht 2025-10-04 07:21:24 +00:00
aec2de9305 src/main/resources/filter.yml gelöscht 2025-10-04 07:21:20 +00:00
7ffd227fa4 src/main/resources/plugin.yml gelöscht 2025-10-04 07:21:16 +00:00
81f3ba5a5f src/main/resources/welcome.yml gelöscht 2025-10-04 07:21:13 +00:00
810649c0a1 src/main/java/de/viper/globalchat/GlobalChat.java gelöscht 2025-10-04 07:21:07 +00:00
99ed66cbab GlobalChat/pom.xml gelöscht 2025-10-04 07:20:59 +00:00
d8e6ab016a GlobalChat/pom.xml aktualisiert 2025-10-04 07:18:29 +00:00
e0292702d7 Dateien nach "GlobalChatSuppressor/src/main/java/de/viper/globalchat/suppressor" hochladen 2025-09-29 20:40:29 +00:00
1042618392 Dateien nach "GlobalChatSuppressor/src/main/resources" hochladen 2025-09-29 20:39:46 +00:00
1b863a9786 Dateien nach "GlobalChatSuppressor" hochladen 2025-09-29 20:39:18 +00:00
1d98c4ea5b src/main/resources/bungee.yml aktualisiert 2025-09-29 20:31:41 +00:00
b291b25a55 src/main/resources/plugin.yml aktualisiert 2025-09-29 20:30:55 +00:00
59c626cde0 Dateien nach "src/main/resources" hochladen 2025-09-29 20:28:43 +00:00
268d0ce638 src/main/java/de/viper/globalchat/GlobalChat.java aktualisiert 2025-09-29 20:27:56 +00:00
7a32160599 pom.xml aktualisiert 2025-09-29 20:27:18 +00:00
c62d1ded12 src/main/resources/bungee.yml aktualisiert 2025-09-29 20:27:06 +00:00
48638aef6d pom.xml aktualisiert 2025-09-29 20:26:13 +00:00
26a337c392 LICENSE gelöscht 2025-09-29 14:39:29 +00:00
12 changed files with 766 additions and 322 deletions

View File

@@ -7,7 +7,7 @@
<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>
@@ -18,14 +18,30 @@
<maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.target>1.8</maven.compiler.target>
</properties> </properties>
<repositories>
<!-- LuckPerms Repository -->
<repository>
<id>luck-repo</id>
<url>https://repo.lucko.me/</url>
</repository>
</repositories>
<dependencies> <dependencies>
<!-- BungeeCord API für Minecraft 1.21.x --> <!-- BungeeCord API -->
<dependency> <dependency>
<groupId>net.md-5</groupId> <groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId> <artifactId>bungeecord-api</artifactId>
<version>1.21-R0.1-SNAPSHOT</version> <version>1.21-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- LuckPerms API -->
<dependency>
<groupId>net.luckperms</groupId>
<artifactId>api</artifactId>
<version>5.4</version>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
@@ -41,7 +57,7 @@
</configuration> </configuration>
</plugin> </plugin>
<!-- Shade Plugin für fertiges JAR --> <!-- Shade Plugin -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>

View File

@@ -0,0 +1,575 @@
package de.viper.globalchat;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.model.user.User;
import net.luckperms.api.cacheddata.CachedMetaData;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.HoverEvent.Action;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ChatEvent;
import net.md_5.bungee.api.event.ServerConnectEvent;
import net.md_5.bungee.api.event.ServerSwitchEvent;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import java.io.*;
import java.nio.file.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
/**
* GlobalChat - Bungee Plugin
* - Filter (filter.yml)
* - Logs (plugins/GlobalChat/logs/YYYY-MM-DD.log)
* - /support, /reply, /info
* - Prefix/Suffix via LuckPerms (Proxy-side)
* - ServerSwitch announcement (sends message to chat when a player switches from one server to another)
* - Attempts to suppress Spigot join/quit messages during server switch
*/
public class GlobalChat extends Plugin implements Listener {
private static final String CHANNEL_CONTROL = "global:control";
private List<String> badWords = new ArrayList<>();
private File logFolder;
private boolean chatMuted = false;
// Optional: falls Spigot-Server OP-Info per PluginMessage sendet
private final Map<UUID, Boolean> playerIsOp = new ConcurrentHashMap<>();
// Letzte Support-Kontakte (staff UUID -> target UUID)
private final Map<UUID, UUID> lastSupportContact = new ConcurrentHashMap<>();
// Tracking für unterdrückte Join-/Quit-Nachrichten
private final Set<UUID> suppressJoinQuit = ConcurrentHashMap.newKeySet();
// ===========================
// Neu: Welcome-Nachrichten
// ===========================
private List<String> welcomeMessages = new ArrayList<>();
private void loadWelcomeMessages() {
File file = new File(getDataFolder(), "welcome.yml");
if (!file.exists()) {
try {
getDataFolder().mkdirs();
file.createNewFile();
try (PrintWriter out = new PrintWriter(file)) {
out.println("welcome-messages:");
out.println(" - \"&aWillkommen, %player%! Viel Spaß auf unserem Server!\"");
out.println(" - \"&aHey %player%, schön dich hier zu sehen! Los geht's!\"");
out.println(" - \"&a%player%, dein Abenteuer beginnt jetzt! Viel Spaß!\"");
out.println(" - \"&aWillkommen an Bord, %player%! Entdecke den Server!\"");
out.println(" - \"&a%player%, herzlich willkommen! Lass uns loslegen!\"");
}
} catch (IOException e) {
e.printStackTrace();
}
}
try {
List<String> lines = Files.readAllLines(file.toPath());
welcomeMessages.clear();
for (String line : lines) {
line = line.trim();
if (line.startsWith("-")) welcomeMessages.add(line.substring(1).trim());
}
getLogger().info("§eGeladene Welcome-Nachrichten: " + welcomeMessages.size());
} catch (IOException e) {
e.printStackTrace();
}
}
private void sendRandomWelcomeMessage(ProxiedPlayer player) {
if (welcomeMessages.isEmpty()) return;
Random rand = new Random();
String message = welcomeMessages.get(rand.nextInt(welcomeMessages.size()));
message = message.replace("%player%", player.getName());
message = ChatColor.translateAlternateColorCodes('&', message);
player.sendMessage(new TextComponent(message));
}
@Override
public void onEnable() {
// Plugin channel registrieren (für Steuer-Nachrichten an Spigot)
try {
getProxy().registerChannel(CHANNEL_CONTROL);
} catch (Throwable ignored) {
getLogger().warning("Konnte Kanal " + CHANNEL_CONTROL + " nicht registrieren.");
}
getProxy().getPluginManager().registerListener(this, this);
loadFilter();
loadWelcomeMessages();
logFolder = new File(getDataFolder(), "logs");
if (!logFolder.exists()) logFolder.mkdirs();
cleanupOldLogs();
// Befehle registrieren
getProxy().getPluginManager().registerCommand(this, new ReloadCommand());
getProxy().getPluginManager().registerCommand(this, new MuteCommand());
getProxy().getPluginManager().registerCommand(this, new SupportCommand());
getProxy().getPluginManager().registerCommand(this, new ReplyCommand());
getProxy().getPluginManager().registerCommand(this, new InfoCommand());
getLogger().info("§aGlobalChat aktiviert (Zensur, Logs, Reload, Mute, Support, Reply & Info)!");
}
@Override
public void onDisable() {
getLogger().info("§cGlobalChat deaktiviert!");
try {
getProxy().unregisterChannel(CHANNEL_CONTROL);
} catch (Throwable ignored) {
getLogger().warning("Konnte Kanal " + CHANNEL_CONTROL + " nicht deregistrieren.");
}
}
// ===========================
// Chatfilter & Global-Chat
// ===========================
@EventHandler
public void onChat(ChatEvent e) {
if (!(e.getSender() instanceof ProxiedPlayer)) return;
if (e.isCommand()) return;
ProxiedPlayer player = (ProxiedPlayer) e.getSender();
String originalMsg = e.getMessage();
// Debugging: Logge alle Chat-Nachrichten, um zu prüfen, ob Join-/Quit-Nachrichten ankommen
getLogger().info("ChatEvent: Spieler=" + player.getName() + ", Nachricht=" + originalMsg);
// Versuche, Join-/Quit-Nachrichten zu filtern
if (suppressJoinQuit.contains(player.getUniqueId()) &&
(originalMsg.contains("joined the Game") || originalMsg.contains("left the Game"))) {
getLogger().info("Unterdrücke Join-/Quit-Nachricht für " + player.getName() + ": " + originalMsg);
e.setCancelled(true);
return;
}
// Globaler Mute
if (chatMuted && !player.hasPermission("globalchat.bypass")) {
player.sendMessage(new TextComponent("§cDer globale Chat ist derzeit deaktiviert!"));
e.setCancelled(true);
return;
}
// Badword-Zensur (nur für Chat-Ausgabe; Log bleibt unzensiert)
String censoredMsg = originalMsg;
for (String bad : badWords) {
if (bad == null || bad.trim().isEmpty()) continue;
censoredMsg = censoredMsg.replaceAll("(?i)" + Pattern.quote(bad), repeat("*", bad.length()));
}
e.setCancelled(true);
String serverName = player.getServer().getInfo().getName();
// Prefix/Suffix holen (LuckPerms Proxy-side)
String[] ps = getPrefixSuffix(player);
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.
String displayTag = "";
if (!prefix.isEmpty()) {
displayTag = prefix;
} else if (!suffix.isEmpty()) {
displayTag = suffix;
}
// saubere Leerzeichen um displayTag
if (!displayTag.isEmpty() && !displayTag.endsWith(" ")) displayTag = displayTag + " ";
// Format: [Server] <tag>Name: Nachricht
StringBuilder out = new StringBuilder();
out.append("§7[").append(serverName).append("] ");
if (!displayTag.isEmpty()) out.append(displayTag);
out.append(player.getName());
out.append("§f: ").append(censoredMsg);
String chatOut = out.toString();
for (ProxiedPlayer p : getProxy().getPlayers()) {
p.sendMessage(new TextComponent(chatOut));
}
// Log unzensiert (ohne color codes)
String logEntry = "[" + serverName + "] " +
(displayTag.isEmpty() ? "" : stripColor(displayTag) + " ") +
player.getName() +
": " + originalMsg;
logMessage(logEntry);
}
// ===========================
// Server connect (pre-switch suppression)
// ===========================
@EventHandler
public void onServerConnect(ServerConnectEvent e) {
if (e.isCancelled()) return;
ProxiedPlayer player = e.getPlayer();
ServerInfo target = e.getTarget();
ServerInfo from = player.getServer() != null ? player.getServer().getInfo() : null;
if (from == null || from.equals(target)) return;
// Markiere Spieler für Unterdrückung
suppressJoinQuit.add(player.getUniqueId());
getLogger().info("Markiert " + player.getName() + " für Join-/Quit-Unterdrückung");
// Suppress quit on old server
try {
sendSuppressJoinQuit(from, player.getUniqueId());
getLogger().info("Sent suppress quit message for " + player.getName() + " to server " + from.getName());
} catch (Throwable ex) {
getLogger().warning("Fehler beim Senden der Quit-Unterdrückung an " + from.getName() + ": " + ex.getMessage());
}
// Suppress join on new server
try {
sendSuppressJoinQuit(target, 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());
}
// 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());
}, 2, java.util.concurrent.TimeUnit.SECONDS);
}
// ===========================
// Server switch announcement
// ===========================
@EventHandler
public void onServerSwitch(ServerSwitchEvent e) {
ProxiedPlayer player = e.getPlayer();
ServerInfo from = e.getFrom(); // kann null sein beim ersten Join
ServerInfo to = player.getServer() != null ? player.getServer().getInfo() : null;
if (to == null) return;
// Wenn from == null -> erster Join, keine Nachricht senden
if (from == null) return;
// Nur senden, wenn Server wirklich gewechselt wurde
if (from.getName().equalsIgnoreCase(to.getName())) return;
String fromName = from.getName();
String toName = to.getName();
// Prefix/Suffix für Anzeige (show either prefix or suffix)
String[] ps = getPrefixSuffix(player);
String prefix = ps[0] == null ? "" : ps[0].trim();
String suffix = ps[1] == null ? "" : ps[1].trim();
String displayTag = "";
if (!prefix.isEmpty()) displayTag = prefix;
else if (!suffix.isEmpty()) displayTag = suffix;
if (!displayTag.isEmpty() && !displayTag.endsWith(" ")) displayTag = displayTag + " ";
StringBuilder msg = new StringBuilder();
msg.append("§7[").append(toName).append("] ");
if (!displayTag.isEmpty()) msg.append(displayTag);
msg.append(player.getName());
msg.append(" §7hat den Server gewechselt: §e")
.append(fromName).append(" §7→ §e").append(toName).append("§7.");
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,7 +1,9 @@
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
depend: [LuckPerms]
commands: commands:
globalreload: globalreload:
description: Lädt den Filter neu description: Lädt den Filter neu

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!"

View File

@@ -0,0 +1,42 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.viper.globalchat</groupId>
<artifactId>GlobalChatSuppressor</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<name>GlobalChatSuppressor</name>
<properties>
<java.version>17</java.version>
<spigot.version>1.21.1-R0.1-SNAPSHOT</spigot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,79 @@
package de.viper.globalchat.suppressor;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class GlobalChatSuppressor extends JavaPlugin implements PluginMessageListener, Listener {
private static final String CHANNEL = "global:control";
private final Set<UUID> suppressJoinQuit = new HashSet<>();
@Override
public void onEnable() {
getServer().getMessenger().registerIncomingPluginChannel(this, CHANNEL, this);
getServer().getMessenger().registerOutgoingPluginChannel(this, CHANNEL);
getServer().getPluginManager().registerEvents(this, this);
getLogger().info("GlobalChatSuppressor aktiviert!");
}
@Override
public void onDisable() {
getServer().getMessenger().unregisterIncomingPluginChannel(this, CHANNEL);
getServer().getMessenger().unregisterOutgoingPluginChannel(this, CHANNEL);
getLogger().info("GlobalChatSuppressor deaktiviert!");
}
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals(CHANNEL)) return;
try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(message))) {
String subChannel = in.readUTF();
if ("suppress".equalsIgnoreCase(subChannel)) {
String uuidStr = in.readUTF();
UUID playerUUID = UUID.fromString(uuidStr);
suppressJoinQuit.add(playerUUID);
getLogger().info("Suppress für Spieler UUID: " + playerUUID + " aktiviert.");
// Entferne die Unterdrückung nach 2 Sekunden (synchron mit BungeeCord)
getServer().getScheduler().runTaskLater(this, () -> {
suppressJoinQuit.remove(playerUUID);
getLogger().info("Suppress für Spieler UUID: " + playerUUID + " entfernt.");
}, 40L); // 40 Ticks = 2 Sekunden
}
} catch (IOException e) {
e.printStackTrace();
}
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
if (suppressJoinQuit.contains(player.getUniqueId())) {
event.setJoinMessage(null);
getLogger().info("Join-Nachricht für " + player.getName() + " unterdrückt.");
suppressJoinQuit.remove(player.getUniqueId());
}
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
if (suppressJoinQuit.contains(player.getUniqueId())) {
event.setQuitMessage(null);
getLogger().info("Quit-Nachricht für " + player.getName() + " unterdrückt.");
suppressJoinQuit.remove(player.getUniqueId());
}
}
}

View File

@@ -0,0 +1,35 @@
package de.viper.globalchat.suppressor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class SuppressManager {
private static final Set<UUID> suppressedPlayers = Collections.synchronizedSet(new HashSet<>());
public static void register(GlobalChatSuppressor plugin) {
// Nichts weiter nötig, die Nachrichten werden beim Join/Quit-Event unterdrückt
// via AsyncPlayerPreLogin oder PlayerJoin/Leave in Hauptplugin
}
public static void suppress(UUID playerUUID) {
suppressedPlayers.add(playerUUID);
}
public static void unsuppress(UUID playerUUID) {
suppressedPlayers.remove(playerUUID);
}
public static boolean isSuppressed(UUID playerUUID) {
return suppressedPlayers.contains(playerUUID);
}
public static Set<UUID> getSuppressedPlayers() {
return suppressedPlayers;
}
}

View File

@@ -0,0 +1,6 @@
name: GlobalChatSuppressor
version: 1.0.0
main: de.viper.globalchat.suppressor.GlobalChatSuppressor
api-version: 1.21
author: M_Viper
commands: {}

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.

View File

@@ -1,299 +0,0 @@
package de.viper.globalchat;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.HoverEvent.Action;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.ChatEvent;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import java.io.*;
import java.nio.file.*;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
public class GlobalChat extends Plugin implements Listener {
private List<String> badWords = new ArrayList<>();
private File logFolder;
private boolean chatMuted = false; // Globaler Mute-Status
// Spieler-UUID -> isOp (vom Spigot gemeldet)
private final Map<UUID, Boolean> playerIsOp = new ConcurrentHashMap<>();
// Letzte Support-Kontakte für /reply
private final Map<UUID, UUID> lastSupportContact = new ConcurrentHashMap<>();
@Override
public void onEnable() {
getProxy().getPluginManager().registerListener(this, this);
// Filter laden
loadFilter();
// Log-Ordner erstellen
logFolder = new File(getDataFolder(), "logs");
if (!logFolder.exists()) logFolder.mkdirs();
cleanupOldLogs();
// Befehle registrieren (Support & Info sind für alle verfügbar)
getProxy().getPluginManager().registerCommand(this, new ReloadCommand());
getProxy().getPluginManager().registerCommand(this, new MuteCommand());
getProxy().getPluginManager().registerCommand(this, new SupportCommand());
getProxy().getPluginManager().registerCommand(this, new ReplyCommand());
getProxy().getPluginManager().registerCommand(this, new InfoCommand());
getLogger().info("§aGlobalChat mit Zensur, Logs, Reload, Mute, Support, Reply & Info aktiviert!");
}
@Override
public void onDisable() {
getLogger().info("§cGlobalChat deaktiviert!");
}
// ===========================
// Chatfilter & Logging
// ===========================
@EventHandler
public void onChat(ChatEvent e) {
if (!(e.getSender() instanceof ProxiedPlayer)) return;
if (e.isCommand()) return;
ProxiedPlayer player = (ProxiedPlayer) e.getSender();
String originalMsg = e.getMessage();
String censoredMsg = originalMsg;
// Globaler Mute check
if (chatMuted && !player.hasPermission("globalchat.bypass")) {
player.sendMessage(new TextComponent("§cDer globale Chat ist derzeit deaktiviert!"));
e.setCancelled(true);
return;
}
// Badword-Zensur
for (String bad : badWords) {
if (bad == null || bad.trim().isEmpty()) continue;
censoredMsg = censoredMsg.replaceAll("(?i)" + Pattern.quote(bad), repeat("*", bad.length()));
}
e.setCancelled(true);
String serverName = player.getServer().getInfo().getName();
String chatOut = "§7[" + serverName + "] §b" + player.getName() + "§f: " + censoredMsg;
for (ProxiedPlayer p : getProxy().getPlayers()) {
p.sendMessage(new TextComponent(chatOut));
}
logMessage("[" + serverName + "] " + player.getName() + ": " + originalMsg);
}
private String repeat(String str, int count) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < count; i++) sb.append(str);
return sb.toString();
}
// ===========================
// Filter laden
// ===========================
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();
}
}
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
// ===========================
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") + ".");
}
}
public class SupportCommand extends Command {
// kein Permission-Parameter -> für alle verfügbar
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);
// Hover-Text
supportMsg.setHoverEvent(new HoverEvent(Action.SHOW_TEXT,
new ComponentBuilder("Klicke, um /reply " + player.getName() + " zu schreiben").create()));
// Klick-Event (füllt die Chatbox mit /reply <Name> )
supportMsg.setClickEvent(new net.md_5.bungee.api.chat.ClickEvent(
net.md_5.bungee.api.chat.ClickEvent.Action.SUGGEST_COMMAND,
"/reply " + player.getName() + " "
));
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);
}
}
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."));
}
}
public class InfoCommand extends Command {
// kein Permission-Parameter -> für alle verfügbar
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------------------------------"));
}
}
}