feat: LoHub Action system + Join system implementation
This commit is contained in:
parent
69cae99476
commit
6a4c8a7886
55 changed files with 3180 additions and 1022 deletions
|
|
@ -16,6 +16,10 @@ public class CustomCommand extends Command {
|
|||
public CustomCommand(String name, CustomCommandManager manager) {
|
||||
super(name);
|
||||
this.manager = manager;
|
||||
init(name);
|
||||
}
|
||||
|
||||
private void init(String name) {
|
||||
this.setDescription("Кастомная команда LoChat");
|
||||
this.setUsage("/" + name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,6 @@ import org.bukkit.entity.Player;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Команда /nick для установки кастомного ника
|
||||
*/
|
||||
public class NickCommand implements CommandExecutor, TabCompleter {
|
||||
private final LoChat plugin;
|
||||
private final NickService nickService;
|
||||
|
|
@ -38,17 +35,14 @@ public class NickCommand implements CommandExecutor, TabCompleter {
|
|||
return true;
|
||||
}
|
||||
|
||||
// /nick - показать текущий ник
|
||||
if (args.length == 0) {
|
||||
return showCurrentNick(player);
|
||||
}
|
||||
|
||||
// /nick reset - сбросить ник
|
||||
if (args[0].equalsIgnoreCase("reset")) {
|
||||
return resetNick(player);
|
||||
}
|
||||
|
||||
// /nick <ник> - установить ник
|
||||
String nickname = String.join(" ", args);
|
||||
return setNick(player, nickname);
|
||||
}
|
||||
|
|
@ -87,27 +81,23 @@ public class NickCommand implements CommandExecutor, TabCompleter {
|
|||
}
|
||||
|
||||
private boolean setNick(Player player, String nickname) {
|
||||
// Валидация
|
||||
if (!nickService.isValidNickname(nickname)) {
|
||||
player.sendMessage(Component.text("Невалидный ник!", NamedTextColor.RED));
|
||||
player.sendMessage(Component.text("Требования:", NamedTextColor.GRAY));
|
||||
player.sendMessage(Component.text("• Длина: 3-16 символов", NamedTextColor.GRAY));
|
||||
player.sendMessage(Component.text("• Разрешены: буквы, цифры, _, русские буквы", NamedTextColor.GRAY));
|
||||
player.sendMessage(Component.text("• Поддержка цветов: <red>текст</red>, <gradient:#FF0000:#00FF00>текст</gradient>", NamedTextColor.GRAY));
|
||||
player.sendMessage(Component.text("• Поддержка цветов: &#FF0000текст, <gradient:&#FF0000:�FF00>текст</gradient>", NamedTextColor.GRAY));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Проверка уникальности
|
||||
if (nickService.isNicknameTaken(nickname)) {
|
||||
var currentNick = nickService.getNickname(player.getUniqueId());
|
||||
// Если это не свой текущий ник
|
||||
if (currentNick.isEmpty() || !currentNick.get().equals(nickname)) {
|
||||
player.sendMessage(Component.text("Этот ник уже занят!", NamedTextColor.RED));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Устанавливаем ник
|
||||
boolean success = nickService.setNickname(player.getUniqueId(), nickname);
|
||||
|
||||
if (success) {
|
||||
|
|
@ -126,8 +116,22 @@ public class NickCommand implements CommandExecutor, TabCompleter {
|
|||
List<String> completions = new ArrayList<>();
|
||||
|
||||
if (args.length == 1) {
|
||||
completions.add("reset");
|
||||
completions.add("<ник>");
|
||||
String input = args[0].toLowerCase();
|
||||
|
||||
if ("reset".startsWith(input)) {
|
||||
completions.add("reset");
|
||||
}
|
||||
|
||||
if (sender instanceof Player player) {
|
||||
var currentNick = nickService.getNickname(player.getUniqueId());
|
||||
if (currentNick.isPresent()) {
|
||||
completions.add(currentNick.get());
|
||||
}
|
||||
}
|
||||
|
||||
completions.add("&#FF0000Красный");
|
||||
completions.add("�FF00Зелёный");
|
||||
completions.add("Русский_Ник");
|
||||
}
|
||||
|
||||
return completions;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
package com.loki.lochat.core.filter;
|
||||
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class AdvancedMessageFilter {
|
||||
|
||||
private static final Pattern URL_PATTERN = Pattern.compile(
|
||||
"(https?://)?([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
private static final Pattern IP_PATTERN = Pattern.compile(
|
||||
"\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b"
|
||||
);
|
||||
|
||||
private static final Pattern REPEAT_PATTERN = Pattern.compile(
|
||||
"(.)\\1{3,}"
|
||||
);
|
||||
|
||||
private final FileConfiguration config;
|
||||
private final List<String> whitelistedDomains;
|
||||
private final List<String> blacklistedDomains;
|
||||
|
||||
public AdvancedMessageFilter(FileConfiguration config) {
|
||||
this.config = config;
|
||||
this.whitelistedDomains = config.getStringList("filters.url.whitelist");
|
||||
this.blacklistedDomains = config.getStringList("filters.url.blacklist");
|
||||
}
|
||||
|
||||
public FilterResult filterMessage(Player player, String message) {
|
||||
if (config.getBoolean("filters.url.enabled", true)) {
|
||||
FilterResult urlResult = filterUrls(player, message);
|
||||
if (!urlResult.isAllowed()) {
|
||||
return urlResult;
|
||||
}
|
||||
message = urlResult.getFilteredMessage();
|
||||
}
|
||||
|
||||
if (config.getBoolean("filters.ip.enabled", true)) {
|
||||
FilterResult ipResult = filterIPs(player, message);
|
||||
if (!ipResult.isAllowed()) {
|
||||
return ipResult;
|
||||
}
|
||||
message = ipResult.getFilteredMessage();
|
||||
}
|
||||
|
||||
if (config.getBoolean("filters.repeat.enabled", true)) {
|
||||
message = filterRepeatingChars(message);
|
||||
}
|
||||
|
||||
return new FilterResult(true, message, null);
|
||||
}
|
||||
|
||||
private FilterResult filterUrls(Player player, String message) {
|
||||
if (player.hasPermission("lochat.bypass.urlfilter")) {
|
||||
return new FilterResult(true, message, null);
|
||||
}
|
||||
|
||||
Matcher matcher = URL_PATTERN.matcher(message);
|
||||
|
||||
while (matcher.find()) {
|
||||
String url = matcher.group();
|
||||
String domain = extractDomain(url);
|
||||
|
||||
if (!whitelistedDomains.isEmpty()) {
|
||||
boolean isWhitelisted = whitelistedDomains.stream()
|
||||
.anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase()));
|
||||
|
||||
if (!isWhitelisted) {
|
||||
return new FilterResult(false, message,
|
||||
config.getString("filters.url.blocked-message", "&#FF6B6BСсылки запрещены!"));
|
||||
}
|
||||
}
|
||||
|
||||
if (!blacklistedDomains.isEmpty()) {
|
||||
boolean isBlacklisted = blacklistedDomains.stream()
|
||||
.anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase()));
|
||||
|
||||
if (isBlacklisted) {
|
||||
return new FilterResult(false, message,
|
||||
config.getString("filters.url.blocked-message", "&#FF6B6BЭта ссылка запрещена!"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new FilterResult(true, message, null);
|
||||
}
|
||||
|
||||
private FilterResult filterIPs(Player player, String message) {
|
||||
if (player.hasPermission("lochat.bypass.ipfilter")) {
|
||||
return new FilterResult(true, message, null);
|
||||
}
|
||||
|
||||
Matcher matcher = IP_PATTERN.matcher(message);
|
||||
|
||||
if (matcher.find()) {
|
||||
String replacement = config.getString("filters.ip.replacement", "[IP скрыт]");
|
||||
String filtered = matcher.replaceAll(replacement);
|
||||
|
||||
if (config.getBoolean("filters.ip.block", false)) {
|
||||
return new FilterResult(false, message,
|
||||
config.getString("filters.ip.blocked-message", "&#FF6B6BIP адреса запрещены!"));
|
||||
}
|
||||
|
||||
return new FilterResult(true, filtered, null);
|
||||
}
|
||||
|
||||
return new FilterResult(true, message, null);
|
||||
}
|
||||
|
||||
private String filterRepeatingChars(String message) {
|
||||
int maxRepeats = config.getInt("filters.repeat.max-repeats", 3);
|
||||
|
||||
Matcher matcher = REPEAT_PATTERN.matcher(message);
|
||||
StringBuffer result = new StringBuffer();
|
||||
|
||||
while (matcher.find()) {
|
||||
String repeated = matcher.group(1);
|
||||
String replacement = repeated.repeat(Math.min(maxRepeats, matcher.group().length()));
|
||||
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
|
||||
}
|
||||
matcher.appendTail(result);
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private String extractDomain(String url) {
|
||||
url = url.replaceFirst("^https?://", "");
|
||||
int slashIndex = url.indexOf('/');
|
||||
if (slashIndex != -1) {
|
||||
url = url.substring(0, slashIndex);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
public static class FilterResult {
|
||||
private final boolean allowed;
|
||||
private final String filteredMessage;
|
||||
private final String blockReason;
|
||||
|
||||
public FilterResult(boolean allowed, String filteredMessage, String blockReason) {
|
||||
this.allowed = allowed;
|
||||
this.filteredMessage = filteredMessage;
|
||||
this.blockReason = blockReason;
|
||||
}
|
||||
|
||||
public boolean isAllowed() {
|
||||
return allowed;
|
||||
}
|
||||
|
||||
public String getFilteredMessage() {
|
||||
return filteredMessage;
|
||||
}
|
||||
|
||||
public String getBlockReason() {
|
||||
return blockReason;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,25 +9,29 @@ import org.bukkit.plugin.java.JavaPlugin;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Реестр сервисов - Dependency Injection контейнер (SOLID DIP)
|
||||
*/
|
||||
public class ServiceRegistry {
|
||||
private final Map<Class<?>, Object> services = new HashMap<>();
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
public ServiceRegistry(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
registerServices();
|
||||
init();
|
||||
}
|
||||
|
||||
public ServiceRegistry(JavaPlugin plugin, ConfigManager configManager, MessageConfig messageConfig) {
|
||||
this.plugin = plugin;
|
||||
initWithDeps(configManager, messageConfig);
|
||||
}
|
||||
|
||||
private void init() {
|
||||
registerServices();
|
||||
}
|
||||
|
||||
private void initWithDeps(ConfigManager configManager, MessageConfig messageConfig) {
|
||||
registerServicesWithDeps(configManager, messageConfig);
|
||||
}
|
||||
|
||||
private void registerServices() {
|
||||
// Регистрируем все сервисы (базовая версия)
|
||||
register(ChatService.class, new ChatServiceImpl(plugin, this));
|
||||
register(MuteService.class, new MuteServiceImpl(plugin));
|
||||
register(CooldownService.class, new CooldownServiceImpl());
|
||||
|
|
@ -36,14 +40,12 @@ public class ServiceRegistry {
|
|||
}
|
||||
|
||||
private void registerServicesWithDeps(ConfigManager configManager, MessageConfig messageConfig) {
|
||||
// Регистрируем все сервисы с зависимостями
|
||||
register(ChatService.class, new ChatServiceImpl(plugin, this));
|
||||
register(MuteService.class, new MuteServiceImpl(plugin));
|
||||
register(CooldownService.class, new CooldownServiceImpl());
|
||||
register(MessageService.class, new MessageServiceImpl(plugin, this));
|
||||
register(PlayerDataService.class, new PlayerDataServiceImpl(plugin));
|
||||
|
||||
// Новые сервисы
|
||||
register(PMService.class, new PMServiceImpl());
|
||||
register(IgnoreService.class, new IgnoreServiceImpl(plugin));
|
||||
register(SpyService.class, new SpyServiceImpl(plugin, messageConfig));
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.loki.lochat.listener;
|
||||
|
||||
import com.loki.lochat.api.service.MessageService;
|
||||
import com.loki.lochat.core.filter.AdvancedMessageFilter;
|
||||
import com.loki.lochat.core.registry.ServiceRegistry;
|
||||
import com.loki.lochat.util.FoliaUtil;
|
||||
import com.loki.lochat.utils.MentionHandler;
|
||||
import com.loki.lochat.utils.TextFormatter;
|
||||
import io.papermc.paper.event.player.AsyncChatEvent;
|
||||
import io.papermc.paper.chat.ChatRenderer;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
|
|
@ -12,24 +14,23 @@ import net.kyori.adventure.text.event.HoverEvent;
|
|||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* Слушатель событий чата с Paper Chat API
|
||||
* Поддерживает hover tooltips, click actions, mentions
|
||||
*/
|
||||
public class ChatEventListener implements Listener {
|
||||
private final JavaPlugin plugin;
|
||||
private final MessageService messageService;
|
||||
private final AdvancedMessageFilter advancedFilter;
|
||||
private final MentionHandler mentionHandler;
|
||||
|
||||
public ChatEventListener(JavaPlugin plugin, ServiceRegistry registry) {
|
||||
this.plugin = plugin;
|
||||
this.messageService = registry.get(MessageService.class);
|
||||
this.advancedFilter = new AdvancedMessageFilter(plugin.getConfig());
|
||||
this.mentionHandler = new MentionHandler(plugin);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
|
|
@ -37,7 +38,6 @@ public class ChatEventListener implements Listener {
|
|||
Player sender = event.getPlayer();
|
||||
String message = PlainTextComponentSerializer.plainText().serialize(event.message());
|
||||
|
||||
// Проверяем фильтры (мут, кулдаун)
|
||||
boolean allowed = messageService.processMessage(sender, message);
|
||||
|
||||
if (!allowed) {
|
||||
|
|
@ -45,13 +45,20 @@ public class ChatEventListener implements Listener {
|
|||
return;
|
||||
}
|
||||
|
||||
// Устанавливаем кастомный рендерер
|
||||
AdvancedMessageFilter.FilterResult filterResult = advancedFilter.filterMessage(sender, message);
|
||||
|
||||
if (!filterResult.isAllowed()) {
|
||||
event.setCancelled(true);
|
||||
sender.sendMessage(com.loki.lochat.utils.ChatFormatter.parse(filterResult.getBlockReason()));
|
||||
return;
|
||||
}
|
||||
|
||||
String filteredMessage = filterResult.getFilteredMessage();
|
||||
event.message(Component.text(filteredMessage));
|
||||
|
||||
event.renderer(new EnhancedChatRenderer(sender, event.message()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Продвинутый рендерер чата с hover/click/mentions
|
||||
*/
|
||||
private class EnhancedChatRenderer implements ChatRenderer {
|
||||
private final Player sender;
|
||||
private final Component originalMessage;
|
||||
|
|
@ -63,47 +70,31 @@ public class ChatEventListener implements Listener {
|
|||
|
||||
@Override
|
||||
public Component render(Player source, Component sourceDisplayName, Component message, Audience viewer) {
|
||||
// Получаем конфиг формата
|
||||
String format = plugin.getConfig().getString("chat.global.format", "{prefix} {player}: {message}");
|
||||
String prefix = plugin.getConfig().getString("chat.global.prefix", "[G]");
|
||||
|
||||
// Создаём компонент с hover и click
|
||||
Component playerComponent = createPlayerComponent(source, sourceDisplayName);
|
||||
|
||||
// Обрабатываем сообщение
|
||||
Component processedMessage = message;
|
||||
|
||||
// 1. Обрабатываем URL (делаем кликабельными)
|
||||
processedMessage = TextFormatter.formatMarkdown(processedMessage);
|
||||
processedMessage = processUrls(processedMessage);
|
||||
|
||||
// 2. Обрабатываем эмодзи шорткоды (:smile: -> 😊)
|
||||
processedMessage = processEmojis(processedMessage);
|
||||
|
||||
// 3. Обрабатываем упоминания если viewer это Player
|
||||
if (viewer instanceof Player viewerPlayer) {
|
||||
processedMessage = processMentions(processedMessage, viewerPlayer);
|
||||
processedMessage = mentionHandler.processMentions(processedMessage, source, viewerPlayer);
|
||||
}
|
||||
|
||||
// Собираем финальное сообщение
|
||||
return Component.text(prefix + " ")
|
||||
.append(playerComponent)
|
||||
.append(Component.text(": "))
|
||||
.append(processedMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создаёт компонент имени игрока с hover и click
|
||||
*/
|
||||
private Component createPlayerComponent(Player player, Component displayName) {
|
||||
// Проверяем включен ли hover
|
||||
if (!plugin.getConfig().getBoolean("chat.hover.enabled", true)) {
|
||||
return displayName.clickEvent(ClickEvent.suggestCommand("/msg " + player.getName() + " "));
|
||||
}
|
||||
|
||||
// Получаем формат из конфига
|
||||
java.util.List<String> hoverLines = plugin.getConfig().getStringList("chat.hover.format");
|
||||
if (hoverLines.isEmpty()) {
|
||||
// Дефолтный формат если конфиг пустой
|
||||
hoverLines = java.util.Arrays.asList(
|
||||
"<gray>Игрок: <white>{player}",
|
||||
"<gray>Мир: <green>{world}",
|
||||
|
|
@ -112,12 +103,10 @@ public class ChatEventListener implements Listener {
|
|||
);
|
||||
}
|
||||
|
||||
// Строим hover text
|
||||
net.kyori.adventure.text.TextComponent.Builder hoverBuilder = Component.text();
|
||||
for (int i = 0; i < hoverLines.size(); i++) {
|
||||
String line = hoverLines.get(i);
|
||||
|
||||
// Заменяем плейсхолдеры
|
||||
line = line.replace("{player}", player.getName())
|
||||
.replace("{world}", player.getWorld().getName())
|
||||
.replace("{ping}", String.valueOf(player.getPing()))
|
||||
|
|
@ -125,62 +114,28 @@ public class ChatEventListener implements Listener {
|
|||
.replace("{health}", String.valueOf(Math.round(player.getHealth())))
|
||||
.replace("{food}", String.valueOf(player.getFoodLevel()));
|
||||
|
||||
// Парсим MiniMessage
|
||||
Component lineComponent = com.loki.lochat.utils.ChatFormatter.parse(line);
|
||||
hoverBuilder.append(lineComponent);
|
||||
|
||||
// Добавляем перенос строки если не последняя
|
||||
if (i < hoverLines.size() - 1) {
|
||||
hoverBuilder.append(Component.newline());
|
||||
}
|
||||
}
|
||||
|
||||
// Click: открываем /msg
|
||||
return displayName
|
||||
.hoverEvent(HoverEvent.showText(hoverBuilder.build()))
|
||||
.clickEvent(ClickEvent.suggestCommand("/msg " + player.getName() + " "));
|
||||
}
|
||||
|
||||
/**
|
||||
* Обрабатывает упоминания (@player) в сообщении
|
||||
*/
|
||||
private Component processMentions(Component message, Player viewer) {
|
||||
String plainText = PlainTextComponentSerializer.plainText().serialize(message);
|
||||
|
||||
// Проверяем упоминание текущего игрока
|
||||
if (plainText.toLowerCase().contains("@" + viewer.getName().toLowerCase())) {
|
||||
// Подсвечиваем упоминание
|
||||
Component highlighted = message.replaceText(builder ->
|
||||
builder.matchLiteral("@" + viewer.getName())
|
||||
.replacement(Component.text("@" + viewer.getName(), NamedTextColor.YELLOW, TextDecoration.BOLD))
|
||||
);
|
||||
|
||||
// Воспроизводим звук уведомления
|
||||
FoliaUtil.runEntityTask(plugin, viewer, () -> {
|
||||
viewer.playSound(viewer.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.5f);
|
||||
});
|
||||
|
||||
return highlighted;
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обрабатывает URL в сообщении (делает кликабельными)
|
||||
*/
|
||||
private Component processUrls(Component message) {
|
||||
String plainText = PlainTextComponentSerializer.plainText().serialize(message);
|
||||
|
||||
// Простой regex для URL
|
||||
String urlPattern = "(https?://[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+)";
|
||||
String urlPattern = "(https?://[\\w\\-._~:/?#\\[\\]@!35538117-c5a8-4bfe-b9f2-b0dcf9374222'()*+,;=%]+)";
|
||||
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(urlPattern);
|
||||
java.util.regex.Matcher matcher = pattern.matcher(plainText);
|
||||
|
||||
Component result = message;
|
||||
while (matcher.find()) {
|
||||
String url = matcher.group(1);
|
||||
// Делаем URL кликабельным с hover
|
||||
Component urlComponent = Component.text(url, NamedTextColor.AQUA, TextDecoration.UNDERLINED)
|
||||
.hoverEvent(HoverEvent.showText(Component.text("Открыть: " + url, NamedTextColor.GRAY)))
|
||||
.clickEvent(ClickEvent.openUrl(url));
|
||||
|
|
@ -193,35 +148,28 @@ public class ChatEventListener implements Listener {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обрабатывает эмодзи шорткоды (:smile: -> ☺)
|
||||
* Использует только MC-совместимые Unicode символы
|
||||
*/
|
||||
private Component processEmojis(Component message) {
|
||||
Component result = message;
|
||||
|
||||
// MC-совместимые Unicode эмодзи
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":smile:").replacement("☺"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":heart:").replacement("♥"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":fire:").replacement("※"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":heart:").replacement("❤"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":star:").replacement("★"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":check:").replacement("✓"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":cross:").replacement("✗"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":skull:").replacement("☠"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":check:").replacement("✔"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":cross:").replacement("✖"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":arrow:").replacement("→"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":skull:").replacement("☠"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":note:").replacement("♪"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":sun:").replacement("☀"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":moon:").replacement("☾"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":peace:").replacement("☮"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":yin:").replacement("☯"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":flower:").replacement("✿"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":moon:").replacement("☽"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":snowflake:").replacement("❄"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":lightning:").replacement("⚡"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":crown:").replacement("♔"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":sword:").replacement("⚔"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":shield:").replacement("⛨"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":fire:").replacement("🔥"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":diamond:").replacement("◆"));
|
||||
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":sword:").replacement("⚔"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":pickaxe:").replacement("⛏"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":bow:").replacement("🏹"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":shield:").replacement("🛡"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":potion:").replacement("🧪"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":book:").replacement("📖"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":crown:").replacement("👑"));
|
||||
result = result.replaceText(builder -> builder.matchLiteral(":gem:").replacement("💎"));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ public class CustomCommandManager {
|
|||
public CustomCommandManager(LoChat plugin) {
|
||||
this.plugin = plugin;
|
||||
this.commands = new HashMap<>();
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
loadCommands();
|
||||
}
|
||||
|
||||
|
|
|
|||
97
src/main/java/com/loki/lochat/managers/EmojiManager.java
Normal file
97
src/main/java/com/loki/lochat/managers/EmojiManager.java
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
package com.loki.lochat.managers;
|
||||
|
||||
import com.loki.lochat.LoChat;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class EmojiManager {
|
||||
|
||||
private final LoChat plugin;
|
||||
private final Map<String, String> emojis = new HashMap<>();
|
||||
private boolean enabled;
|
||||
private boolean requirePermission;
|
||||
|
||||
public EmojiManager(LoChat plugin) {
|
||||
this.plugin = plugin;
|
||||
load();
|
||||
}
|
||||
|
||||
public void load() {
|
||||
File file = new File(plugin.getDataFolder(), "emojis.yml");
|
||||
if (!file.exists()) {
|
||||
plugin.saveResource("emojis.yml", false);
|
||||
}
|
||||
|
||||
FileConfiguration config = YamlConfiguration.loadConfiguration(file);
|
||||
emojis.clear();
|
||||
|
||||
// Настройки
|
||||
enabled = config.getBoolean("settings.enabled", true);
|
||||
requirePermission = config.getBoolean("settings.require-permission", false);
|
||||
|
||||
// Загружаем все категории
|
||||
for (String category : config.getKeys(false)) {
|
||||
if (category.equals("settings")) continue;
|
||||
|
||||
ConfigurationSection section = config.getConfigurationSection(category);
|
||||
if (section != null) {
|
||||
for (String key : section.getKeys(false)) {
|
||||
String value = section.getString(key);
|
||||
if (value != null) {
|
||||
emojis.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plugin.getLogger().info("Загружено " + emojis.size() + " смайликов");
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
load();
|
||||
}
|
||||
|
||||
/**
|
||||
* Заменяет смайлики в сообщении
|
||||
*/
|
||||
public String process(String message, Player player) {
|
||||
if (!enabled) return message;
|
||||
|
||||
// Проверка права
|
||||
if (requirePermission && player != null && !player.hasPermission("chat.emoji.use")) {
|
||||
return message;
|
||||
}
|
||||
|
||||
String result = message;
|
||||
for (Map.Entry<String, String> entry : emojis.entrySet()) {
|
||||
result = result.replace(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public String process(String message) {
|
||||
if (!enabled) return message;
|
||||
|
||||
String result = message;
|
||||
for (Map.Entry<String, String> entry : emojis.entrySet()) {
|
||||
result = result.replace(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, String> getEmojis() {
|
||||
return emojis;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
}
|
||||
106
src/main/java/com/loki/lochat/utils/MentionHandler.java
Normal file
106
src/main/java/com/loki/lochat/utils/MentionHandler.java
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
package com.loki.lochat.utils;
|
||||
|
||||
import com.loki.lochat.util.FoliaUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class MentionHandler {
|
||||
|
||||
private static final Pattern EVERYONE_PATTERN = Pattern.compile("@everyone\\b");
|
||||
private static final Pattern HERE_PATTERN = Pattern.compile("@here\\b");
|
||||
private static final Pattern ROLE_PATTERN = Pattern.compile("@role:([\\w.]+)");
|
||||
private static final Pattern PLAYER_PATTERN = Pattern.compile("@(\\w+)");
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
public MentionHandler(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public Component processMentions(Component message, Player sender, Player viewer) {
|
||||
String plainText = PlainTextComponentSerializer.plainText().serialize(message);
|
||||
List<Player> mentionedPlayers = new ArrayList<>();
|
||||
|
||||
if (sender.hasPermission("lochat.mention.everyone")) {
|
||||
Matcher everyoneMatcher = EVERYONE_PATTERN.matcher(plainText);
|
||||
if (everyoneMatcher.find()) {
|
||||
mentionedPlayers.addAll(Bukkit.getOnlinePlayers());
|
||||
message = highlightMention(message, "@everyone");
|
||||
}
|
||||
}
|
||||
|
||||
if (sender.hasPermission("lochat.mention.here")) {
|
||||
Matcher hereMatcher = HERE_PATTERN.matcher(plainText);
|
||||
if (hereMatcher.find()) {
|
||||
mentionedPlayers.addAll(Bukkit.getOnlinePlayers());
|
||||
message = highlightMention(message, "@here");
|
||||
}
|
||||
}
|
||||
|
||||
if (sender.hasPermission("lochat.mention.role")) {
|
||||
Matcher roleMatcher = ROLE_PATTERN.matcher(plainText);
|
||||
while (roleMatcher.find()) {
|
||||
String permission = roleMatcher.group(1);
|
||||
String fullMention = "@role:" + permission;
|
||||
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
if (player.hasPermission(permission)) {
|
||||
mentionedPlayers.add(player);
|
||||
}
|
||||
}
|
||||
|
||||
message = highlightMention(message, fullMention);
|
||||
}
|
||||
}
|
||||
|
||||
Matcher playerMatcher = PLAYER_PATTERN.matcher(plainText);
|
||||
while (playerMatcher.find()) {
|
||||
String playerName = playerMatcher.group(1);
|
||||
Player mentioned = Bukkit.getPlayerExact(playerName);
|
||||
|
||||
if (mentioned != null && mentioned.isOnline()) {
|
||||
mentionedPlayers.add(mentioned);
|
||||
message = highlightMention(message, "@" + playerName);
|
||||
}
|
||||
}
|
||||
|
||||
if (mentionedPlayers.contains(viewer)) {
|
||||
playMentionSound(viewer);
|
||||
message = highlightForViewer(message, viewer);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
private Component highlightMention(Component message, String mention) {
|
||||
return message.replaceText(builder ->
|
||||
builder.matchLiteral(mention)
|
||||
.replacement(Component.text(mention, NamedTextColor.YELLOW, TextDecoration.BOLD))
|
||||
);
|
||||
}
|
||||
|
||||
private Component highlightForViewer(Component message, Player viewer) {
|
||||
String viewerMention = "@" + viewer.getName();
|
||||
return message.replaceText(builder ->
|
||||
builder.matchLiteral(viewerMention)
|
||||
.replacement(Component.text(viewerMention, NamedTextColor.GOLD, TextDecoration.BOLD))
|
||||
);
|
||||
}
|
||||
|
||||
private void playMentionSound(Player player) {
|
||||
FoliaUtil.runEntityTask(plugin, player, () -> {
|
||||
player.playSound(player.getLocation(), Sound.ENTITY_EXPERIENCE_ORB_PICKUP, 1.0f, 1.5f);
|
||||
});
|
||||
}
|
||||
}
|
||||
95
src/main/java/com/loki/lochat/utils/TextFormatter.java
Normal file
95
src/main/java/com/loki/lochat/utils/TextFormatter.java
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
package com.loki.lochat.utils;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class TextFormatter {
|
||||
|
||||
private static final Pattern BOLD_PATTERN = Pattern.compile("\\*\\*(.+?)\\*\\*");
|
||||
private static final Pattern ITALIC_PATTERN = Pattern.compile("\\*(.+?)\\*");
|
||||
private static final Pattern UNDERLINE_PATTERN = Pattern.compile("__(.+?)__");
|
||||
private static final Pattern STRIKETHROUGH_PATTERN = Pattern.compile("~~(.+?)~~");
|
||||
private static final Pattern CODE_PATTERN = Pattern.compile("`(.+?)`");
|
||||
|
||||
public static Component formatMarkdown(Component message) {
|
||||
String text = PlainTextComponentSerializer.plainText().serialize(message);
|
||||
|
||||
text = formatBold(text);
|
||||
text = formatItalic(text);
|
||||
text = formatUnderline(text);
|
||||
text = formatStrikethrough(text);
|
||||
text = formatCode(text);
|
||||
|
||||
return ChatFormatter.parse(text);
|
||||
}
|
||||
|
||||
private static String formatBold(String text) {
|
||||
Matcher matcher = BOLD_PATTERN.matcher(text);
|
||||
StringBuffer result = new StringBuffer();
|
||||
|
||||
while (matcher.find()) {
|
||||
String content = matcher.group(1);
|
||||
matcher.appendReplacement(result, "<bold>" + content + "</bold>");
|
||||
}
|
||||
matcher.appendTail(result);
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static String formatItalic(String text) {
|
||||
Matcher matcher = ITALIC_PATTERN.matcher(text);
|
||||
StringBuffer result = new StringBuffer();
|
||||
|
||||
while (matcher.find()) {
|
||||
String content = matcher.group(1);
|
||||
if (!content.startsWith("*") && !content.endsWith("*")) {
|
||||
matcher.appendReplacement(result, "<italic>" + content + "</italic>");
|
||||
}
|
||||
}
|
||||
matcher.appendTail(result);
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static String formatUnderline(String text) {
|
||||
Matcher matcher = UNDERLINE_PATTERN.matcher(text);
|
||||
StringBuffer result = new StringBuffer();
|
||||
|
||||
while (matcher.find()) {
|
||||
String content = matcher.group(1);
|
||||
matcher.appendReplacement(result, "<underlined>" + content + "</underlined>");
|
||||
}
|
||||
matcher.appendTail(result);
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static String formatStrikethrough(String text) {
|
||||
Matcher matcher = STRIKETHROUGH_PATTERN.matcher(text);
|
||||
StringBuffer result = new StringBuffer();
|
||||
|
||||
while (matcher.find()) {
|
||||
String content = matcher.group(1);
|
||||
matcher.appendReplacement(result, "<strikethrough>" + content + "</strikethrough>");
|
||||
}
|
||||
matcher.appendTail(result);
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private static String formatCode(String text) {
|
||||
Matcher matcher = CODE_PATTERN.matcher(text);
|
||||
StringBuffer result = new StringBuffer();
|
||||
|
||||
while (matcher.find()) {
|
||||
String content = matcher.group(1);
|
||||
matcher.appendReplacement(result, "󙀸`" + content + "`");
|
||||
}
|
||||
matcher.appendTail(result);
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue