refactor: Remove emoji system and chat filters, implement SOLID architecture

This commit is contained in:
loki5512344 2026-02-23 12:10:51 +01:00
parent 16a5e09118
commit 74482851aa
72 changed files with 2654 additions and 1287 deletions

View file

@ -1,110 +1,99 @@
package com.loki.lochat;
import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.api.service.MuteService;
import com.loki.lochat.commands.*;
import com.loki.lochat.config.ConfigManager;
import com.loki.lochat.config.MessageConfig;
import com.loki.lochat.core.registry.ServiceRegistry;
import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.integrations.LibertyBansHook;
import com.loki.lochat.integrations.PlaceholderAPIHook;
import com.loki.lochat.listeners.ChatListener;
import com.loki.lochat.managers.*;
import com.loki.lochat.listener.ChatEventListener;
import com.loki.lochat.listener.PlayerEventListener;
import com.loki.lochat.managers.AutoMessageManager;
import com.loki.lochat.managers.CustomCommandManager;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Главный класс плагина LoChat
* Использует SOLID архитектуру с Dependency Injection через ServiceRegistry
*/
public final class LoChat extends JavaPlugin {
private static LoChat instance;
private ServiceRegistry serviceRegistry;
private ConfigManager configManager;
private MessageConfig messageConfig;
private ChatManager chatManager;
private PMManager pmManager;
private IgnoreManager ignoreManager;
private AutoMessageManager autoMessageManager;
private AntiSpamManager antiSpamManager;
private CooldownManager cooldownManager;
private MentionManager mentionManager;
private SpyManager spyManager;
private EmojiManager emojiManager;
private CustomCommandManager customCommandManager;
private GradientModule gradientModule;
private LibertyBansHook libertyBansHook;
private MuteManager muteManager;
private HeadEmojiManager headEmojiManager;
private com.loki.lochat.integrations.SkinsRestorerHook skinsRestorerHook;
private AutoMessageManager autoMessageManager;
private CustomCommandManager customCommandManager;
@Override
public void onEnable() {
instance = this;
getLogger().info("Загрузка LoChat v" + getDescription().getVersion() + " для Paper/Folia " + Bukkit.getMinecraftVersion() + "...");
// Инициализация конфигов
// Конфиги
configManager = new ConfigManager(this);
messageConfig = new MessageConfig(this);
// Инициализация менеджеров
emojiManager = new EmojiManager(this);
ignoreManager = new IgnoreManager(this);
pmManager = new PMManager();
antiSpamManager = new AntiSpamManager(this);
cooldownManager = new CooldownManager();
mentionManager = new MentionManager(this);
spyManager = new SpyManager(this);
// Service Registry (SOLID DI) - с зависимостями
serviceRegistry = new ServiceRegistry(this, configManager, messageConfig);
getLogger().info("ServiceRegistry инициализирован (SOLID DI)");
// Менеджеры
customCommandManager = new CustomCommandManager(this);
muteManager = new MuteManager(this);
chatManager = new ChatManager(this);
autoMessageManager = new AutoMessageManager(this);
// Инициализация интеграций
skinsRestorerHook = new com.loki.lochat.integrations.SkinsRestorerHook(this);
headEmojiManager = new com.loki.lochat.managers.HeadEmojiManager(this);
// Интеграции
initIntegrations();
// Инициализация модуля градиентов (интегрированный LoPreff)
gradientModule = new GradientModule(this);
if (gradientModule.init()) {
getLogger().info("Gradient модуль (LoPreff) загружен!");
}
// Инициализация LibertyBans интеграции
libertyBansHook = new LibertyBansHook(this);
// Регистрация команд
// Команды и слушатели
registerCommands();
registerListeners();
// Регистрация листенеров
getServer().getPluginManager().registerEvents(new ChatListener(this), this);
// Запуск автосообщений
// Автосообщения
autoMessageManager.start();
// PlaceholderAPI
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
new PlaceholderAPIHook(this).register();
getLogger().info("PlaceholderAPI подключен!");
}
getLogger().info("LoChat успешно запущен!");
}
@Override
public void onDisable() {
if (autoMessageManager != null) {
autoMessageManager.stop();
}
if (ignoreManager != null) {
ignoreManager.save();
}
if (gradientModule != null) {
gradientModule.shutdown();
}
if (muteManager != null) {
muteManager.save();
}
if (autoMessageManager != null) autoMessageManager.stop();
// Сохранение данных через сервисы
IgnoreService ignoreService = serviceRegistry.get(IgnoreService.class);
if (ignoreService != null) ignoreService.save();
MuteService muteService = serviceRegistry.get(MuteService.class);
if (muteService != null) muteService.save();
if (gradientModule != null) gradientModule.shutdown();
getLogger().info("LoChat отключен!");
}
private void initIntegrations() {
// Gradient модуль
gradientModule = new GradientModule(this);
if (gradientModule.init()) {
getLogger().info("Gradient модуль (LoPreff) загружен!");
}
// LibertyBans
new LibertyBansHook(this);
// PlaceholderAPI
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
new PlaceholderAPIHook(this).register();
getLogger().info("PlaceholderAPI подключен!");
}
}
private void registerCommands() {
getCommand("g").setExecutor(new GlobalChatCommand(this));
getCommand("globalchat").setExecutor(new GlobalChatCommand(this));
getCommand("l").setExecutor(new LocalChatCommand(this));
getCommand("msg").setExecutor(new MsgCommand(this));
getCommand("reply").setExecutor(new ReplyCommand(this));
@ -114,123 +103,52 @@ public final class LoChat extends JavaPlugin {
getCommand("chatspy").setExecutor(new ChatSpyCommand(this));
getCommand("clearchat").setExecutor(new ClearChatCommand(this));
LoChatCommand loChatCommand = new LoChatCommand(this);
getCommand("lochat").setExecutor(loChatCommand);
getCommand("lochat").setTabCompleter(loChatCommand);
LoChatCommand loChatCmd = new LoChatCommand(this);
getCommand("lochat").setExecutor(loChatCmd);
getCommand("lochat").setTabCompleter(loChatCmd);
// Команды мутов
MuteCommand muteCommand = new MuteCommand(this);
getCommand("lmute").setExecutor(muteCommand);
getCommand("lmute").setTabCompleter(muteCommand);
MuteCommand muteCmd = new MuteCommand(this);
getCommand("lmute").setExecutor(muteCmd);
getCommand("lmute").setTabCompleter(muteCmd);
UnmuteCommand unmuteCommand = new UnmuteCommand(this);
getCommand("lunmute").setExecutor(unmuteCommand);
getCommand("lunmute").setTabCompleter(unmuteCommand);
UnmuteCommand unmuteCmd = new UnmuteCommand(this);
getCommand("lunmute").setExecutor(unmuteCmd);
getCommand("lunmute").setTabCompleter(unmuteCmd);
getCommand("lmutelist").setExecutor(new MuteListCommand(this));
MuteHistoryCommand muteHistoryCommand = new MuteHistoryCommand(this);
getCommand("lmutehistory").setExecutor(muteHistoryCommand);
getCommand("lmutehistory").setTabCompleter(muteHistoryCommand);
MuteHistoryCommand historyCmd = new MuteHistoryCommand(this);
getCommand("lmutehistory").setExecutor(historyCmd);
getCommand("lmutehistory").setTabCompleter(historyCmd);
MuteBlameCommand muteBlameCommand = new MuteBlameCommand(this);
getCommand("lmuteblame").setExecutor(muteBlameCommand);
getCommand("lmuteblame").setTabCompleter(muteBlameCommand);
MuteBlameCommand blameCmd = new MuteBlameCommand(this);
getCommand("lmuteblame").setExecutor(blameCmd);
getCommand("lmuteblame").setTabCompleter(blameCmd);
// Команда настройки очистки чата
ClearChatConfigCommand clearChatConfigCommand = new ClearChatConfigCommand(this);
getCommand("clearchatconfig").setExecutor(clearChatConfigCommand);
getCommand("clearchatconfig").setTabCompleter(clearChatConfigCommand);
// Внутренняя команда для информации об игроке
getCommand("lochat").setExecutor(new PlayerInfoCommand(this));
ClearChatConfigCommand clearCfgCmd = new ClearChatConfigCommand(this);
getCommand("clearchatconfig").setExecutor(clearCfgCmd);
getCommand("clearchatconfig").setTabCompleter(clearCfgCmd);
}
private void registerListeners() {
getServer().getPluginManager().registerEvents(new ChatEventListener(this, serviceRegistry), this);
getServer().getPluginManager().registerEvents(new PlayerEventListener(serviceRegistry), this);
getLogger().info("Слушатели зарегистрированы (новая архитектура)");
}
public void reload() {
configManager.reload();
messageConfig.reload();
emojiManager.reload();
autoMessageManager.stop();
autoMessageManager.start();
if (gradientModule != null) {
gradientModule.reload();
}
if (customCommandManager != null) {
customCommandManager.reload();
}
}
public static LoChat getInstance() {
return instance;
}
public ConfigManager getConfigManager() {
return configManager;
}
public MessageConfig getMessageConfig() {
return messageConfig;
}
public ChatManager getChatManager() {
return chatManager;
}
public PMManager getPmManager() {
return pmManager;
}
public IgnoreManager getIgnoreManager() {
return ignoreManager;
}
public AutoMessageManager getAutoMessageManager() {
return autoMessageManager;
}
public AntiSpamManager getAntiSpamManager() {
return antiSpamManager;
}
public CooldownManager getCooldownManager() {
return cooldownManager;
}
public MentionManager getMentionManager() {
return mentionManager;
}
public SpyManager getSpyManager() {
return spyManager;
}
public EmojiManager getEmojiManager() {
return emojiManager;
}
public GradientModule getGradientModule() {
return gradientModule;
}
public CustomCommandManager getCustomCommandManager() {
return customCommandManager;
}
public LibertyBansHook getLibertyBansHook() {
return libertyBansHook;
}
public MuteManager getMuteManager() {
return muteManager;
}
public HeadEmojiManager getHeadEmojiManager() {
return headEmojiManager;
}
public com.loki.lochat.integrations.SkinsRestorerHook getSkinsRestorerHook() {
return skinsRestorerHook;
autoMessageManager.reload();
if (gradientModule != null) gradientModule.reload();
if (customCommandManager != null) customCommandManager.reload();
}
// Геттеры
public static LoChat getInstance() { return instance; }
public ServiceRegistry getServiceRegistry() { return serviceRegistry; }
public ConfigManager getConfigManager() { return configManager; }
public MessageConfig getMessageConfig() { return messageConfig; }
public GradientModule getGradientModule() { return gradientModule; }
public CustomCommandManager getCustomCommandManager() { return customCommandManager; }
}

View file

@ -0,0 +1,13 @@
package com.loki.lochat.api;
import org.bukkit.entity.Player;
/**
* Сервис для отправки сообщений в чат
*/
public interface ChatService {
void sendGlobalMessage(Player sender, Object message);
void sendLocalMessage(Player sender, Object message);
boolean toggleGlobalChat(java.util.UUID player);
boolean isGlobalChatDisabled(java.util.UUID player);
}

View file

@ -0,0 +1,13 @@
package com.loki.lochat.api;
import java.util.UUID;
/**
* Сервис для управления кулдаунами
*/
public interface CooldownService {
boolean isOnCooldown(UUID player, String type, int cooldownSeconds);
int getRemainingCooldown(UUID player, String type, int cooldownSeconds);
void setCooldown(UUID player, String type);
void removeCooldown(UUID player);
}

View file

@ -0,0 +1,14 @@
package com.loki.lochat.api;
import org.bukkit.entity.Player;
/**
* Интерфейс для фильтрации сообщений
*/
public interface MessageFilter {
/**
* Применить фильтр к сообщению
* @return отфильтрованное сообщение или null если заблокировано
*/
Object apply(Player player, Object message, String plainMessage);
}

View file

@ -0,0 +1,15 @@
package com.loki.lochat.api;
import java.util.UUID;
/**
* Сервис для управления мутами
*/
public interface MuteService {
void mute(UUID uuid, String playerName, long duration, String reason, String mutedBy);
boolean unmute(UUID uuid, String unmutedBy);
boolean isMuted(UUID uuid);
long getRemainingTime(UUID uuid);
String formatTime(long millis);
long parseTime(String timeStr);
}

View file

@ -0,0 +1,15 @@
package com.loki.lochat.api.filter;
import com.loki.lochat.data.model.ChatMessage;
import org.bukkit.entity.Player;
/**
* Интерфейс фильтра сообщений (Chain of Responsibility)
*/
public interface MessageFilter {
/**
* Применить фильтр к сообщению
* @return true если сообщение прошло фильтр, false если заблокировано
*/
boolean apply(Player player, ChatMessage message);
}

View file

@ -0,0 +1,15 @@
package com.loki.lochat.api.service;
import org.bukkit.entity.Player;
import java.util.UUID;
/**
* Сервис для отправки сообщений в чат
*/
public interface ChatService {
void sendGlobalMessage(Player sender, Object message);
void sendLocalMessage(Player sender, Object message);
boolean toggleGlobalChat(UUID player);
boolean isGlobalChatDisabled(UUID player);
}

View file

@ -0,0 +1,13 @@
package com.loki.lochat.api.service;
import java.util.UUID;
/**
* Сервис для управления кулдаунами
*/
public interface CooldownService {
boolean isOnCooldown(UUID player, String type, int cooldownSeconds);
int getRemainingCooldown(UUID player, String type, int cooldownSeconds);
void setCooldown(UUID player, String type);
void removeCooldown(UUID player);
}

View file

@ -0,0 +1,51 @@
package com.loki.lochat.api.service;
import java.util.Set;
import java.util.UUID;
/**
* Сервис для управления списком игнорируемых игроков
*/
public interface IgnoreService {
/**
* Проверяет, игнорирует ли игрок другого
*/
boolean isIgnoring(UUID player, UUID target);
/**
* Добавляет игрока в список игнорируемых
* @return true если успешно добавлен
*/
boolean addIgnore(UUID player, UUID target);
/**
* Удаляет игрока из списка игнорируемых
* @return true если успешно удалён
*/
boolean removeIgnore(UUID player, UUID target);
/**
* Получает список всех игнорируемых игроков
*/
Set<UUID> getIgnoredPlayers(UUID player);
/**
* Получает количество игнорируемых игроков
*/
int getIgnoredCount(UUID player);
/**
* Очищает список игнорируемых для игрока
*/
void clearIgnores(UUID player);
/**
* Сохраняет данные на диск
*/
void save();
/**
* Загружает данные с диска
*/
void load();
}

View file

@ -0,0 +1,37 @@
package com.loki.lochat.api.service;
import org.bukkit.entity.Player;
import java.util.Set;
/**
* Сервис для обработки упоминаний игроков в чате
*/
public interface MentionService {
/**
* Обрабатывает упоминания в сообщении
* @return сообщение с подсвеченными никами
*/
String processMentions(String message, Set<Player> mentionedPlayers);
/**
* Создаёт персонализированное сообщение для игрока
* Если его ник упомянут — выделяет его особым образом
*/
String getPersonalizedMessage(String message, Player viewer);
/**
* Проверяет, упомянут ли игрок в сообщении (по нику без @)
*/
boolean isPlayerMentioned(String message, Player player);
/**
* Уведомляет упомянутых игроков звуком
*/
void notifyMentioned(Set<Player> players);
/**
* Уведомляет игрока если его ник упомянут (без @)
*/
void notifyIfMentioned(String message, Player viewer, Player sender);
}

View file

@ -0,0 +1,14 @@
package com.loki.lochat.api.service;
import org.bukkit.entity.Player;
/**
* Сервис обработки сообщений
*/
public interface MessageService {
/**
* Обработать входящее сообщение чата
* @return true если сообщение обработано успешно
*/
boolean processMessage(Player player, String message);
}

View file

@ -0,0 +1,42 @@
package com.loki.lochat.api.service;
import com.loki.lochat.data.model.MuteData;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* Сервис для управления мутами
*/
public interface MuteService {
// Основные операции
void mute(UUID uuid, String playerName, long duration, String reason, String mutedBy);
boolean unmute(UUID uuid, String unmutedBy);
boolean isMuted(UUID uuid);
MuteData getMuteData(UUID uuid);
long getRemainingTime(UUID uuid);
// Утилиты времени
String formatTime(long millis);
long parseTime(String timeStr);
// Права доступа
long getMaxDuration(Player player);
boolean canMuteForDuration(Player player, long duration);
// История
List<MuteData.MuteHistoryEntry> getPlayerHistory(UUID uuid);
List<MuteData.MuteHistoryEntry> getMutesByOperator(String operatorName);
UUID getUUIDByName(String name);
// Список активных мутов
Map<UUID, MuteData> getActiveMutes();
// Форматирование сообщений
String formatMessage(String message, String player, String operator, String duration, String reason);
// Сохранение
void save();
}

View file

@ -0,0 +1,29 @@
package com.loki.lochat.api.service;
import java.util.Optional;
import java.util.UUID;
/**
* Сервис для управления личными сообщениями
*/
public interface PMService {
/**
* Устанавливает последнего собеседника для игрока
*/
void setLastConversation(UUID player, UUID target);
/**
* Получает последнего собеседника игрока
*/
Optional<UUID> getLastConversation(UUID player);
/**
* Удаляет информацию о последнем разговоре
*/
void removeConversation(UUID player);
/**
* Проверяет, есть ли у игрока активный разговор
*/
boolean hasConversation(UUID player);
}

View file

@ -0,0 +1,11 @@
package com.loki.lochat.api.service;
import java.util.UUID;
/**
* Сервис для работы с данными игроков
*/
public interface PlayerDataService {
void clearPlayerData(UUID player);
void saveAll();
}

View file

@ -0,0 +1,35 @@
package com.loki.lochat.api.service;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
/**
* Сервис для шпионажа за чатом
*/
public interface SpyService {
/**
* Переключает режим шпиона для игрока
* @return true если шпион включен, false если выключен
*/
boolean toggleSpy(java.util.UUID player);
/**
* Проверяет, находится ли игрок в режиме шпиона
*/
boolean isSpying(java.util.UUID player);
/**
* Отправляет сообщение всем шпионам (для обычного чата)
*/
void sendToSpies(Player sender, Component message, boolean isGlobal);
/**
* Отправляет информацию о личном сообщении шпионам
*/
void broadcastPM(Player sender, Player receiver, String message);
/**
* Удаляет игрока из списка шпионов
*/
void removeSpy(java.util.UUID player);
}

View file

@ -1,6 +1,7 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.SpyService;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@ -8,12 +9,18 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Команда для переключения режима шпиона
* /chatspy
*/
public class ChatSpyCommand implements CommandExecutor {
private final LoChat plugin;
private final SpyService spyService;
public ChatSpyCommand(LoChat plugin) {
this.plugin = plugin;
this.spyService = plugin.getServiceRegistry().get(SpyService.class);
}
@Override
@ -23,7 +30,7 @@ public class ChatSpyCommand implements CommandExecutor {
return true;
}
boolean enabled = plugin.getSpyManager().toggleSpy(player.getUniqueId());
boolean enabled = spyService.toggleSpy(player.getUniqueId());
String msgKey = enabled ? "spy.enabled" : "spy.disabled";
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get(msgKey)));

View file

@ -1,6 +1,7 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.ChatService;
import com.loki.lochat.utils.ChatFormatter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
@ -10,13 +11,20 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Команда для глобального чата
* /g <сообщение> - отправить сообщение в глобальный чат
* /globalchat toggle - переключить режим глобального чата
*/
public class GlobalChatCommand implements CommandExecutor {
private final LoChat plugin;
private final ChatService chatService;
private static final MiniMessage MM = MiniMessage.miniMessage();
public GlobalChatCommand(LoChat plugin) {
this.plugin = plugin;
this.chatService = plugin.getServiceRegistry().get(ChatService.class);
}
@Override
@ -29,7 +37,7 @@ public class GlobalChatCommand implements CommandExecutor {
// /globalchat toggle
if (command.getName().equalsIgnoreCase("globalchat")) {
if (args.length > 0 && args[0].equalsIgnoreCase("toggle")) {
boolean enabled = plugin.getChatManager().toggleGlobalChat(player.getUniqueId());
boolean enabled = chatService.toggleGlobalChat(player.getUniqueId());
String msgKey = enabled ? "global.toggled-on" : "global.toggled-off";
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get(msgKey)));
} else {
@ -46,10 +54,10 @@ public class GlobalChatCommand implements CommandExecutor {
String message = String.join(" ", args);
// Конвертируем String в Component для новой сигнатуры ChatManager
// Конвертируем String в Component для новой сигнатуры ChatService
Component messageComponent = MM.deserialize(message);
plugin.getChatManager().sendGlobalMessage(player, messageComponent);
chatService.sendGlobalMessage(player, messageComponent);
return true;
}
}
}

View file

@ -1,6 +1,7 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
@ -9,12 +10,18 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Команда для добавления игрока в список игнорируемых
* /ignore <ник>
*/
public class IgnoreCommand implements CommandExecutor {
private final LoChat plugin;
private final IgnoreService ignoreService;
public IgnoreCommand(LoChat plugin) {
this.plugin = plugin;
this.ignoreService = plugin.getServiceRegistry().get(IgnoreService.class);
}
@Override
@ -40,12 +47,12 @@ public class IgnoreCommand implements CommandExecutor {
return true;
}
if (plugin.getIgnoreManager().isIgnoring(player.getUniqueId(), target.getUniqueId())) {
if (ignoreService.isIgnoring(player.getUniqueId(), target.getUniqueId())) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.already-ignored")));
return true;
}
plugin.getIgnoreManager().addIgnore(player.getUniqueId(), target.getUniqueId());
ignoreService.addIgnore(player.getUniqueId(), target.getUniqueId());
player.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("ignore.added", "{player}", target.getName())
));

View file

@ -1,7 +1,6 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;

View file

@ -1,6 +1,7 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.ChatService;
import com.loki.lochat.utils.ChatFormatter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
@ -10,13 +11,19 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Команда для локального чата
* /l <сообщение> - отправить сообщение в локальный чат
*/
public class LocalChatCommand implements CommandExecutor {
private final LoChat plugin;
private final ChatService chatService;
private static final MiniMessage MM = MiniMessage.miniMessage();
public LocalChatCommand(LoChat plugin) {
this.plugin = plugin;
this.chatService = plugin.getServiceRegistry().get(ChatService.class);
}
@Override
@ -33,10 +40,10 @@ public class LocalChatCommand implements CommandExecutor {
String message = String.join(" ", args);
// Конвертируем String в Component для новой сигнатуры ChatManager
// Конвертируем String в Component для новой сигнатуры ChatService
Component messageComponent = MM.deserialize(message);
plugin.getChatManager().sendLocalMessage(player, messageComponent);
chatService.sendLocalMessage(player, messageComponent);
return true;
}
}
}

View file

@ -1,6 +1,9 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.api.service.PMService;
import com.loki.lochat.api.service.SpyService;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
@ -10,12 +13,22 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Команда для отправки личных сообщений
* /msg <ник> <сообщение>
*/
public class MsgCommand implements CommandExecutor {
private final LoChat plugin;
private final PMService pmService;
private final IgnoreService ignoreService;
private final SpyService spyService;
public MsgCommand(LoChat plugin) {
this.plugin = plugin;
this.pmService = plugin.getServiceRegistry().get(PMService.class);
this.ignoreService = plugin.getServiceRegistry().get(IgnoreService.class);
this.spyService = plugin.getServiceRegistry().get(SpyService.class);
}
@Override
@ -46,7 +59,7 @@ public class MsgCommand implements CommandExecutor {
}
// Проверка игнора
if (plugin.getIgnoreManager().isIgnoring(target.getUniqueId(), player.getUniqueId())) {
if (ignoreService.isIgnoring(target.getUniqueId(), player.getUniqueId())) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.ignored")));
return true;
}
@ -83,11 +96,11 @@ public class MsgCommand implements CommandExecutor {
}
// Шпион
plugin.getSpyManager().broadcastPM(player, target, message);
spyService.broadcastPM(player, target, message);
// Сохраняем последнего собеседника
plugin.getPmManager().setLastConversation(player.getUniqueId(), target.getUniqueId());
plugin.getPmManager().setLastConversation(target.getUniqueId(), player.getUniqueId());
pmService.setLastConversation(player.getUniqueId(), target.getUniqueId());
pmService.setLastConversation(target.getUniqueId(), player.getUniqueId());
// Логирование
if (plugin.getConfigManager().isPmLogEnabled()) {

View file

@ -1,7 +1,8 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.managers.MuteManager;
import com.loki.lochat.api.service.MuteService;
import com.loki.lochat.data.model.MuteData;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@ -17,15 +18,17 @@ import java.util.Date;
import java.util.List;
/**
* Команда /muteblame <модератор> - история выдачи мутов модератором
* Команда /lmuteblame <модератор> - история мутов выданных модератором
*/
public class MuteBlameCommand implements CommandExecutor, TabCompleter {
private final LoChat plugin;
private final MuteService muteService;
private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
public MuteBlameCommand(LoChat plugin) {
this.plugin = plugin;
this.muteService = plugin.getServiceRegistry().get(MuteService.class);
}
@Override
@ -37,12 +40,13 @@ public class MuteBlameCommand implements CommandExecutor, TabCompleter {
}
if (args.length < 1) {
sender.sendMessage("§cИспользование: /muteblame <модератор>");
sender.sendMessage("§cИспользование: /lmuteblame <модератор>");
return true;
}
String operatorName = args[0];
List<MuteManager.MuteHistoryEntry> mutes = plugin.getMuteManager().getMutesByOperator(operatorName);
List<MuteData.MuteHistoryEntry> mutes = muteService.getMutesByOperator(operatorName);
if (mutes.isEmpty()) {
sender.sendMessage("§aМодератор §e" + operatorName + " §aне выдавал мутов");
@ -52,13 +56,13 @@ public class MuteBlameCommand implements CommandExecutor, TabCompleter {
sender.sendMessage("§6§l=== Муты выданные " + operatorName + " (" + mutes.size() + ") ===");
int index = 1;
for (MuteManager.MuteHistoryEntry entry : mutes) {
for (MuteData.MuteHistoryEntry entry : mutes) {
// Статус
String status;
if (entry.unmuted) {
status = "§aРазмучен";
} else {
if (entry.isPermanent()) {
if (entry.duration == 0) {
status = "§cАктивен (навсегда)";
} else {
long endTime = entry.mutedAt + entry.duration;
@ -66,12 +70,13 @@ public class MuteBlameCommand implements CommandExecutor, TabCompleter {
status = "§7Истёк";
} else {
long remaining = endTime - System.currentTimeMillis();
status = "§cАктивен (" + plugin.getMuteManager().formatTime(remaining) + ")";
status = "§cАктивен (" + muteService.formatTime(remaining) + ")";
}
}
}
String durationStr = entry.isPermanent() ? "Навсегда" : plugin.getMuteManager().formatTime(entry.duration);
String durationStr = entry.duration == 0 ? "Навсегда" : muteService.formatTime(entry.duration);
String playerName = entry.playerName != null ? entry.playerName : "???";
sender.sendMessage("§7" + index + ". §f" + playerName + " §7- " + status);

View file

@ -1,6 +1,7 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.MuteService;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
@ -22,9 +23,11 @@ import java.util.UUID;
public class MuteCommand implements CommandExecutor, TabCompleter {
private final LoChat plugin;
private final MuteService muteService;
public MuteCommand(LoChat plugin) {
this.plugin = plugin;
this.muteService = plugin.getServiceRegistry().get(MuteService.class);
}
@Override
@ -76,12 +79,10 @@ public class MuteCommand implements CommandExecutor, TabCompleter {
// Определяем длительность
long duration;
if (timeStr == null || timeStr.isEmpty()) {
// Используем максимальное доступное время из прав
if (sender instanceof Player player) {
duration = plugin.getMuteManager().getMaxDuration(player);
duration = muteService.getMaxDuration(player);
if (duration == -1) {
// Нет прав на длительность - используем дефолт
duration = plugin.getMuteManager().parseTime(defaultDuration);
duration = muteService.parseTime(defaultDuration);
}
} else {
duration = 0; // Консоль = перманентный
@ -89,7 +90,7 @@ public class MuteCommand implements CommandExecutor, TabCompleter {
} else if (timeStr.equalsIgnoreCase("perm") || timeStr.equals("0")) {
duration = 0; // Перманентный
} else {
duration = plugin.getMuteManager().parseTime(timeStr);
duration = muteService.parseTime(timeStr);
if (duration < 0) {
sender.sendMessage("§cНеверный формат времени! Используйте: 1d, 2h, 30m, 60s, perm");
return true;
@ -98,9 +99,9 @@ public class MuteCommand implements CommandExecutor, TabCompleter {
// Проверяем право на длительность
if (sender instanceof Player player) {
if (!plugin.getMuteManager().canMuteForDuration(player, duration)) {
long maxDur = plugin.getMuteManager().getMaxDuration(player);
String maxStr = maxDur == 0 ? "перманентный" : plugin.getMuteManager().formatTime(maxDur);
if (!muteService.canMuteForDuration(player, duration)) {
long maxDur = muteService.getMaxDuration(player);
String maxStr = maxDur == 0 ? "перманентный" : muteService.formatTime(maxDur);
sender.sendMessage("§cВы не можете мутить на такой срок! Максимум: " + maxStr);
return true;
}
@ -117,16 +118,16 @@ public class MuteCommand implements CommandExecutor, TabCompleter {
String finalTargetName = com.loki.lochat.utils.PlayerUtil.getPlayerName(targetUUID);
// Проверяем, не замучен ли уже
if (plugin.getMuteManager().isMuted(targetUUID)) {
if (muteService.isMuted(targetUUID)) {
sender.sendMessage("§cИгрок уже замучен!");
return true;
}
// Мутим игрока
String operatorName = sender.getName();
plugin.getMuteManager().mute(targetUUID, finalTargetName, duration, reason, operatorName);
muteService.mute(targetUUID, finalTargetName, duration, reason, operatorName);
String timeDisplay = duration == 0 ? "навсегда" : plugin.getMuteManager().formatTime(duration);
String timeDisplay = duration == 0 ? "навсегда" : muteService.formatTime(duration);
// Уведомляем отправителя
sender.sendMessage("§aИгрок §e" + finalTargetName + " §aзамучен на §e" + timeDisplay);
@ -137,16 +138,15 @@ public class MuteCommand implements CommandExecutor, TabCompleter {
String msgKey = duration == 0 ? "mute.messages.you-muted-permanent" : "mute.messages.you-muted";
String msg = plugin.getConfigManager().getString(msgKey,
duration == 0 ? "§cВы замучены навсегда! Причина: %reason%" : "§cВы замучены на %duration%! Причина: %reason%");
msg = plugin.getMuteManager().formatMessage(msg, finalTargetName, operatorName, timeDisplay, reason);
msg = muteService.formatMessage(msg, finalTargetName, operatorName, timeDisplay, reason);
target.sendMessage(ChatFormatter.parse(msg));
}
// Broadcast
if (silent) {
// Тихий мут - только тем кто видит
String silentMsg = plugin.getConfigManager().getString("mute.messages.silent-muted",
"§8[Тихо] §c%player% §7замучен на §c%duration% §7(%operator%). Причина: %reason%");
silentMsg = plugin.getMuteManager().formatMessage(silentMsg, finalTargetName, operatorName, timeDisplay, reason);
silentMsg = muteService.formatMessage(silentMsg, finalTargetName, operatorName, timeDisplay, reason);
for (Player p : Bukkit.getOnlinePlayers()) {
if (p.hasPermission("lochat.mute.see-silent")) {
@ -154,10 +154,9 @@ public class MuteCommand implements CommandExecutor, TabCompleter {
}
}
} else {
// Обычный broadcast
String broadcastMsg = plugin.getConfigManager().getString("mute.messages.muted",
"§c%player% §7был замучен на §c%duration% §7модератором §c%operator%§7. Причина: §c%reason%");
broadcastMsg = plugin.getMuteManager().formatMessage(broadcastMsg, finalTargetName, operatorName, timeDisplay, reason);
broadcastMsg = muteService.formatMessage(broadcastMsg, finalTargetName, operatorName, timeDisplay, reason);
Bukkit.broadcast(ChatFormatter.parse(broadcastMsg));
}

View file

@ -1,7 +1,8 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.managers.MuteManager;
import com.loki.lochat.api.service.MuteService;
import com.loki.lochat.data.model.MuteData;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@ -23,10 +24,12 @@ import java.util.UUID;
public class MuteHistoryCommand implements CommandExecutor, TabCompleter {
private final LoChat plugin;
private final MuteService muteService;
private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
public MuteHistoryCommand(LoChat plugin) {
this.plugin = plugin;
this.muteService = plugin.getServiceRegistry().get(MuteService.class);
}
@Override
@ -48,7 +51,7 @@ public class MuteHistoryCommand implements CommandExecutor, TabCompleter {
UUID targetUUID = com.loki.lochat.utils.PlayerUtil.findPlayerUUID(targetName);
if (targetUUID == null) {
// Пробуем найти по имени в истории
targetUUID = plugin.getMuteManager().getUUIDByName(targetName);
targetUUID = muteService.getUUIDByName(targetName);
}
if (targetUUID == null) {
@ -56,7 +59,7 @@ public class MuteHistoryCommand implements CommandExecutor, TabCompleter {
return true;
}
List<MuteManager.MuteHistoryEntry> history = plugin.getMuteManager().getPlayerHistory(targetUUID);
List<MuteData.MuteHistoryEntry> history = muteService.getPlayerHistory(targetUUID);
if (history.isEmpty()) {
sender.sendMessage("§aУ игрока §e" + targetName + " §aнет истории мутов");
@ -68,7 +71,7 @@ public class MuteHistoryCommand implements CommandExecutor, TabCompleter {
int index = 1;
// Выводим от новых к старым
for (int i = history.size() - 1; i >= 0; i--) {
MuteManager.MuteHistoryEntry entry = history.get(i);
MuteData.MuteHistoryEntry entry = history.get(i);
// Статус
String status;
@ -76,7 +79,7 @@ public class MuteHistoryCommand implements CommandExecutor, TabCompleter {
status = "§aРазмучен";
} else {
// Проверяем, активен ли ещё мут
if (entry.isPermanent()) {
if (entry.duration == 0) {
status = "§cАктивен (навсегда)";
} else {
long endTime = entry.mutedAt + entry.duration;
@ -84,13 +87,13 @@ public class MuteHistoryCommand implements CommandExecutor, TabCompleter {
status = "§7Истёк";
} else {
long remaining = endTime - System.currentTimeMillis();
status = "§cАктивен (" + plugin.getMuteManager().formatTime(remaining) + ")";
status = "§cАктивен (" + muteService.formatTime(remaining) + ")";
}
}
}
// Длительность
String durationStr = entry.isPermanent() ? "Навсегда" : plugin.getMuteManager().formatTime(entry.duration);
String durationStr = entry.duration == 0 ? "Навсегда" : muteService.formatTime(entry.duration);
sender.sendMessage("§7" + index + ". §f" + dateFormat.format(new Date(entry.mutedAt)) + " §7- " + status);
sender.sendMessage(" §7Длительность: §f" + durationStr);

View file

@ -1,7 +1,8 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.managers.MuteManager;
import com.loki.lochat.api.service.MuteService;
import com.loki.lochat.data.model.MuteData;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.Command;
@ -20,10 +21,12 @@ import java.util.UUID;
public class MuteListCommand implements CommandExecutor {
private final LoChat plugin;
private final MuteService muteService;
private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
public MuteListCommand(LoChat plugin) {
this.plugin = plugin;
this.muteService = plugin.getServiceRegistry().get(MuteService.class);
}
@Override
@ -34,7 +37,7 @@ public class MuteListCommand implements CommandExecutor {
return true;
}
Map<UUID, MuteManager.MuteData> activeMutes = plugin.getMuteManager().getActiveMutes();
Map<UUID, MuteData> activeMutes = muteService.getActiveMutes();
if (activeMutes.isEmpty()) {
sender.sendMessage("§aНет активных мутов");
@ -44,12 +47,12 @@ public class MuteListCommand implements CommandExecutor {
sender.sendMessage("§6§l=== Активные муты (" + activeMutes.size() + ") ===");
int index = 1;
for (Map.Entry<UUID, MuteManager.MuteData> entry : activeMutes.entrySet()) {
for (Map.Entry<UUID, MuteData> entry : activeMutes.entrySet()) {
UUID uuid = entry.getKey();
MuteManager.MuteData data = entry.getValue();
MuteData data = entry.getValue();
// Получаем имя игрока
String playerName = data.playerName;
String playerName = data.getPlayerName();
if (playerName == null) {
OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid);
playerName = offlinePlayer.getName() != null ? offlinePlayer.getName() : uuid.toString().substring(0, 8);
@ -60,13 +63,13 @@ public class MuteListCommand implements CommandExecutor {
if (data.isPermanent()) {
timeStr = "§4Навсегда";
} else {
long remaining = data.endTime - System.currentTimeMillis();
timeStr = "§e" + plugin.getMuteManager().formatTime(remaining);
long remaining = data.getEndTime() - System.currentTimeMillis();
timeStr = "§e" + muteService.formatTime(remaining);
}
sender.sendMessage("§7" + index + ". §f" + playerName + " §7- " + timeStr);
sender.sendMessage(" §7Причина: §f" + (data.reason != null ? data.reason : "Не указана"));
sender.sendMessage(" §7Выдал: §f" + data.mutedBy + " §7(" + dateFormat.format(new Date(data.mutedAt)) + ")");
sender.sendMessage(" §7Причина: §f" + (data.getReason() != null ? data.getReason() : "Не указана"));
sender.sendMessage(" §7Выдал: §f" + data.getMutedBy() + " §7(" + dateFormat.format(new Date(data.getMutedAt())) + ")");
index++;
}

View file

@ -1,6 +1,9 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.api.service.PMService;
import com.loki.lochat.api.service.SpyService;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
@ -10,14 +13,25 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
import java.util.UUID;
/**
* Команда для ответа на последнее личное сообщение
* /reply <сообщение>
*/
public class ReplyCommand implements CommandExecutor {
private final LoChat plugin;
private final PMService pmService;
private final IgnoreService ignoreService;
private final SpyService spyService;
public ReplyCommand(LoChat plugin) {
this.plugin = plugin;
this.pmService = plugin.getServiceRegistry().get(PMService.class);
this.ignoreService = plugin.getServiceRegistry().get(IgnoreService.class);
this.spyService = plugin.getServiceRegistry().get(SpyService.class);
}
@Override
@ -36,12 +50,13 @@ public class ReplyCommand implements CommandExecutor {
return true;
}
UUID lastUuid = plugin.getPmManager().getLastConversation(player.getUniqueId());
if (lastUuid == null) {
Optional<UUID> lastUuidOpt = pmService.getLastConversation(player.getUniqueId());
if (lastUuidOpt.isEmpty()) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.no-reply")));
return true;
}
UUID lastUuid = lastUuidOpt.get();
Player target = Bukkit.getPlayer(lastUuid);
if (target == null) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.player-offline")));
@ -49,7 +64,7 @@ public class ReplyCommand implements CommandExecutor {
}
// Проверка игнора
if (plugin.getIgnoreManager().isIgnoring(target.getUniqueId(), player.getUniqueId())) {
if (ignoreService.isIgnoring(target.getUniqueId(), player.getUniqueId())) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.ignored")));
return true;
}
@ -80,10 +95,10 @@ public class ReplyCommand implements CommandExecutor {
}
// Шпион
plugin.getSpyManager().broadcastPM(player, target, message);
spyService.broadcastPM(player, target, message);
// Обновляем последнего собеседника
plugin.getPmManager().setLastConversation(target.getUniqueId(), player.getUniqueId());
pmService.setLastConversation(target.getUniqueId(), player.getUniqueId());
// Логирование
if (plugin.getConfigManager().isPmLogEnabled()) {

View file

@ -1,6 +1,7 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
@ -9,12 +10,18 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Команда для удаления игрока из списка игнорируемых
* /unignore <ник>
*/
public class UnignoreCommand implements CommandExecutor {
private final LoChat plugin;
private final IgnoreService ignoreService;
public UnignoreCommand(LoChat plugin) {
this.plugin = plugin;
this.ignoreService = plugin.getServiceRegistry().get(IgnoreService.class);
}
@Override
@ -35,12 +42,12 @@ public class UnignoreCommand implements CommandExecutor {
return true;
}
if (!plugin.getIgnoreManager().isIgnoring(player.getUniqueId(), target.getUniqueId())) {
if (!ignoreService.isIgnoring(player.getUniqueId(), target.getUniqueId())) {
player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.not-ignored")));
return true;
}
plugin.getIgnoreManager().removeIgnore(player.getUniqueId(), target.getUniqueId());
ignoreService.removeIgnore(player.getUniqueId(), target.getUniqueId());
player.sendMessage(ChatFormatter.parse(
plugin.getMessageConfig().get("ignore.removed", "{player}", target.getName())
));

View file

@ -1,6 +1,7 @@
package com.loki.lochat.commands;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.MuteService;
import com.loki.lochat.utils.ChatFormatter;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
@ -21,9 +22,11 @@ import java.util.UUID;
public class UnmuteCommand implements CommandExecutor, TabCompleter {
private final LoChat plugin;
private final MuteService muteService;
public UnmuteCommand(LoChat plugin) {
this.plugin = plugin;
this.muteService = plugin.getServiceRegistry().get(MuteService.class);
}
@Override
@ -70,7 +73,7 @@ public class UnmuteCommand implements CommandExecutor, TabCompleter {
String operatorName = sender.getName();
// Размучиваем
if (plugin.getMuteManager().unmute(targetUUID, operatorName)) {
if (muteService.unmute(targetUUID, operatorName)) {
sender.sendMessage("§aИгрок §e" + finalTargetName + " §aразмучен");
// Уведомляем игрока (если онлайн)
@ -82,7 +85,7 @@ public class UnmuteCommand implements CommandExecutor, TabCompleter {
if (silent) {
String silentMsg = plugin.getConfigManager().getString("mute.messages.silent-unmuted",
"§8[Тихо] §a%player% §7размучен (%operator%)");
silentMsg = plugin.getMuteManager().formatMessage(silentMsg, finalTargetName, operatorName, null, null);
silentMsg = muteService.formatMessage(silentMsg, finalTargetName, operatorName, null, null);
for (Player p : Bukkit.getOnlinePlayers()) {
if (p.hasPermission("lochat.mute.see-silent")) {
@ -92,7 +95,7 @@ public class UnmuteCommand implements CommandExecutor, TabCompleter {
} else {
String broadcastMsg = plugin.getConfigManager().getString("mute.messages.unmuted",
"§a%player% §7был размучен модератором §a%operator%");
broadcastMsg = plugin.getMuteManager().formatMessage(broadcastMsg, finalTargetName, operatorName, null, null);
broadcastMsg = muteService.formatMessage(broadcastMsg, finalTargetName, operatorName, null, null);
Bukkit.broadcast(ChatFormatter.parse(broadcastMsg));
}
} else {

View file

@ -60,20 +60,6 @@ public class ConfigManager {
config.set("mentions.self-highlight", "&#FFD700{player}");
}
if (!config.contains("filter.enabled")) {
config.set("filter.enabled", true);
config.set("filter.words", java.util.Arrays.asList("хуй", "пизд", "блять", "ебать", "сука"));
config.set("filter.action", "censor");
config.set("filter.replacement", "***");
}
if (!config.contains("antispam.enabled")) {
config.set("antispam.enabled", true);
config.set("antispam.max-caps-percent", 70);
config.set("antispam.max-repeat-chars", 5);
config.set("antispam.similar-message-delay", 30);
}
if (!config.contains("automessages.enabled")) {
config.set("automessages.enabled", true);
config.set("automessages.interval", 300);
@ -85,12 +71,6 @@ public class ConfigManager {
config.set("clearchat.message.text", "&#04CADFЧат очищен администратором {player}");
}
if (!config.contains("head-emoji.enabled")) {
config.set("head-emoji.enabled", true);
config.set("head-emoji.allow-player-heads", true);
config.set("head-emoji.max-heads-per-message", 3);
}
if (!config.contains("lopreff.enabled")) {
config.set("lopreff.enabled", true);
config.set("lopreff.use-gradient-name", true);
@ -217,23 +197,6 @@ public class ConfigManager {
public String getSelfMentionHighlight() {
return ChatFormatter.convertAllColors(config.getString("mentions.self-highlight", "&#FFD700{player}"));
}
// Filter
public boolean isFilterEnabled() {
return config.getBoolean("filter.enabled", true);
}
public List<String> getFilterWords() {
return config.getStringList("filter.words");
}
public String getFilterAction() {
return config.getString("filter.action", "censor");
}
public String getFilterReplacement() {
return config.getString("filter.replacement", "***");
}
// Clear chat
public boolean isClearChatMessageEnabled() {
@ -254,23 +217,6 @@ public class ConfigManager {
plugin.saveConfig();
}
// Anti-spam
public boolean isAntiSpamEnabled() {
return config.getBoolean("antispam.enabled", true);
}
public int getMaxCapsPercent() {
return config.getInt("antispam.max-caps-percent", 70);
}
public int getMaxRepeatChars() {
return config.getInt("antispam.max-repeat-chars", 5);
}
public int getSimilarMessageDelay() {
return config.getInt("antispam.similar-message-delay", 30);
}
// Auto messages
public boolean isAutoMessagesEnabled() {
return config.getBoolean("automessages.enabled", true);

View file

@ -13,7 +13,6 @@ public class MessageConfig {
private final LoChat plugin;
private FileConfiguration messages;
private File messagesFile;
public MessageConfig(LoChat plugin) {
this.plugin = plugin;
@ -21,7 +20,7 @@ public class MessageConfig {
}
private void loadMessages() {
messagesFile = new File(plugin.getDataFolder(), "messages.yml");
File messagesFile = new File(plugin.getDataFolder(), "messages.yml");
if (!messagesFile.exists()) {
plugin.saveResource("messages.yml", false);
}

View file

@ -0,0 +1,43 @@
package com.loki.lochat.core.filter;
import com.loki.lochat.api.filter.MessageFilter;
import com.loki.lochat.api.service.CooldownService;
import com.loki.lochat.data.model.ChatMessage;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Фильтр кулдаунов
*/
public class CooldownFilter implements MessageFilter {
private final CooldownService cooldownService;
private final FileConfiguration config;
public CooldownFilter(CooldownService cooldownService, JavaPlugin plugin) {
this.cooldownService = cooldownService;
this.config = plugin.getConfig();
}
@Override
public boolean apply(Player player, ChatMessage message) {
if (player.hasPermission("chat.bypass.cooldown")) {
return true;
}
String chatType = message.getChatType();
int cooldown = config.getInt("chat." + chatType + ".cooldown", 0);
if (cooldown > 0) {
if (cooldownService.isOnCooldown(player.getUniqueId(), chatType, cooldown)) {
int remaining = cooldownService.getRemainingCooldown(
player.getUniqueId(), chatType, cooldown);
player.sendMessage("§cПодождите " + remaining + " сек.");
return false;
}
}
cooldownService.setCooldown(player.getUniqueId(), chatType);
return true;
}
}

View file

@ -0,0 +1,32 @@
package com.loki.lochat.core.filter;
import com.loki.lochat.api.filter.MessageFilter;
import com.loki.lochat.api.service.MuteService;
import com.loki.lochat.data.model.ChatMessage;
import org.bukkit.entity.Player;
/**
* Фильтр проверки мута
*/
public class MuteFilter implements MessageFilter {
private final MuteService muteService;
public MuteFilter(MuteService muteService) {
this.muteService = muteService;
}
@Override
public boolean apply(Player player, ChatMessage message) {
if (muteService.isMuted(player.getUniqueId())) {
sendMuteNotification(player);
return false;
}
return true;
}
private void sendMuteNotification(Player player) {
long remaining = muteService.getRemainingTime(player.getUniqueId());
String timeStr = muteService.formatTime(remaining);
player.sendMessage("§cВы замучены! Осталось: " + timeStr);
}
}

View file

@ -0,0 +1,69 @@
package com.loki.lochat.core.registry;
import com.loki.lochat.api.service.*;
import com.loki.lochat.config.ConfigManager;
import com.loki.lochat.config.MessageConfig;
import com.loki.lochat.core.service.*;
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();
}
public ServiceRegistry(JavaPlugin plugin, ConfigManager configManager, MessageConfig messageConfig) {
this.plugin = plugin;
registerServicesWithDeps(configManager, messageConfig);
}
private void registerServices() {
// Регистрируем все сервисы (базовая версия)
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));
}
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));
register(MentionService.class, new MentionServiceImpl(configManager));
}
public <T> void register(Class<T> serviceClass, T implementation) {
services.put(serviceClass, implementation);
}
@SuppressWarnings("unchecked")
public <T> T get(Class<T> serviceClass) {
Object service = services.get(serviceClass);
if (service == null) {
throw new IllegalStateException("Service not registered: " + serviceClass.getName());
}
return (T) service;
}
public void shutdown() {
services.clear();
}
}

View file

@ -0,0 +1,88 @@
package com.loki.lochat.core.service;
import com.loki.lochat.api.service.ChatService;
import com.loki.lochat.core.registry.ServiceRegistry;
import com.loki.lochat.util.DistanceUtil;
import com.loki.lochat.utils.ChatFormatter;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Реализация сервиса чата
*/
public class ChatServiceImpl implements ChatService {
private final JavaPlugin plugin;
private final Set<UUID> globalChatDisabled = ConcurrentHashMap.newKeySet();
public ChatServiceImpl(JavaPlugin plugin, ServiceRegistry registry) {
this.plugin = plugin;
}
@Override
public void sendGlobalMessage(Player sender, Object message) {
String format = plugin.getConfig().getString("chat.global.format",
"<prefix><player>: <message>");
String prefix = plugin.getConfig().getString("chat.global.prefix", "[G]");
Component messageComponent = (message instanceof Component)
? (Component) message
: ChatFormatter.parse(message.toString());
String formatted = format
.replace("<prefix>", prefix)
.replace("<player>", sender.getName());
Component finalComponent = ChatFormatter.parse(formatted)
.replaceText(builder -> builder.matchLiteral("<message>").replacement(messageComponent));
for (Player player : Bukkit.getOnlinePlayers()) {
if (!isGlobalChatDisabled(player.getUniqueId())) {
player.sendMessage(finalComponent);
}
}
}
@Override
public void sendLocalMessage(Player sender, Object message) {
int radius = plugin.getConfig().getInt("chat.local.radius", 100);
String format = plugin.getConfig().getString("chat.local.format",
"<player>: <message>");
Component messageComponent = (message instanceof Component)
? (Component) message
: ChatFormatter.parse(message.toString());
String formatted = format.replace("<player>", sender.getName());
Component finalComponent = ChatFormatter.parse(formatted)
.replaceText(builder -> builder.matchLiteral("<message>").replacement(messageComponent));
for (Player player : Bukkit.getOnlinePlayers()) {
if (DistanceUtil.isInRange(sender, player, radius)) {
player.sendMessage(finalComponent);
}
}
}
@Override
public boolean toggleGlobalChat(UUID player) {
if (globalChatDisabled.contains(player)) {
globalChatDisabled.remove(player);
return true;
} else {
globalChatDisabled.add(player);
return false;
}
}
@Override
public boolean isGlobalChatDisabled(UUID player) {
return globalChatDisabled.contains(player);
}
}

View file

@ -0,0 +1,52 @@
package com.loki.lochat.core.service;
import com.loki.lochat.api.service.CooldownService;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Реализация сервиса кулдаунов
*/
public class CooldownServiceImpl implements CooldownService {
private final Map<UUID, Long> globalCooldowns = new ConcurrentHashMap<>();
private final Map<UUID, Long> localCooldowns = new ConcurrentHashMap<>();
@Override
public boolean isOnCooldown(UUID player, String type, int cooldownSeconds) {
Map<UUID, Long> cooldowns = getCooldownMap(type);
Long lastTime = cooldowns.get(player);
if (lastTime == null) return false;
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
return elapsed < cooldownSeconds;
}
@Override
public int getRemainingCooldown(UUID player, String type, int cooldownSeconds) {
Map<UUID, Long> cooldowns = getCooldownMap(type);
Long lastTime = cooldowns.get(player);
if (lastTime == null) return 0;
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
return Math.max(0, cooldownSeconds - (int) elapsed);
}
@Override
public void setCooldown(UUID player, String type) {
getCooldownMap(type).put(player, System.currentTimeMillis());
}
@Override
public void removeCooldown(UUID player) {
globalCooldowns.remove(player);
localCooldowns.remove(player);
}
private Map<UUID, Long> getCooldownMap(String type) {
return type.equals("global") ? globalCooldowns : localCooldowns;
}
}

View file

@ -0,0 +1,114 @@
package com.loki.lochat.core.service;
import com.loki.lochat.api.service.IgnoreService;
import com.loki.lochat.gradient.util.FoliaUtil;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import org.bukkit.configuration.file.YamlConfiguration;
/**
* Реализация сервиса игнорирования
*/
public class IgnoreServiceImpl implements IgnoreService {
private final JavaPlugin plugin;
private final File ignoreFile;
private final Map<UUID, Set<UUID>> ignoreMap = new ConcurrentHashMap<>();
public IgnoreServiceImpl(JavaPlugin plugin) {
this.plugin = plugin;
this.ignoreFile = new File(plugin.getDataFolder(), "ignores.yml");
load();
}
@Override
public void load() {
if (!ignoreFile.exists()) return;
YamlConfiguration config = YamlConfiguration.loadConfiguration(ignoreFile);
for (String key : config.getKeys(false)) {
try {
UUID uuid = UUID.fromString(key);
List<String> ignoredList = config.getStringList(key);
Set<UUID> ignored = ConcurrentHashMap.newKeySet();
for (String ignoredStr : ignoredList) {
try {
ignored.add(UUID.fromString(ignoredStr));
} catch (Exception ignored2) {}
}
if (!ignored.isEmpty()) {
ignoreMap.put(uuid, ignored);
}
} catch (Exception ignored) {}
}
}
@Override
public void save() {
YamlConfiguration config = new YamlConfiguration();
for (Map.Entry<UUID, Set<UUID>> entry : ignoreMap.entrySet()) {
List<String> ignoredList = new ArrayList<>();
for (UUID ignored : entry.getValue()) {
ignoredList.add(ignored.toString());
}
config.set(entry.getKey().toString(), ignoredList);
}
try {
config.save(ignoreFile);
} catch (IOException e) {
plugin.getLogger().severe("Не удалось сохранить игноры: " + e.getMessage());
}
}
@Override
public boolean isIgnoring(UUID player, UUID target) {
Set<UUID> ignored = ignoreMap.get(player);
return ignored != null && ignored.contains(target);
}
@Override
public boolean addIgnore(UUID player, UUID target) {
Set<UUID> ignored = ignoreMap.computeIfAbsent(player, k -> ConcurrentHashMap.newKeySet());
boolean added = ignored.add(target);
if (added) saveAsync();
return added;
}
@Override
public boolean removeIgnore(UUID player, UUID target) {
Set<UUID> ignored = ignoreMap.get(player);
if (ignored == null) return false;
boolean removed = ignored.remove(target);
if (removed) saveAsync();
return removed;
}
@Override
public Set<UUID> getIgnoredPlayers(UUID player) {
return ignoreMap.getOrDefault(player, Collections.emptySet());
}
@Override
public int getIgnoredCount(UUID player) {
Set<UUID> ignored = ignoreMap.get(player);
return ignored == null ? 0 : ignored.size();
}
@Override
public void clearIgnores(UUID player) {
ignoreMap.remove(player);
saveAsync();
}
private void saveAsync() {
FoliaUtil.runAsync(plugin, this::save);
}
}

View file

@ -0,0 +1,132 @@
package com.loki.lochat.core.service;
import com.loki.lochat.api.service.MentionService;
import com.loki.lochat.config.ConfigManager;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Реализация сервиса упоминаний
*/
public class MentionServiceImpl implements MentionService {
private final ConfigManager configManager;
private static final Pattern MENTION_PATTERN = Pattern.compile("@(\\w+)");
// Хранит для какого игрока какие части сообщения выделить
private final Map<Player, Set<String>> highlightedNicks = new HashMap<>();
public MentionServiceImpl(ConfigManager configManager) {
this.configManager = configManager;
}
@Override
public String processMentions(String message, Set<Player> mentionedPlayers) {
if (!configManager.isMentionsEnabled()) {
return message;
}
highlightedNicks.clear();
String highlight = configManager.getMentionHighlight();
Matcher matcher = MENTION_PATTERN.matcher(message);
StringBuilder result = new StringBuilder();
while (matcher.find()) {
String name = matcher.group(1);
Player mentioned = Bukkit.getPlayer(name);
if (mentioned != null && mentioned.isOnline()) {
mentionedPlayers.add(mentioned);
String replacement = highlight.replace("{player}", mentioned.getName());
matcher.appendReplacement(result, replacement);
// Запоминаем что этот игрок упомянут
highlightedNicks.computeIfAbsent(mentioned, k -> new HashSet<>()).add(mentioned.getName());
}
}
matcher.appendTail(result);
return result.toString();
}
@Override
public String getPersonalizedMessage(String message, Player viewer) {
if (!configManager.isMentionsEnabled()) {
return message;
}
String viewerName = viewer.getName();
String result = message;
// Проверяем упоминание через @ник
String highlight = configManager.getMentionHighlight();
String highlightedName = highlight.replace("{player}", viewerName);
// Если в сообщении есть выделенный ник этого игрока — добавляем дополнительное выделение
if (result.contains(highlightedName)) {
// Уже выделено через processMentions, добавляем эффект
String extraHighlight = "<bold>" + highlightedName + "</bold>";
result = result.replace(highlightedName, extraHighlight);
}
// Также проверяем просто ник в сообщении (без @)
// Регистронезависимый поиск
Pattern nickPattern = Pattern.compile("(?i)\\b" + Pattern.quote(viewerName) + "\\b");
Matcher nickMatcher = nickPattern.matcher(result);
if (nickMatcher.find()) {
// Ник найден в сообщении — выделяем для этого игрока
String selfHighlight = configManager.getSelfMentionHighlight();
result = nickMatcher.replaceAll(selfHighlight.replace("{player}", viewerName));
}
return result;
}
@Override
public boolean isPlayerMentioned(String message, Player player) {
String name = player.getName();
Pattern pattern = Pattern.compile("(?i)\\b" + Pattern.quote(name) + "\\b");
return pattern.matcher(message).find();
}
@Override
public void notifyMentioned(Set<Player> players) {
if (!configManager.isMentionSoundEnabled()) {
return;
}
String soundName = configManager.getMentionSoundType();
Sound sound;
try {
sound = Sound.valueOf(soundName);
} catch (IllegalArgumentException e) {
sound = Sound.BLOCK_NOTE_BLOCK_PLING;
}
for (Player player : players) {
player.playSound(player.getLocation(), sound, 1.0f, 1.0f);
}
}
@Override
public void notifyIfMentioned(String message, Player viewer, Player sender) {
if (viewer.equals(sender)) return;
if (!configManager.isMentionSoundEnabled()) return;
if (isPlayerMentioned(message, viewer)) {
String soundName = configManager.getMentionSoundType();
try {
Sound sound = Sound.valueOf(soundName);
viewer.playSound(viewer.getLocation(), sound, 1.0f, 1.0f);
} catch (IllegalArgumentException ignored) {}
}
}
}

View file

@ -0,0 +1,58 @@
package com.loki.lochat.core.service;
import com.loki.lochat.api.filter.MessageFilter;
import com.loki.lochat.api.service.*;
import com.loki.lochat.core.filter.CooldownFilter;
import com.loki.lochat.core.filter.MuteFilter;
import com.loki.lochat.core.registry.ServiceRegistry;
import com.loki.lochat.data.model.ChatMessage;
import com.loki.lochat.utils.ChatFormatter;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.ArrayList;
import java.util.List;
/**
* Реализация сервиса обработки сообщений (Chain of Responsibility)
*/
public class MessageServiceImpl implements MessageService {
private final JavaPlugin plugin;
private final List<MessageFilter> filters;
private final ChatService chatService;
public MessageServiceImpl(JavaPlugin plugin, ServiceRegistry registry) {
this.plugin = plugin;
this.chatService = registry.get(ChatService.class);
this.filters = new ArrayList<>();
// Регистрируем фильтры в порядке применения
filters.add(new MuteFilter(registry.get(MuteService.class)));
filters.add(new CooldownFilter(registry.get(CooldownService.class), plugin));
}
@Override
public boolean processMessage(Player player, String rawMessage) {
// Создаем объект сообщения
ChatMessage message = ChatMessage.create(player, rawMessage);
// Применяем фильтры
for (MessageFilter filter : filters) {
if (!filter.apply(player, message)) {
return false; // Сообщение заблокировано
}
}
// Отправляем сообщение
Component formattedMessage = ChatFormatter.parse(message.getContent());
if (message.isGlobal()) {
chatService.sendGlobalMessage(player, formattedMessage);
} else {
chatService.sendLocalMessage(player, formattedMessage);
}
return true;
}
}

View file

@ -0,0 +1,324 @@
package com.loki.lochat.core.service;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.loki.lochat.api.service.MuteService;
import com.loki.lochat.data.model.MuteData;
import com.loki.lochat.gradient.util.FoliaUtil;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.*;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Реализация сервиса мутов
*/
public class MuteServiceImpl implements MuteService {
private final JavaPlugin plugin;
private final Map<UUID, MuteData> mutes = new ConcurrentHashMap<>();
private final Map<UUID, List<MuteData.MuteHistoryEntry>> history = new ConcurrentHashMap<>();
private final File dataFile;
private final File historyFile;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public MuteServiceImpl(JavaPlugin plugin) {
this.plugin = plugin;
this.dataFile = new File(plugin.getDataFolder(), "mutes.json");
this.historyFile = new File(plugin.getDataFolder(), "mute-history.json");
load();
loadHistory();
}
@Override
public void mute(UUID uuid, String playerName, long duration, String reason, String mutedBy) {
long endTime = duration > 0 ? System.currentTimeMillis() + duration : 0;
MuteData data = new MuteData(uuid, playerName, endTime, reason, mutedBy);
mutes.put(uuid, data);
// Добавляем в историю
addToHistory(uuid, playerName, duration, reason, mutedBy);
saveAsync();
}
@Override
public boolean unmute(UUID uuid, String unmutedBy) {
MuteData removed = mutes.remove(uuid);
if (removed != null) {
updateHistoryUnmute(uuid, unmutedBy);
saveAsync();
return true;
}
return false;
}
@Override
public boolean isMuted(UUID uuid) {
MuteData data = mutes.get(uuid);
if (data == null) return false;
if (data.getEndTime() > 0 && System.currentTimeMillis() > data.getEndTime()) {
mutes.remove(uuid);
saveAsync();
return false;
}
return true;
}
@Override
public MuteData getMuteData(UUID uuid) {
if (!isMuted(uuid)) return null;
return mutes.get(uuid);
}
@Override
public long getRemainingTime(UUID uuid) {
MuteData data = mutes.get(uuid);
if (data == null) return 0;
if (data.getEndTime() == 0) return -1; // Перманентный
return Math.max(0, data.getEndTime() - System.currentTimeMillis());
}
@Override
public String formatTime(long millis) {
if (millis <= 0) return "0с";
long seconds = millis / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
if (days > 0) {
return days + "д " + (hours % 24) + "ч";
} else if (hours > 0) {
return hours + "ч " + (minutes % 60) + "м";
} else if (minutes > 0) {
return minutes + "м " + (seconds % 60) + "с";
} else {
return seconds + "с";
}
}
@Override
public long parseTime(String timeStr) {
if (timeStr == null || timeStr.isEmpty()) return 0;
timeStr = timeStr.toLowerCase();
long multiplier = 1000; // базово в миллисекундах
try {
if (timeStr.endsWith("d")) {
return Long.parseLong(timeStr.replace("d", "")) * 24 * 60 * 60 * multiplier;
} else if (timeStr.endsWith("h")) {
return Long.parseLong(timeStr.replace("h", "")) * 60 * 60 * multiplier;
} else if (timeStr.endsWith("m")) {
return Long.parseLong(timeStr.replace("m", "")) * 60 * multiplier;
} else if (timeStr.endsWith("s")) {
return Long.parseLong(timeStr.replace("s", "")) * multiplier;
} else {
return Long.parseLong(timeStr) * 60 * multiplier;
}
} catch (NumberFormatException e) {
return -1;
}
}
@Override
public long getMaxDuration(Player player) {
if (player.hasPermission("lochat.mute.dur.perm")) {
return 0; // Перманентный
}
String[] durations = {"30d", "14d", "7d", "3d", "1d", "12h", "6h", "1h", "30m", "10m"};
for (String dur : durations) {
if (player.hasPermission("lochat.mute.dur." + dur)) {
return parseTime(dur);
}
}
return -1; // Нет прав
}
@Override
public boolean canMuteForDuration(Player player, long duration) {
if (player.hasPermission("lochat.mute.dur.perm")) {
return true;
}
if (duration == 0) {
return false;
}
long maxDuration = getMaxDuration(player);
if (maxDuration == -1) return false;
if (maxDuration == 0) return true;
return duration <= maxDuration;
}
@Override
public List<MuteData.MuteHistoryEntry> getPlayerHistory(UUID uuid) {
return history.getOrDefault(uuid, new ArrayList<>());
}
@Override
public List<MuteData.MuteHistoryEntry> getMutesByOperator(String operatorName) {
List<MuteData.MuteHistoryEntry> result = new ArrayList<>();
for (List<MuteData.MuteHistoryEntry> playerHistory : history.values()) {
for (MuteData.MuteHistoryEntry entry : playerHistory) {
if (entry.mutedBy != null && entry.mutedBy.equalsIgnoreCase(operatorName)) {
result.add(entry);
}
}
}
result.sort((a, b) -> Long.compare(b.mutedAt, a.mutedAt));
return result;
}
@Override
public UUID getUUIDByName(String name) {
for (Map.Entry<UUID, List<MuteData.MuteHistoryEntry>> entry : history.entrySet()) {
for (MuteData.MuteHistoryEntry historyEntry : entry.getValue()) {
if (historyEntry.playerName != null && historyEntry.playerName.equalsIgnoreCase(name)) {
return entry.getKey();
}
}
}
return null;
}
@Override
public Map<UUID, MuteData> getActiveMutes() {
mutes.entrySet().removeIf(entry -> {
MuteData data = entry.getValue();
return data.getEndTime() > 0 && System.currentTimeMillis() > data.getEndTime();
});
return new HashMap<>(mutes);
}
@Override
public String formatMessage(String message, String player, String operator, String duration, String reason) {
return message
.replace("%player%", player != null ? player : "")
.replace("%operator%", operator != null ? operator : "")
.replace("%duration%", duration != null ? duration : "")
.replace("%reason%", reason != null ? reason : "");
}
@Override
public void save() {
try {
if (!dataFile.getParentFile().exists()) {
dataFile.getParentFile().mkdirs();
}
Map<String, MuteData> toSave = new ConcurrentHashMap<>();
mutes.forEach((uuid, data) -> toSave.put(uuid.toString(), data));
try (Writer writer = new FileWriter(dataFile)) {
gson.toJson(toSave, writer);
}
} catch (IOException e) {
plugin.getLogger().warning("Ошибка сохранения мутов: " + e.getMessage());
}
}
private void load() {
if (!dataFile.exists()) return;
try (Reader reader = new FileReader(dataFile)) {
Type type = new TypeToken<Map<String, MuteData>>(){}.getType();
Map<String, MuteData> loaded = gson.fromJson(reader, type);
if (loaded != null) {
loaded.forEach((key, value) -> {
try {
mutes.put(UUID.fromString(key), value);
} catch (IllegalArgumentException ignored) {}
});
}
} catch (IOException e) {
plugin.getLogger().warning("Ошибка загрузки мутов: " + e.getMessage());
}
}
private void loadHistory() {
if (!historyFile.exists()) return;
try (Reader reader = new FileReader(historyFile)) {
Type type = new TypeToken<Map<String, List<MuteData.MuteHistoryEntry>>>(){}.getType();
Map<String, List<MuteData.MuteHistoryEntry>> loaded = gson.fromJson(reader, type);
if (loaded != null) {
loaded.forEach((key, value) -> {
try {
history.put(UUID.fromString(key), new ArrayList<>(value));
} catch (IllegalArgumentException ignored) {}
});
}
} catch (IOException e) {
plugin.getLogger().warning("Ошибка загрузки истории мутов: " + e.getMessage());
}
}
private void saveHistory() {
try {
if (!historyFile.getParentFile().exists()) {
historyFile.getParentFile().mkdirs();
}
Map<String, List<MuteData.MuteHistoryEntry>> toSave = new ConcurrentHashMap<>();
history.forEach((uuid, data) -> toSave.put(uuid.toString(), data));
try (Writer writer = new FileWriter(historyFile)) {
gson.toJson(toSave, writer);
}
} catch (IOException e) {
plugin.getLogger().warning("Ошибка сохранения истории мутов: " + e.getMessage());
}
}
private void addToHistory(UUID uuid, String playerName, long duration, String reason, String mutedBy) {
List<MuteData.MuteHistoryEntry> playerHistory = history.computeIfAbsent(uuid, k -> new ArrayList<>());
playerHistory.add(new MuteData.MuteHistoryEntry(
playerName,
duration,
reason,
mutedBy,
System.currentTimeMillis(),
false,
null,
0
));
saveHistoryAsync();
}
private void updateHistoryUnmute(UUID uuid, String unmutedBy) {
List<MuteData.MuteHistoryEntry> playerHistory = history.get(uuid);
if (playerHistory != null && !playerHistory.isEmpty()) {
for (int i = playerHistory.size() - 1; i >= 0; i--) {
MuteData.MuteHistoryEntry entry = playerHistory.get(i);
if (!entry.unmuted) {
entry.unmuted = true;
entry.unmutedBy = unmutedBy;
entry.unmutedAt = System.currentTimeMillis();
break;
}
}
}
saveHistoryAsync();
}
private void saveHistoryAsync() {
FoliaUtil.runAsync(plugin, this::saveHistory);
}
private void saveAsync() {
FoliaUtil.runAsync(plugin, () -> {
save();
saveHistory();
});
}
}

View file

@ -0,0 +1,35 @@
package com.loki.lochat.core.service;
import com.loki.lochat.api.service.PMService;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Реализация сервиса личных сообщений
*/
public class PMServiceImpl implements PMService {
private final Map<UUID, UUID> lastConversation = new ConcurrentHashMap<>();
@Override
public void setLastConversation(UUID player, UUID target) {
lastConversation.put(player, target);
}
@Override
public Optional<UUID> getLastConversation(UUID player) {
return Optional.ofNullable(lastConversation.get(player));
}
@Override
public void removeConversation(UUID player) {
lastConversation.remove(player);
}
@Override
public boolean hasConversation(UUID player) {
return lastConversation.containsKey(player);
}
}

View file

@ -0,0 +1,28 @@
package com.loki.lochat.core.service;
import com.loki.lochat.api.service.CooldownService;
import com.loki.lochat.api.service.PlayerDataService;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.UUID;
/**
* Реализация сервиса данных игроков
*/
public class PlayerDataServiceImpl implements PlayerDataService {
private final CooldownService cooldownService;
public PlayerDataServiceImpl(JavaPlugin plugin) {
this.cooldownService = new CooldownServiceImpl();
}
@Override
public void clearPlayerData(UUID player) {
cooldownService.removeCooldown(player);
}
@Override
public void saveAll() {
// Сохранение всех данных при выключении
}
}

View file

@ -0,0 +1,84 @@
package com.loki.lochat.core.service;
import com.loki.lochat.api.service.SpyService;
import com.loki.lochat.config.MessageConfig;
import com.loki.lochat.utils.ChatFormatter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Реализация сервиса шпионажа за чатом
*/
public class SpyServiceImpl implements SpyService {
private final JavaPlugin plugin;
private final MessageConfig messageConfig;
private final Set<UUID> spyEnabled = ConcurrentHashMap.newKeySet();
public SpyServiceImpl(JavaPlugin plugin, MessageConfig messageConfig) {
this.plugin = plugin;
this.messageConfig = messageConfig;
}
@Override
public boolean toggleSpy(UUID player) {
if (spyEnabled.contains(player)) {
spyEnabled.remove(player);
return false;
} else {
spyEnabled.add(player);
return true;
}
}
@Override
public boolean isSpying(UUID player) {
return spyEnabled.contains(player);
}
@Override
public void broadcastPM(Player sender, Player receiver, String message) {
String format = messageConfig.get("spy.format");
for (UUID spyUuid : spyEnabled) {
Player spy = Bukkit.getPlayer(spyUuid);
if (spy != null && spy.isOnline() && !spy.equals(sender) && !spy.equals(receiver)) {
spy.sendMessage(ChatFormatter.parse(format
.replace("{sender}", sender.getName())
.replace("{receiver}", receiver.getName())
.replace("{message}", message)));
}
}
}
@Override
public void sendToSpies(Player sender, Component message, boolean isGlobal) {
if (spyEnabled.isEmpty()) return;
String chatType = isGlobal ? "Global" : "Local";
String plainMessage = PlainTextComponentSerializer.plainText().serialize(message);
String format = messageConfig.get("spy.chat-format",
"§7[SPY] §e{type} §7{sender}: §f{message}");
for (UUID spyUuid : spyEnabled) {
Player spy = Bukkit.getPlayer(spyUuid);
if (spy != null && spy.isOnline() && !spy.equals(sender)) {
spy.sendMessage(ChatFormatter.parse(format
.replace("{type}", chatType)
.replace("{sender}", sender.getName())
.replace("{message}", plainMessage)));
}
}
}
@Override
public void removeSpy(UUID player) {
spyEnabled.remove(player);
}
}

View file

@ -0,0 +1,39 @@
package com.loki.lochat.data.model;
import org.bukkit.entity.Player;
/**
* Модель сообщения чата (Value Object)
*/
public class ChatMessage {
private final Player sender;
private String content;
private final boolean isGlobal;
private final long timestamp;
private ChatMessage(Player sender, String content, boolean isGlobal) {
this.sender = sender;
this.content = content;
this.isGlobal = isGlobal;
this.timestamp = System.currentTimeMillis();
}
public static ChatMessage create(Player sender, String rawMessage) {
// Определяем тип чата
boolean isGlobal = rawMessage.startsWith("!") &&
sender.hasPermission("chat.global.use");
String content = isGlobal ?
rawMessage.substring(1).trim() :
rawMessage.trim();
return new ChatMessage(sender, content, isGlobal);
}
public Player getSender() { return sender; }
public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
public boolean isGlobal() { return isGlobal; }
public long getTimestamp() { return timestamp; }
public String getChatType() { return isGlobal ? "global" : "local"; }
}

View file

@ -0,0 +1,71 @@
package com.loki.lochat.data.model;
import java.util.UUID;
/**
* Модель данных мута (Value Object)
*/
public class MuteData {
private UUID uuid;
private String playerName;
public long endTime; // 0 = перманентный (public for JSON serialization)
public String reason;
public String mutedBy;
public long mutedAt;
public MuteData() {} // For JSON deserialization
public MuteData(UUID uuid, String playerName, long endTime, String reason, String mutedBy) {
this.uuid = uuid;
this.playerName = playerName;
this.endTime = endTime;
this.reason = reason;
this.mutedBy = mutedBy;
this.mutedAt = System.currentTimeMillis();
}
public MuteData(long endTime, String reason, String mutedBy, long mutedAt, String playerName) {
this.endTime = endTime;
this.reason = reason;
this.mutedBy = mutedBy;
this.mutedAt = mutedAt;
this.playerName = playerName;
}
public UUID getUuid() { return uuid; }
public String getPlayerName() { return playerName; }
public long getEndTime() { return endTime; }
public String getReason() { return reason; }
public String getMutedBy() { return mutedBy; }
public long getMutedAt() { return mutedAt; }
public boolean isPermanent() { return endTime == 0; }
/**
* Запись истории мута
*/
public static class MuteHistoryEntry {
public String playerName;
public long duration;
public String reason;
public String mutedBy;
public long mutedAt;
public boolean unmuted;
public String unmutedBy;
public long unmutedAt;
public MuteHistoryEntry() {} // For JSON deserialization
public MuteHistoryEntry(String playerName, long duration, String reason,
String mutedBy, long mutedAt, boolean unmuted,
String unmutedBy, long unmutedAt) {
this.playerName = playerName;
this.duration = duration;
this.reason = reason;
this.mutedBy = mutedBy;
this.mutedAt = mutedAt;
this.unmuted = unmuted;
this.unmutedBy = unmutedBy;
this.unmutedAt = unmutedAt;
}
}
}

View file

@ -10,7 +10,6 @@ import com.loki.lochat.gradient.data.GradientPlayerData;
import com.loki.lochat.gradient.hooks.GradientLuckPermsHook;
import com.loki.lochat.gradient.listeners.GradientGUIListener;
import com.loki.lochat.gradient.listeners.GradientPlayerListener;
import com.loki.lochat.gradient.util.DisplayNameUtil;
import com.loki.lochat.gradient.util.GradientUtil;
import org.black_ixx.playerpoints.PlayerPoints;
import org.black_ixx.playerpoints.PlayerPointsAPI;

View file

@ -16,7 +16,10 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.regex.Pattern;

View file

@ -3,7 +3,6 @@ package com.loki.lochat.gradient.gui;
import com.loki.lochat.gradient.GradientModule;
import com.loki.lochat.gradient.util.GradientConstants;
import com.loki.lochat.gradient.util.GradientUtil;
import com.loki.lochat.utils.ChatFormatter;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;

View file

@ -1,6 +1,6 @@
package com.loki.lochat.gradient.util;
import java.awt.Color;
import java.awt.*;
import java.util.List;
import java.util.regex.Pattern;

View file

@ -1,6 +1,7 @@
package com.loki.lochat.integrations;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.MuteService;
import org.bukkit.entity.Player;
/**
@ -10,9 +11,11 @@ import org.bukkit.entity.Player;
public class LibertyBansHook {
private final LoChat plugin;
private final MuteService muteService;
public LibertyBansHook(LoChat plugin) {
this.plugin = plugin;
this.muteService = plugin.getServiceRegistry().get(MuteService.class);
// Проверяем наличие LibertyBans
if (plugin.getServer().getPluginManager().getPlugin("LibertyBans") != null) {
@ -24,7 +27,7 @@ public class LibertyBansHook {
* Проверяет, замучен ли игрок через встроенную систему LoChat
*/
public boolean isMuted(Player player) {
return plugin.getMuteManager().isMuted(player.getUniqueId());
return muteService.isMuted(player.getUniqueId());
}
public boolean isEnabled() {

View file

@ -1,6 +1,7 @@
package com.loki.lochat.integrations;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.*;
import com.loki.lochat.gradient.GradientModule;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.Bukkit;
@ -9,14 +10,23 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
import java.util.UUID;
public class PlaceholderAPIHook extends PlaceholderExpansion {
private final LoChat plugin;
private final IgnoreService ignoreService;
private final ChatService chatService;
private final PMService pmService;
private final SpyService spyService;
public PlaceholderAPIHook(LoChat plugin) {
this.plugin = plugin;
this.ignoreService = plugin.getServiceRegistry().get(IgnoreService.class);
this.chatService = plugin.getServiceRegistry().get(ChatService.class);
this.pmService = plugin.getServiceRegistry().get(PMService.class);
this.spyService = plugin.getServiceRegistry().get(SpyService.class);
}
@Override
@ -47,15 +57,15 @@ public class PlaceholderAPIHook extends PlaceholderExpansion {
GradientModule gradient = plugin.getGradientModule();
return switch (params.toLowerCase()) {
case "ignored_count" -> String.valueOf(plugin.getIgnoreManager().getIgnoredCount(uuid));
case "global_enabled" -> String.valueOf(!plugin.getChatManager().isGlobalChatDisabled(uuid));
case "ignored_count" -> String.valueOf(ignoreService.getIgnoredCount(uuid));
case "global_enabled" -> String.valueOf(!chatService.isGlobalChatDisabled(uuid));
case "last_pm" -> {
UUID last = plugin.getPmManager().getLastConversation(uuid);
if (last == null) yield "";
var lastPlayer = Bukkit.getOfflinePlayer(last);
Optional<UUID> lastOpt = pmService.getLastConversation(uuid);
if (lastOpt.isEmpty()) yield "";
var lastPlayer = Bukkit.getOfflinePlayer(lastOpt.get());
yield lastPlayer.getName() != null ? lastPlayer.getName() : "";
}
case "spy_enabled" -> String.valueOf(plugin.getSpyManager().isSpying(uuid));
case "spy_enabled" -> String.valueOf(spyService.isSpying(uuid));
// Gradient placeholders (совместимость с LoPreff)
// Используем TAB формат §x§R§R§G§G§B§B для совместимости с TAB плагином

View file

@ -0,0 +1,37 @@
package com.loki.lochat.listener;
import com.loki.lochat.api.service.MessageService;
import com.loki.lochat.core.registry.ServiceRegistry;
import com.loki.lochat.util.FoliaUtil;
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.java.JavaPlugin;
/**
* Слушатель событий чата (минимальная ответственность)
*/
public class ChatEventListener implements Listener {
private final JavaPlugin plugin;
private final MessageService messageService;
public ChatEventListener(JavaPlugin plugin, ServiceRegistry registry) {
this.plugin = plugin;
this.messageService = registry.get(MessageService.class);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onChat(AsyncPlayerChatEvent event) {
event.setCancelled(true);
Player player = event.getPlayer();
String message = event.getMessage();
// Folia-safe обработка
FoliaUtil.runEntityTask(plugin, player, () -> {
messageService.processMessage(player, message);
});
}
}

View file

@ -0,0 +1,25 @@
package com.loki.lochat.listener;
import com.loki.lochat.api.service.PlayerDataService;
import com.loki.lochat.core.registry.ServiceRegistry;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
/**
* Слушатель событий игрока
*/
public class PlayerEventListener implements Listener {
private final PlayerDataService playerDataService;
public PlayerEventListener(ServiceRegistry registry) {
this.playerDataService = registry.get(PlayerDataService.class);
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
playerDataService.clearPlayerData(player.getUniqueId());
}
}

View file

@ -1,216 +0,0 @@
package com.loki.lochat.listeners;
import com.loki.lochat.LoChat;
import com.loki.lochat.gradient.util.FoliaUtil;
import com.loki.lochat.managers.AntiSpamManager;
import io.papermc.paper.event.player.AsyncChatEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextReplacementConfig;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
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.PlayerQuitEvent;
public class ChatListener implements Listener {
private final LoChat plugin;
private static final PlainTextComponentSerializer PLAIN =
PlainTextComponentSerializer.plainText();
public ChatListener(LoChat plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onChat(AsyncChatEvent event) {
event.setCancelled(true);
Player player = event.getPlayer();
Component originalMessage = event.message();
String plainMessage = PLAIN.serialize(originalMessage);
// Paper/Folia-safe
FoliaUtil.runEntityTask(plugin, player, () -> {
processChat(player, originalMessage, plainMessage);
});
}
private void processChat(Player player, Component message, String plainMessage) {
// ===== MUTE CHECK =====
if (plugin.getMuteManager().isMuted(player.getUniqueId())) {
var muteData = plugin.getMuteManager().getMuteData(player.getUniqueId());
if (muteData != null) {
String msg;
if (muteData.isPermanent()) {
msg = plugin.getConfigManager().getString("mute.messages.cannot-chat-permanent",
"§cВы замучены навсегда!");
} else {
long remaining = plugin.getMuteManager().getRemainingTime(player.getUniqueId());
String timeStr = plugin.getMuteManager().formatTime(remaining);
msg = plugin.getConfigManager().getString("mute.messages.cannot-chat",
"§cВы замучены! Осталось: %duration%");
msg = msg.replace("%duration%", timeStr);
}
player.sendMessage(com.loki.lochat.utils.ChatFormatter.parse(msg));
}
return;
}
// Продолжаем обработку чата
processChatInternal(player, message, plainMessage);
}
private void processChatInternal(Player player, Component message, String plainMessage) {
// ===== GLOBAL / LOCAL =====
String globalSymbol = plugin.getConfigManager().getGlobalSymbol();
boolean isGlobal = plainMessage.startsWith(globalSymbol)
&& player.hasPermission("chat.global.use");
String chatType = isGlobal ? "global" : "local";
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;
// ===== FILTER =====
processedMessage = applyMessageFilter(player, processedMessage, processedPlain);
if (processedMessage == null) return; // Сообщение заблокировано
processedPlain = PLAIN.serialize(processedMessage);
// ===== COOLDOWN =====
if (!checkCooldown(player, chatType)) return;
// ===== ANTI SPAM =====
if (!checkAntiSpam(player, processedPlain)) return;
plugin.getCooldownManager().setCooldown(player.getUniqueId(), chatType);
// ===== SPY =====
plugin.getSpyManager().sendToSpies(player, processedMessage, isGlobal);
// ===== HEAD EMOJI =====
// Обрабатываем головы игроков, если функция включена
if (plugin.getHeadEmojiManager().isHeadEmojiEnabled() &&
plugin.getHeadEmojiManager().canUseHeadEmoji(player)) {
processedMessage = plugin.getHeadEmojiManager().processHeads(processedMessage, player);
}
// ===== SEND =====
if (isGlobal) {
plugin.getChatManager().sendGlobalMessage(player, processedMessage);
} else {
plugin.getChatManager().sendLocalMessage(player, processedMessage);
}
}
private Component applyMessageFilter(Player player, Component message, String plainMessage) {
if (!plugin.getConfigManager().isFilterEnabled() || player.hasPermission("chat.bypass.filter")) {
return message;
}
String filteredMessage = applyFilter(plainMessage);
if (!filteredMessage.equals(plainMessage)) {
String action = plugin.getConfigManager().getFilterAction();
switch (action.toLowerCase()) {
case "block" -> {
player.sendMessage(plugin.getMessageConfig().getComponent("filter.blocked"));
return null;
}
case "warn" -> {
player.sendMessage(plugin.getMessageConfig().getComponent("filter.warning"));
return null;
}
case "censor" -> {
return Component.text(filteredMessage);
}
}
}
return message;
}
private boolean checkCooldown(Player player, String chatType) {
int cooldown = chatType.equals("global")
? 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))
);
return false;
}
}
return true;
}
private boolean checkAntiSpam(Player player, String message) {
if (player.hasPermission("chat.bypass.antispam")) {
return true;
}
AntiSpamManager.SpamResult result = plugin.getAntiSpamManager()
.checkMessage(player.getUniqueId(), message);
switch (result) {
case TOO_MANY_CAPS -> {
player.sendMessage(plugin.getMessageConfig().getComponent("antispam.caps"));
return false;
}
case REPEAT_CHARS -> {
player.sendMessage(plugin.getMessageConfig().getComponent("antispam.repeat"));
return false;
}
case SIMILAR_MESSAGE -> {
player.sendMessage(plugin.getMessageConfig().getComponent("antispam.similar"));
return false;
}
case ALLOWED -> {
return true;
}
}
return true;
}
private String applyFilter(String message) {
if (!plugin.getConfigManager().isFilterEnabled()) {
return message;
}
String filtered = message;
String replacement = plugin.getConfigManager().getFilterReplacement();
for (String word : plugin.getConfigManager().getFilterWords()) {
if (word == null || word.trim().isEmpty()) continue;
// Заменяем слово с учетом регистра и границ слов
String pattern = "(?i)\\b" + java.util.regex.Pattern.quote(word.trim()) + "\\b";
filtered = filtered.replaceAll(pattern, replacement);
}
return filtered;
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
plugin.getPmManager().removeConversation(player.getUniqueId());
plugin.getCooldownManager().removeCooldown(player.getUniqueId());
plugin.getAntiSpamManager().clearPlayer(player.getUniqueId());
plugin.getSpyManager().removeSpy(player.getUniqueId());
}
}

View file

@ -1,128 +0,0 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class AntiSpamManager {
private final LoChat plugin;
private final Map<UUID, String> lastMessages = new ConcurrentHashMap<>();
private final Map<UUID, Long> lastMessageTime = new ConcurrentHashMap<>();
public AntiSpamManager(LoChat plugin) {
this.plugin = plugin;
}
public enum SpamResult {
ALLOWED,
TOO_MANY_CAPS,
REPEAT_CHARS,
SIMILAR_MESSAGE
}
public SpamResult checkMessage(UUID player, String message) {
if (!plugin.getConfigManager().isAntiSpamEnabled()) {
return SpamResult.ALLOWED;
}
// Проверка CAPS
if (hasTooManyCaps(message)) {
return SpamResult.TOO_MANY_CAPS;
}
// Проверка повторяющихся символов
if (hasRepeatChars(message)) {
return SpamResult.REPEAT_CHARS;
}
// Проверка похожих сообщений
if (isSimilarMessage(player, message)) {
return SpamResult.SIMILAR_MESSAGE;
}
// Сохраняем сообщение
lastMessages.put(player, message.toLowerCase());
lastMessageTime.put(player, System.currentTimeMillis());
return SpamResult.ALLOWED;
}
private boolean hasTooManyCaps(String message) {
if (message.length() < 5) return false;
int maxPercent = plugin.getConfigManager().getMaxCapsPercent();
long upperCount = message.chars().filter(Character::isUpperCase).count();
long letterCount = message.chars().filter(Character::isLetter).count();
if (letterCount == 0) return false;
int percent = (int) ((upperCount * 100) / letterCount);
return percent > maxPercent;
}
private boolean hasRepeatChars(String message) {
int maxRepeat = plugin.getConfigManager().getMaxRepeatChars();
int count = 1;
char lastChar = 0;
for (char c : message.toCharArray()) {
if (c == lastChar) {
count++;
if (count > maxRepeat) return true;
} else {
count = 1;
lastChar = c;
}
}
return false;
}
private boolean isSimilarMessage(UUID player, String message) {
String lastMsg = lastMessages.get(player);
Long lastTime = lastMessageTime.get(player);
if (lastMsg == null || lastTime == null) return false;
int delay = plugin.getConfigManager().getSimilarMessageDelay();
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
if (elapsed > delay) return false;
// Проверка схожести (простая)
return message.toLowerCase().equals(lastMsg) ||
similarity(message.toLowerCase(), lastMsg) > 0.8;
}
private double similarity(String s1, String s2) {
int maxLen = Math.max(s1.length(), s2.length());
if (maxLen == 0) return 1.0;
int distance = levenshteinDistance(s1, s2);
return 1.0 - ((double) distance / maxLen);
}
private int levenshteinDistance(String s1, String s2) {
int[][] dp = new int[s1.length() + 1][s2.length() + 1];
for (int i = 0; i <= s1.length(); i++) dp[i][0] = i;
for (int j = 0; j <= s2.length(); j++) dp[0][j] = j;
for (int i = 1; i <= s1.length(); i++) {
for (int j = 1; j <= s2.length(); j++) {
int cost = s1.charAt(i - 1) == s2.charAt(j - 1) ? 0 : 1;
dp[i][j] = Math.min(Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + cost);
}
}
return dp[s1.length()][s2.length()];
}
public void clearPlayer(UUID player) {
lastMessages.remove(player);
lastMessageTime.remove(player);
}
}

View file

@ -145,11 +145,8 @@ public class AutoMessageManager {
boolean isFirstLine = true;
for (String line : lines) {
// Заменяем смайлики через EmojiManager
String processed = ChatFormatter.replaceEmojis(line);
// Заменяем плейсхолдеры
processed = processed.replace("%online%", String.valueOf(Bukkit.getOnlinePlayers().size()));
String processed = line.replace("%online%", String.valueOf(Bukkit.getOnlinePlayers().size()));
// Добавляем префикс к первой непустой строке
Component formatted;

View file

@ -1,6 +1,7 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.MentionService;
import com.loki.lochat.utils.ChatFormatter;
import com.loki.lochat.utils.DistanceUtil;
import net.kyori.adventure.text.Component;
@ -14,15 +15,20 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* Менеджер чата - использует MentionService для обработки упоминаний
*/
public class ChatManager {
private final LoChat plugin;
private final MentionService mentionService;
private final Set<UUID> globalChatDisabled = ConcurrentHashMap.newKeySet();
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
private static final PlainTextComponentSerializer PLAIN = PlainTextComponentSerializer.plainText();
public ChatManager(LoChat plugin) {
this.plugin = plugin;
this.mentionService = plugin.getServiceRegistry().get(MentionService.class);
}
public void sendGlobalMessage(Player sender, Component message) {
@ -36,7 +42,7 @@ public class ChatManager {
// Обработка упоминаний @ник
Set<Player> mentionedPlayers = new HashSet<>();
String processedMessage = plugin.getMentionManager().processMentions(messageText, mentionedPlayers);
String processedMessage = mentionService.processMentions(messageText, mentionedPlayers);
String format = plugin.getConfigManager().getGlobalFormat();
String prefix = plugin.getConfigManager().getGlobalPrefix();
@ -49,7 +55,7 @@ public class ChatManager {
if (isGlobalChatDisabled(player.getUniqueId())) continue;
// Персонализируем сообщение для этого игрока (выделяем его ник)
String personalizedMsg = plugin.getMentionManager().getPersonalizedMessage(processedMessage, player);
String personalizedMsg = mentionService.getPersonalizedMessage(processedMessage, player);
// Формируем итоговую строку
String formatted = format
@ -62,11 +68,11 @@ public class ChatManager {
player.sendMessage(component);
// Проверяем упоминание ника без @ и уведомляем
plugin.getMentionManager().notifyIfMentioned(messageText, player, sender);
mentionService.notifyIfMentioned(messageText, player, sender);
}
// Уведомление упомянутых через @
plugin.getMentionManager().notifyMentioned(mentionedPlayers);
mentionService.notifyMentioned(mentionedPlayers);
plugin.getLogger().info("[G] " + sender.getName() + ": " + ChatFormatter.stripTags(messageText));
}
@ -81,7 +87,7 @@ public class ChatManager {
// Обработка упоминаний @ник
Set<Player> mentionedPlayers = new HashSet<>();
String processedMessage = plugin.getMentionManager().processMentions(messageText, mentionedPlayers);
String processedMessage = mentionService.processMentions(messageText, mentionedPlayers);
int radius = plugin.getConfigManager().getLocalRadius();
String format = plugin.getConfigManager().getLocalFormat();
@ -96,7 +102,7 @@ public class ChatManager {
for (Player player : Bukkit.getOnlinePlayers()) {
if (DistanceUtil.isInRange(sender, player, radius)) {
// Персонализируем сообщение для этого игрока
String personalizedMsg = plugin.getMentionManager().getPersonalizedMessage(processedMessage, player);
String personalizedMsg = mentionService.getPersonalizedMessage(processedMessage, player);
// Формируем итоговую строку
String formatted = prefix + " " + format
@ -110,7 +116,7 @@ public class ChatManager {
recipients.add(player);
// Проверяем упоминание ника без @
plugin.getMentionManager().notifyIfMentioned(messageText, player, sender);
mentionService.notifyIfMentioned(messageText, player, sender);
if (!player.equals(sender)) {
foundRecipient = true;
@ -120,7 +126,7 @@ public class ChatManager {
// Уведомление упомянутых через @ (только тех кто в радиусе)
mentionedPlayers.retainAll(recipients);
plugin.getMentionManager().notifyMentioned(mentionedPlayers);
mentionService.notifyMentioned(mentionedPlayers);
if (!foundRecipient) {
sender.sendMessage(plugin.getMessageConfig().getComponent("local.no-players"));
@ -153,4 +159,4 @@ public class ChatManager {
public boolean isGlobalChatDisabled(UUID player) {
return globalChatDisabled.contains(player);
}
}
}

View file

@ -23,8 +23,6 @@ public class CustomCommandManager {
private final LoChat plugin;
private final Map<String, CustomCommandData> commands;
private File commandsFile;
private FileConfiguration commandsConfig;
public CustomCommandManager(LoChat plugin) {
this.plugin = plugin;
@ -33,12 +31,12 @@ public class CustomCommandManager {
}
private void loadCommands() {
commandsFile = new File(plugin.getDataFolder(), "custom-commands.yml");
File commandsFile = new File(plugin.getDataFolder(), "custom-commands.yml");
if (!commandsFile.exists()) {
plugin.saveResource("custom-commands.yml", false);
}
commandsConfig = YamlConfiguration.loadConfiguration(commandsFile);
FileConfiguration commandsConfig = YamlConfiguration.loadConfiguration(commandsFile);
// Очищаем старые команды
commands.clear();
@ -117,7 +115,6 @@ public class CustomCommandManager {
// Выполняем команду в зависимости от типа
switch (data.type.toLowerCase()) {
case "chat" -> sendChatMessage(data.target, player, message);
case "broadcast" -> sendBroadcast(data.target, message);
case "title" -> sendTitle(data.target, player, message);
case "actionbar" -> sendActionBar(data.target, player, message);
@ -186,20 +183,17 @@ public class CustomCommandManager {
private void sendBroadcast(String target, String message) {
Component component = ChatFormatter.parse(message);
switch (target.toLowerCase()) {
case "all" -> {
for (Player player : Bukkit.getOnlinePlayers()) {
player.sendMessage(component);
}
if (target.equalsIgnoreCase("all")) {
for (Player player : Bukkit.getOnlinePlayers()) {
player.sendMessage(component);
}
default -> {
if (target.startsWith("permission:")) {
String permission = target.substring(11);
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.hasPermission(permission)) {
player.sendMessage(component);
}
} else {
if (target.startsWith("permission:")) {
String permission = target.substring(11);
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.hasPermission(permission)) {
player.sendMessage(component);
}
}
}

View file

@ -1,100 +0,0 @@
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;
}
}

View file

@ -1,172 +0,0 @@
package com.loki.lochat.managers;
import com.loki.lochat.LoChat;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Менеджер для обработки голов игроков в чате
* Поддерживает рисование головы из смайликов и отправку голов игроков
*/
public class HeadEmojiManager {
private final LoChat plugin;
// Паттерны для поиска голов игроков
private static final Pattern HEAD_PATTERN = Pattern.compile(":head_([a-zA-Z0-9_]{3,16}):");
private static final Pattern PLAYER_HEAD_PATTERN = Pattern.compile("@head\\s+([a-zA-Z0-9_]{3,16})");
public HeadEmojiManager(LoChat plugin) {
this.plugin = plugin;
}
/**
* Обрабатывает сообщение и заменяет головы игроков
*/
public Component processHeads(Component message, Player sender) {
String plainText = net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer.plainText().serialize(message);
// Обрабатываем :head_nickname:
plainText = processHeadEmojis(plainText);
// Обрабатываем @head nickname
plainText = processPlayerHeadCommands(plainText, sender);
return net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(plainText);
}
/**
* Обрабатывает :head_nickname: паттерны
*/
private String processHeadEmojis(String message) {
Matcher matcher = HEAD_PATTERN.matcher(message);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String playerName = matcher.group(1);
Player target = Bukkit.getPlayer(playerName);
if (target != null && target.isOnline()) {
String headEmoji = generateHeadEmoji(target);
matcher.appendReplacement(result, headEmoji);
} else {
// Если игрок не найден, оставляем как есть
matcher.appendReplacement(result, matcher.group(0));
}
}
matcher.appendTail(result);
return result.toString();
}
/**
* Обрабатывает @head nickname команды
*/
private String processPlayerHeadCommands(String message, Player sender) {
Matcher matcher = PLAYER_HEAD_PATTERN.matcher(message);
StringBuffer result = new StringBuffer();
while (matcher.find()) {
String playerName = matcher.group(1);
Player target = Bukkit.getPlayer(playerName);
if (target != null && target.isOnline()) {
String headDisplay = createClickableHead(target, sender);
matcher.appendReplacement(result, headDisplay);
} else {
// Если игрок не найден, показываем ошибку
matcher.appendReplacement(result, "§c[Игрок " + playerName + " не найден]");
}
}
matcher.appendTail(result);
return result.toString();
}
/**
* Генерирует ASCII арт головы игрока
*/
private String generateHeadEmoji(Player player) {
// Получаем доминирующий цвет скина (упрощенная версия)
String skinColor = getSkinColor(player);
String hairColor = getHairColor(player);
StringBuilder head = new StringBuilder();
head.append("§8[§r"); // Начало рамки
// Генерируем упрощенную голову 3x3
head.append(hairColor).append("███§r");
head.append(skinColor).append("█").append("§0█").append(skinColor).append("█§r"); // Глаза
head.append(skinColor).append("███§r");
head.append("§8]§r"); // Конец рамки
return head.toString();
}
/**
* Создает кликабельную голову игрока
*/
private String createClickableHead(Player target, Player sender) {
// Создаем компонент с hover и click событиями
return "§6[§e" + target.getName() + " Head§6]§r";
}
/**
* Получает цвет скина игрока (упрощенная версия)
*/
private String getSkinColor(Player player) {
// В реальной реализации здесь был бы анализ текстуры скина
// Для демонстрации используем случайные цвета
String[] skinColors = {"§f", "§7", "§6", "§c", "§e"};
return skinColors[player.getName().hashCode() % skinColors.length];
}
/**
* Получает цвет волос игрока (упрощенная версия)
*/
private String getHairColor(Player player) {
// В реальной реализации здесь был бы анализ текстуры скина
String[] hairColors = {"§0", "§8", "§6", "§c", "§e", "§a"};
return hairColors[(player.getName().hashCode() + 1) % hairColors.length];
}
/**
* Создает Component с hover эффектом для головы игрока
*/
public Component createHeadComponent(Player target) {
Component headText = Component.text("[" + target.getName() + " Head]")
.color(NamedTextColor.GOLD)
.hoverEvent(HoverEvent.showText(
Component.text("Голова игрока: " + target.getName())
.color(NamedTextColor.YELLOW)
.append(Component.newline())
.append(Component.text("Нажмите, чтобы получить информацию")
.color(NamedTextColor.GRAY))
))
.clickEvent(ClickEvent.runCommand("/lochat playerinfo " + target.getName()));
return headText;
}
/**
* Проверяет, включена ли функция голов игроков
*/
public boolean isHeadEmojiEnabled() {
return plugin.getConfigManager().getBoolean("head-emoji.enabled", true);
}
/**
* Проверяет, может ли игрок использовать головы
*/
public boolean canUseHeadEmoji(Player player) {
return player.hasPermission("lochat.heademoji.use");
}
}

View file

@ -0,0 +1,51 @@
package com.loki.lochat.util;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.bukkit.command.CommandSender;
/**
* Утилита для форматирования сообщений
*/
public class ChatFormatter {
private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
private static final LegacyComponentSerializer LEGACY = LegacyComponentSerializer.legacyAmpersand();
public static Object parse(String message) {
try {
return MINI_MESSAGE.deserialize(message);
} catch (Exception e) {
return LEGACY.deserialize(message);
}
}
public static String toPlain(Object message) {
if (message instanceof Component component) {
return LegacyComponentSerializer.legacySection().serialize(component);
}
return message.toString();
}
public static void sendMessage(CommandSender sender, Object message) {
if (message instanceof Component component) {
sender.sendMessage(component);
} else {
sender.sendMessage(message.toString());
}
}
public static void broadcast(Object message) {
if (message instanceof Component component) {
org.bukkit.Bukkit.broadcast(component);
}
}
public static String stripTags(String message) {
return message.replaceAll("<[^>]+>", "").replaceAll("§[0-9a-fk-or]", "");
}
public static String convertAllColors(String message) {
return message.replace("&", "§");
}
}

View file

@ -0,0 +1,17 @@
package com.loki.lochat.util;
import org.bukkit.entity.Player;
/**
* Утилита для проверки расстояния между игроками
*/
public class DistanceUtil {
public static boolean isInRange(Player player1, Player player2, int radius) {
if (!player1.getWorld().equals(player2.getWorld())) {
return false;
}
return player1.getLocation().distance(player2.getLocation()) <= radius;
}
}

View file

@ -0,0 +1,60 @@
package com.loki.lochat.util;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Утилита для совместимости с Folia
*/
public class FoliaUtil {
private static boolean isFolia;
static {
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
isFolia = true;
} catch (ClassNotFoundException e) {
isFolia = false;
}
}
public static boolean isFolia() {
return isFolia;
}
public static void runAsync(JavaPlugin plugin, Runnable task) {
if (isFolia) {
Bukkit.getAsyncScheduler().runNow(plugin, scheduledTask -> task.run());
} else {
Bukkit.getScheduler().runTaskAsynchronously(plugin, task);
}
}
public static void runEntityTask(JavaPlugin plugin, Entity entity, Runnable task) {
if (isFolia) {
entity.getScheduler().run(plugin, scheduledTask -> task.run(), null);
} else {
Bukkit.getScheduler().runTask(plugin, task);
}
}
/**
* Запускает глобальную повторяющуюся задачу
* @param plugin плагин
* @param task задача
* @param delayTicks задержка перед первым запуском (в тиках)
* @param periodTicks период повторения (в тиках)
*/
public static void runTimerAsync(JavaPlugin plugin, Runnable task, long delayTicks, long periodTicks) {
if (isFolia) {
// В Folia используем AsyncScheduler для глобальных задач
long delayMs = delayTicks * 50; // 1 тик = 50мс
long periodMs = periodTicks * 50;
Bukkit.getAsyncScheduler().runAtFixedRate(plugin, scheduledTask -> task.run(), delayMs, periodMs, java.util.concurrent.TimeUnit.MILLISECONDS);
} else {
// В Paper/Spigot используем обычный scheduler
Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, task, delayTicks, periodTicks);
}
}
}

View file

@ -0,0 +1,56 @@
package com.loki.lochat.util;
/**
* Утилита для парсинга времени (DRY - вынесена общая логика)
*/
public class TimeParser {
/**
* Парсит строку времени в миллисекунды
* @param timeStr строка вида "1d", "2h", "30m", "60s"
* @return миллисекунды или -1 при ошибке
*/
public static long parse(String timeStr) {
if (timeStr == null || timeStr.isEmpty()) return 0;
timeStr = timeStr.toLowerCase();
try {
if (timeStr.endsWith("d")) {
return Long.parseLong(timeStr.replace("d", "")) * 24 * 60 * 60 * 1000L;
} else if (timeStr.endsWith("h")) {
return Long.parseLong(timeStr.replace("h", "")) * 60 * 60 * 1000L;
} else if (timeStr.endsWith("m")) {
return Long.parseLong(timeStr.replace("m", "")) * 60 * 1000L;
} else if (timeStr.endsWith("s")) {
return Long.parseLong(timeStr.replace("s", "")) * 1000L;
} else {
return Long.parseLong(timeStr) * 60 * 1000L; // По умолчанию минуты
}
} catch (NumberFormatException e) {
return -1;
}
}
/**
* Форматирует миллисекунды в читаемую строку
*/
public static String format(long millis) {
if (millis <= 0) return "0с";
long seconds = millis / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
if (days > 0) {
return days + "д " + (hours % 24) + "ч";
} else if (hours > 0) {
return hours + "ч " + (minutes % 60) + "м";
} else if (minutes > 0) {
return minutes + "м " + (seconds % 60) + "с";
} else {
return seconds + "с";
}
}
}

View file

@ -122,6 +122,23 @@ public final class ChatFormatter {
public static String toPlain(Component component) {
return component == null ? "" : PLAIN.serialize(component);
}
public static String toPlain(Object obj) {
if (obj == null) return "";
if (obj instanceof Component component) {
return PLAIN.serialize(component);
}
return obj.toString();
}
/**
* Отправляет сообщение игроку
*/
public static void sendMessage(Player player, Component component) {
if (player != null && component != null) {
player.sendMessage(component);
}
}
/* ===================== COLORS ===================== */
@ -133,14 +150,14 @@ public final class ChatFormatter {
return convertLegacyFormats(message);
}
/* ===================== EMOJIS ===================== */
/* ===================== EMOJIS (REMOVED) ===================== */
public static String replaceEmojis(String message) {
return message;
return message; // Эмодзи удалены
}
public static String replaceEmojis(String message, Player player) {
return message;
return message; // Эмодзи удалены
}
/* ===================== CHAT ===================== */

View file

@ -98,58 +98,6 @@ mentions:
highlight: "<gradient:#FFFF00:#FFD700>@{player}</gradient>"
# Подсветка собственного ника
self-highlight: "<gradient:#FFD700:#FFA500>{player}</gradient>"
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ФИЛЬТР МАТА │
# └─────────────────────────────────────────────────────────────────────────────┘
filter:
# Включить фильтр мата
enabled: true
# Список запрещенных слов (простая проверка)
words:
- "хуй"
- "пизд"
- "блять"
- "ебать"
- "сука"
- "мудак"
- "гандон"
- "пидор"
- "говно"
- "шлюха"
# Регулярные выражения для обнаружения мата с обходами
patterns:
- "х\\s*у\\s*[йяёе]" # хуй, хуя, хуё, хуе
- "[хx]\\s*у\\s*[йяёе]" # латиница: xuй
- "п\\s*[иi]\\s*з\\s*д" # пизд, пiзд
- "п\\s*[иi]\\s*д\\s*[оа]\\s*р" # пидор, пидар
- "[еeё]\\s*б\\s*[аоуыи]" # ебать, ебут, ебал
- "б\\s*л\\s*я\\s*[тдь]" # блять, блядь
- "с\\s*у\\s*к\\s*[аи]" # сука, суки
- "м\\s*у\\s*д\\s*[аи]" # муда, муди
# Действие при обнаружении мата: censor, block, warn
action: "censor"
# Замена для цензуры
replacement: "***"
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ АНТИ-СПАМ │
# └─────────────────────────────────────────────────────────────────────────────┘
antispam:
# Включить анти-спам
enabled: true
# Максимальный процент заглавных букв
max-caps-percent: 70
# Максимальное количество повторяющихся символов
max-repeat-chars: 5
# Задержка для проверки похожих сообщений (секунды)
similar-message-delay: 30
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ АВТОСООБЩЕНИЯ │
# └─────────────────────────────────────────────────────────────────────────────┘

View file

@ -1,281 +0,0 @@
# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║ LoChat Emojis ║
# ║ Смайлики для чата ║
# ║ Версия: 1.5.5-1.20.1 ║
# ╚══════════════════════════════════════════════════════════════════════════════╝
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ЭМОЦИИ И ЛИЦА │
# └─────────────────────────────────────────────────────────────────────────────┘
emojis:
# Основные эмоции
":)": "😊"
":(": "😢"
":D": "😃"
";)": "😉"
":P": "😛"
":o": "😮"
":|": "😐"
":/": "😕"
":*": "😘"
"<3": "❤️"
"</3": "💔"
# Расширенные эмоции
":smile:": "😊"
":sad:": "😢"
":happy:": "😃"
":wink:": "😉"
":tongue:": "😛"
":surprised:": "😮"
":neutral:": "😐"
":confused:": "😕"
":kiss:": "😘"
":love:": "❤️"
":broken_heart:": "💔"
":angry:": "😠"
":rage:": "😡"
":cry:": "😭"
":laugh:": "😂"
":joy:": "😂"
":rofl:": "🤣"
":cool:": "😎"
":nerd:": "🤓"
":thinking:": "🤔"
":sleepy:": "😴"
":sick:": "🤒"
":dizzy:": "😵"
":crazy:": "🤪"
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ЖЕСТЫ И ДЕЙСТВИЯ │
# └─────────────────────────────────────────────────────────────────────────────┘
# Жесты
":thumbsup:": "👍"
":thumbsdown:": "👎"
":ok:": "👌"
":peace:": "✌️"
":fist:": "✊"
":wave:": "👋"
":clap:": "👏"
":pray:": "🙏"
":muscle:": "💪"
":point_right:": "👉"
":point_left:": "👈"
":point_up:": "👆"
":point_down:": "👇"
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ПРИРОДА И ПОГОДА │
# └─────────────────────────────────────────────────────────────────────────────┘
# Природа
":sun:": "☀️"
":moon:": "🌙"
":star:": "⭐"
":cloud:": "☁️"
":rain:": "🌧️"
":snow:": "❄️"
":fire:": "🔥"
":water:": "💧"
":tree:": "🌳"
":flower:": "🌸"
":rose:": "🌹"
":leaf:": "🍃"
":rainbow:": "🌈"
":lightning:": "⚡"
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ЖИВОТНЫЕ │
# └─────────────────────────────────────────────────────────────────────────────┘
# Животные
":cat:": "🐱"
":dog:": "🐶"
":wolf:": "🐺"
":fox:": "🦊"
":bear:": "🐻"
":panda:": "🐼"
":pig:": "🐷"
":cow:": "🐄"
":horse:": "🐴"
":sheep:": "🐑"
":chicken:": "🐔"
":duck:": "🦆"
":eagle:": "🦅"
":fish:": "🐟"
":shark:": "🦈"
":whale:": "🐋"
":dolphin:": "🐬"
":turtle:": "🐢"
":snake:": "🐍"
":dragon:": "🐉"
":unicorn:": "🦄"
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ЕДА И НАПИТКИ │
# └─────────────────────────────────────────────────────────────────────────────┘
# Еда
":apple:": "🍎"
":banana:": "🍌"
":orange:": "🍊"
":strawberry:": "🍓"
":watermelon:": "🍉"
":grapes:": "🍇"
":cherry:": "🍒"
":peach:": "🍑"
":pineapple:": "🍍"
":bread:": "🍞"
":cheese:": "🧀"
":meat:": "🥩"
":pizza:": "🍕"
":burger:": "🍔"
":fries:": "🍟"
":hotdog:": "🌭"
":taco:": "🌮"
":cake:": "🎂"
":cookie:": "🍪"
":donut:": "🍩"
":ice_cream:": "🍦"
":candy:": "🍬"
":chocolate:": "🍫"
# Напитки
":coffee:": "☕"
":tea:": "🍵"
":beer:": "🍺"
":wine:": "🍷"
":cocktail:": "🍹"
":milk:": "🥛"
":water:": "💧"
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ИГРЫ И РАЗВЛЕЧЕНИЯ │
# └─────────────────────────────────────────────────────────────────────────────┘
# Игры
":game:": "🎮"
":dice:": "🎲"
":cards:": "🃏"
":chess:": "♟️"
":football:": "⚽"
":basketball:": "🏀"
":tennis:": "🎾"
":bowling:": "🎳"
":dart:": "🎯"
":trophy:": "🏆"
":medal:": "🏅"
":crown:": "👑"
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ТРАНСПОРТ │
# └─────────────────────────────────────────────────────────────────────────────┘
# Транспорт
":car:": "🚗"
":bus:": "🚌"
":truck:": "🚚"
":bike:": "🚲"
":motorcycle:": "🏍️"
":plane:": "✈️"
":helicopter:": "🚁"
":rocket:": "🚀"
":ship:": "🚢"
":boat:": "⛵"
":train:": "🚂"
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ПРЕДМЕТЫ И СИМВОЛЫ │
# └─────────────────────────────────────────────────────────────────────────────┘
# Предметы
":sword:": "⚔️"
":shield:": "🛡️"
":bow:": "🏹"
":hammer:": "🔨"
":pickaxe:": "⛏️"
":axe:": "🪓"
":shovel:": "🗡️"
":key:": "🔑"
":lock:": "🔒"
":unlock:": "🔓"
":gem:": "💎"
":diamond:": "💎"
":gold:": "🥇"
":silver:": "🥈"
":bronze:": "🥉"
":coin:": "🪙"
":money:": "💰"
":gift:": "🎁"
":balloon:": "🎈"
":party:": "🎉"
":confetti:": "🎊"
":fireworks:": "🎆"
# Символы
":check:": "✅"
":cross:": "❌"
":warning:": "⚠️"
":info:": "ℹ️"
":question:": "❓"
":exclamation:": "❗"
":plus:": "➕"
":minus:": "➖"
":multiply:": "✖️"
":divide:": "➗"
":arrow_up:": "⬆️"
":arrow_down:": "⬇️"
":arrow_left:": "⬅️"
":arrow_right:": "➡️"
":100:": "💯"
":infinity:": "♾️"
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ MINECRAFT СПЕЦИФИЧНЫЕ │
# └─────────────────────────────────────────────────────────────────────────────┘
# Minecraft блоки и предметы
":creeper:": "💥"
":tnt:": "🧨"
":redstone:": "🔴"
":emerald:": "💚"
":iron:": "⚪"
":coal:": "⚫"
":wood:": "🟤"
":stone:": "🗿"
":dirt:": "🟫"
":grass:": "🟩"
":sand:": "🟨"
":water_bucket:": "🪣"
":lava_bucket:": "🔥"
":bed:": "🛏️"
":chest:": "📦"
":furnace:": "🔥"
":crafting:": "🔧"
":enchant:": "✨"
":potion:": "🧪"
":book:": "📚"
":map:": "🗺️"
":compass:": "🧭"
":clock:": "🕐"
# ┌─────────────────────────────────────────────────────────────────────────────┐
# │ ГОЛОВЫ ИГРОКОВ │
# └─────────────────────────────────────────────────────────────────────────────┘
# Примеры использования голов игроков:
# :head_Steve: - показать голову игрока Steve
# @head Notch - показать интерактивную голову игрока Notch
#
# Требуется право: lochat.heademoji.use
head_examples:
usage: |
<gradient:#9370DB:#8A2BE2>═══════ Головы игроков ═══════</gradient>
<gradient:#87CEEB:#4682B4>:head_nickname:</gradient> <gray>- показать голову игрока</gray>
<gradient:#87CEEB:#4682B4>@head nickname</gradient> <gray>- интерактивная голова</gray>
<gray>Требуется право:</gray> <white>lochat.heademoji.use</white>

View file

@ -1,3 +1,4 @@
#file: noinspection YAMLDuplicatedKeys,YAMLDuplicatedKeys
# ╔══════════════════════════════════════════════════════════════════════════════╗
# ║ LoChat Gradient Messages ║
# ║ Сообщения градиентного модуля ║

View file

@ -2,7 +2,7 @@ package com.loki.lochat.gradient.util;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.assertEquals;
class GradientConstantsTest {

View file

@ -0,0 +1,64 @@
package com.loki.lochat.utils;
import net.kyori.adventure.text.Component;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class ChatFormatterTest {
@Test
void testParseSimpleText() {
Component result = ChatFormatter.parse("Hello World");
assertNotNull(result);
}
@Test
void testParseWithColors() {
Component result = ChatFormatter.parse("<red>Red Text</red>");
assertNotNull(result);
}
@Test
void testParseWithHex() {
Component result = ChatFormatter.parse("<#FF0000>Hex Color</#FF0000>");
assertNotNull(result);
}
@Test
void testStripTags() {
String result = ChatFormatter.stripTags("<red>Text</red>");
assertEquals("Text", result);
}
@Test
void testConvertLegacyColors() {
String result = ChatFormatter.convertAllColors("&cRed &aGreen");
assertTrue(result.contains("<red>") || result.contains("Red"));
}
@Test
void testReplaceEmojis() {
String result = ChatFormatter.replaceEmojis("Test :smile:");
assertEquals("Test :smile:", result);
}
@Test
void testToPlainComponent() {
Component comp = Component.text("Plain");
String result = ChatFormatter.toPlain(comp);
assertEquals("Plain", result);
}
@Test
void testToPlainObject() {
String result = ChatFormatter.toPlain("String");
assertEquals("String", result);
}
@Test
void testToPlainNull() {
String result = ChatFormatter.toPlain((Object) null);
assertEquals("", result);
}
}