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 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 ChatFormatter() {} /** * Конвертирует все форматы цветов в MiniMessage * Поддерживает: &коды, §коды, &#HEX, §x§...HEX, MiniMessage теги */ public static String convertAllColors(String message) { if (message == null) return ""; String result = message; // 1. Конвертируем §x§R§R§G§G§B§B -> <#RRGGBB> result = convertSectionHexColors(result); // 2. Конвертируем §коды -> &коды (для унификации) result = convertSectionToAmpersand(result); // 3. Нормализуем существующие MiniMessage HEX теги в lowercase result = normalizeMiniMessageHex(result); // 4. Конвертируем &#RRGGBB -> <#RRGGBB> (только если еще не в MiniMessage формате) 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(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(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; } /** * Форматирует глобальное сообщение с поддержкой LoPreff */ public static Component formatGlobalMessage(String format, String prefix, Player player, String message, boolean hasColorPermission) { String playerDisplay = getPlayerDisplay(player); String finalMessage = processPlayerMessage(message, player, hasColorPermission); String formatted = format .replace("", prefix) .replace("", playerDisplay) .replace("", finalMessage); return parse(formatted); } /** * Форматирует локальное сообщение с поддержкой LoPreff */ public static Component formatLocalMessage(String format, String prefix, Player player, String message, boolean hasColorPermission) { String playerDisplay = getPlayerDisplay(player); String finalMessage = processPlayerMessage(message, player, hasColorPermission); String formatted = prefix + " " + format .replace("", playerDisplay) .replace("", finalMessage); return parse(formatted); } private static String getPlayerDisplay(Player player) { LoChat plugin = LoChat.getInstance(); if (plugin != null && plugin.getGradientModule() != null && plugin.getGradientModule().isEnabled()) { String name = plugin.getGradientModule().getFormattedName(player); // Конвертируем §x§... формат в MiniMessage return convertAllColors(name); } return player.getName(); } 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)); } }