diff --git a/src.zip b/src.zip deleted file mode 100644 index e07272d..0000000 Binary files a/src.zip and /dev/null differ diff --git a/src/main/java/com/loki/lochat/listeners/ChatListener.java b/src/main/java/com/loki/lochat/listeners/ChatListener.java index 89de658..8d54d2b 100644 --- a/src/main/java/com/loki/lochat/listeners/ChatListener.java +++ b/src/main/java/com/loki/lochat/listeners/ChatListener.java @@ -3,7 +3,6 @@ package com.loki.lochat.listeners; import com.loki.lochat.LoChat; import com.loki.lochat.managers.AntiSpamManager; import com.loki.lochat.managers.FilterManager; -import com.loki.lochat.utils.ChatFormatter; import io.papermc.paper.event.player.AsyncChatEvent; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextReplacementConfig; @@ -14,10 +13,13 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; +import java.util.regex.Pattern; + public class ChatListener implements Listener { private final LoChat plugin; - private static final PlainTextComponentSerializer PLAIN = PlainTextComponentSerializer.plainText(); + private static final PlainTextComponentSerializer PLAIN = + PlainTextComponentSerializer.plainText(); public ChatListener(LoChat plugin) { this.plugin = plugin; @@ -28,85 +30,126 @@ public class ChatListener implements Listener { event.setCancelled(true); Player player = event.getPlayer(); - Component message = event.message(); - - // Plain текст только для проверок - String plainMessage = PLAIN.serialize(message); + Component originalMessage = event.message(); + String plainMessage = PLAIN.serialize(originalMessage); - // Обработка через RegionScheduler для Folia + // Folia-safe player.getScheduler().run(plugin, task -> { - processChat(player, plainMessage); + processChat(player, originalMessage, plainMessage); }, null); } - private void processChat(Player player, String message) { - // Проверка мута + private void processChat(Player player, Component message, String plainMessage) { + + // ===== MUTE ===== if (plugin.getMuteManager().isMuted(player.getUniqueId())) { - String timeLeft = plugin.getMuteManager().formatRemainingTime(player.getUniqueId()); + String timeLeft = plugin.getMuteManager() + .formatRemainingTime(player.getUniqueId()); + if (timeLeft.equals("навсегда")) { - player.sendMessage(plugin.getMessageConfig().getComponent("mute.permanent")); + player.sendMessage( + plugin.getMessageConfig().getComponent("mute.permanent") + ); } else { - player.sendMessage(plugin.getMessageConfig().getComponent("mute.you-muted", "{time}", timeLeft)); + player.sendMessage( + plugin.getMessageConfig() + .getComponent("mute.you-muted", "{time}", timeLeft) + ); } return; } + // ===== GLOBAL / LOCAL ===== String globalSymbol = plugin.getConfigManager().getGlobalSymbol(); - boolean isGlobal = message.startsWith(globalSymbol) && player.hasPermission("chat.global.use"); + boolean isGlobal = plainMessage.startsWith(globalSymbol) + && player.hasPermission("chat.global.use"); + String chatType = isGlobal ? "global" : "local"; - String processedMessage = isGlobal ? message.substring(globalSymbol.length()).trim() : message; - if (processedMessage.isEmpty()) return; + Component processedMessage = isGlobal + ? message.replaceText(TextReplacementConfig.builder() + .matchLiteral(globalSymbol) + .once() + .replacement(Component.empty()) + .build()) + : message; + + String processedPlain = PLAIN.serialize(processedMessage).trim(); + if (processedPlain.isEmpty()) return; + + // ===== COOLDOWN ===== + int cooldown = isGlobal + ? plugin.getConfigManager().getGlobalCooldown() + : plugin.getConfigManager().getLocalCooldown(); - // Проверка кулдауна - int cooldown = isGlobal ? plugin.getConfigManager().getGlobalCooldown() : plugin.getConfigManager().getLocalCooldown(); if (cooldown > 0 && !player.hasPermission("chat.bypass.cooldown")) { - if (plugin.getCooldownManager().isOnCooldown(player.getUniqueId(), chatType, cooldown)) { - int remaining = plugin.getCooldownManager().getRemainingCooldown(player.getUniqueId(), chatType, cooldown); - player.sendMessage(plugin.getMessageConfig().getComponent("cooldown.wait", "{time}", String.valueOf(remaining))); + if (plugin.getCooldownManager() + .isOnCooldown(player.getUniqueId(), chatType, cooldown)) { + + int remaining = plugin.getCooldownManager() + .getRemainingCooldown(player.getUniqueId(), chatType, cooldown); + + player.sendMessage( + plugin.getMessageConfig() + .getComponent("cooldown.wait", "{time}", String.valueOf(remaining)) + ); return; } } - // Проверка анти-спама + // ===== ANTI SPAM ===== if (!player.hasPermission("chat.bypass.antispam")) { - AntiSpamManager.SpamResult spamResult = plugin.getAntiSpamManager().checkMessage(player.getUniqueId(), processedMessage); - switch (spamResult) { + AntiSpamManager.SpamResult result = + plugin.getAntiSpamManager() + .checkMessage(player.getUniqueId(), processedPlain); + + switch (result) { case TOO_MANY_CAPS -> { - player.sendMessage(plugin.getMessageConfig().getComponent("antispam.caps")); + player.sendMessage( + plugin.getMessageConfig().getComponent("antispam.caps") + ); return; } case REPEAT_CHARS -> { - player.sendMessage(plugin.getMessageConfig().getComponent("antispam.repeat")); + player.sendMessage( + plugin.getMessageConfig().getComponent("antispam.repeat") + ); return; } case SIMILAR_MESSAGE -> { - player.sendMessage(plugin.getMessageConfig().getComponent("antispam.similar")); + player.sendMessage( + plugin.getMessageConfig().getComponent("antispam.similar") + ); return; } } } - // Проверка фильтра + // ===== FILTER ===== if (!player.hasPermission("chat.bypass.filter")) { - FilterManager.FilterResult filterResult = plugin.getFilterManager().checkMessage(processedMessage); - switch (filterResult) { + FilterManager.FilterResult filter = + plugin.getFilterManager().checkMessage(processedPlain); + + switch (filter) { case BLOCKED -> { - player.sendMessage(plugin.getMessageConfig().getComponent("filter.blocked")); + player.sendMessage( + plugin.getMessageConfig().getComponent("filter.blocked") + ); return; } case WARNED -> { - player.sendMessage(plugin.getMessageConfig().getComponent("filter.warned")); - processedMessage = plugin.getFilterManager().censorMessage(processedMessage); + player.sendMessage( + plugin.getMessageConfig().getComponent("filter.warned") + ); + processedMessage = censor(processedMessage); } - case CENSORED -> processedMessage = plugin.getFilterManager().censorMessage(processedMessage); + case CENSORED -> processedMessage = censor(processedMessage); } } - // Устанавливаем кулдаун plugin.getCooldownManager().setCooldown(player.getUniqueId(), chatType); - // Отправка сообщения + // ===== SEND ===== if (isGlobal) { plugin.getChatManager().sendGlobalMessage(player, processedMessage); } else { @@ -114,6 +157,13 @@ public class ChatListener implements Listener { } } + private Component censor(Component component) { + return component.replaceText(TextReplacementConfig.builder() + .match(Pattern.compile("(?i)badword")) + .replacement("***") + .build()); + } + @EventHandler public void onQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); diff --git a/src/main/java/com/loki/lochat/utils/ChatFormatter.java b/src/main/java/com/loki/lochat/utils/ChatFormatter.java index b1f84ab..3e0f192 100644 --- a/src/main/java/com/loki/lochat/utils/ChatFormatter.java +++ b/src/main/java/com/loki/lochat/utils/ChatFormatter.java @@ -1,372 +1,23 @@ package com.loki.lochat.utils; -import com.loki.lochat.LoChat; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; -import org.bukkit.entity.Player; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +public final class ChatFormatter { -public class ChatFormatter { - - private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage(); - - // Паттерны для цветов - private static final Pattern HEX_PATTERN = Pattern.compile("&#([A-Fa-f0-9]{6})"); - private static final Pattern HEX_PATTERN_NO_AMPERSAND = Pattern.compile("(?"); + private static final MiniMessage MM = MiniMessage.miniMessage(); private ChatFormatter() {} - /** - * Конвертирует все форматы цветов в MiniMessage - * Поддерживает: &коды, §коды, &#HEX, §x§...HEX - * Идемпотентный - не конвертирует уже конвертированные теги - */ - public static String convertAllColors(String message) { - if (message == null) return ""; - - // Если уже содержит MiniMessage теги - не конвертируем повторно - if (message.contains(" - result = convertSectionHexColors(result); - - // 2. Конвертируем §коды -> &коды (для унификации) - result = convertSectionToAmpersand(result); - - // 3. Нормализуем существующие MiniMessage HEX теги <#RRGGBB> -> - result = normalizeMiniMessageHex(result); - - // 4. Конвертируем &#RRGGBB -> - result = convertHexColors(result); - - // 5. Конвертируем &коды -> MiniMessage теги - result = convertLegacyColors(result); - - return result; - } - - /** - * Нормализует существующие MiniMessage HEX теги в формат - */ - public static String normalizeMiniMessageHex(String message) { - if (message == null) return ""; - Matcher matcher = MINIMESSAGE_HEX_PATTERN.matcher(message); - StringBuilder result = new StringBuilder(); - while (matcher.find()) { - matcher.appendReplacement(result, ""); - } - matcher.appendTail(result); - return result.toString(); - } - - /** - * Конвертирует §x§R§R§G§G§B§B в - */ - public static String convertSectionHexColors(String message) { - if (message == null) return ""; - Matcher matcher = SECTION_HEX_PATTERN.matcher(message); - StringBuilder result = new StringBuilder(); - while (matcher.find()) { - String match = matcher.group(); - // §x§R§R§G§G§B§B -> RRGGBB - String hex = match.replace("§x", "").replace("§", "").toLowerCase(); - matcher.appendReplacement(result, ""); - } - matcher.appendTail(result); - return result.toString(); - } - - /** - * Конвертирует §коды в &коды - */ - public static String convertSectionToAmpersand(String message) { - if (message == null) return ""; - return SECTION_COLOR_PATTERN.matcher(message).replaceAll("&$1"); - } - - /** - * Конвертирует &#RRGGBB и #RRGGBB в MiniMessage формат - */ - public static String convertHexColors(String message) { - String result = message; - - // Конвертируем &#RRGGBB -> - Matcher matcher = HEX_PATTERN.matcher(result); - StringBuilder sb = new StringBuilder(); - while (matcher.find()) { - matcher.appendReplacement(sb, ""); - } - matcher.appendTail(sb); - result = sb.toString(); - - // Конвертируем #RRGGBB (без &, но не если это часть &#RRGGBB или "); - } - matcher.appendTail(sb); - return sb.toString(); - } - - /** - * Конвертирует &коды в MiniMessage теги - * &a -> , &b -> , &l -> , etc. - */ - public static String convertLegacyColors(String message) { - StringBuilder result = new StringBuilder(); - Matcher matcher = LEGACY_COLOR_PATTERN.matcher(message); - - while (matcher.find()) { - String code = matcher.group(1).toLowerCase(); - String replacement = switch (code) { - case "0" -> ""; - case "1" -> ""; - case "2" -> ""; - case "3" -> ""; - case "4" -> ""; - case "5" -> ""; - case "6" -> ""; - case "7" -> ""; - case "8" -> ""; - case "9" -> ""; - case "a" -> ""; - case "b" -> ""; - case "c" -> ""; - case "d" -> ""; - case "e" -> ""; - case "f" -> ""; - case "k" -> ""; - case "l" -> ""; - case "m" -> ""; - case "n" -> ""; - case "o" -> ""; - case "r" -> ""; - default -> "&" + code; - }; - matcher.appendReplacement(result, Matcher.quoteReplacement(replacement)); - } - matcher.appendTail(result); - - return result.toString(); - } - - /** - * Заменяет смайлики в сообщении - */ - public static String replaceEmojis(String message, Player player) { - LoChat plugin = LoChat.getInstance(); - if (plugin != null && plugin.getEmojiManager() != null) { - return plugin.getEmojiManager().process(message, player); - } - return message; - } - - /** - * Заменяет смайлики без проверки прав - */ - public static String replaceEmojis(String message) { - LoChat plugin = LoChat.getInstance(); - if (plugin != null && plugin.getEmojiManager() != null) { - return plugin.getEmojiManager().process(message); - } - return message; - } - - /** - * @deprecated Use replaceEmojis(String) instead - */ - @Deprecated - public static String replaceEmojis(String message, Map emojis) { - return replaceEmojis(message); - } - - /** - * Полная обработка сообщения игрока - * Цвета + смайлики + цвет чата - */ - public static String processPlayerMessage(String message, Player player, boolean hasColorPermission) { - String result = message; - - // Заменяем смайлики - result = replaceEmojis(result, player); - - // Проверяем есть ли цветовые коды в сообщении - boolean hasColorCodes = LEGACY_COLOR_PATTERN.matcher(result).find() - || SECTION_COLOR_PATTERN.matcher(result).find() - || HEX_PATTERN.matcher(result).find() - || HEX_PATTERN_NO_AMPERSAND.matcher(result).find() - || MINIMESSAGE_HEX_PATTERN.matcher(result).find(); - - // Если есть право на цвета И есть цветовые коды - конвертируем - if (hasColorPermission && hasColorCodes) { - result = convertAllColors(result); - } else { - // Убираем цветовые коды если нет прав - if (!hasColorPermission) { - result = stripLegacyColors(result); - } - - // Применяем цвет чата если установлен (когда нет своих цветов) - LoChat plugin = LoChat.getInstance(); - if (plugin != null && plugin.getChatColorManager() != null) { - result = plugin.getChatColorManager().applyChatColor(player.getUniqueId(), result); - } - } - - return result; - } - - /** - * Убирает &коды и §коды из сообщения - */ - public static String stripLegacyColors(String message) { - String result = LEGACY_COLOR_PATTERN.matcher(message).replaceAll(""); - return SECTION_COLOR_PATTERN.matcher(result).replaceAll(""); - } - - public static Component parse(String message) { - return MINI_MESSAGE.deserialize(message); - } - - public static Component parseWithColors(String message) { - return MINI_MESSAGE.deserialize(convertAllColors(message)); - } - - public static Component parse(String message, String... replacements) { - String result = message; - for (int i = 0; i < replacements.length - 1; i += 2) { - result = result.replace(replacements[i], replacements[i + 1]); - } - return MINI_MESSAGE.deserialize(result); - } - - public static Component parseWithColors(String message, String... replacements) { - String result = message; - for (int i = 0; i < replacements.length - 1; i += 2) { - result = result.replace(replacements[i], replacements[i + 1]); - } - return MINI_MESSAGE.deserialize(convertAllColors(result)); - } - - public static String toPlain(Component component) { - return PlainTextComponentSerializer.plainText().serialize(component); - } - - public static String stripTags(String message) { - if (message == null) return ""; - // Убираем MiniMessage теги и legacy коды - String result = MINI_MESSAGE.stripTags(message); - result = stripLegacyColors(result); - result = HEX_PATTERN.matcher(result).replaceAll(""); - result = MINIMESSAGE_HEX_PATTERN.matcher(result).replaceAll(""); - return result; - } - - /** - * Полностью очищает все цветовые коды из строки - */ - public static String stripAllColors(String message) { - if (message == null) return ""; - String result = message; - - // Убираем MiniMessage HEX теги <#RRGGBB> - result = MINIMESSAGE_HEX_PATTERN.matcher(result).replaceAll(""); - - // Убираем &#RRGGBB - result = HEX_PATTERN.matcher(result).replaceAll(""); - - // Убираем #RRGGBB (без &) - result = HEX_PATTERN_NO_AMPERSAND.matcher(result).replaceAll(""); - - // Убираем &коды - result = stripLegacyColors(result); - - // Убираем MiniMessage теги - result = MINI_MESSAGE.stripTags(result); - - return result; - } - - public static Component formatPmSent(String format, String targetName, String message) { - return parse(format - .replace("{player}", targetName) - .replace("{message}", message)); - } - - public static Component formatPmReceived(String format, String senderName, String message) { - return parse(format - .replace("{player}", senderName) - .replace("{message}", message)); - } - - /** - * Форматирует исходящее PM с поддержкой градиентных имён - */ - public static Component formatPmSentNew(String format, Player sender, Player receiver, String message) { - LoChat plugin = LoChat.getInstance(); - String senderDisplay = sender.getName(); - String receiverDisplay = receiver.getName(); - - // Используем градиентные имена если включено - if (plugin != null && plugin.getMessageConfig().isPmUseGradientNames() - && plugin.getGradientModule() != null && plugin.getGradientModule().isEnabled()) { - senderDisplay = plugin.getGradientModule().getFormattedName(sender); - receiverDisplay = plugin.getGradientModule().getFormattedName(receiver); - - // Конвертируем legacy коды в MiniMessage формат - senderDisplay = convertAllColors(senderDisplay); - receiverDisplay = convertAllColors(receiverDisplay); - } - - return parse(format - .replace("{sender}", sender.getName()) - .replace("{receiver}", receiver.getName()) - .replace("{sender_display}", senderDisplay) - .replace("{receiver_display}", receiverDisplay) - .replace("{message}", message)); - } - - /** - * Форматирует входящее PM с поддержкой градиентных имён - */ - public static Component formatPmReceivedNew(String format, Player sender, Player receiver, String message) { - LoChat plugin = LoChat.getInstance(); - String senderDisplay = sender.getName(); - String receiverDisplay = receiver.getName(); - - // Используем градиентные имена если включено - if (plugin != null && plugin.getMessageConfig().isPmUseGradientNames() - && plugin.getGradientModule() != null && plugin.getGradientModule().isEnabled()) { - senderDisplay = plugin.getGradientModule().getFormattedName(sender); - receiverDisplay = plugin.getGradientModule().getFormattedName(receiver); - - // Конвертируем legacy коды в MiniMessage формат - senderDisplay = convertAllColors(senderDisplay); - receiverDisplay = convertAllColors(receiverDisplay); - } - - return parse(format - .replace("{sender}", sender.getName()) - .replace("{receiver}", receiver.getName()) - .replace("{sender_display}", senderDisplay) - .replace("{receiver_display}", receiverDisplay) - .replace("{message}", message)); - } - - public static Component formatAnnouncement(String format, String message) { - return parse(format.replace("", message)); + public static Component format( + String format, + Component name, + Component prefix, + Component message + ) { + return MM.deserialize(format) + .replaceText(b -> b.matchLiteral("{name}").replacement(name)) + .replaceText(b -> b.matchLiteral("{prefix}").replacement(prefix)) + .replaceText(b -> b.matchLiteral("{message}").replacement(message)); } } diff --git a/src/main/resources/chat.yml b/src/main/resources/chat.yml new file mode 100644 index 0000000..2bad806 --- /dev/null +++ b/src/main/resources/chat.yml @@ -0,0 +1,3 @@ +formats: + global: "[G] {prefix}{name}: {message}" + local: "{prefix}{name}: {message}"