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'
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..05c9b39 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/
@@ -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
@@ -91,7 +116,7 @@
org.spigotmc
spigot-api
- 1.20.1-R0.1-SNAPSHOT
+ 1.18.2-R0.1-SNAPSHOT
provided
@@ -109,7 +134,7 @@
net.luckperms
api
- 5.4
+ 5.5
provided
@@ -121,13 +146,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
@@ -136,12 +161,18 @@
2.0.6
compile
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.10.3
+ test
+
- 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/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..0708e9a 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;
@@ -27,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()));
@@ -42,6 +46,9 @@ 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));
if (Config.CHANGE_TABLIST_NAME.getBoolean()) {
@@ -61,23 +68,42 @@ public boolean onCommand(CommandSender sender, Command command, String label, St
Bukkit.broadcastMessage("\n");
}
- Player clearer = 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);
+ }
- 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();
- clearer = (Player) sender;
+ // 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 {
+ msg = Utils.replacePlayerPlaceholders(clearer, rawMsg + (clearer != null ? "%prefix%displayname%suffix" : who));
}
- Bukkit.broadcastMessage(Locales.MESSAGES_CLEAR.getString(clearer) + who);
+
+ 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 4288205..b32824b 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;
@@ -61,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) {
@@ -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/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/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/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/Locales.java b/src/main/java/de/jeter/chatex/utils/Locales.java
index fa36077..78e7dda 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 {
@@ -32,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"),
@@ -45,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");
@@ -79,7 +83,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 +96,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 +129,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..924272d 100644
--- a/src/main/java/de/jeter/chatex/utils/RGBColors.java
+++ b/src/main/java/de/jeter/chatex/utils/RGBColors.java
@@ -1,7 +1,6 @@
package de.jeter.chatex.utils;
import de.jeter.chatex.ChatEx;
-import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
@@ -14,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) {
@@ -47,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;
}
@@ -75,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) {
- ex.printStackTrace();
- }
- }
- 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);
diff --git a/src/main/java/de/jeter/chatex/utils/Utils.java b/src/main/java/de/jeter/chatex/utils/Utils.java
index 1d8af0a..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);
}
@@ -62,11 +74,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 acdfc51..78042dc 100644
--- a/src/main/java/de/jeter/chatex/utils/adManager/SmartAdManager.java
+++ b/src/main/java/de/jeter/chatex/utils/adManager/SmartAdManager.java
@@ -21,19 +21,21 @@
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("((? 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));
+ }
+}