From 4cdeceffd61277635a8900af241b91a59f31a316 Mon Sep 17 00:00:00 2001 From: TheJeterLP Date: Fri, 28 Aug 2026 20:30:17 +0200 Subject: [PATCH 1/7] Fix MemoryLeak in AdManager, fixes #177 Other minor adjustments, Update to Java 17, Minecraft 1.18 is now minimal version. --- ensure-java-16 | 20 ----- jitpack.yml | 7 +- pom.xml | 18 ++--- .../java/de/jeter/chatex/ChannelHandler.java | 5 +- .../java/de/jeter/chatex/PlayerListener.java | 5 ++ .../jeter/chatex/utils/AntiSpamManager.java | 18 +++-- .../de/jeter/chatex/utils/ChatLogger.java | 73 ++++++++++++------- .../java/de/jeter/chatex/utils/Config.java | 5 +- .../java/de/jeter/chatex/utils/Locales.java | 7 +- .../java/de/jeter/chatex/utils/RGBColors.java | 3 +- .../utils/adManager/SmartAdManager.java | 8 +- src/main/resources/plugin.yml | 2 +- 12 files changed, 95 insertions(+), 76 deletions(-) delete mode 100644 ensure-java-16 diff --git a/ensure-java-16 b/ensure-java-16 deleted file mode 100644 index b7d264f..0000000 --- a/ensure-java-16 +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -JV=`java -version 2>&1 >/dev/null | head -1` -echo $JV | sed -E 's/^.*version "([^".]*)\.[^"]*".*$/\1/' - -if [ "$JV" != 16 ]; then - case "$1" in - install) - echo "Installing SDKMAN..." - curl -s "https://get.sdkman.io" | bash - source ~/.sdkman/bin/sdkman-init.sh - sdk version - sdk install java 16.0.2-open - ;; - use) - echo "must source ~/.sdkman/bin/sdkman-init.sh" - exit 1 - ;; - esac -fi \ No newline at end of file diff --git a/jitpack.yml b/jitpack.yml index 8b41472..33c3123 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1,10 +1,9 @@ jdk: - - openjdk16 + - openjdk17 before_install: - - echo "Before Install" - - bash ensure-java-16 install + - sdk install java 17.0.1-open + - sdk use java 17.0.1-open install: - echo "Install" - - if ! bash ensure-java-16 use; then source ~/.sdkman/bin/sdkman-init.sh; fi - java -version - mvn install \ No newline at end of file diff --git a/pom.xml b/pom.xml index 96f8954..875d99e 100644 --- a/pom.xml +++ b/pom.xml @@ -4,9 +4,9 @@ de.jeter ChatEx ChatEx - 2024 + 2026 - 3.2.2 + 3.3.0 ChatManagement plugin for Bukkit https://www.spigotmc.org/resources/chatex-continued.71041/ @@ -91,7 +91,7 @@ org.spigotmc spigot-api - 1.20.1-R0.1-SNAPSHOT + 1.18.2-R0.1-SNAPSHOT provided @@ -109,7 +109,7 @@ net.luckperms api - 5.4 + 5.5 provided @@ -121,13 +121,13 @@ org.purpurmc.purpur purpur-api - 1.20.1-R0.1-SNAPSHOT + 1.18.2-R0.1-SNAPSHOT provided org.bstats bstats-bukkit - 3.0.2 + 3.2.1 compile @@ -139,9 +139,9 @@ - 16 - 16 - 16 + 17 + 17 + 17 UTF-8 diff --git a/src/main/java/de/jeter/chatex/ChannelHandler.java b/src/main/java/de/jeter/chatex/ChannelHandler.java index d47ef8e..d98dc1e 100644 --- a/src/main/java/de/jeter/chatex/ChannelHandler.java +++ b/src/main/java/de/jeter/chatex/ChannelHandler.java @@ -27,6 +27,7 @@ import java.io.*; import java.util.concurrent.TimeUnit; +import java.util.logging.Level; public class ChannelHandler implements PluginMessageListener { @@ -66,7 +67,7 @@ public void onPluginMessageReceived(String channel, Player player, byte[] messag millis = msgin.readLong(); msg = msgin.readUTF(); } catch (IOException ex) { - ex.printStackTrace(); + ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not read cross-server chat message", ex); msg = "null"; } @@ -90,7 +91,7 @@ public void sendMessage(Player p, String message) { msgout.writeLong(System.currentTimeMillis()); msgout.writeUTF(message); } catch (IOException exception) { - exception.printStackTrace(); + ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not write cross-server chat message", exception); } out.writeShort(msgbytes.toByteArray().length); diff --git a/src/main/java/de/jeter/chatex/PlayerListener.java b/src/main/java/de/jeter/chatex/PlayerListener.java index 4288205..c991a09 100644 --- a/src/main/java/de/jeter/chatex/PlayerListener.java +++ b/src/main/java/de/jeter/chatex/PlayerListener.java @@ -18,9 +18,11 @@ */ package de.jeter.chatex; +import de.jeter.chatex.utils.AntiSpamManager; import de.jeter.chatex.utils.Config; import de.jeter.chatex.utils.Locales; import de.jeter.chatex.utils.Utils; +import de.jeter.chatex.utils.adManager.SmartAdManager; import de.jeter.updatechecker.Result; import de.jeter.updatechecker.UpdateChecker; import net.md_5.bungee.api.chat.ClickEvent; @@ -75,6 +77,9 @@ public void onJoin(PlayerJoinEvent e) { @EventHandler(priority = EventPriority.LOWEST) public void onQuit(final PlayerQuitEvent e) { + AntiSpamManager.getInstance().remove(e.getPlayer().getUniqueId()); + SmartAdManager.clearPlayer(e.getPlayer().getUniqueId()); + if (!Config.CHANGE_JOIN_AND_QUIT.getBoolean()) { return; } diff --git a/src/main/java/de/jeter/chatex/utils/AntiSpamManager.java b/src/main/java/de/jeter/chatex/utils/AntiSpamManager.java index 5296600..01910ad 100644 --- a/src/main/java/de/jeter/chatex/utils/AntiSpamManager.java +++ b/src/main/java/de/jeter/chatex/utils/AntiSpamManager.java @@ -20,14 +20,15 @@ import org.bukkit.entity.Player; -import java.util.HashMap; import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; public class AntiSpamManager { private static final AntiSpamManager instance = new AntiSpamManager(); - private final Map map = new HashMap<>(); + private final Map map = new ConcurrentHashMap<>(); private AntiSpamManager() { @@ -38,15 +39,16 @@ public static AntiSpamManager getInstance() { } public void put(Player chatter) { - map.put(chatter, System.currentTimeMillis()); + map.put(chatter.getUniqueId(), System.currentTimeMillis()); } public boolean isAllowed(Player chatter) { - if (!map.containsKey(chatter) || !Config.ANTISPAM_ENABLED.getBoolean() || chatter.hasPermission("chatex.antispam.bypass")) { + Long lastMessage = map.get(chatter.getUniqueId()); + if (lastMessage == null || !Config.ANTISPAM_ENABLED.getBoolean() || chatter.hasPermission("chatex.antispam.bypass")) { return true; } - long lastChat = map.get(chatter) + (Config.ANTISPAM_SECONDS.getInt() * 1000L); + long lastChat = lastMessage + (Config.ANTISPAM_SECONDS.getInt() * 1000L); long current = System.currentTimeMillis(); return current > lastChat; @@ -57,13 +59,17 @@ public long getRemainingSeconds(Player chatter) { return 0; } - long lastChat = map.get(chatter) + (Config.ANTISPAM_SECONDS.getInt() * 1000L); + long lastChat = map.get(chatter.getUniqueId()) + (Config.ANTISPAM_SECONDS.getInt() * 1000L); long current = System.currentTimeMillis(); long diff = lastChat - current; return TimeUnit.MILLISECONDS.toSeconds(diff); } + public void remove(UUID uuid) { + map.remove(uuid); + } + public void clear() { map.clear(); } diff --git a/src/main/java/de/jeter/chatex/utils/ChatLogger.java b/src/main/java/de/jeter/chatex/utils/ChatLogger.java index cec8439..fba0f77 100644 --- a/src/main/java/de/jeter/chatex/utils/ChatLogger.java +++ b/src/main/java/de/jeter/chatex/utils/ChatLogger.java @@ -25,64 +25,76 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Calendar; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.logging.Level; public class ChatLogger { + private static final DateTimeFormatter FILE_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + private static final DateTimeFormatter TIME_ONLY_FORMAT = DateTimeFormatter.ofPattern("[HH:mm:ss] "); + private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("[yyyy-MM-dd HH:mm:ss] "); + private static BufferedWriter chatWriter = null; private static BufferedWriter adWriter = null; + private static LocalDate chatWriterDate = null; - public static void load() { + public static synchronized void load() { try { File logFolder = new File(ChatEx.getInstance().getDataFolder(), "logs"); if (Config.LOGCHAT.getBoolean() || Config.ADS_LOG.getBoolean()) { logFolder.mkdirs(); } if (Config.LOGCHAT.getBoolean()) { - File chatLog = new File(logFolder, fileName()); - chatLog.createNewFile(); - chatWriter = new BufferedWriter(new FileWriter(chatLog, true)); + chatWriterDate = LocalDate.now(); + chatWriter = openWriter(logFolder, fileName(chatWriterDate)); } if (Config.ADS_LOG.getBoolean()) { - File adLog = new File(logFolder, "ads.log"); - adLog.createNewFile(); - adWriter = new BufferedWriter(new FileWriter(adLog, true)); + adWriter = openWriter(logFolder, "ads.log"); } } catch (IOException ex) { - ex.printStackTrace(); + ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not open chat log files", ex); } } - public static void close() { + private static BufferedWriter openWriter(File logFolder, String name) throws IOException { + File logFile = new File(logFolder, name); + logFile.createNewFile(); + return new BufferedWriter(new FileWriter(logFile, true)); + } + + public static synchronized void close() { try { if (chatWriter != null) { chatWriter.close(); + chatWriter = null; } if (adWriter != null) { adWriter.close(); + adWriter = null; } } catch (IOException ex) { - ex.printStackTrace(); + ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not close chat log files", ex); } } - public static void writeToFile(Player player, String message) { + public static synchronized void writeToFile(Player player, String message) { if (!Config.LOGCHAT.getBoolean() || chatWriter == null) { return; } try { + rotateIfNeeded(); chatWriter.write(prefix(false) + player.getName() + " (uuid: " + player.getUniqueId() + "): " + message); chatWriter.newLine(); chatWriter.flush(); } catch (IOException ex) { - ex.printStackTrace(); + ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not write to chat log", ex); } } - public static void writeToAdFile(Player player, String message) { + public static synchronized void writeToAdFile(Player player, String message) { if (!Config.ADS_LOG.getBoolean() || adWriter == null) { return; } @@ -91,20 +103,29 @@ public static void writeToAdFile(Player player, String message) { adWriter.newLine(); adWriter.flush(); } catch (IOException ex) { - ex.printStackTrace(); + ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not write to ad log", ex); + } + } + + private static void rotateIfNeeded() throws IOException { + LocalDate today = LocalDate.now(); + if (today.equals(chatWriterDate)) { + return; } + + chatWriter.close(); + File logFolder = new File(ChatEx.getInstance().getDataFolder(), "logs"); + chatWriter = openWriter(logFolder, fileName(today)); + chatWriterDate = today; } - private static String fileName() { - DateFormat date = new SimpleDateFormat("yyyy-MM-dd"); - Calendar cal = Calendar.getInstance(); - return date.format(cal.getTime()) + ".log"; + private static String fileName(LocalDate date) { + return FILE_DATE_FORMAT.format(date) + ".log"; } - private static String prefix(boolean day) { - DateFormat date = day ? new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss] ") : new SimpleDateFormat("[HH:mm:ss] "); - Calendar cal = Calendar.getInstance(); - return date.format(cal.getTime()); + private static String prefix(boolean withDate) { + LocalDateTime now = LocalDateTime.now(); + return (withDate ? DATE_TIME_FORMAT : TIME_ONLY_FORMAT).format(now); } -} \ No newline at end of file +} diff --git a/src/main/java/de/jeter/chatex/utils/Config.java b/src/main/java/de/jeter/chatex/utils/Config.java index 8b34d97..39a0296 100644 --- a/src/main/java/de/jeter/chatex/utils/Config.java +++ b/src/main/java/de/jeter/chatex/utils/Config.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.logging.Level; public enum Config { @@ -110,7 +111,7 @@ public static void load() { try { cfg.save(f); } catch (IOException ex) { - ex.printStackTrace(); + ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not save config.yml", ex); } } @@ -164,7 +165,7 @@ public void set(Object value, boolean save) { try { cfg.save(f); } catch (IOException ex) { - ex.printStackTrace(); + ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not save config.yml", ex); } reload(false); } diff --git a/src/main/java/de/jeter/chatex/utils/Locales.java b/src/main/java/de/jeter/chatex/utils/Locales.java index fa36077..3228032 100644 --- a/src/main/java/de/jeter/chatex/utils/Locales.java +++ b/src/main/java/de/jeter/chatex/utils/Locales.java @@ -24,6 +24,7 @@ import java.io.File; import java.io.IOException; +import java.util.logging.Level; public enum Locales { @@ -79,7 +80,7 @@ public static void load() { } cfg.save(f); } catch (IOException ioex) { - ioex.printStackTrace(); + ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not save locale file", ioex); } } } else { @@ -92,7 +93,7 @@ public static void load() { } cfg.save(f); } catch (IOException ex) { - ex.printStackTrace(); + ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not save locale file", ex); } } } @@ -125,7 +126,7 @@ public void set(Object value, boolean save) { try { cfg.save(f); } catch (IOException ex) { - ex.printStackTrace(); + ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not save locale file", ex); } reload(false); } diff --git a/src/main/java/de/jeter/chatex/utils/RGBColors.java b/src/main/java/de/jeter/chatex/utils/RGBColors.java index a3b436c..659df51 100644 --- a/src/main/java/de/jeter/chatex/utils/RGBColors.java +++ b/src/main/java/de/jeter/chatex/utils/RGBColors.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -86,7 +87,7 @@ private static boolean isNotSupported() { LogHelper.debug(ver + " INT: " + toCheck); supported = toCheck >= 116; } catch (Exception ex) { - ex.printStackTrace(); + ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not determine server version for RGB color support", ex); } } return !supported; diff --git a/src/main/java/de/jeter/chatex/utils/adManager/SmartAdManager.java b/src/main/java/de/jeter/chatex/utils/adManager/SmartAdManager.java index acdfc51..e406c2b 100644 --- a/src/main/java/de/jeter/chatex/utils/adManager/SmartAdManager.java +++ b/src/main/java/de/jeter/chatex/utils/adManager/SmartAdManager.java @@ -21,14 +21,14 @@ import de.jeter.chatex.utils.*; import org.bukkit.entity.Player; -import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; public class SmartAdManager implements AdManager { - private static final Map uuidErrorMap = new HashMap<>(); + private static final Map uuidErrorMap = new ConcurrentHashMap<>(); private static final Pattern ipPattern = Pattern.compile("((? Date: Fri, 28 Aug 2026 20:47:01 +0200 Subject: [PATCH 2/7] Add UnitTests, ensure Compatibility between 1.18 and 26.2, some performance improvements. --- pom.xml | 31 ++ src/main/java/de/jeter/chatex/ChatEx.java | 9 +- .../java/de/jeter/chatex/ChatListener.java | 265 +++++++++++------- .../java/de/jeter/chatex/CommandHandler.java | 3 + .../java/de/jeter/chatex/api/ChatExAPI.java | 12 +- .../jeter/chatex/plugins/PluginManager.java | 33 +-- .../de/jeter/chatex/utils/CustomCharts.java | 2 +- .../jeter/chatex/utils/DomainDictionary.java | 17 +- .../java/de/jeter/chatex/utils/Utils.java | 6 +- .../utils/adManager/SmartAdManager.java | 6 +- .../chatex/utils/DomainDictionaryTest.java | 41 +++ 11 files changed, 279 insertions(+), 146 deletions(-) create mode 100644 src/test/java/de/jeter/chatex/utils/DomainDictionaryTest.java diff --git a/pom.xml b/pom.xml index 875d99e..05c9b39 100644 --- a/pom.xml +++ b/pom.xml @@ -32,6 +32,31 @@ + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.6.0 + + google_checks.xml + warning + false + false + true + + + + verify + + check + + + + org.apache.maven.plugins maven-shade-plugin @@ -136,6 +161,12 @@ 2.0.6 compile + + org.junit.jupiter + junit-jupiter + 5.10.3 + test + diff --git a/src/main/java/de/jeter/chatex/ChatEx.java b/src/main/java/de/jeter/chatex/ChatEx.java index bebcfb0..0060101 100644 --- a/src/main/java/de/jeter/chatex/ChatEx.java +++ b/src/main/java/de/jeter/chatex/ChatEx.java @@ -29,6 +29,7 @@ public class ChatEx extends JavaPlugin { private static ChatEx INSTANCE; private UpdateChecker updatechecker = null; + private ChatListener chatListener; public static ChatEx getInstance() { return INSTANCE; @@ -43,8 +44,10 @@ public void onEnable() { PluginManager.load(); ChatLogger.load(); RGBColors.load(); + DomainDictionary.load(); - getServer().getPluginManager().registerEvents(new ChatListener(), this); + chatListener = new ChatListener(); + chatListener.register(this); getServer().getPluginManager().registerEvents(new PlayerListener(), this); getCommand("chatex").setExecutor(new CommandHandler()); @@ -75,4 +78,8 @@ public void onDisable() { public UpdateChecker getUpdateChecker() { return this.updatechecker; } + + public ChatListener getChatListener() { + return this.chatListener; + } } diff --git a/src/main/java/de/jeter/chatex/ChatListener.java b/src/main/java/de/jeter/chatex/ChatListener.java index 279c689..f14f8bb 100644 --- a/src/main/java/de/jeter/chatex/ChatListener.java +++ b/src/main/java/de/jeter/chatex/ChatListener.java @@ -26,10 +26,10 @@ import de.jeter.chatex.utils.adManager.SmartAdManager; import org.bukkit.Bukkit; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent; +import org.bukkit.plugin.Plugin; import java.util.UnknownFormatConversionException; import java.util.regex.Matcher; @@ -38,173 +38,220 @@ public class ChatListener implements Listener { private final AdManager adManager = Config.ADS_SMART_MANAGER.getBoolean() ? new SmartAdManager() : new SimpleAdManager(); + private EventPriority registeredPriority; - @EventHandler(priority = EventPriority.LOWEST) - public void onLowest(final AsyncPlayerChatEvent event) { - if (Config.PRIORITY.getString().equalsIgnoreCase("LOWEST")) { - executeChatEvent(event); - } - } - - @EventHandler(priority = EventPriority.LOW) - public void onLow(final AsyncPlayerChatEvent event) { - if (Config.PRIORITY.getString().equalsIgnoreCase("LOW")) { - executeChatEvent(event); - } - } - - @EventHandler(priority = EventPriority.NORMAL) - public void onNormal(final AsyncPlayerChatEvent event) { - if (Config.PRIORITY.getString().equalsIgnoreCase("NORMAL")) { - executeChatEvent(event); - } + /** + * Registers this listener for {@link AsyncPlayerChatEvent} at the priority configured + * in {@link Config#PRIORITY}, instead of relying on one fixed {@code @EventHandler} per priority. + */ + public void register(Plugin plugin) { + registeredPriority = resolvePriority(); + Bukkit.getPluginManager().registerEvent( + AsyncPlayerChatEvent.class, + this, + registeredPriority, + (listener, event) -> executeChatEvent((AsyncPlayerChatEvent) event), + plugin + ); } - @EventHandler(priority = EventPriority.HIGH) - public void onHigh(final AsyncPlayerChatEvent event) { - if (Config.PRIORITY.getString().equalsIgnoreCase("HIGH")) { - executeChatEvent(event); + /** + * Re-registers this listener if the configured {@link Config#PRIORITY} changed, e.g. after {@code /chatex reload}. + */ + public void reregister(Plugin plugin) { + if (resolvePriority() == registeredPriority) { + return; } + AsyncPlayerChatEvent.getHandlerList().unregister(this); + register(plugin); } - @EventHandler(priority = EventPriority.HIGHEST) - public void onHighest(final AsyncPlayerChatEvent event) { - if (Config.PRIORITY.getString().equalsIgnoreCase("HIGHEST")) { - executeChatEvent(event); + private EventPriority resolvePriority() { + try { + return EventPriority.valueOf(Config.PRIORITY.getString().toUpperCase()); + } catch (IllegalArgumentException ex) { + ChatEx.getInstance().getLogger().warning("Unknown EventPriority '" + Config.PRIORITY.getString() + "' configured, falling back to LOWEST."); + return EventPriority.LOWEST; } } - @EventHandler(priority = EventPriority.MONITOR) - public void onMonitor(final AsyncPlayerChatEvent event) { - if (Config.PRIORITY.getString().equalsIgnoreCase("MONITOR")) { - executeChatEvent(event); - } + /** + * Carries the message-format pipeline state between the individual processing steps below. + */ + private record RoutingResult(String format, String chatMessage, boolean global) { } private void executeChatEvent(AsyncPlayerChatEvent event) { LogHelper.debug("ChatEvent fired with priority: " + Config.PRIORITY.getString().toUpperCase() + ", ChatEx reacting to it..."); Player player = event.getPlayer(); - if (!player.hasPermission("chatex.allowchat")) { - String msg = Locales.COMMAND_RESULT_NO_PERM.getString(player).replaceAll("%perm", "chatex.allowchat"); - player.sendMessage(msg); - event.setCancelled(true); + if (!hasChatPermission(event, player)) { return; } - String format = PluginManager.getInstance().getMessageFormat(event.getPlayer()); - LogHelper.debug("Format: " + format); - LogHelper.debug("Prefix: " + PluginManager.getInstance().getPrefix(event.getPlayer())); - LogHelper.debug("Suffix: " + PluginManager.getInstance().getSuffix(event.getPlayer())); + String format = PluginManager.getMessageFormat(player); + logFormatDebug(player, format); String chatMessage = event.getMessage(); - if (!AntiSpamManager.getInstance().isAllowed(event.getPlayer())) { - long remainingTime = AntiSpamManager.getInstance().getRemainingSeconds(event.getPlayer()); - String message = Locales.ANTI_SPAM_DENIED.getString(event.getPlayer()).replaceAll("%time%", remainingTime + ""); - MessageBlockedBySpamManagerEvent messageBlockedBySpamManagerEvent = new MessageBlockedBySpamManagerEvent(event.getPlayer(), chatMessage, message, remainingTime); - Bukkit.getPluginManager().callEvent(messageBlockedBySpamManagerEvent); - event.setCancelled(!messageBlockedBySpamManagerEvent.isCancelled()); - if (!messageBlockedBySpamManagerEvent.isCancelled()) { - event.getPlayer().sendMessage(messageBlockedBySpamManagerEvent.getPluginMessage()); - return; + // Every apply*-step below returns null if it already cancelled/handled the event itself. + chatMessage = applyAntiSpam(event, player, chatMessage); + if (chatMessage == null) { + return; + } + + chatMessage = applyAdBlocker(event, player, chatMessage); + if (chatMessage == null) { + return; + } + + chatMessage = applyWordBlocker(event, player, chatMessage); + if (chatMessage == null) { + return; + } + + RoutingResult routing = applyRouting(event, player, format, chatMessage); + if (routing == null) { + return; + } + + forwardCrossServerMessage(player, routing); + finalizeMessage(event, player, routing); + } + + private boolean hasChatPermission(AsyncPlayerChatEvent event, Player player) { + if (player.hasPermission("chatex.allowchat")) { + return true; + } + String msg = Locales.COMMAND_RESULT_NO_PERM.getString(player).replaceAll("%perm", "chatex.allowchat"); + player.sendMessage(msg); + event.setCancelled(true); + return false; + } + + private void logFormatDebug(Player player, String format) { + LogHelper.debug("Format: " + format); + LogHelper.debug("Prefix: " + PluginManager.getPrefix(player)); + LogHelper.debug("Suffix: " + PluginManager.getSuffix(player)); + } + + private String applyAntiSpam(AsyncPlayerChatEvent event, Player player, String chatMessage) { + if (!AntiSpamManager.getInstance().isAllowed(player)) { + long remainingTime = AntiSpamManager.getInstance().getRemainingSeconds(player); + String message = Locales.ANTI_SPAM_DENIED.getString(player).replaceAll("%time%", remainingTime + ""); + MessageBlockedBySpamManagerEvent blockedEvent = new MessageBlockedBySpamManagerEvent(player, chatMessage, message, remainingTime); + Bukkit.getPluginManager().callEvent(blockedEvent); + event.setCancelled(!blockedEvent.isCancelled()); + if (!blockedEvent.isCancelled()) { + player.sendMessage(blockedEvent.getPluginMessage()); + return null; } - chatMessage = messageBlockedBySpamManagerEvent.getMessage(); + chatMessage = blockedEvent.getMessage(); } AntiSpamManager.getInstance().put(player); - LogHelper.debug("Player did not activate the AntiSpam. Continuing..."); + return chatMessage; + } + private String applyAdBlocker(AsyncPlayerChatEvent event, Player player, String chatMessage) { if (adManager.checkForAds(chatMessage, player)) { String message = Locales.MESSAGES_AD.getString(null).replaceAll("%perm", "chatex.bypassads"); - MessageBlockedByAdManagerEvent messageBlockedByAdManagerEvent = new MessageBlockedByAdManagerEvent(player, chatMessage, message); - Bukkit.getPluginManager().callEvent(messageBlockedByAdManagerEvent); - chatMessage = messageBlockedByAdManagerEvent.getMessage(); - event.setCancelled(!messageBlockedByAdManagerEvent.isCancelled()); - if (!messageBlockedByAdManagerEvent.isCancelled()) { - event.getPlayer().sendMessage(messageBlockedByAdManagerEvent.getPluginMessage()); - return; + MessageBlockedByAdManagerEvent blockedEvent = new MessageBlockedByAdManagerEvent(player, chatMessage, message); + Bukkit.getPluginManager().callEvent(blockedEvent); + chatMessage = blockedEvent.getMessage(); + event.setCancelled(!blockedEvent.isCancelled()); + if (!blockedEvent.isCancelled()) { + player.sendMessage(blockedEvent.getPluginMessage()); + return null; } } - LogHelper.debug("Player did not activate the AdBlocker. Continuing..."); + return chatMessage; + } - for(String block : Config.BLOCKED_WORDS.getStringList()) { - if(chatMessage.toLowerCase().contains(block.toLowerCase())) { + private String applyWordBlocker(AsyncPlayerChatEvent event, Player player, String chatMessage) { + for (String block : Config.BLOCKED_WORDS.getStringList()) { + if (chatMessage.toLowerCase().contains(block.toLowerCase())) { LogHelper.debug("Player activated wordblocker! ChatMessage: " + chatMessage + " contains blockedWord: " + block); String message = Locales.MESSAGES_BLOCKED.getString(null); - MessageContainsBlockedWordEvent messageContainsBlockedWordEvent = new MessageContainsBlockedWordEvent(player, chatMessage, message); - Bukkit.getPluginManager().callEvent(messageContainsBlockedWordEvent); - event.setCancelled(!messageContainsBlockedWordEvent.isCancelled()); - chatMessage = messageContainsBlockedWordEvent.getMessage(); - if (!messageContainsBlockedWordEvent.isCancelled()) { - event.getPlayer().sendMessage(messageContainsBlockedWordEvent.getPluginMessage()); - return; + MessageContainsBlockedWordEvent blockedEvent = new MessageContainsBlockedWordEvent(player, chatMessage, message); + Bukkit.getPluginManager().callEvent(blockedEvent); + event.setCancelled(!blockedEvent.isCancelled()); + chatMessage = blockedEvent.getMessage(); + if (!blockedEvent.isCancelled()) { + player.sendMessage(blockedEvent.getPluginMessage()); + return null; } } } - LogHelper.debug("Player did not use a blocked word. Continuing..."); LogHelper.debug("ChatMessage: " + chatMessage); + return chatMessage; + } + + private RoutingResult applyRouting(AsyncPlayerChatEvent event, Player player, String format, String chatMessage) { boolean global = false; if (Config.RANGEMODE.getBoolean() || Config.BUNGEECORD.getBoolean()) { LogHelper.debug("Message starts with prefix (" + Config.RANGEPREFIX.getString() + "): " + chatMessage.startsWith(Config.RANGEPREFIX.getString())); if ((Config.RANGEMODE.getBoolean() && chatMessage.startsWith(Config.RANGEPREFIX.getString())) || Config.BUNGEECORD.getBoolean()) { LogHelper.debug("Global mode enabled!"); - if (player.hasPermission("chatex.chat.global")) { - chatMessage = chatMessage.replaceFirst(Pattern.quote(Config.RANGEPREFIX.getString()), ""); - format = PluginManager.getInstance().getGlobalMessageFormat(player); - global = true; - - PlayerUsesGlobalChatEvent playerUsesGlobalChatEvent = new PlayerUsesGlobalChatEvent(player, chatMessage); - Bukkit.getPluginManager().callEvent(playerUsesGlobalChatEvent); - chatMessage = playerUsesGlobalChatEvent.getMessage(); - if (playerUsesGlobalChatEvent.isCancelled()) { - event.setCancelled(true); - return; - } - } else { + if (!player.hasPermission("chatex.chat.global")) { player.sendMessage(Locales.COMMAND_RESULT_NO_PERM.getString(player).replaceAll("%perm", "chatex.chat.global")); event.setCancelled(true); - return; + return null; } - } else { - if (Config.RANGEMODE.getBoolean()) { - LogHelper.debug("Range mode enabled!"); - event.getRecipients().clear(); - if (Utils.getLocalRecipients(player).size() == 1 && Config.SHOW_NO_RECEIVER_MSG.getBoolean()) { - player.sendMessage(Locales.NO_LISTENING_PLAYERS.getString(player)); - event.setCancelled(true); - return; - } else { - event.getRecipients().addAll(Utils.getLocalRecipients(player)); - - PlayerUsesRangeModeEvent playerUsesRangeModeEvent = new PlayerUsesRangeModeEvent(player, chatMessage); - Bukkit.getPluginManager().callEvent(playerUsesRangeModeEvent); - chatMessage = playerUsesRangeModeEvent.getMessage(); - if (playerUsesRangeModeEvent.isCancelled()) { - event.setCancelled(true); - return; - } - } + + chatMessage = chatMessage.replaceFirst(Pattern.quote(Config.RANGEPREFIX.getString()), ""); + format = PluginManager.getGlobalMessageFormat(player); + global = true; + + PlayerUsesGlobalChatEvent globalChatEvent = new PlayerUsesGlobalChatEvent(player, chatMessage); + Bukkit.getPluginManager().callEvent(globalChatEvent); + chatMessage = globalChatEvent.getMessage(); + if (globalChatEvent.isCancelled()) { + event.setCancelled(true); + return null; + } + } else if (Config.RANGEMODE.getBoolean()) { + LogHelper.debug("Range mode enabled!"); + event.getRecipients().clear(); + if (Utils.getLocalRecipients(player).size() == 1 && Config.SHOW_NO_RECEIVER_MSG.getBoolean()) { + player.sendMessage(Locales.NO_LISTENING_PLAYERS.getString(player)); + event.setCancelled(true); + return null; + } + + event.getRecipients().addAll(Utils.getLocalRecipients(player)); + + PlayerUsesRangeModeEvent rangeModeEvent = new PlayerUsesRangeModeEvent(player, chatMessage); + Bukkit.getPluginManager().callEvent(rangeModeEvent); + chatMessage = rangeModeEvent.getMessage(); + if (rangeModeEvent.isCancelled()) { + event.setCancelled(true); + return null; } } } - if (global && Config.BUNGEECORD.getBoolean()) { + return new RoutingResult(format, chatMessage, global); + } + + private void forwardCrossServerMessage(Player player, RoutingResult routing) { + if (routing.global() && Config.BUNGEECORD.getBoolean()) { LogHelper.debug("Local mode & Bungeecord mode enabled! Spreading Cross server message..."); - String msgToSend = Utils.replacePlayerPlaceholders(player, format.replaceAll("%message", Matcher.quoteReplacement(chatMessage))); + String msgToSend = Utils.replacePlayerPlaceholders(player, routing.format().replaceAll("%message", Matcher.quoteReplacement(routing.chatMessage()))); ChannelHandler.getInstance().sendMessage(player, msgToSend); } + } + private void finalizeMessage(AsyncPlayerChatEvent event, Player player, RoutingResult routing) { LogHelper.debug("Replacing Placeholder in format..."); - format = Utils.replacePlayerPlaceholders(player, format); + String format = Utils.replacePlayerPlaceholders(player, routing.format()); format = Utils.escape(format); format = format.replace("%%message", "%2$s"); LogHelper.debug("Format after replacing: " + format); - try { event.setFormat(format); } catch (UnknownFormatConversionException ex) { @@ -214,8 +261,8 @@ private void executeChatEvent(AsyncPlayerChatEvent event) { event.setFormat(format); } - event.setMessage(Utils.translateColorCodes(chatMessage, player)); - ChatLogger.writeToFile(player, chatMessage); + event.setMessage(Utils.translateColorCodes(routing.chatMessage(), player)); + ChatLogger.writeToFile(player, routing.chatMessage()); LogHelper.debug("Everything done! Method end."); } diff --git a/src/main/java/de/jeter/chatex/CommandHandler.java b/src/main/java/de/jeter/chatex/CommandHandler.java index fdf82c8..1cce7b3 100644 --- a/src/main/java/de/jeter/chatex/CommandHandler.java +++ b/src/main/java/de/jeter/chatex/CommandHandler.java @@ -19,6 +19,7 @@ package de.jeter.chatex; import de.jeter.chatex.utils.Config; +import de.jeter.chatex.utils.DomainDictionary; import de.jeter.chatex.utils.Locales; import de.jeter.chatex.utils.Utils; import org.bukkit.Bukkit; @@ -42,6 +43,8 @@ public boolean onCommand(CommandSender sender, Command command, String label, St if (args[0].equalsIgnoreCase("reload")) { if (sender.hasPermission("chatex.reload")) { Config.reload(true); + DomainDictionary.load(); + ChatEx.getInstance().getChatListener().reregister(ChatEx.getInstance()); sender.sendMessage(Locales.MESSAGES_RELOAD.getString(null)); if (Config.CHANGE_TABLIST_NAME.getBoolean()) { diff --git a/src/main/java/de/jeter/chatex/api/ChatExAPI.java b/src/main/java/de/jeter/chatex/api/ChatExAPI.java index 9fcae53..ed5dd6a 100644 --- a/src/main/java/de/jeter/chatex/api/ChatExAPI.java +++ b/src/main/java/de/jeter/chatex/api/ChatExAPI.java @@ -25,7 +25,7 @@ public class ChatExAPI { public String getPermissionHandlerName() { - return PluginManager.getInstance().getName(); + return PluginManager.getName(); } public AntiSpamManager getAntiSpamManager() { @@ -33,22 +33,22 @@ public AntiSpamManager getAntiSpamManager() { } public String getPrefix(Player p) { - return PluginManager.getInstance().getPrefix(p); + return PluginManager.getPrefix(p); } public String getSuffix(Player p) { - return PluginManager.getInstance().getSuffix(p); + return PluginManager.getSuffix(p); } public String[] getGroupNames(Player p) { - return PluginManager.getInstance().getGroupNames(p); + return PluginManager.getGroupNames(p); } public String getMessageFormat(Player p) { - return PluginManager.getInstance().getMessageFormat(p); + return PluginManager.getMessageFormat(p); } public String getGlobalMessageFormat(Player p) { - return PluginManager.getInstance().getGlobalMessageFormat(p); + return PluginManager.getGlobalMessageFormat(p); } } \ No newline at end of file diff --git a/src/main/java/de/jeter/chatex/plugins/PluginManager.java b/src/main/java/de/jeter/chatex/plugins/PluginManager.java index f932180..606453b 100644 --- a/src/main/java/de/jeter/chatex/plugins/PluginManager.java +++ b/src/main/java/de/jeter/chatex/plugins/PluginManager.java @@ -23,17 +23,16 @@ import de.jeter.chatex.utils.Utils; import org.bukkit.entity.Player; -public class PluginManager implements PermissionsPlugin { +/** + * Picks and delegates to whichever {@link PermissionsPlugin} handler is available on this + * server (LuckPerms, Vault or a no-op fallback). Purely static: there is no per-instance state, + * so unlike an earlier version of this class it no longer wraps that in a redundant singleton. + */ +public class PluginManager { private static PermissionsPlugin handler; - private static PluginManager INSTANCE; - - public static PermissionsPlugin getInstance() { - return INSTANCE; - } public static void load() { - INSTANCE = new PluginManager(); if (HookManager.checkLuckperms()) { handler = new LuckPerms(); } else if (HookManager.checkVault() && Vault.setupChat()) { @@ -60,33 +59,27 @@ public static void load() { } } - @Override - public String getName() { + public static String getName() { return handler.getName(); } - @Override - public String getPrefix(Player p) { + public static String getPrefix(Player p) { return handler.getPrefix(p); } - @Override - public String getSuffix(Player p) { + public static String getSuffix(Player p) { return handler.getSuffix(p); } - @Override - public String[] getGroupNames(Player p) { + public static String[] getGroupNames(Player p) { return handler.getGroupNames(p); } - @Override - public String getMessageFormat(Player p) { + public static String getMessageFormat(Player p) { return Utils.replaceColors(handler.getMessageFormat(p)); } - @Override - public String getGlobalMessageFormat(Player p) { + public static String getGlobalMessageFormat(Player p) { return Utils.replaceColors(handler.getGlobalMessageFormat(p)); } -} \ No newline at end of file +} diff --git a/src/main/java/de/jeter/chatex/utils/CustomCharts.java b/src/main/java/de/jeter/chatex/utils/CustomCharts.java index a6abe18..41b3680 100644 --- a/src/main/java/de/jeter/chatex/utils/CustomCharts.java +++ b/src/main/java/de/jeter/chatex/utils/CustomCharts.java @@ -11,7 +11,7 @@ public static void addUpdateCheckerChart(Metrics metrics) { } public static void addPermissionsPluginChart(Metrics metrics) { - metrics.addCustomChart(new SimplePie("used_permissions_plugin", () -> PluginManager.getInstance().getName())); + metrics.addCustomChart(new SimplePie("used_permissions_plugin", () -> PluginManager.getName())); } } diff --git a/src/main/java/de/jeter/chatex/utils/DomainDictionary.java b/src/main/java/de/jeter/chatex/utils/DomainDictionary.java index 7cfdfcc..d4fb579 100644 --- a/src/main/java/de/jeter/chatex/utils/DomainDictionary.java +++ b/src/main/java/de/jeter/chatex/utils/DomainDictionary.java @@ -19,18 +19,27 @@ package de.jeter.chatex.utils; import java.util.HashSet; +import java.util.Set; public class DomainDictionary { - private static final HashSet endingSet = new HashSet<>(Config.ADS_SMART_DOMAIN_ENDINGS.getStringList()); + private static Set endingSet = new HashSet<>(); + + public static void load() { + endingSet = new HashSet<>(Config.ADS_SMART_DOMAIN_ENDINGS.getStringList()); + } public static boolean containsTopLevelEnding(String checkString) { + return containsTopLevelEnding(checkString, endingSet); + } + + static boolean containsTopLevelEnding(String checkString, Set endings) { String[] parts = checkString.split("\\."); String ending = parts[parts.length - 1]; StringBuilder stringBuilder = new StringBuilder(); - for (char Character : ending.toCharArray()) { - stringBuilder.append(Character); - if (endingSet.contains(stringBuilder.toString())) { + for (char c : ending.toCharArray()) { + stringBuilder.append(c); + if (endings.contains(stringBuilder.toString())) { return true; } } diff --git a/src/main/java/de/jeter/chatex/utils/Utils.java b/src/main/java/de/jeter/chatex/utils/Utils.java index 1d8af0a..f573d39 100644 --- a/src/main/java/de/jeter/chatex/utils/Utils.java +++ b/src/main/java/de/jeter/chatex/utils/Utils.java @@ -62,11 +62,11 @@ public static String replacePlayerPlaceholders(Player player, String format) { String result = format; result = result.replace("%displayname", player.getDisplayName()); - result = result.replace("%prefix", PluginManager.getInstance().getPrefix(player)); - result = result.replace("%suffix", PluginManager.getInstance().getSuffix(player)); + result = result.replace("%prefix", PluginManager.getPrefix(player)); + result = result.replace("%suffix", PluginManager.getSuffix(player)); result = result.replace("%player", player.getName()); result = result.replace("%world", player.getWorld().getName()); - result = result.replace("%group", PluginManager.getInstance().getGroupNames(player).length > 0 ? PluginManager.getInstance().getGroupNames(player)[0] : "none"); + result = result.replace("%group", PluginManager.getGroupNames(player).length > 0 ? PluginManager.getGroupNames(player)[0] : "none"); if (HookManager.checkPlaceholderAPI()) { LogHelper.debug("PlaceholderAPI is installed! Replacing..."); diff --git a/src/main/java/de/jeter/chatex/utils/adManager/SmartAdManager.java b/src/main/java/de/jeter/chatex/utils/adManager/SmartAdManager.java index e406c2b..78042dc 100644 --- a/src/main/java/de/jeter/chatex/utils/adManager/SmartAdManager.java +++ b/src/main/java/de/jeter/chatex/utils/adManager/SmartAdManager.java @@ -33,7 +33,9 @@ public class SmartAdManager implements AdManager { private static final Pattern webPattern = Pattern.compile("((([a-zA-Z0-9_-]{2,256}\\.)*)?[a-zA-Z0-9_-]{2,256}\\.[a-zA-Z_-]{2,256})(\\/[-a-zA-Z0-9@:%_\\\\+~#?&\\/=]*)?"); //replace any spaces in the range of ADS_MAX_LENGTH near . or , removes () and [] to prevent example(.)com - private static final String urlCompactorPatternString = "[\\(\\)\\]\\[]|([\\s:\\/](?=.{0," + Config.ADS_MAX_LENGTH.getInt() + "}[\\.]))|((?<=[\\.].{0,4})\\s*)"; + private static String urlCompactorPatternString() { + return "[\\(\\)\\]\\[]|([\\s:\\/](?=.{0," + Config.ADS_MAX_LENGTH.getInt() + "}[\\.]))|((?<=[\\.].{0,4})\\s*)"; + } //Ips are clear private static boolean checkForIPPattern(String message) { @@ -59,7 +61,7 @@ private static double checkForWebPattern(String message) { double error = 0; if (message.contains(",") || message.contains(".")) { message = Config.ADS_REPLACE_COMMAS.getBoolean() ? message.replaceAll(",", ".") : message; - message = message.replaceAll(urlCompactorPatternString, ""); + message = message.replaceAll(urlCompactorPatternString(), ""); Matcher regexMatcher = webPattern.matcher(message); while (regexMatcher.find()) { if (regexMatcher.group().length() != 0) { diff --git a/src/test/java/de/jeter/chatex/utils/DomainDictionaryTest.java b/src/test/java/de/jeter/chatex/utils/DomainDictionaryTest.java new file mode 100644 index 0000000..65098cb --- /dev/null +++ b/src/test/java/de/jeter/chatex/utils/DomainDictionaryTest.java @@ -0,0 +1,41 @@ +package de.jeter.chatex.utils; + +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DomainDictionaryTest { + + private static final Set ENDINGS = Set.of("com", "net", "de", "co.uk"); + + @Test + void matchesKnownTopLevelEnding() { + assertTrue(DomainDictionary.containsTopLevelEnding("example.com", ENDINGS)); + assertTrue(DomainDictionary.containsTopLevelEnding("example.de", ENDINGS)); + } + + @Test + void matchesOnLeftAnchoredPrefixOfTheEnding() { + // The scan builds the ending left-to-right and checks each prefix against + // the dictionary, so "coma" already matches on its "com" prefix. + assertTrue(DomainDictionary.containsTopLevelEnding("example.coma", ENDINGS)); + } + + @Test + void doesNotMatchUnknownEnding() { + assertFalse(DomainDictionary.containsTopLevelEnding("example.xyz", ENDINGS)); + } + + @Test + void emptyEndingSetNeverMatches() { + assertFalse(DomainDictionary.containsTopLevelEnding("example.com", Set.of())); + } + + @Test + void usesLastDotSeparatedPartOnly() { + assertFalse(DomainDictionary.containsTopLevelEnding("com.example.xyz", ENDINGS)); + } +} From 67b23f609c9f706325790d2edaf06dce0025e830 Mon Sep 17 00:00:00 2001 From: TheJeterLP Date: Fri, 28 Aug 2026 21:01:25 +0200 Subject: [PATCH 3/7] Fix RGC-Color Codes when they started with & character --- .../java/de/jeter/chatex/utils/RGBColors.java | 38 +++---------------- 1 file changed, 6 insertions(+), 32 deletions(-) diff --git a/src/main/java/de/jeter/chatex/utils/RGBColors.java b/src/main/java/de/jeter/chatex/utils/RGBColors.java index 659df51..924272d 100644 --- a/src/main/java/de/jeter/chatex/utils/RGBColors.java +++ b/src/main/java/de/jeter/chatex/utils/RGBColors.java @@ -1,13 +1,11 @@ package de.jeter.chatex.utils; import de.jeter.chatex.ChatEx; -import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.configuration.ConfigurationSection; import java.util.HashMap; import java.util.Map; -import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -15,15 +13,8 @@ public class RGBColors { private static final HashMap placeHolderColorMap = new HashMap<>(); - private static Boolean supported = null; - public static void load() { - ChatEx.getInstance().getLogger().info("Server version:" + Bukkit.getVersion()); - if (isNotSupported()) { - ChatEx.getInstance().getLogger().info("This server version doesn't support custom color codes!"); - return; - } - ChatEx.getInstance().getLogger().info("Version is later than 1.16. Loading RGB ColorCodes!"); + ChatEx.getInstance().getLogger().info("Loading RGB ColorCodes!"); ConfigurationSection configurationSection = Config.RGB_COLORS.getConfigurationSection(); if (configurationSection == null) { @@ -48,12 +39,12 @@ public static void load() { } public static String translateCustomColorCodes(String s) { - if (isNotSupported()) { - return s; - } s = translateSingleMessageColorCodes(s); for (Map.Entry stringColorEntry : placeHolderColorMap.entrySet()) { - s = s.replace(stringColorEntry.getKey(), stringColorEntry.getValue()); + // Custom color placeholders are typed as "&" (e.g. "&$g"); the leading "&" + // must be consumed here too, otherwise it survives as a stray character in the + // output since it's no longer followed by a valid legacy color code. + s = s.replace("&" + stringColorEntry.getKey(), stringColorEntry.getValue()); } return s; } @@ -76,27 +67,10 @@ public static String translateSingleMessageColorCodes(String s) { return s; } - private static boolean isNotSupported() { - if (supported == null) { - try { - final String version = Bukkit.getVersion(); - String ver = version.split("\\(MC: ")[1]; - String[] numbers = ver.replaceAll("\\)", "").split("\\."); - ver = numbers[0] + numbers[1]; - int toCheck = Integer.valueOf(ver); - LogHelper.debug(ver + " INT: " + toCheck); - supported = toCheck >= 116; - } catch (Exception ex) { - ChatEx.getInstance().getLogger().log(Level.SEVERE, "Could not determine server version for RGB color support", ex); - } - } - return !supported; - } - public static String translateGradientCodes(String message) { final Pattern hexPattern = Pattern.compile("#([A-Fa-f0-9]{6})"); Matcher matcher = hexPattern.matcher(message); - StringBuffer buffer = new StringBuffer(message.length() + 4 * 8); + StringBuilder buffer = new StringBuilder(message.length() + 4 * 8); while (matcher.find()) { String group = matcher.group(1); From d418706d7bd1a11712c1f2cb20a789d31d9a05a6 Mon Sep 17 00:00:00 2001 From: TheJeterLP Date: Fri, 28 Aug 2026 21:14:57 +0200 Subject: [PATCH 4/7] Replace placeholders in ChatEx clear command, reload locales --- .../java/de/jeter/chatex/CommandHandler.java | 18 +++++++++++------- src/main/resources/locales/de-DE.yml | 2 +- src/main/resources/locales/es-ES.yml | 2 +- src/main/resources/locales/fr-FR.yml | 2 +- src/main/resources/locales/ja-JP.yml | 2 +- src/main/resources/locales/pt-BR.yml | 2 +- src/main/resources/locales/ru-RU.yml | 2 +- src/main/resources/locales/tr-TR.yml | 2 +- src/main/resources/locales/zh-CN.yml | 2 +- 9 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/main/java/de/jeter/chatex/CommandHandler.java b/src/main/java/de/jeter/chatex/CommandHandler.java index 1cce7b3..2c1a146 100644 --- a/src/main/java/de/jeter/chatex/CommandHandler.java +++ b/src/main/java/de/jeter/chatex/CommandHandler.java @@ -43,6 +43,7 @@ public boolean onCommand(CommandSender sender, Command command, String label, St if (args[0].equalsIgnoreCase("reload")) { if (sender.hasPermission("chatex.reload")) { Config.reload(true); + Locales.reload(true); DomainDictionary.load(); ChatEx.getInstance().getChatListener().reregister(ChatEx.getInstance()); sender.sendMessage(Locales.MESSAGES_RELOAD.getString(null)); @@ -65,15 +66,18 @@ public boolean onCommand(CommandSender sender, Command command, String label, St } Player clearer = null; - - String who = Locales.COMMAND_CLEAR_UNKNOWN.getString(null); - if ((sender instanceof ConsoleCommandSender) || (sender instanceof BlockCommandSender)) { - who = Locales.COMMAND_CLEAR_CONSOLE.getString(null); - } else if (sender instanceof Player) { - who = sender.getName(); + String who; + if (sender instanceof Player) { clearer = (Player) sender; + who = "%prefix%displayname%suffix"; + } else if (sender instanceof ConsoleCommandSender || sender instanceof BlockCommandSender) { + who = Locales.COMMAND_CLEAR_CONSOLE.getString(null); + } else { + who = Locales.COMMAND_CLEAR_UNKNOWN.getString(null); } - Bukkit.broadcastMessage(Locales.MESSAGES_CLEAR.getString(clearer) + who); + + String msg = Locales.MESSAGES_CLEAR.getString(clearer) + who; + Bukkit.broadcastMessage(Utils.replacePlayerPlaceholders(clearer, msg)); } else { sender.sendMessage(Locales.COMMAND_RESULT_NO_PERM.getString(null).replaceAll("%perm", "chatex.clear")); } diff --git a/src/main/resources/locales/de-DE.yml b/src/main/resources/locales/de-DE.yml index 2f0878d..081cfb0 100644 --- a/src/main/resources/locales/de-DE.yml +++ b/src/main/resources/locales/de-DE.yml @@ -15,7 +15,7 @@ Messages: Reload: Success: '&aChatEx wurde neugeladen.' Clear: - Success: '&aDer Chat wurde geleert von ' + Success: '&aDer Chat wurde geleert von %prefix%displayname%suffix' CommandResult: NoPermission: '&4[FEHLER] &7Dafür hast du nicht die nötigen Rechte! &c(%perm)' WrongUsage: '&c[FEHLER] &7Falsche Benutzung! Benutze &6%cmd help&7!' diff --git a/src/main/resources/locales/es-ES.yml b/src/main/resources/locales/es-ES.yml index f482d2e..9702614 100644 --- a/src/main/resources/locales/es-ES.yml +++ b/src/main/resources/locales/es-ES.yml @@ -10,7 +10,7 @@ Messages: Reload: Success: '&aLa configuración ha sido recargada.' Clear: - Success: '&aEl chat ha sido borrado ' + Success: '&aEl chat ha sido borrado %prefix%displayname%suffix' Chat: AdDetected: '&4[ERROR] &7La publicidad no está permitida! &c(%perm)' BlockedWord: '&4[ERROR] &7Trataste de usar una palabra que no está permitida!' diff --git a/src/main/resources/locales/fr-FR.yml b/src/main/resources/locales/fr-FR.yml index 2def85a..f45fe1b 100644 --- a/src/main/resources/locales/fr-FR.yml +++ b/src/main/resources/locales/fr-FR.yml @@ -12,7 +12,7 @@ Messages: Reload: Success: '&aConfiguration rechargée.' Clear: - Success: '&aLe chat a été effacé par ' + Success: '&aLe chat a été effacé par %prefix%displayname%suffix' CommandResult: NoPermission: "&4[ERREUR] &7Vous n'avez pas la permission ! &c(%perm%)" WrongUsage: '&c[ERREUR] &7Mauvaise utilisation ! Tapez &6%cmd help&7!' diff --git a/src/main/resources/locales/ja-JP.yml b/src/main/resources/locales/ja-JP.yml index 49b0b56..2cc014c 100644 --- a/src/main/resources/locales/ja-JP.yml +++ b/src/main/resources/locales/ja-JP.yml @@ -10,7 +10,7 @@ Messages: Reload: Success: '&a設定が再読み込みされました。' Clear: - Success: '&aチャットは削除されました。 ' + Success: '&aチャットは削除されました。 %prefix%displayname%suffix' Chat: AdDetected: '&4[エラー] &7広告は許可されていません! &c(%perm)' BlockedWord: '&4[エラー] &7禁止されている単語を使用しようとしました!' diff --git a/src/main/resources/locales/pt-BR.yml b/src/main/resources/locales/pt-BR.yml index 3172955..aad053e 100644 --- a/src/main/resources/locales/pt-BR.yml +++ b/src/main/resources/locales/pt-BR.yml @@ -10,7 +10,7 @@ Messages: Reload: Success: '&aConfig foi recarregada.' Clear: - Success: '&aO chat foi limpo por ' + Success: '&aO chat foi limpo por %prefix%displayname%suffix' Chat: AdDetected: '&4[ERRO] &7Propaganda nao e permitida! &c(%perm)' AdNotify: "&c%jogador tentou escrever propaganda no chat. Ele/ela escreveu: \n\ diff --git a/src/main/resources/locales/ru-RU.yml b/src/main/resources/locales/ru-RU.yml index 288e17b..98be770 100644 --- a/src/main/resources/locales/ru-RU.yml +++ b/src/main/resources/locales/ru-RU.yml @@ -15,7 +15,7 @@ Messages: Reload: Success: '&f[&4Внимание&f] &7ChatEx был перезагружен!' Clear: - Success: '&f[&4Внимание&f] &7Чат был очищен! ' + Success: '&f[&4Внимание&f] &7Чат был очищен! %prefix%displayname%suffix' CommandResult: NoPermission: '&f[&4Внимание&f] &7У вас нет прав на это! &7(&e%perm&7)' WrongUsage: '&f[&4Внимание&f] &7неизвестная команда, используйте: &e%cmd help&7!' diff --git a/src/main/resources/locales/tr-TR.yml b/src/main/resources/locales/tr-TR.yml index 82d9d72..39c98d3 100644 --- a/src/main/resources/locales/tr-TR.yml +++ b/src/main/resources/locales/tr-TR.yml @@ -15,7 +15,7 @@ Messages: Reload: Success: '&aChatEx yeniden yüklendi.' Clear: - Success: '&aSohbet şu kişi tarafından boşaltıldı: ' + Success: '&aSohbet şu kişi tarafından boşaltıldı: %prefix%displayname%suffix' CommandResult: NoPermission: '&4[HATA] &7Bunun için gerekli izinlere sahip değilsiniz! &c(%perm)' WrongUsage: '&c[HATA] &7 Yanlış kullanım! &6%cmd help&7'i kullan!' diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index f5ffc3e..a02a3a5 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -10,7 +10,7 @@ Messages: Reload: Success: '&a配置文件已重新加载' Clear: - Success: '&a聊天已被清除,操作者:' + Success: '&a聊天已被清除,操作者:%prefix%displayname%suffix' Chat: AdDetected: '&4[错误] &7禁止发送广告! &c(%perm)' BlockedWord: '&4[错误] &7你正尝试发送被禁止的文字!' From 099ef7aa315316290970370e9b515c87abd816d1 Mon Sep 17 00:00:00 2001 From: TheJeterLP Date: Fri, 28 Aug 2026 21:23:02 +0200 Subject: [PATCH 5/7] Add missing localized texts. --- .../java/de/jeter/chatex/CommandHandler.java | 45 +++++++++++++------ .../java/de/jeter/chatex/PlayerListener.java | 2 +- .../java/de/jeter/chatex/utils/Locales.java | 5 ++- src/main/resources/locales/de-DE.yml | 4 ++ src/main/resources/locales/es-ES.yml | 4 ++ src/main/resources/locales/fr-FR.yml | 4 ++ src/main/resources/locales/ja-JP.yml | 6 ++- src/main/resources/locales/pt-BR.yml | 4 ++ src/main/resources/locales/ru-RU.yml | 4 ++ src/main/resources/locales/tr-TR.yml | 6 ++- src/main/resources/locales/zh-CN.yml | 4 ++ 11 files changed, 71 insertions(+), 17 deletions(-) diff --git a/src/main/java/de/jeter/chatex/CommandHandler.java b/src/main/java/de/jeter/chatex/CommandHandler.java index 2c1a146..0708e9a 100644 --- a/src/main/java/de/jeter/chatex/CommandHandler.java +++ b/src/main/java/de/jeter/chatex/CommandHandler.java @@ -28,13 +28,16 @@ import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; public class CommandHandler implements CommandExecutor, TabCompleter { @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (args.length == 0) { - sender.sendMessage("§aChatEx plugin by " + ChatEx.getInstance().getDescription().getAuthors() + " (" + ChatEx.getInstance().getDescription().getVersion() + ")"); + sender.sendMessage(Locales.COMMAND_INFO.getString(null) + .replaceAll("%authors", Matcher.quoteReplacement(String.valueOf(ChatEx.getInstance().getDescription().getAuthors()))) + .replaceAll("%version", Matcher.quoteReplacement(ChatEx.getInstance().getDescription().getVersion()))); return true; } else if (args.length > 1) { sender.sendMessage(Locales.COMMAND_RESULT_WRONG_USAGE.getString(null).replaceAll("%cmd", command.getName())); @@ -65,26 +68,42 @@ public boolean onCommand(CommandSender sender, Command command, String label, St Bukkit.broadcastMessage("\n"); } - Player clearer = null; - String who; - if (sender instanceof Player) { - clearer = (Player) sender; - who = "%prefix%displayname%suffix"; - } else if (sender instanceof ConsoleCommandSender || sender instanceof BlockCommandSender) { - who = Locales.COMMAND_CLEAR_CONSOLE.getString(null); + Player clearer = sender instanceof Player ? (Player) sender : null; + String who = null; + if (clearer == null) { + who = (sender instanceof ConsoleCommandSender || sender instanceof BlockCommandSender) + ? Locales.COMMAND_CLEAR_CONSOLE.getString(null) + : Locales.COMMAND_CLEAR_UNKNOWN.getString(null); + } + + // The locale text is the source of truth for where "who cleared the chat" is + // placed (via %prefix%displayname%suffix, same as every bundled translation). + // Appending it in code below is only a fallback for locale files saved before + // this was configurable, i.e. that don't contain %displayname at all yet. + String rawMsg = Locales.MESSAGES_CLEAR.getString(null); + String msg; + if (rawMsg.contains("%displayname")) { + msg = clearer != null + ? Locales.MESSAGES_CLEAR.getString(clearer) + : rawMsg.replace("%prefix", "").replace("%displayname", who).replace("%suffix", ""); } else { - who = Locales.COMMAND_CLEAR_UNKNOWN.getString(null); + msg = Utils.replacePlayerPlaceholders(clearer, rawMsg + (clearer != null ? "%prefix%displayname%suffix" : who)); } - String msg = Locales.MESSAGES_CLEAR.getString(clearer) + who; - Bukkit.broadcastMessage(Utils.replacePlayerPlaceholders(clearer, msg)); + Bukkit.broadcastMessage(msg); } else { sender.sendMessage(Locales.COMMAND_RESULT_NO_PERM.getString(null).replaceAll("%perm", "chatex.clear")); } return true; } else if (args[0].equalsIgnoreCase("help") || args[0].equalsIgnoreCase("?")) { - sender.sendMessage("§a/" + command.getName() + " reload - " + Locales.COMMAND_RELOAD_DESCRIPTION.getString(null)); - sender.sendMessage("§a/" + command.getName() + " clear - " + Locales.COMMAND_CLEAR_DESCRIPTION.getString(null)); + sender.sendMessage(Locales.COMMAND_HELP_LINE.getString(null) + .replaceAll("%cmd", Matcher.quoteReplacement(command.getName())) + .replaceAll("%subcommand", "reload") + .replaceAll("%description", Matcher.quoteReplacement(Locales.COMMAND_RELOAD_DESCRIPTION.getString(null)))); + sender.sendMessage(Locales.COMMAND_HELP_LINE.getString(null) + .replaceAll("%cmd", Matcher.quoteReplacement(command.getName())) + .replaceAll("%subcommand", "clear") + .replaceAll("%description", Matcher.quoteReplacement(Locales.COMMAND_CLEAR_DESCRIPTION.getString(null)))); return true; } else { sender.sendMessage(Locales.COMMAND_RESULT_WRONG_USAGE.getString(null).replaceAll("%cmd", "/chatex")); diff --git a/src/main/java/de/jeter/chatex/PlayerListener.java b/src/main/java/de/jeter/chatex/PlayerListener.java index c991a09..b32824b 100644 --- a/src/main/java/de/jeter/chatex/PlayerListener.java +++ b/src/main/java/de/jeter/chatex/PlayerListener.java @@ -63,7 +63,7 @@ public void onJoin(PlayerJoinEvent e) { if (checker.getResult() == Result.UPDATE_FOUND) { try { TextComponent msg = new TextComponent(Locales.UPDATE_FOUND.getString(null).replaceAll("%oldversion", ChatEx.getInstance().getDescription().getVersion()).replaceAll("%newversion", ChatEx.getInstance().getUpdateChecker().getLatestRemoteVersion())); - msg.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text("§aClick to download"))); + msg.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(Locales.UPDATE_FOUND_HOVER.getString(null)))); msg.setClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, checker.getDownloadLink())); e.getPlayer().spigot().sendMessage(msg); } catch (NoClassDefFoundError ex) { diff --git a/src/main/java/de/jeter/chatex/utils/Locales.java b/src/main/java/de/jeter/chatex/utils/Locales.java index 3228032..78e7dda 100644 --- a/src/main/java/de/jeter/chatex/utils/Locales.java +++ b/src/main/java/de/jeter/chatex/utils/Locales.java @@ -33,7 +33,9 @@ public enum Locales { COMMAND_CLEAR_CONSOLE("Commands.Clear.Console", "CONSOLE"), COMMAND_CLEAR_UNKNOWN("Commands.Clear.Unknown", "UNKNOWN"), MESSAGES_RELOAD("Messages.Commands.Reload.Success", "&aConfig was reloaded."), - MESSAGES_CLEAR("Messages.Commands.Clear.Success", "&aThe chat has been cleared by "), + MESSAGES_CLEAR("Messages.Commands.Clear.Success", "&aThe chat has been cleared by %prefix%displayname%suffix"), + COMMAND_INFO("Messages.Info", "&aChatEx plugin by %authors (%version)"), + COMMAND_HELP_LINE("Messages.Commands.Help.Line", "&a/%cmd %subcommand - %description"), MESSAGES_AD("Messages.Chat.AdDetected", "&4[ERROR] &7Advertising is not allowed! &c(%perm)"), MESSAGES_BLOCKED("Messages.Chat.BlockedWord", "&4[ERROR] &7You tried to write a word that is blocked!"), MESSAGES_AD_NOTIFY("Messages.Chat.AdNotify", "&c%player tried to write an ad in chat. He wrote: \n&a %message"), @@ -46,6 +48,7 @@ public enum Locales { PLAYER_QUIT("Messages.Player.Quit", "%prefix%displayname%suffix &eleft the game!"), NO_LISTENING_PLAYERS("Messages.Chat.NoOneListens", "&cNo players are near you to hear you talking! Try to use the global mode to chat globally."), UPDATE_FOUND("Messages.UpdateFound", "&a[ChatEx]&7 A new update has been found on SpigotMC. Current version: %oldversion New version: %newversion. Click this message to download it!"), + UPDATE_FOUND_HOVER("Messages.UpdateFoundHover", "&aClick to download"), ; private static final File localeFolder = new File(ChatEx.getInstance().getDataFolder(), "locales"); diff --git a/src/main/resources/locales/de-DE.yml b/src/main/resources/locales/de-DE.yml index 081cfb0..1a9cc5d 100644 --- a/src/main/resources/locales/de-DE.yml +++ b/src/main/resources/locales/de-DE.yml @@ -6,6 +6,7 @@ Commands: Console: 'KONSOLE' Unknown: 'UNBEKANNT' Messages: + Info: '&aChatEx Plugin von %authors (%version)' Chat: AdDetected: '&4[FEHLER] &7Werbung ist nicht erlaubt! &c(%perm)' AdNotify: "&c%player Hat versucht Werbung zu schreiben: \n&a %message" @@ -16,6 +17,8 @@ Messages: Success: '&aChatEx wurde neugeladen.' Clear: Success: '&aDer Chat wurde geleert von %prefix%displayname%suffix' + Help: + Line: '&a/%cmd %subcommand - %description' CommandResult: NoPermission: '&4[FEHLER] &7Dafür hast du nicht die nötigen Rechte! &c(%perm)' WrongUsage: '&c[FEHLER] &7Falsche Benutzung! Benutze &6%cmd help&7!' @@ -24,5 +27,6 @@ Messages: Quit: '%prefix%displayname%suffix &ehat den Server verlassen!' Kick: '%prefix%displayname%suffix &ewurde vom Server gekickt!' UpdateFound: '&a[ChatEx]&7 Ein neues Update wurde veröffentlicht. Aktuelle Version: %oldversion Neue version: %newversion' + UpdateFoundHover: '&aKlicken zum Herunterladen' UpdateDownloaded: '&a[ChatEx]&7 Ein neues Update wurde installiert. Es wird beim nächsten Neustart aktiv.' diff --git a/src/main/resources/locales/es-ES.yml b/src/main/resources/locales/es-ES.yml index 9702614..99c3148 100644 --- a/src/main/resources/locales/es-ES.yml +++ b/src/main/resources/locales/es-ES.yml @@ -6,11 +6,14 @@ Commands: Console: CONSOLE Unknown: UNKNOWN Messages: + Info: '&aPlugin ChatEx de %authors (%version)' Commands: Reload: Success: '&aLa configuración ha sido recargada.' Clear: Success: '&aEl chat ha sido borrado %prefix%displayname%suffix' + Help: + Line: '&a/%cmd %subcommand - %description' Chat: AdDetected: '&4[ERROR] &7La publicidad no está permitida! &c(%perm)' BlockedWord: '&4[ERROR] &7Trataste de usar una palabra que no está permitida!' @@ -30,3 +33,4 @@ Messages: Quit: '%prefix%displayname%suffix &ese fue del juego!' UpdateFound: '&a[ChatEx]&7 Una nueva actualización fue encontrada en SpigotMC. Versión actual: %oldversion Nueva versión: %newversion' + UpdateFoundHover: '&aHaz clic para descargar' diff --git a/src/main/resources/locales/fr-FR.yml b/src/main/resources/locales/fr-FR.yml index f45fe1b..c7bb870 100644 --- a/src/main/resources/locales/fr-FR.yml +++ b/src/main/resources/locales/fr-FR.yml @@ -6,6 +6,8 @@ Commands: Console: 'CONSOLE' Unknown: 'INCONNU' Messages: + Info: '&aPlugin ChatEx par %authors (%version)' + UpdateFoundHover: '&aCliquez pour télécharger' Chat: AdDetected: '&4Les publicités ne sont pas autorisées ! (%perm)' Commands: @@ -13,6 +15,8 @@ Messages: Success: '&aConfiguration rechargée.' Clear: Success: '&aLe chat a été effacé par %prefix%displayname%suffix' + Help: + Line: '&a/%cmd %subcommand - %description' CommandResult: NoPermission: "&4[ERREUR] &7Vous n'avez pas la permission ! &c(%perm%)" WrongUsage: '&c[ERREUR] &7Mauvaise utilisation ! Tapez &6%cmd help&7!' diff --git a/src/main/resources/locales/ja-JP.yml b/src/main/resources/locales/ja-JP.yml index 2cc014c..95cdb21 100644 --- a/src/main/resources/locales/ja-JP.yml +++ b/src/main/resources/locales/ja-JP.yml @@ -6,11 +6,14 @@ Commands: Console: CONSOLE Unknown: UNKNOWN Messages: + Info: '&aChatEx プラグイン 作者: %authors (%version)' Commands: Reload: Success: '&a設定が再読み込みされました。' Clear: Success: '&aチャットは削除されました。 %prefix%displayname%suffix' + Help: + Line: '&a/%cmd %subcommand - %description' Chat: AdDetected: '&4[エラー] &7広告は許可されていません! &c(%perm)' BlockedWord: '&4[エラー] &7禁止されている単語を使用しようとしました!' @@ -26,5 +29,6 @@ Messages: Join: '%prefix%displayname%suffix &eゲームに参加しました!' Kick: '%prefix%displayname%suffix &eゲームから除外されました!' Quit: '%prefix%displayname%suffix &eゲームから退出しました!' - UpdateFound: '&a[ChatEx]&7 SpigotMC で新しいアップデートが見つかりました。 + UpdateFound: '&a[ChatEx]&7 SpigotMC で新しいアップデートが見つかりました。 現在のバージョン: %oldversion 最新のバージョン: %newversion' + UpdateFoundHover: '&aクリックしてダウンロード' diff --git a/src/main/resources/locales/pt-BR.yml b/src/main/resources/locales/pt-BR.yml index aad053e..76df2ae 100644 --- a/src/main/resources/locales/pt-BR.yml +++ b/src/main/resources/locales/pt-BR.yml @@ -6,11 +6,15 @@ Commands: Console: CONSOLE Unknown: DESCONHECIDO Messages: + Info: '&aPlugin ChatEx por %authors (%version)' + UpdateFoundHover: '&aClique para baixar' Commands: Reload: Success: '&aConfig foi recarregada.' Clear: Success: '&aO chat foi limpo por %prefix%displayname%suffix' + Help: + Line: '&a/%cmd %subcommand - %description' Chat: AdDetected: '&4[ERRO] &7Propaganda nao e permitida! &c(%perm)' AdNotify: "&c%jogador tentou escrever propaganda no chat. Ele/ela escreveu: \n\ diff --git a/src/main/resources/locales/ru-RU.yml b/src/main/resources/locales/ru-RU.yml index 98be770..cd51d5f 100644 --- a/src/main/resources/locales/ru-RU.yml +++ b/src/main/resources/locales/ru-RU.yml @@ -6,6 +6,8 @@ Commands: Console: 'Консоль' Unknown: 'Неизвестно' Messages: + Info: '&aПлагин ChatEx от %authors (%version)' + UpdateFoundHover: '&aНажмите, чтобы скачать' Chat: AdDetected: '&f[&4Внимание&f] &7Реклама в чате запрещена! &7(&e%perm&7)' AdNotify: '&f[&4Внимание&f] &e%player &7пытался рекламировать в чате: &e%message' @@ -16,6 +18,8 @@ Messages: Success: '&f[&4Внимание&f] &7ChatEx был перезагружен!' Clear: Success: '&f[&4Внимание&f] &7Чат был очищен! %prefix%displayname%suffix' + Help: + Line: '&a/%cmd %subcommand - %description' CommandResult: NoPermission: '&f[&4Внимание&f] &7У вас нет прав на это! &7(&e%perm&7)' WrongUsage: '&f[&4Внимание&f] &7неизвестная команда, используйте: &e%cmd help&7!' diff --git a/src/main/resources/locales/tr-TR.yml b/src/main/resources/locales/tr-TR.yml index 39c98d3..d47c67c 100644 --- a/src/main/resources/locales/tr-TR.yml +++ b/src/main/resources/locales/tr-TR.yml @@ -6,6 +6,7 @@ Commands: Console: 'KONSOL' Unknown: 'BİLİNMEYEN' Messages: + Info: '&aChatEx eklentisi, %authors tarafından (%version)' Chat: AdDetected: '&4[HATA] &7Reklama izin verilmiyor! &c(%perm)' AdNotify: "&c%player Bir reklam yazmaya çalıştın: \n&a %message" @@ -16,13 +17,16 @@ Messages: Success: '&aChatEx yeniden yüklendi.' Clear: Success: '&aSohbet şu kişi tarafından boşaltıldı: %prefix%displayname%suffix' + Help: + Line: '&a/%cmd %subcommand - %description' CommandResult: NoPermission: '&4[HATA] &7Bunun için gerekli izinlere sahip değilsiniz! &c(%perm)' - WrongUsage: '&c[HATA] &7 Yanlış kullanım! &6%cmd help&7'i kullan!' + WrongUsage: '&c[HATA] &7 Yanlış kullanım! &6%cmd help&7''i kullan!' Player: Join: '%prefix%displayname%suffix &esunucuya girdi!' Quit: '%prefix%displayname%suffix &esunucudan ayrıldı!' Kick: '%prefix%displayname%suffix &esunucu tarafından atıldı!' UpdateFound: '&a[ChatEx]&7 Yeni bir güncelleme yayınlandı. Şimdiki versiyonu: %oldversion Yeni versiyon: %newversion' + UpdateFoundHover: '&aİndirmek için tıklayın' UpdateDownloaded: '&a[ChatEx]&7 Yeni bir güncelleme yüklendi. Bir sonraki yeniden başlatmada aktif hale gelecektir.' diff --git a/src/main/resources/locales/zh-CN.yml b/src/main/resources/locales/zh-CN.yml index a02a3a5..735dbb1 100644 --- a/src/main/resources/locales/zh-CN.yml +++ b/src/main/resources/locales/zh-CN.yml @@ -6,11 +6,14 @@ Commands: Console: 控制台 Unknown: 未知 Messages: + Info: '&aChatEx 插件,作者:%authors (%version)' Commands: Reload: Success: '&a配置文件已重新加载' Clear: Success: '&a聊天已被清除,操作者:%prefix%displayname%suffix' + Help: + Line: '&a/%cmd %subcommand - %description' Chat: AdDetected: '&4[错误] &7禁止发送广告! &c(%perm)' BlockedWord: '&4[错误] &7你正尝试发送被禁止的文字!' @@ -26,3 +29,4 @@ Messages: Kick: '%prefix%displayname%suffix &e被踢出服务器!' Quit: '%prefix%displayname%suffix &e离开服务器!' UpdateFound: '&a[ChatEx]&7 在 SpigotMC 发现可用的插件更新。当前版本:%oldversion 新版本:%newversion' + UpdateFoundHover: '&a点击下载' From d0166beb9588473c4118259620499b4983b23d81 Mon Sep 17 00:00:00 2001 From: TheJeterLP Date: Fri, 28 Aug 2026 21:25:58 +0200 Subject: [PATCH 6/7] Update Workflows --- .github/dependabot.yml | 4 ++-- .github/workflows/autobuild.yml | 4 ++-- .github/workflows/create-release.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4fa7ae5..89fc5a8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,7 +8,7 @@ updates: - package-ecosystem: "maven" # See documentation for possible values directory: "/" # Location of package manifests schedule: - interval: "daily" + interval: "weekly" commit-message: # Prefix all commit messages with "npm: " prefix: "maven" @@ -17,7 +17,7 @@ updates: # default location of `.github/workflows` directory: "/" schedule: - interval: "daily" + interval: "weekly" commit-message: # Prefix all commit messages with "npm: " prefix: "gh-actions" diff --git a/.github/workflows/autobuild.yml b/.github/workflows/autobuild.yml index 524e85a..2f39634 100644 --- a/.github/workflows/autobuild.yml +++ b/.github/workflows/autobuild.yml @@ -12,10 +12,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v7.0.1 - name: Set up JDK 17 - uses: actions/setup-java@v4.2.1 + uses: actions/setup-java@v6.0.0 with: distribution: 'zulu' java-version: '17' diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 033612f..d25c8bc 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -12,11 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4.1.7 + uses: actions/checkout@v7.0.1 with: fetch-depth: 0 - name: Set up JDK - uses: actions/setup-java@v4.2.1 + uses: actions/setup-java@v6.0.0 with: distribution: 'zulu' java-version: '17' From 2692b35e55f488f6629e7eca22759ebe6015411d Mon Sep 17 00:00:00 2001 From: TheJeterLP Date: Fri, 28 Aug 2026 22:09:04 +0200 Subject: [PATCH 7/7] Bugfixes for RGB Colors --- src/main/java/de/jeter/chatex/utils/Utils.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/jeter/chatex/utils/Utils.java b/src/main/java/de/jeter/chatex/utils/Utils.java index f573d39..6f514fd 100644 --- a/src/main/java/de/jeter/chatex/utils/Utils.java +++ b/src/main/java/de/jeter/chatex/utils/Utils.java @@ -27,16 +27,28 @@ import java.util.ArrayList; import java.util.List; +import java.util.regex.Pattern; public class Utils { + private static final Pattern COLOR_CODE_PATTERN = Pattern.compile("(?i)&[0-9a-fk-or]|#[0-9a-f]{6}"); + public static String translateColorCodes(String string, Player p) { - return p.hasPermission("chatex.chat.color") ? replaceColors(string) : string; + if (p.hasPermission("chatex.chat.color")) { + return replaceColors(string); + } + if (COLOR_CODE_PATTERN.matcher(string).find()) { + p.sendMessage(Locales.COMMAND_RESULT_NO_PERM.getString(p).replaceAll("%perm", "chatex.chat.color")); + } + return string; } public static String replaceColors(String message) { - message = RGBColors.translateGradientCodes(message); + // Must run before translateGradientCodes: it matches "&#RRGGBB" (consuming the "&"), + // whereas translateGradientCodes matches bare "#RRGGBB" regardless of a leading "&" and + // would otherwise steal the hex code first, leaving a stray unconverted "&" behind. message = RGBColors.translateCustomColorCodes(message); + message = RGBColors.translateGradientCodes(message); return ChatColor.translateAlternateColorCodes('&', message); }