From bba480002f2cd2037f54e54f74910787939858f3 Mon Sep 17 00:00:00 2001 From: loki5512344 Date: Mon, 6 Apr 2026 00:18:25 +0200 Subject: [PATCH] refactor: restructure services and filters architecture - Remove deprecated service interfaces (CooldownService, IgnoreService, PMService, PlayerDataService, SpyService) - Add new unified service interfaces (MessagingService, PlayerService, PunishmentService) - Extract FilterResult as standalone class from AdvancedMessageFilter - Implement individual filter classes (CharacterFilter, FloodFilter, SpamFilter, SwearFilter, UrlFilter) - Add moderation commands (BanCommand, WarnCommand, SilentWarnCommand, UnbanCommand) - Introduce data models for punishment tracking (BanRecord, WarnEntry) - Refactor messaging service with new MessagingServiceImpl implementation - Add mute service utilities (MuteDataStorage, MutePermissionChecker) - Implement PunishmentServiceImpl with snapshot tracking - Add Discord integration components (DiscordConfig, DiscordMessageService) - Introduce command handlers for gradient module (ColorCommandHandler, PrefixCommandHandler) - Add renderer components (EmojiComponent, GradientBuilder, UrlProcessor) - Add utility classes (ColorConverter, MessageSender, PlaceholderProcessor) - Update configuration files and plugin metadata - Add documentation files (CODE_REVIEW_RU.md, REFACTORING_SERVICES.md) - Consolidate service architecture for improved maintainability and separation of concerns --- CODE_REVIEW_RU.md | 356 ++++++++++++++ MIGRATION_PLAN.md | 90 ---- REFACTORING_SERVICES.md | 287 ++++++++++++ bin/main/com/loki/lochat/LoChat.class | Bin 5261 -> 5427 bytes .../lochat/api/service/CooldownService.class | Bin 384 -> 0 bytes .../lochat/api/service/IgnoreService.class | Bin 504 -> 0 bytes .../lochat/api/service/MessagingService.class | Bin 0 -> 1041 bytes .../loki/lochat/api/service/PMService.class | Bin 444 -> 0 bytes .../api/service/PlayerDataService.class | Bin 215 -> 0 bytes .../lochat/api/service/PlayerService.class | Bin 0 -> 504 bytes .../api/service/PunishmentService.class | Bin 0 -> 813 bytes .../loki/lochat/api/service/SpyService.class | Bin 421 -> 0 bytes .../loki/lochat/commands/CommandManager.class | Bin 5660 -> 6030 bytes .../commands/admin/ChatSpyCommand.class | Bin 2563 -> 2581 bytes .../commands/chat/GlobalChatCommand.class | Bin 3461 -> 3461 bytes .../commands/messaging/IgnoreCommand.class | Bin 3044 -> 3053 bytes .../messaging/IgnoreListCommand.class | Bin 3794 -> 3803 bytes .../commands/messaging/MsgCommand.class | Bin 5230 -> 4966 bytes .../commands/messaging/ReplyCommand.class | Bin 5195 -> 4931 bytes .../commands/messaging/UnignoreCommand.class | Bin 2954 -> 2963 bytes .../commands/moderation/BanCommand.class | Bin 0 -> 6889 bytes .../commands/moderation/MuteCommand.class | Bin 8487 -> 8985 bytes .../moderation/SilentWarnCommand.class | Bin 0 -> 4921 bytes .../commands/moderation/UnbanCommand.class | Bin 0 -> 4313 bytes .../commands/moderation/WarnCommand.class | Bin 0 -> 5479 bytes .../commands/nick/PlayerInfoCommand.class | Bin 3028 -> 9112 bytes .../loki/lochat/commands/rp/DoCommand.class | Bin 4414 -> 4408 bytes .../loki/lochat/commands/rp/MeCommand.class | Bin 4319 -> 4313 bytes .../com/loki/lochat/commands/rp/RpUtil.class | Bin 2466 -> 2467 bytes .../loki/lochat/commands/rp/TryCommand.class | Bin 5092 -> 5086 bytes .../loki/lochat/config/AppearanceConfig.class | Bin 6812 -> 6948 bytes .../loki/lochat/config/ConfigManager.class | Bin 8930 -> 9024 bytes .../loki/lochat/config/MessagesConfig.class | Bin 6808 -> 9808 bytes .../loki/lochat/core/PluginInitializer.class | Bin 5487 -> 5640 bytes .../com/loki/lochat/core/PluginShutdown.class | Bin 2499 -> 2673 bytes .../AdvancedMessageFilter$FilterResult.class | Bin 1769 -> 0 bytes .../core/filter/AdvancedMessageFilter.class | Bin 19486 -> 5380 bytes .../lochat/core/filter/CooldownFilter.class | Bin 3065 -> 3057 bytes .../lochat/core/filter/FilterResult.class | Bin 0 -> 2017 bytes .../core/filter/filters/CharacterFilter.class | Bin 0 -> 2526 bytes .../core/filter/filters/FloodFilter.class | Bin 0 -> 3282 bytes .../core/filter/filters/SpamFilter.class | Bin 0 -> 4688 bytes .../core/filter/filters/SwearFilter.class | Bin 0 -> 5656 bytes .../core/filter/filters/UrlFilter.class | Bin 0 -> 4978 bytes .../core/registry/ServiceRegistry.class | Bin 4395 -> 4562 bytes .../lochat/core/service/ChatServiceImpl.class | Bin 7245 -> 7180 bytes .../core/service/CooldownServiceImpl.class | Bin 2358 -> 0 bytes .../core/service/IgnoreServiceImpl.class | Bin 6464 -> 0 bytes .../core/service/MessageServiceImpl.class | Bin 2494 -> 2325 bytes .../core/service/MessagingServiceImpl.class | Bin 0 -> 9899 bytes .../core/service/MuteServiceImpl$1.class | Bin 754 -> 0 bytes .../lochat/core/service/MuteServiceImpl.class | Bin 14154 -> 14096 bytes .../lochat/core/service/PMServiceImpl.class | Bin 1535 -> 0 bytes .../core/service/PlayerDataServiceImpl.class | Bin 6090 -> 0 bytes .../core/service/PlayerServiceImpl.class | Bin 0 -> 7276 bytes .../service/PunishmentServiceImpl$1.class | Bin 0 -> 893 bytes ...shmentServiceImpl$PunishmentSnapshot.class | Bin 0 -> 840 bytes .../core/service/PunishmentServiceImpl.class | Bin 0 -> 11201 bytes .../lochat/core/service/SpyServiceImpl.class | Bin 3874 -> 0 bytes .../core/service/mute/MuteDataStorage$1.class | Bin 0 -> 779 bytes .../core/service/mute/MuteDataStorage.class | Bin 0 -> 7326 bytes .../service/mute/MutePermissionChecker.class | Bin 0 -> 1926 bytes .../loki/lochat/data/model/BanRecord.class | Bin 0 -> 1070 bytes .../loki/lochat/data/model/WarnEntry.class | Bin 0 -> 635 bytes .../loki/lochat/gradient/GradientModule.class | Bin 9162 -> 6635 bytes .../commands/GradientAdminCommand.class | Bin 18226 -> 11330 bytes .../commands/GradientColorCommand.class | Bin 13542 -> 13543 bytes .../commands/GradientPrefixCommand.class | Bin 12174 -> 11686 bytes .../handlers/ColorCommandHandler.class | Bin 0 -> 7042 bytes .../handlers/PrefixCommandHandler.class | Bin 0 -> 6730 bytes .../gradient/config/GradientConfig.class | Bin 5279 -> 5241 bytes .../gradient/gui/GradientConfirmGUI.class | Bin 10304 -> 10302 bytes .../hooks/GradientLuckPermsHook.class | Bin 5908 -> 0 bytes .../listeners/GradientGUIListener.class | Bin 7528 -> 7139 bytes .../listeners/GradientPlayerListener.class | Bin 3486 -> 3487 bytes .../gradient/service/GradientService.class | Bin 0 -> 1949 bytes .../gradient/service/PrefixService.class | Bin 0 -> 4334 bytes .../gradient/util/DisplayNameUtil.class | Bin 7403 -> 5375 bytes .../integrations/DiscordIntegration.class | Bin 11229 -> 3033 bytes .../lochat/integrations/LibertyBansHook.class | Bin 1806 -> 1779 bytes .../integrations/PlaceholderAPIHook.class | Bin 4771 -> 4507 bytes .../integrations/discord/DiscordConfig.class | Bin 0 -> 5149 bytes .../discord/DiscordMessageService.class | Bin 0 -> 7771 bytes .../listener/ChatEventListener$ChatMode.class | Bin 0 -> 1562 bytes .../lochat/listener/ChatEventListener.class | Bin 8672 -> 9230 bytes .../lochat/listener/ModerationListener.class | Bin 0 -> 1935 bytes .../lochat/listener/PlayerEventListener.class | Bin 2449 -> 2323 bytes .../lochat/managers/AutoMessageManager.class | Bin 8482 -> 8428 bytes ...stomCommandManager$CustomCommandData.class | Bin 2544 -> 2544 bytes .../managers/CustomCommandManager.class | Bin 13746 -> 8707 bytes .../managers/commands/MessageSender.class | Bin 0 -> 4375 bytes .../commands/PlaceholderProcessor.class | Bin 0 -> 2337 bytes .../renderer/EnhancedChatRenderer.class | Bin 15997 -> 10576 bytes .../renderer/components/EmojiComponent.class | Bin 0 -> 2058 bytes .../renderer/components/GradientBuilder.class | Bin 0 -> 3856 bytes .../renderer/components/UrlProcessor.class | Bin 0 -> 3470 bytes .../com/loki/lochat/util/TimeParser.class | Bin 1998 -> 0 bytes .../com/loki/lochat/utils/ChatFormatter.class | Bin 9665 -> 7782 bytes .../lochat/{util => utils}/DistanceUtil.class | Bin 831 -> 833 bytes .../lochat/{util => utils}/FoliaUtil.class | Bin 4024 -> 4026 bytes .../loki/lochat/utils/MentionHandler.class | Bin 6776 -> 6777 bytes .../lochat/utils/color/ColorConverter.class | Bin 0 -> 5064 bytes bin/main/config.yml | 4 + bin/main/config/appearance.yml | 48 +- bin/main/config/messages.yml | 19 + bin/main/messages.yml | 51 +- bin/main/plugin.yml | 60 ++- .../lochat/api/service/CooldownService.java | 16 - .../lochat/api/service/IgnoreService.java | 53 --- .../lochat/api/service/MessagingService.java | 111 +++++ .../loki/lochat/api/service/PMService.java | 29 -- .../lochat/api/service/PlayerDataService.java | 12 - .../lochat/api/service/PlayerService.java | 56 +++ .../loki/lochat/api/service/SpyService.java | 36 -- .../lochat/commands/admin/ChatSpyCommand.java | 8 +- .../commands/messaging/IgnoreCommand.java | 10 +- .../commands/messaging/IgnoreListCommand.java | 8 +- .../lochat/commands/messaging/MsgCommand.java | 22 +- .../commands/messaging/ReplyCommand.java | 20 +- .../commands/messaging/UnignoreCommand.java | 10 +- .../loki/lochat/commands/rp/DoCommand.java | 12 +- .../loki/lochat/commands/rp/MeCommand.java | 12 +- .../com/loki/lochat/commands/rp/RpUtil.java | 2 +- .../loki/lochat/commands/rp/TryCommand.java | 12 +- .../loki/lochat/config/AppearanceConfig.java | 241 ++-------- .../com/loki/lochat/config/ConfigManager.java | 144 +++--- .../com/loki/lochat/core/PluginShutdown.java | 18 +- .../core/filter/AdvancedMessageFilter.java | 437 ++---------------- .../lochat/core/filter/CooldownFilter.java | 14 +- .../loki/lochat/core/filter/FilterResult.java | 20 + .../core/filter/filters/CharacterFilter.java | 27 ++ .../core/filter/filters/FloodFilter.java | 51 ++ .../core/filter/filters/SpamFilter.java | 83 ++++ .../core/filter/filters/SwearFilter.java | 72 +++ .../lochat/core/filter/filters/UrlFilter.java | 106 +++++ .../lochat/core/registry/ServiceRegistry.java | 25 +- .../lochat/core/service/ChatServiceImpl.java | 4 +- .../core/service/CooldownServiceImpl.java | 52 --- .../core/service/IgnoreServiceImpl.java | 115 ----- .../core/service/MessageServiceImpl.java | 4 +- .../core/service/MessagingServiceImpl.java | 219 +++++++++ .../lochat/core/service/MuteServiceImpl.java | 201 ++++---- .../lochat/core/service/PMServiceImpl.java | 35 -- ...erviceImpl.java => PlayerServiceImpl.java} | 110 +++-- .../core/service/PunishmentServiceImpl.java | 2 +- .../lochat/core/service/SpyServiceImpl.java | 80 ---- .../core/service/mute/MuteDataStorage.java | 117 +++++ .../service/mute/MutePermissionChecker.java | 48 ++ .../loki/lochat/gradient/GradientModule.java | 171 ++----- .../commands/GradientAdminCommand.java | 253 +--------- .../commands/GradientColorCommand.java | 2 +- .../commands/GradientPrefixCommand.java | 11 +- .../handlers/ColorCommandHandler.java | 129 ++++++ .../handlers/PrefixCommandHandler.java | 123 +++++ .../gradient/gui/GradientConfirmGUI.java | 4 +- .../gradient/hooks/GradientLuckPermsHook.java | 95 ---- .../listeners/GradientGUIListener.java | 5 +- .../listeners/GradientPlayerListener.java | 2 +- .../gradient/service/GradientService.java | 57 +++ .../gradient/service/PrefixService.java | 114 +++++ .../lochat/gradient/util/DisplayNameUtil.java | 67 +-- .../integrations/DiscordIntegration.java | 263 ++--------- .../integrations/PlaceholderAPIHook.java | 18 +- .../integrations/discord/DiscordConfig.java | 126 +++++ .../discord/DiscordMessageService.java | 166 +++++++ .../lochat/listener/ChatEventListener.java | 153 +++--- .../lochat/listener/PlayerEventListener.java | 23 +- .../lochat/managers/AutoMessageManager.java | 2 +- .../lochat/managers/CustomCommandManager.java | 166 +------ .../managers/commands/MessageSender.java | 73 +++ .../commands/PlaceholderProcessor.java | 36 ++ .../lochat/renderer/EnhancedChatRenderer.java | 150 +----- .../renderer/components/EmojiComponent.java | 36 ++ .../renderer/components/GradientBuilder.java | 48 ++ .../renderer/components/UrlProcessor.java | 53 +++ .../java/com/loki/lochat/util/TimeParser.java | 57 --- .../com/loki/lochat/utils/ChatFormatter.java | 241 +++------- .../lochat/{util => utils}/DistanceUtil.java | 2 +- .../lochat/{util => utils}/FoliaUtil.java | 2 +- .../com/loki/lochat/utils/MentionHandler.java | 2 +- .../lochat/utils/color/ColorConverter.java | 144 ++++++ 181 files changed, 3455 insertions(+), 2908 deletions(-) create mode 100644 CODE_REVIEW_RU.md delete mode 100644 MIGRATION_PLAN.md create mode 100644 REFACTORING_SERVICES.md delete mode 100644 bin/main/com/loki/lochat/api/service/CooldownService.class delete mode 100644 bin/main/com/loki/lochat/api/service/IgnoreService.class create mode 100644 bin/main/com/loki/lochat/api/service/MessagingService.class delete mode 100644 bin/main/com/loki/lochat/api/service/PMService.class delete mode 100644 bin/main/com/loki/lochat/api/service/PlayerDataService.class create mode 100644 bin/main/com/loki/lochat/api/service/PlayerService.class create mode 100644 bin/main/com/loki/lochat/api/service/PunishmentService.class delete mode 100644 bin/main/com/loki/lochat/api/service/SpyService.class create mode 100644 bin/main/com/loki/lochat/commands/moderation/BanCommand.class create mode 100644 bin/main/com/loki/lochat/commands/moderation/SilentWarnCommand.class create mode 100644 bin/main/com/loki/lochat/commands/moderation/UnbanCommand.class create mode 100644 bin/main/com/loki/lochat/commands/moderation/WarnCommand.class delete mode 100644 bin/main/com/loki/lochat/core/filter/AdvancedMessageFilter$FilterResult.class create mode 100644 bin/main/com/loki/lochat/core/filter/FilterResult.class create mode 100644 bin/main/com/loki/lochat/core/filter/filters/CharacterFilter.class create mode 100644 bin/main/com/loki/lochat/core/filter/filters/FloodFilter.class create mode 100644 bin/main/com/loki/lochat/core/filter/filters/SpamFilter.class create mode 100644 bin/main/com/loki/lochat/core/filter/filters/SwearFilter.class create mode 100644 bin/main/com/loki/lochat/core/filter/filters/UrlFilter.class delete mode 100644 bin/main/com/loki/lochat/core/service/CooldownServiceImpl.class delete mode 100644 bin/main/com/loki/lochat/core/service/IgnoreServiceImpl.class create mode 100644 bin/main/com/loki/lochat/core/service/MessagingServiceImpl.class delete mode 100644 bin/main/com/loki/lochat/core/service/MuteServiceImpl$1.class delete mode 100644 bin/main/com/loki/lochat/core/service/PMServiceImpl.class delete mode 100644 bin/main/com/loki/lochat/core/service/PlayerDataServiceImpl.class create mode 100644 bin/main/com/loki/lochat/core/service/PlayerServiceImpl.class create mode 100644 bin/main/com/loki/lochat/core/service/PunishmentServiceImpl$1.class create mode 100644 bin/main/com/loki/lochat/core/service/PunishmentServiceImpl$PunishmentSnapshot.class create mode 100644 bin/main/com/loki/lochat/core/service/PunishmentServiceImpl.class delete mode 100644 bin/main/com/loki/lochat/core/service/SpyServiceImpl.class create mode 100644 bin/main/com/loki/lochat/core/service/mute/MuteDataStorage$1.class create mode 100644 bin/main/com/loki/lochat/core/service/mute/MuteDataStorage.class create mode 100644 bin/main/com/loki/lochat/core/service/mute/MutePermissionChecker.class create mode 100644 bin/main/com/loki/lochat/data/model/BanRecord.class create mode 100644 bin/main/com/loki/lochat/data/model/WarnEntry.class create mode 100644 bin/main/com/loki/lochat/gradient/commands/handlers/ColorCommandHandler.class create mode 100644 bin/main/com/loki/lochat/gradient/commands/handlers/PrefixCommandHandler.class delete mode 100644 bin/main/com/loki/lochat/gradient/hooks/GradientLuckPermsHook.class create mode 100644 bin/main/com/loki/lochat/gradient/service/GradientService.class create mode 100644 bin/main/com/loki/lochat/gradient/service/PrefixService.class create mode 100644 bin/main/com/loki/lochat/integrations/discord/DiscordConfig.class create mode 100644 bin/main/com/loki/lochat/integrations/discord/DiscordMessageService.class create mode 100644 bin/main/com/loki/lochat/listener/ChatEventListener$ChatMode.class create mode 100644 bin/main/com/loki/lochat/listener/ModerationListener.class create mode 100644 bin/main/com/loki/lochat/managers/commands/MessageSender.class create mode 100644 bin/main/com/loki/lochat/managers/commands/PlaceholderProcessor.class create mode 100644 bin/main/com/loki/lochat/renderer/components/EmojiComponent.class create mode 100644 bin/main/com/loki/lochat/renderer/components/GradientBuilder.class create mode 100644 bin/main/com/loki/lochat/renderer/components/UrlProcessor.class delete mode 100644 bin/main/com/loki/lochat/util/TimeParser.class rename bin/main/com/loki/lochat/{util => utils}/DistanceUtil.class (59%) rename bin/main/com/loki/lochat/{util => utils}/FoliaUtil.class (92%) create mode 100644 bin/main/com/loki/lochat/utils/color/ColorConverter.class delete mode 100644 src/main/java/com/loki/lochat/api/service/CooldownService.java delete mode 100644 src/main/java/com/loki/lochat/api/service/IgnoreService.java create mode 100644 src/main/java/com/loki/lochat/api/service/MessagingService.java delete mode 100644 src/main/java/com/loki/lochat/api/service/PMService.java delete mode 100644 src/main/java/com/loki/lochat/api/service/PlayerDataService.java create mode 100644 src/main/java/com/loki/lochat/api/service/PlayerService.java delete mode 100644 src/main/java/com/loki/lochat/api/service/SpyService.java create mode 100644 src/main/java/com/loki/lochat/core/filter/FilterResult.java create mode 100644 src/main/java/com/loki/lochat/core/filter/filters/CharacterFilter.java create mode 100644 src/main/java/com/loki/lochat/core/filter/filters/FloodFilter.java create mode 100644 src/main/java/com/loki/lochat/core/filter/filters/SpamFilter.java create mode 100644 src/main/java/com/loki/lochat/core/filter/filters/SwearFilter.java create mode 100644 src/main/java/com/loki/lochat/core/filter/filters/UrlFilter.java delete mode 100644 src/main/java/com/loki/lochat/core/service/CooldownServiceImpl.java delete mode 100644 src/main/java/com/loki/lochat/core/service/IgnoreServiceImpl.java create mode 100644 src/main/java/com/loki/lochat/core/service/MessagingServiceImpl.java delete mode 100644 src/main/java/com/loki/lochat/core/service/PMServiceImpl.java rename src/main/java/com/loki/lochat/core/service/{PlayerDataServiceImpl.java => PlayerServiceImpl.java} (57%) delete mode 100644 src/main/java/com/loki/lochat/core/service/SpyServiceImpl.java create mode 100644 src/main/java/com/loki/lochat/core/service/mute/MuteDataStorage.java create mode 100644 src/main/java/com/loki/lochat/core/service/mute/MutePermissionChecker.java create mode 100644 src/main/java/com/loki/lochat/gradient/commands/handlers/ColorCommandHandler.java create mode 100644 src/main/java/com/loki/lochat/gradient/commands/handlers/PrefixCommandHandler.java delete mode 100644 src/main/java/com/loki/lochat/gradient/hooks/GradientLuckPermsHook.java create mode 100644 src/main/java/com/loki/lochat/gradient/service/GradientService.java create mode 100644 src/main/java/com/loki/lochat/gradient/service/PrefixService.java create mode 100644 src/main/java/com/loki/lochat/integrations/discord/DiscordConfig.java create mode 100644 src/main/java/com/loki/lochat/integrations/discord/DiscordMessageService.java create mode 100644 src/main/java/com/loki/lochat/managers/commands/MessageSender.java create mode 100644 src/main/java/com/loki/lochat/managers/commands/PlaceholderProcessor.java create mode 100644 src/main/java/com/loki/lochat/renderer/components/EmojiComponent.java create mode 100644 src/main/java/com/loki/lochat/renderer/components/GradientBuilder.java create mode 100644 src/main/java/com/loki/lochat/renderer/components/UrlProcessor.java delete mode 100644 src/main/java/com/loki/lochat/util/TimeParser.java rename src/main/java/com/loki/lochat/{util => utils}/DistanceUtil.java (93%) rename src/main/java/com/loki/lochat/{util => utils}/FoliaUtil.java (98%) create mode 100644 src/main/java/com/loki/lochat/utils/color/ColorConverter.java diff --git a/CODE_REVIEW_RU.md b/CODE_REVIEW_RU.md new file mode 100644 index 0000000..326900b --- /dev/null +++ b/CODE_REVIEW_RU.md @@ -0,0 +1,356 @@ +# 🔍 Code Review: Проблемы архитектуры (SOLID, DRY, KISS) + +## 🔴 Критические проблемы + +### 1. DRY: Дублирование кода в конфигах +**Файлы:** `AppearanceConfig.java`, `MessagesConfig.java`, `FiltersConfig.java` + +**Проблема:** +- Каждый конфиг содержит 30-50 геттеров с одинаковой структурой +- ~300 строк повторяющегося кода +- При изменении логики нужно править в 3 местах + +**Решение:** +```java +// Создать базовый класс с generic методами +public abstract class BaseConfig { + protected String getString(String path, String def) { ... } + protected int getInt(String path, int def) { ... } + protected boolean getBoolean(String path, boolean def) { ... } +} + +// Использовать в конфигах +public class MessagesConfig extends BaseConfig { + public String getNoPermission() { + return getString("general.no_permission", "&#CF6679Нет прав!"); + } +} +``` + +--- + +### 2. SRP: ConfigManager делает слишком много +**Файл:** `ConfigManager.java` + +**Проблема:** +- Управляет 5 конфигами +- Содержит логику обновления версий +- Имеет 30+ геттеров для настроек +- Нарушает Single Responsibility Principle + +**Решение:** +```java +// Разделить на отдельные классы +class ConfigManager { + private final AppearanceConfig appearance; + private final MessagesConfig messages; + // Только управление конфигами +} + +class ConfigUpdater { + // Логика обновления версий + void updateConfig(FileConfiguration config) { ... } +} +``` + +--- + +### 3. KISS: ColorConverter неэффективен +**Файл:** `ColorConverter.java` + +**Проблема:** +```java +private static String convertAmpersandColors(String message) { + return message + .replaceAll("&0", "") + .replaceAll("&1", "") + // ... 16 вызовов replaceAll +} +``` +- Каждый `replaceAll` компилирует regex +- O(n * 16) сложность +- Дублирование для `§` и `&` + +**Решение:** +```java +private static final Map COLOR_MAP = Map.ofEntries( + Map.entry("&0", ""), + Map.entry("§0", ""), + Map.entry("&1", ""), + Map.entry("§1", "") + // ... +); + +private static String convertColors(String message) { + for (Map.Entry entry : COLOR_MAP.entrySet()) { + message = message.replace(entry.getKey(), entry.getValue()); + } + return message; +} +``` + +--- + +### 4. DRY: Дублирование проверок permissions +**Файл:** `AdvancedMessageFilter.java` + +**Проблема:** +```java +if (!player.hasPermission("lochat.bypass.swear") && !player.hasPermission("lochat.bypass.filter")) +if (!player.hasPermission("lochat.bypass.flood") && !player.hasPermission("lochat.bypass.filter")) +if (!player.hasPermission("lochat.bypass.spam") && !player.hasPermission("lochat.bypass.filter")) +``` + +**Решение:** +```java +private boolean canBypassFilter(Player player, String filterType) { + return player.hasPermission("lochat.bypass." + filterType) + || player.hasPermission("lochat.bypass.filter"); +} + +// Использование +if (!canBypassFilter(player, "swear")) { + FilterResult swear = swearFilter.filter(player, message); + // ... +} +``` + +--- + +### 5. OCP: EnhancedChatRenderer не расширяем +**Файл:** `EnhancedChatRenderer.java` + +**Проблема:** +```java +switch (placeholder) { + case "emoji" -> result = result.append(emoji); + case "prefix" -> result = result.append(prefix); + // Добавление нового плейсхолдера требует изменения кода +} +``` + +**Решение:** +```java +// Strategy pattern для плейсхолдеров +interface PlaceholderResolver { + Component resolve(Player player, boolean isGlobal); +} + +Map resolvers = Map.of( + "emoji", (p, g) -> buildEmojiComponent(g), + "prefix", (p, g) -> buildPrefix(g), + "player", (p, g) -> buildPlayerComponent(p) +); + +// Использование +Component resolved = resolvers.getOrDefault(placeholder, + (p, g) -> Component.text("{" + placeholder + "}")) + .resolve(player, isGlobal); +``` + +--- + +### 6. ISP: MuteServiceImpl не реализован полностью +**Файл:** `MuteServiceImpl.java` + +**Проблема:** +```java +@Override +public void save() { + // TODO: Implement save to file +} + +private void load() { + // TODO: Implement load from file +} +``` + +**Решение:** +- Либо реализовать методы +- Либо разделить интерфейс на `MuteService` и `PersistentMuteService` + +--- + +### 7. Отсутствует зависимость VoiceChat +**Файл:** `MuteServiceImpl.java` + +**Проблема:** +```java +import de.maxhenkel.voicechat.api.BukkitVoicechatService; // ❌ Не найден +``` + +**Решение:** +```gradle +// build.gradle.kts +dependencies { + compileOnly("de.maxhenkel.voicechat:voicechat-api:2.5.0") +} +``` + +Или сделать опциональной зависимостью: +```java +private void initVoiceChat() { + try { + Class.forName("de.maxhenkel.voicechat.api.BukkitVoicechatService"); + // Инициализация + } catch (ClassNotFoundException e) { + plugin.getLogger().info("VoiceChat not found, voice mute disabled"); + } +} +``` + +--- + +### 8. SRP: ChatEventListener слишком сложный +**Файл:** `ChatEventListener.java` + +**Проблема:** +- Метод `onChat` содержит 80+ строк +- Обрабатывает: режим чата, фильтрацию, радиус, Discord, статистику +- Нарушает Single Responsibility + +**Решение:** +```java +class ChatEventListener { + private final ChatModeHandler modeHandler; + private final MessageFilterHandler filterHandler; + private final LocalChatHandler localHandler; + private final DiscordHandler discordHandler; + private final StatsHandler statsHandler; + + @EventHandler + public void onChat(AsyncChatEvent event) { + Player sender = event.getPlayer(); + String message = extractMessage(event); + + // Делегируем обработку + ChatMode mode = modeHandler.determineMode(message); + if (!filterHandler.filter(sender, message)) { + event.setCancelled(true); + return; + } + + if (mode == ChatMode.LOCAL) { + localHandler.handleLocal(event, sender); + } + + discordHandler.sendToDiscord(sender, message, mode); + statsHandler.recordMessage(sender, mode); + } +} +``` + +--- + +### 9. DRY: Дублирование конвертации цветов +**Файл:** `ColorConverter.java` + +**Проблема:** +```java +private static String convertSectionColors(String message) { ... } +private static String convertAmpersandColors(String message) { ... } +``` +Идентичная логика, только разные символы. + +**Решение:** +```java +private static String convertColors(String message, char symbol) { + return message + .replaceAll(symbol + "0", "") + .replaceAll(symbol + "1", "") + // ... +} + +public static String convertLegacyFormats(String message) { + message = convertColors(message, '§'); + message = convertColors(message, '&'); + return message; +} +``` + +--- + +### 10. DIP: Прямая зависимость от LoChat +**Файлы:** `EnhancedChatRenderer.java`, `ChatEventListener.java`, и др. + +**Проблема:** +```java +com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin; +``` +Нарушает Dependency Inversion Principle. + +**Решение:** +```java +// Создать интерфейс +interface ChatPlugin { + ConfigManager getConfigManager(); + GradientModule getGradientModule(); + DiscordIntegration getDiscordIntegration(); +} + +// LoChat реализует интерфейс +class LoChat extends JavaPlugin implements ChatPlugin { ... } + +// Использование +class EnhancedChatRenderer { + private final ChatPlugin plugin; + + public EnhancedChatRenderer(ChatPlugin plugin, boolean isGlobal) { + this.plugin = plugin; + // Нет каста + } +} +``` + +--- + +## 📊 Статистика проблем + +| Принцип | Нарушений | Критичность | +|---------|-----------|-------------| +| DRY | 4 | 🔴 Высокая | +| SRP | 3 | 🔴 Высокая | +| KISS | 2 | 🟡 Средняя | +| OCP | 1 | 🟡 Средняя | +| DIP | 1 | 🟡 Средняя | +| ISP | 1 | 🟢 Низкая | + +--- + +## ✅ Что сделано хорошо + +1. **ServiceRegistry** - правильная реализация DI +2. **Разделение на модули** - gradient, integrations, core +3. **Использование интерфейсов** - api.service.* +4. **PluginInitializer/PluginShutdown** - разделение логики инициализации +5. **BaseConfig** - базовый класс для конфигов (уже есть!) + +--- + +## 🎯 Приоритеты исправления + +### Высокий приоритет +1. Исправить отсутствующую зависимость VoiceChat +2. Упростить ColorConverter (KISS) +3. Вынести проверки permissions в отдельный метод (DRY) + +### Средний приоритет +4. Разделить ChatEventListener на handler'ы (SRP) +5. Убрать касты к LoChat через интерфейс (DIP) +6. Сделать EnhancedChatRenderer расширяемым (OCP) + +### Низкий приоритет +7. Реализовать save/load в MuteServiceImpl (ISP) +8. Упростить ConfigManager (SRP) +9. Объединить методы конвертации цветов (DRY) + +--- + +## 📝 Итого + +Проект имеет хорошую базовую архитектуру (ServiceRegistry, модули, интерфейсы), но есть проблемы с: +- **Дублированием кода** (DRY) - особенно в конфигах и фильтрах +- **Слишком большими классами** (SRP) - ConfigManager, ChatEventListener +- **Неэффективными алгоритмами** (KISS) - ColorConverter + +Рекомендую начать с исправления критических проблем (VoiceChat, ColorConverter, permissions), затем постепенно рефакторить остальное. diff --git a/MIGRATION_PLAN.md b/MIGRATION_PLAN.md deleted file mode 100644 index ffe0533..0000000 --- a/MIGRATION_PLAN.md +++ /dev/null @@ -1,90 +0,0 @@ -# План миграции команд на новую архитектуру - -> **Статус (актуально для репозитория):** централизованная регистрация в `CommandManager` и раскладка по пакетам **уже внедрены**. Ниже — что сделано и что остаётся опциональной доработкой. - -**Что осталось по плану:** миграция команд **завершена**. Дальше — по желанию: перевести оставшиеся `CommandExecutor` на `PlayerCommand`/`AdminCommand`, добавить тесты/ручной регресс, короткую доку «как добавить команду». - -## Что уже сделано - -### Архитектура -- `commands/base/BaseCommand`, `PlayerCommand`, `AdminCommand` -- `CommandManager` — единая точка регистрации (`reg` / `regTab` / `registerBaseCommand`) - -### Пакеты (фактическая структура) -| Пакет | Назначение | -|--------|------------| -| `commands/chat/` | глобальный / локальный чат (`g`, `l`) | -| `commands/messaging/` | ЛС, игнор (`msg`, `reply`, `ignore`, `unignore`, `ignorelist`) | -| `commands/moderation/` | муты, варны, баны (`lmute`, `lunmute`, `lmutelist`, `lmutehistory`, `lmuteblame`, `warn`, `silentwarn`, `lban`, `lunban`) | -| `commands/nick/` | ник, инфо (`nick`, `playerinfo`) | -| `commands/admin/` | админ-утилиты (`announce`, `chatspy`, `clearchat`, `clearchatconfig`, `lochat`, `lochatreload`, `discordadmin`); в репозитории также есть `HubCommand.java` (проверьте `plugin.yml`, если нужна команда хаба) | -| `commands/rp/` | RP (`me`, `try`, `do`, `roll`) | -| Корень `commands/` | `CustomCommandsCommand`, `CustomCommand` | - -Папки `social/` в проекте нет — социальные команды лежат в `messaging/` и `nick/`. - -### Регистрация в `CommandManager` (выполнено) -- [x] Этап 1 — чат: `GlobalChatCommand`, `LocalChatCommand` -- [x] Этап 2 — ЛС: `MsgCommand`, `ReplyCommand` -- [x] Этап 3 — социальное: `IgnoreCommand`, `UnignoreCommand`, `IgnoreListCommand`, `NickCommand` (+ `PlayerInfoCommand`) -- [x] Этап 4 — модерация: все перечисленные в таблице пакета `moderation/` -- [x] Этап 5 — админ: `AnnounceCommand`, `ClearChatCommand`, `ClearChatConfigCommand`, `ChatSpyCommand`, `LoChatCommand`, `ReloadConfigCommand`, `DiscordCommand` -- [x] Этап 6 — `CustomCommandsCommand` и движок кастомных команд - -## Что ещё можно сделать (не блокирует работу) - -### Унификация базового класса -Многие команды по-прежнему реализуют `CommandExecutor` напрямую, а не `PlayerCommand` / `AdminCommand`. Имеет смысл постепенно переводить на базовые классы там, где это убирает дублирование проверок и сообщений. - -### Тесты и ручная проверка -- [ ] Стабильные сценарии для приоритетных команд (чат, ЛС, муты) — ручные или автотесты -- [ ] Проверка TabCompleter на зарегистрированных командах с `regTab` - -### Документация -- [ ] Короткий `CONTRIBUTING` или раздел в README: как добавить команду через `CommandManager` и `plugin.yml` - -## Процесс миграции одной команды (если рефакторите дальше) - -### 1. Новая команда на базовом классе -```java -public class NewCommand extends PlayerCommand { - public NewCommand(LoChat plugin) { - super(plugin); - } - - @Override - protected boolean executePlayerCommand(Player player, Command command, - String label, String[] args) { - return true; - } -} -``` - -### 2. Проверки -- Поведение и права -- Сообщения из `MessagesConfig` / конфигов -- TabCompleter при необходимости - -### 3. Регистрация -В `CommandManager`: `reg("name", new NewCommand(plugin))` или `regTab`, плюс запись в `plugin.yml`. - -### 4. Удаление старого кода -После замены — удалить старый класс и неиспользуемые импорты. - -## Чек-лист для команды - -- [ ] Корректный базовый класс или явный `CommandExecutor` с единым стилем обработки ошибок -- [ ] Права из `plugin.yml` -- [ ] Сообщения из конфигурации -- [ ] Валидация аргументов -- [ ] TabCompleter при необходимости - -## Ожидаемые эффекты от доведения до единого стиля - -- Меньше дублирования проверок отправителя и прав -- Проще добавлять новые команды -- Проще сопровождать код при едином паттерне - ---- - -**Следующие шаги (по желанию):** выбрать 2–3 самых «шумных» по коду команды → перевести на `PlayerCommand`/`AdminCommand` → добавить минимальные тесты или чек-лист ручной регрессии. diff --git a/REFACTORING_SERVICES.md b/REFACTORING_SERVICES.md new file mode 100644 index 0000000..f620025 --- /dev/null +++ b/REFACTORING_SERVICES.md @@ -0,0 +1,287 @@ +# 🔧 Рефакторинг сервисов - уменьшение количества файлов + +## Текущая ситуация +- **11 интерфейсов** в `api/service/` +- **12 реализаций** в `core/service/` +- **23 файла** только для сервисов +- Некоторые сервисы по 30 строк кода + +## Проблема +Слишком много мелких файлов, которые можно объединить по функциональности. + +--- + +## 🎯 План рефакторинга + +### 1. Объединить простые сервисы в один + +**Было:** +``` +PMService.java (28 строк) +SpyService.java (69 строк) +IgnoreService.java (97 строк) +``` + +**Станет:** +```java +// MessagingService.java - всё что связано с общением +public interface MessagingService { + // PM + void setLastConversation(UUID sender, UUID receiver); + UUID getLastConversation(UUID player); + + // Spy + boolean toggleSpy(UUID player); + boolean isSpying(UUID player); + void broadcastPM(Player sender, Player receiver, String message); + + // Ignore + void addIgnore(UUID player, UUID target); + void removeIgnore(UUID player, UUID target); + boolean isIgnoring(UUID player, UUID target); + Set getIgnoreList(UUID player); +} +``` + +**Экономия:** 3 интерфейса + 3 реализации = **6 файлов → 2 файла** + +--- + +### 2. Объединить Mute + Punishment + +**Было:** +``` +MuteService.java + MuteServiceImpl.java (212 строк) +PunishmentService.java + PunishmentServiceImpl.java (173 строки) +MuteHistoryManager.java (77 строк) +``` + +**Станет:** +```java +// ModerationService.java - всё что связано с модерацией +public interface ModerationService { + // Mutes + void mute(UUID uuid, String name, long duration, String reason, String by); + boolean unmute(UUID uuid, String by); + boolean isMuted(UUID uuid); + MuteData getMuteData(UUID uuid); + + // Bans + void ban(UUID uuid, String name, long duration, String reason, String by); + boolean unban(UUID uuid, String by); + boolean isBanned(UUID uuid); + BanData getBanData(UUID uuid); + + // Warns + void warn(UUID uuid, String name, String reason, String by); + List getWarns(UUID uuid); + + // History + List getHistory(UUID uuid); + List getHistoryByModerator(String name); +} +``` + +**Экономия:** 3 интерфейса + 3 реализации = **6 файлов → 2 файла** + +--- + +### 3. Объединить Cooldown + PlayerData + +**Было:** +``` +CooldownService.java + CooldownServiceImpl.java (40 строк) +PlayerDataService.java + PlayerDataServiceImpl.java (102 строки) +``` + +**Станет:** +```java +// PlayerService.java - всё что связано с данными игрока +public interface PlayerService { + // Cooldowns + boolean hasCooldown(UUID player, String type); + void setCooldown(UUID player, String type, long duration); + long getRemainingCooldown(UUID player, String type); + void clearCooldowns(UUID player); + + // Statistics + void recordMessage(UUID player, String chatType); + PlayerStats getStats(UUID player); + void saveAll(); +} +``` + +**Экономия:** 2 интерфейса + 2 реализации = **4 файла → 2 файла** + +--- + +### 4. Оставить как есть (нормальный размер) + +``` +ChatService.java + ChatServiceImpl.java (114 строк) ✅ +MessageService.java + MessageServiceImpl.java (36 строк) ✅ +MentionService.java + MentionServiceImpl.java (104 строки) ✅ +NickService.java + NickServiceImpl.java (155 строк) ✅ +``` + +Эти сервисы имеют чёткую ответственность и нормальный размер. + +--- + +## 📊 Результат рефакторинга + +| До | После | Экономия | +|----|-------|----------| +| 11 интерфейсов | 7 интерфейсов | -4 | +| 12 реализаций | 8 реализаций | -4 | +| **23 файла** | **15 файлов** | **-8 файлов** | + +--- + +## 🔨 Пошаговая реализация + +### Шаг 1: Создать MessagingService + +```java +// api/service/MessagingService.java +public interface MessagingService { + // PM + void setLastConversation(UUID sender, UUID receiver); + UUID getLastConversation(UUID player); + + // Spy + boolean toggleSpy(UUID player); + boolean isSpying(UUID player); + void broadcastPM(Player sender, Player receiver, String message); + void sendToSpies(Player sender, Component message, boolean isGlobal); + void removeSpy(UUID player); + + // Ignore + void addIgnore(UUID player, UUID target); + void removeIgnore(UUID player, UUID target); + boolean isIgnoring(UUID player, UUID target); + Set getIgnoreList(UUID player); + void save(); +} + +// core/service/MessagingServiceImpl.java +public class MessagingServiceImpl implements MessagingService { + private final JavaPlugin plugin; + private final MessageConfig messageConfig; + + // PM state + private final Map lastConversation = new ConcurrentHashMap<>(); + + // Spy state + private final Set spyEnabled = ConcurrentHashMap.newKeySet(); + + // Ignore state + private final Map> ignoreMap = new ConcurrentHashMap<>(); + + public MessagingServiceImpl(JavaPlugin plugin, MessageConfig messageConfig) { + this.plugin = plugin; + this.messageConfig = messageConfig; + loadIgnoreData(); + } + + // Реализация всех методов... +} +``` + +### Шаг 2: Обновить ServiceRegistry + +```java +// Было +register(PMService.class, new PMServiceImpl()); +register(SpyService.class, new SpyServiceImpl(messageConfig)); +register(IgnoreService.class, new IgnoreServiceImpl(plugin)); + +// Стало +MessagingService messaging = new MessagingServiceImpl(plugin, messageConfig); +register(MessagingService.class, messaging); + +// Для обратной совместимости (опционально) +register(PMService.class, messaging); +register(SpyService.class, messaging); +register(IgnoreService.class, messaging); +``` + +### Шаг 3: Удалить старые файлы + +```bash +# Удалить интерфейсы +rm src/main/java/com/loki/lochat/api/service/PMService.java +rm src/main/java/com/loki/lochat/api/service/SpyService.java +rm src/main/java/com/loki/lochat/api/service/IgnoreService.java + +# Удалить реализации +rm src/main/java/com/loki/lochat/core/service/PMServiceImpl.java +rm src/main/java/com/loki/lochat/core/service/SpyServiceImpl.java +rm src/main/java/com/loki/lochat/core/service/IgnoreServiceImpl.java +``` + +--- + +## ⚠️ Альтернативный подход (если не хочешь ломать API) + +Можно оставить старые интерфейсы, но сделать их делегатами: + +```java +// Старый интерфейс остаётся +public interface PMService { + void setLastConversation(UUID sender, UUID receiver); + UUID getLastConversation(UUID player); +} + +// Но реализация делегирует в MessagingService +public class PMServiceAdapter implements PMService { + private final MessagingService messaging; + + public PMServiceAdapter(MessagingService messaging) { + this.messaging = messaging; + } + + @Override + public void setLastConversation(UUID sender, UUID receiver) { + messaging.setLastConversation(sender, receiver); + } + + @Override + public UUID getLastConversation(UUID player) { + return messaging.getLastConversation(player); + } +} +``` + +Это позволит: +- Сохранить обратную совместимость +- Уменьшить количество реализаций (3 → 1) +- Постепенно мигрировать код на новый API + +--- + +## 🎯 Рекомендация + +**Начни с MessagingService** - это самое простое объединение: +1. PM, Spy, Ignore логически связаны (общение между игроками) +2. Все три сервиса маленькие (28-97 строк) +3. Объединение даст ~200 строк - нормальный размер + +**Потом ModerationService** - это сложнее: +1. Mute + Punishment + History = ~460 строк +2. Нужно будет рефакторить внутреннюю структуру +3. Но логически это одна область (модерация) + +**PlayerService оставь на потом** - там всё норм. + +--- + +## 📝 Итого + +Объединение сервисов: +- ✅ Уменьшит количество файлов с 23 до 15 +- ✅ Упростит навигацию по коду +- ✅ Сгруппирует связанную функциональность +- ⚠️ Потребует обновления кода, который использует старые сервисы + +Начни с MessagingService - это быстро и безопасно. diff --git a/bin/main/com/loki/lochat/LoChat.class b/bin/main/com/loki/lochat/LoChat.class index ead34dafadd3dad20164803fccdfdabc84424377..b8b59b0f49e0b040b6b527d0c37b32cfaba1e33b 100644 GIT binary patch delta 2088 zcmZuy`(IR55dU8Gad+>Y#Ve*_VrrtniY`7g5lL4;5X4**L6k1*by;> zLJ5WZUsIulVJ_zJ&E^>;3-F|jg%U=$Mf~oj&R{SQb1zRahDDesVT@moRRzMjFQRRW zZO|jpKwCJA;#ka3geCmCxz@TAPs=FblP!}>%dni6Sf)xVxMrE@^q^EkSSDd&Ugbe` z96w``?d1&Pku762zhcQ7Ux89P1-*u00v=W1!CHoAu#Vri6st81b|^x*fuR-~`D&|2 zt!Hq631tJrW^Ca{t%ucT8D!XmvWdZmt$da2RktyWMwU=EGX&7WPs!D4h+z~|p$s#$ zp`9x>uNq+(gGYoi#?Xl_-fAmTw=+1ADU>@Hp2tpp!`3P7=8NqG^LGsdy_aDu#wpl^ z7a8^;M}ivB{efsqk9gWzTea}ksDQuB@Csh#hwZ16xxB{kI$c`5GSS|H12W!}Fq3O+ z0zV{WsSIxkKbB}ri^ObroA)aV9Tj+2l;=GO{k+${j31D4c&)>4{{Tm19F~yF|FMqe zcdSl@;eF&N_y|Y%HOC~r)9Q38=)n;M$As;tynq#0j|=g0zJ<-TpAh1g65QOG;a)1^ zD+#lFZLRK5TQES2uUU(^wf2BJszFqJnwBc8NKeN>Q2ZhD8%;_c>fAaQG^CSm9KB|u5rZzy5V`s>Tf(=Pe-Tds28vym z;He|;D-&2na=b!pDif%3xz1yif$9XjDRF%QbxD!Fc!}x^Y)YUp^;JtiPl^5nf+?{z zfsT|IO`u!&B&GNr+0<|Q|PvUH8f|chxyEY;FBH+ zc@o!roh*fIgDXy@HE@XhQFT8Qt_^%ltotSu!3=ytOdr_k20o)*$H0!N^#$!3Zze-) z%sOe{)K#49N3VnvFjw}{VWl*{>Tw<>OrhgkP-tC8L(7Xy)X*4NAE*A=K*g)2@@*vZ zdXhHLZ_BU@8d4b;1Xf^~=5*G}sM4&6#rf3eK{~!lvf677LP$49!i?=ke7&qPzZ=bHNY@Tzz{P736|HW%t|Uc zJ;*Yw>`evYTojF>Qp?qsKI9ia^bfdJtFN_cwa+jLYnl)H?#Dg9efIh7eeRvLjb+Q% z{+(X{unX5_WM52bqO)_z>FscJ*1G(Dr`uKK=?%Di5(@ZYOcK9gN#i3i$BY{o(vZ%L zx*{c$!2qKS3E2!bitY=t@I|b-Lh6e2C(}p6YiNOL{1P?H@;2{6UP_8`05DO-P4>LT1M|r*Rq|(k{ zf>{JR8C>Y%e;FH;ZiZDz5W&Y7deF<;Om&Kv!3sqLA7>bVk6$u1C;^7mSR;Z%3{T(? zUuWK}j4;F@UId?HcnU}Okam_)v0D<&l{n7u0$$_} z%hYypv=a=;ND+BnW_ShZ5|Vr_x5rO?t?wOld%We{POsP1>z6SOZo+F4by1aa6o%Ku zYJ)%E^aad#lRM=?vjcAns!sAr`Ks;|Ci$t@G=9Sn7sv1p(q+7lNfSQcr(?6@$1y46 zBjJB6A(Q9DX2#0+RQR7s*i2ib^53O3#M&1kHpPos{%l2DyJY+Z-TGUEq6Vy%*{@I|Xl`ibAP7D$Y*S6b8<(L$OKMu(>Hq~|*F?4%{q z2HPCg&yW#=yXa{Q1rxE6YyqGJxSOIN`<`21sT%swyX&@DWY{!hk!-qvTnXnE#m(Hb)8O(RLGhPEJ_;kO+@xWlF= zh`zAdAA~<_4hAtSa+3H-hBX|f{Zz5v(?J!Z6dTp>Y-lyvPO(nI^Fh2s7F9Avs!zkK z;v@-gQ0dEj?4t<;U0^+Oh{P?(BI33pA0;S24GPhQ9q6W;>8IOw1jRIAC73`N&Y+z7 zSAhknmAH&*{6for!`>CdUcXK3TepZs711q7PoawHNJOc9B;jLCEEgtI!+<)P;rbOh zQ*P&^siREPX%%B+h@_>Is8Peaq=?|tBDIDONey3tER=>bq})rMTf-;h`IkK6>vOTW zE;J?Sbge<(0~7Wm9*uO}ny83oe2*W=6~DyGxQd^V`X3!HQEUJJ diff --git a/bin/main/com/loki/lochat/api/service/CooldownService.class b/bin/main/com/loki/lochat/api/service/CooldownService.class deleted file mode 100644 index 10afb4b07c26d7a26c361463397d07588dff0aa3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 384 zcmb7=&rSj{5Ql#Q$RhtVkypS-OzZ=2G8&UjOi0|ofm^AQwb*UVZdu;V0}tRs5y~Rr z-~}%;o#y*8?eAy%y#erus|Yp1y>gi_?o)@NKBO1&ON)Z@QY#iu&Y5?&w#i|R&>&nd zWhsS`HWib_l9eZPbuqC=mT-IbS}`lHjhIg3=kZ~#oW$qarsH@tCk#^dZ=6YO!_|NN zn9wQMpS0l_YezFeKj+Mqd=hW|hz;J<`8PJnTQ%BoOK$OP~>gxj-`< V2oYLUzeKz09drXh=!IHCe+L8vYzF`U diff --git a/bin/main/com/loki/lochat/api/service/IgnoreService.class b/bin/main/com/loki/lochat/api/service/IgnoreService.class deleted file mode 100644 index 41ead542b1613d4aaed330953a44ec4875c63b43..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 504 zcmaixO-sWt9LAr_xo&g1H}x)F){8lKoCu-_1woTCH|XM z98U1kr+@OxlP6C;KHuH}T;R|{i@}e~ifi+%=o3AOoQoI5Gigf|NggDru`;YD4{e6g zQj~&gktY21aVevmp|3K>sWf3Yp4~X2$d%@cMQ}C$J^K#~JrT#XH^acnl_}--I8J0< z|HpS)yh@uf9Br8TYp$Wp=VTeGBo%pKCBx}|{Mp)e7xJ7OhCe4?nj)o}O*ef43LI%E zY@Lz9{Q=!Gv@=mslC7Cv4cds9iV96(BYCYftxRfzrrVRMvYx15FMA$G)W;%L-~eM3MiEjK5#)=gakoIky5qNQVzX2yO!%_W3O!Qiu^SW z`~ZFwV(eXLXbSCRXJ>Zi`OSOlA3wi+2Y~nRya_7=UM6}*Gd-2~NKTleESJ=BQ_6(X zeQqriQi-@KG@(IYD+i5p+^ar_xcboOE+~n6WuG z(O7Xur*my2Woe1ua7pU;S4W3>mTQHT=-5lxnHbH|1PwUcCouXScRyaYjfo$v86=FC^5ktVHV9240Ish9VPa$GR{H|0FZ~vUUE3yz(-x62Kw6 zWB@(9FxO!f{KaX)8rEA_U4b^{9asv*q@ f_u;`G{D+AD2p)&N?RxGB?1cQOkJz~zm^}LhcMuzH`a-@?pKDV o%S>7X!iMAzG$qnaw4@*;*b=-gXagPTx3N>>yXXogtLvZM2eVjwU;qFB diff --git a/bin/main/com/loki/lochat/api/service/PlayerDataService.class b/bin/main/com/loki/lochat/api/service/PlayerDataService.class deleted file mode 100644 index c46becd15fbc9466c706b8f5795cb060dc958bd1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 215 zcmYL@OA5k35Jan^CK?wCf@g>eoddWE1VIoKLfp5*pc6+U`GHq+;Q>69I8h;+hJL*6 zs@MDR1Q25A!6D3Lb`d5!YdhqLOTjlS3YFJdDzP(sS9!!G$3NnsOBknIvoL%;itQm) zvLuY8QJl9rVG?Ypsq1DuXGq5uE@ diff --git a/bin/main/com/loki/lochat/api/service/PlayerService.class b/bin/main/com/loki/lochat/api/service/PlayerService.class new file mode 100644 index 0000000000000000000000000000000000000000..fe4f9870481a53404921123acc1100e96973c653 GIT binary patch literal 504 zcmb7B%T7Wu6r7@RkvE8+pn;A309Hg|g3(|kxNuj?$+cK+OD0AD?gU046w&P+=HJv*Fr2sW9ZD@LW7A?#QM}62H;nmF%Km5%w9nYmo}B zMH2J*!mulySCr zFklRqed+7Yn|ags`Stz*03A3gLWaPJt3n#6iNKFLVurG*pb^)Ja5;U5rHDo$mu4_$ zijX5v8?%JbfJvX;569d!1d7b_UYM2y`c21P$3_J7>8W?so}caS1EYoXS7KgU&jd<7 zH>qqy;CM;5^{bI1BGV>N9tdAD6Knq0Ad3a(&Z2jZ8Ae0p@qngnucgtm_5})8e+KEQ zSfbtf@-RID*&$nT0sU2Wk}e=0ODp$we$d z)oe30SoC9o70hBD3Xo>92qomp$Yw0R235<{9 literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/api/service/SpyService.class b/bin/main/com/loki/lochat/api/service/SpyService.class deleted file mode 100644 index 477480e5d60a8c7af494671f4e172bf2037b7b74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 421 zcmaJ-NlwE+5UdGgaRMX~e?ZBFet=IbLW<;wNG2R|8`~{2vE#AEV+((c0}tR)h;cy5 zDO|dGDOGpZ*Z0RKfIG~F=n34!dQZ8|lne2jv?NPK6`MxIOkvrEzv&R?0<)cLB;~S5 z={eeQYy~{4(==yC6u6!T2SROCPMgj0(O+x@MyhgCRipxwii_k$ho$05;C>!xlTuV? znX<%%Rko#dE?YK!P_U(})kcv_8aJyAljV0y4|-qf!gc)Z;?+q;M$06Ym0hm{mj6kf zUUqydw8mZPcgmjOy>3o^<{0Bp*CysCm9xO~=+$kfHxLjwb4CMwXI&oybQTW(a0KsA Rk1;vm1uh*^;L61wra!Vtchvv@ diff --git a/bin/main/com/loki/lochat/commands/CommandManager.class b/bin/main/com/loki/lochat/commands/CommandManager.class index ea875b394e96ec2e6e05ef8ec45f9d527f3c902c..d4c76f77e1762e71fc22a36fa3843dcbcbcfdad0 100644 GIT binary patch delta 1832 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 delta 94 zcmZpbZk68for_Um@((U|UJ(Ws22lo11~CRn2Jy{4+**v5(hLF&G7QQLvJ9#W3Jit} wiVV&SN(`Yukx~W~h6r>1O_dJX$;y7YZ!DG)-mWaoM14Ryq@P8011H+2mk;8 diff --git a/bin/main/com/loki/lochat/commands/messaging/IgnoreCommand.class b/bin/main/com/loki/lochat/commands/messaging/IgnoreCommand.class index 1f5437981df532a1858d99b4a3beb3d67942469c..511fd7c58a628c1c210c58c2c6cf75396b96c4d5 100644 GIT binary patch delta 66 zcmaDN{#Ja#5>bKN)Z*gA^vt~U;MAhB%;Z!?20fq2a~WkgePI$4ZyaINK^5A(pHYXF)Z*gA^vt~U;MAhB%;Z!?20fq23mIiNePI$4?;K&&K^5A3n310s E06}dSFaQ7m delta 54 zcmcaDdr5Y}3PIk?^t}9{)Zo;jvdrXEMh5MPdt})>!D1619bwd*xK{?s-F$|TpBDf> CZ4_hx diff --git a/bin/main/com/loki/lochat/commands/messaging/MsgCommand.class b/bin/main/com/loki/lochat/commands/messaging/MsgCommand.class index 2f394a21f2e59b7d2a2026164e3a1f8d2bfeb1bb..3ce7de1279fc6dcefb64ea391880461d170ac349 100644 GIT binary patch delta 2028 zcmah~YgANK6#mZKJNFL5fq@Z_D>#6FJZ8WcB^ALJsi{Z^Wag_z7-$G)5D=r+WM!s3 z^=OKgr9JFHnW-bAN!UZn)ZXtb?P34&Z>?O_zIT8ns~@ce=bZgG-`;nBd!P9|_u^RV zslSi(0hob{o2=Us<66A!?Vd)Tzp=*K*6FMBG89&b3#Qbl1%n1r&Pp6Mj%dU&NV$27 zqQGG1P+_qm0ZA$n#ZHzYjYP5tv-$EU2A7VIJcKX+ZAB^^Dn^T9lUqtdx@a=Fl`$Mq zupWq)vY6~*1W^>7Y}IiKImY2Cu~eR}xG_P+c(Gf~lZtV*I3v%DnuHP+lNqu%D-PaX z9`HB#8abw*RLnHzDN`|B#WbxtJ$@Gf!s~B3H>#Dp954Itn^U1QeUJ5Y=L>QWjNJI=eQGhi9Smi3yRAYhq4y;skm39 zMKL3S(WJK*nv_8Tcfw5s%3Vdt;N$(yuMCvz3%)h$8*?4_67r^s%6kv>0M)m8!vF|)#-`T>8S~< z_SX+^q#Ix2cv%OJHNXvPZC5K*uLrNGc$JA%o+a(W>!O^`BO>15IDmsgzo19aRgWq2U-uAKnsKF;g?$IL`65o=U8qN=2Y?D3Jub z%kdsgh;=aqsZPAl@qwO|N?BEUTD*3es}DI&U;c%`K{ki@!Ok0$q-_aH?xNAz>-^FvtBi<&TkH|p-1 zEV+BJtVGe2lUKT}458j|i+CxTIfRDpD?OTqc@TVtri9VVpqY&=j8+N5ZgdR05j3tu zd3iy|}F(clY9+5;b2lYl^0Z@POPy6_gA%rou+&6vWZvn2FI;{WMHK zIz43>ScoyGA?9t}5D)*r$?R~kR1_H0RY!_GNF8%vBOW4(hzLB4M|5K8 z^e7&qp!(@?Y$Bqv@h3Jzz>HB5yrIi3n++`%FK{#U_myxZ)nNd`P zZDdM^oJ1S7LK4N%HwvO}5{X8KnDl3alF<8NHU%-0zHgup=7g}_=_=^O&M=5I*8t-o zJZ)(A7}|)ETITs+H*$xh7g;Y}DWTe)zzCNsjQvET%y}4x%+PrvgfPN# zZ%T$TB07gDBfZ(*jkHLdcgTE_e#nfqAb13)fDq2=QxxH?kfskPqN#Dih4Gk7tj$F+ zR$wBUFbP4@Y(WXViBoV4r3OtCA~hJKiC&zIRCK*weMb593GOq>FA-KEKEvlk6A|Gi meocR21^rR@FUZY=FYy)a7P@|p{(r!ElB{&PfS>Uzw0{AD)3xOQ delta 2209 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 A&j0`b delta 2110 zcmZWqd0bRg6#nkan|W{ETmcD%Ck&zj&I}=GDLN|dCW?xpR%pZk1CGol4jL_vneB_w z%?eAi%q%m@lo7~Gvotl!7TYbYtZX;!ul)7vyct5RKjxf!mV3^3@BPl3y}5_%>cPJ| zb^#cJKbzH-ZkCY0%+s*gTkT=!-@LBtaeY>Jf0kkj0jkgW~iqSTVj9doQGgqLFh>ctv!j$(tKBY>bdXdYu; zh($6QM0Ytg64qhK-bEAqp>UIe#e^~B9}o!UH+Vw6CKctlUccuG6$^2LzPgbi#qKCs z;9l%5@VNtZ1=Fh*c&fw0W!%h=5OX(%y}p9kvx~>8h~O3(%NV)`8|n(G8td!5;exR- z#jzZ>GOnYrIh1wjy3e0OD$I{q>_gy#}2fK zlj=Nrt`3e@uu~LUi~1E|7sqRQ`vkpxNwBV~c^dX`ypFwMlQlo15N~qq(~-!8WSZOW zNv2VMi{k(eiZfQP?@t7ITY^1Of|bC7_4s$$v7h_5(h^lBzmW)hr;{3+4!^ zbV$gc<~C@^gaf@XjJ^?E_a|kW|FA1-q119}c zY(w526t;lp?M9)?v=M1}&h6+CjbK<5BcmACj$#9(%`vC$o4y(*X|pEnT8(tg6nB>; zUlqkQZMd!-p3dYP&D4(h4DIk4;4*9GC_*h4?r4l+$@v|6ylFRXYQs{OtjYTkxuE!p zi&CkjLz830yL#MiB${8asWSv1Q&^q@k7=LH7M!g)Kknm}p!@doW^m2@-bzmVIFy1PeI zztdM+&^K=E){pcCRfXNol}pl2ukIigI8ll` z%)|iHQ05BaM>8>G3$fxjapDw)Fe3_?4MUj|!UaEsv(Wwl DW&GaD diff --git a/bin/main/com/loki/lochat/commands/messaging/UnignoreCommand.class b/bin/main/com/loki/lochat/commands/messaging/UnignoreCommand.class index 85d73b123f588f06e5ca382f64546c847a4aa0b1..01ae39db123431900f0fef1e55f57f559c21b6fe 100644 GIT binary patch delta 66 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 Q^9}LX#Cn`>@)W#(0e$>%b^rhX delta 1493 zcmaKrOH7Nl+-qSLK2LHN!m zFDF|rW#{Hw#>R$+)Q_e0;T?qMbh>#mJ-d)O)9b+upD?&{)eK%?u7fcD3F%*6PQE-c zm&tm#2c=$I#aEVlx4|Ba65j0M$l{gh<-ufT+S2bcxF7o^dnTJ)&Su8vva??HaX@GP ze{N!BG&qRcge{p)yZE#~TkC?0g9n9nTQp2)ZL=;uBeFx}5zD1r;Fx8Qm=hv9MV>4~ zq^*O-3_izkH4^&9)yD~gr#PwV%UU$1)QPfd&S{?0Iir3lYv!!-hVR%V`hxl}oaTZG zMhsS}#qZoQK`0Vu)q%asjp?y~1yc)C3>HjG%-o7%I{R z5-gVpA0KdvQC7gGC8TpI#m}$pVw_cyI-qrk*EXS2Z3+)qEfmx~fx(!Nsr`f=#&ZSy z9p#18A=@C_tg&>gmAc$V-5+SyalAc`7<@X!(yg;j$BfPf(OV!*q~k0|Q2}`=_?C{A z|E8-qD6f{6pYl9*163Dm(~GxJld;6#d_A_a>i=#jhufjEQGFL{-r6A= zuiLv-1|3&7A1^E58xj$t)je_8)xFwkbu(UTZ(3us+K7h-UbWgxDkF`A2#Rfn2zN>8 zXq8$TOFoorVJjSL6NSch?iT43?UR67EU!~tHHKr)#HZ|3hie*Rdx(v z7J^Yn)VWSEtg~0$sHw~inLJ?fkjcX)M@$|ydEDf)!S{lx&p5iy=QWJqMJQ_DaK4X{ zs3Vw?K+#1VAIZ^nYgDiU9$BwR`(8%?ucHZX;F!n>yonUv5_%i&;s);Eox&>*$(I5v zg;(Z;u<~e{go@K7bXuM+(W%e8NMLX)P%-DH#+&E*bSM(P1{+ij8&>0IP%yA z*JI6~vrkTb2(;xT$XT$O|LRsTqcyWkV$r&LsiEh(!eBa{*ulKq_2`g~!XFRU+d ONh}`GUSx)s5d9182;f}+ diff --git a/bin/main/com/loki/lochat/commands/moderation/SilentWarnCommand.class b/bin/main/com/loki/lochat/commands/moderation/SilentWarnCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..697f2d4a186a043448ac483b2094800318faaa93 GIT binary patch literal 4921 zcmc&&`+FQ!8Gg_1CbO3bZ8s&&w3L=!$PKz}6Oz_{6j%V;HHe7OJ(gTlaI`V?xpxUr(2_LDrb)xj_#SZl^Qa$hUFd7 z9jm9fsvsh;bW%U6r?R>=p6VZ&G%}uodVzJeGoBhLOih`W&PoNl_8d1d1CJ%d=sC&Yot9;Lege^}^}e!6!y9pQ({fXK zc`?;zdwqp$wo4LdlZq5JGr1V+9@`o-$M@@&J}&d2wI)TR2otPAlILv2I~BZx#?7Zk z#uu!XS8)?=W-3wRe#3RC%q^ZL&~|y1)uLTdY$vS68nWpaE5#<)FB%DSqFVvcS^IW) zsJIo(!G;Ol9Wb1n>AI{v0#{e^Ex@Afa2z|ZOTkX^@iIPE8>pfOyGcbl*=JWCznm_X z`5c@d$6mZ!!9Ly_+pJ1CkS&ZYg-a9(#r+wZbLbc3|JY7 za@cF=NnjO@syK$@jI};GD(`TC+XElUTNkFlmCPJ2GVUG~_ew_^X`xgwpmIf`&B%EkI)>);H`ZimJ5B?^;wmf=?FtDpqvTibrqk-V>HfWt zTWWL*{*_o={;4VM@ifC5E||1xX;ED{0odKJ;ia2J0?9c0Sr&68nPmgR`jls~R=QIa zUaDvyxiVAx^}OHFC^*XkS_V>2Q6}_@R z;eXQYRKzUJwv`UYhRktGmv@=K!~YK(x);*sHrlmnegvzcw_&kfjE$J=NtsK(AoI%T z`L~dCJC0YdOct_NRh*agCE?j=`>GI4g73afG9I{&y?q!V)PbLnNXl;?eo-c6v(I;!s|~n{&n5hI*p~!W zwx7k)_Kq`XZ10%C)yY*eSbYww&S3rXlnP;^e?o-M)rjyjC5AQpT3U@OvpEM(6qMk-UG`GKi#u?l=gDstP9m#ETxFv*>XzbAH zX7H|A?3u%RLWrE!A}>dt3a0xjr^8!kG1ytJ)t?Vm-&bDkn8Td{or>K2G*%=(phY4a z&*H9g7@Nh!DMY0jt23(A&*JXRm=-&am=+0d?Tpc`^;*3a(-bWlnM0u-zDd)`4{5O( zd^q`0Ey`C(mptad>ZQ*3EIu)ZM`?h13N4yChc5`6!->v>mPmeO4qpqAn@hBWMnSsy z^$?`YHv~>zd@FLAkqi4EOftOSNM-0(FytE<^cIGB6T{ri2;auSEY*Y;WR0@4{zf8_yO@9#S8c$cfEvH?l_BIcGgMKJ&6IyrWyRIlLQOc5Iu#>TJ#bd6iu1K zuLUTi)z9EJCy>O6h?9<|Pt?oJMi1@s?mi~dL$M}7+6n#w2exg6X2DP89 z{E(03AR|tjN?39fV98N{B}acG3SnQah-D0i@F)D4!O*aq$UMV9Y-N#oix?^R3nf(s yrYQASN=ekZ5xz*3u?48SK`@F)+;EY5>K6)dw}j|=)GZFta`<2f{EgBf{QbZFlT)t% literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/commands/moderation/UnbanCommand.class b/bin/main/com/loki/lochat/commands/moderation/UnbanCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..0c0e18e62ffe72b31e7c2c0c3fdba2277dfc4332 GIT binary patch literal 4313 zcmc&%YgZfB72Ovk4UZuK;SkHjF*Y_vkTAB}#28V>7)(MdV`_|Dlcr@5V=+dWQD#Pt zNt)K_+iCi4+9Z9Y?jvnNTPKY%*p1_U%359hK>m+c-F-&`l0c#P)Gihp&AsQI$3FX< zd-TtL-?#-}0RK=>C9p4LXSIwyXYwO8t9u&Xv${3oYFT^6aCFbKEp5V@)~&(9hJuhl z<9Yppu4Qy9t(}-YZ=^g05rJK{lh&s5b93gJ8Z9_=?4ps%d$yyXMqoz;xl{TyMROU$ zGaP|PE|X82mcZ5|{VDS=X%EuPgg{d+Z<+3F*08*B!?|Fl3;}I}xSlgLx3H~^tv^65 zk$%%My~6^nog2;ursAgsLW4x34%KK<(SWExb!Yr^Ed;ixs6|~JTCi2Y{Q@nO{Z$de zHi2l`SatTKkv3h=nHSjK8Q)+!WjluE1R`1x-|CTsf(Ha1UTW_dpeWF#1Ce4*G1k(R_B=aG2o?Qxr|wDLr#q zcTD*nEQY*U(-k;S0f)`m$PCuntJD-Y*O{!qEfPyodfc$c=)@)xiIQN(J;$`ti6=KM z#U~lx$-L#6S!2YyV7ev)8Pgp-E6F=xS+?hw4FhWTjh#2VX-79LSJR7|+NkY~<};au zDEp(2ayzCP7=tI8(=B4ewWkSWLz`|>o)!i|}$(pXq zUL$Z{DanE?#3$=;1V^Z}>lhr+@Sj7lFEfCJ>j!Q=OR&qFL8J;#bZ#$-@&s<=S%Cn$7 zl8{&>RUF5tK)p+L1|bOS?ff|B-htkgS3Z1U<^7dgD>pxUY2`Yf z@2p&1d2eNDAIs?ThBHAFR7`-a z@Sby~H505(j1c497323Ymf&d%xUBMGDv=TxMn6JZ}E z>9nch{3?@zdyrM}o8KOU2V*9V?DOS$RnfmEuKWIpfCD zdBaK>rR}F|y#ix-b4kX?4zX*Kju!MhnrC)p6BKx06LFSpIHOM+3@y45AYOyIlV)3M zuFw^EM`YbJsav6k``xO%K4~eG&ztn8v0$i}8tzzr)*8k=JvDb+&-u56g0GT%<+S1~ zvAz+Xn$H8Fn3BM3bzI@2U8{tfG3)PTJ2H&YCkeJ2?%lYABh63C3^fWl2PA z^`b1I@i-3&pl&$v-Nlje^#(i#(IgWiof8m ztT%mzw1ig)lgn&AaI>if%~m9tA&&zL%qthV!vp|t!0riz?ERgOJi zTWPB(3xm9ADIermcEK>WdmZ;H@=n2f)L(*9(4SU+FerGIV2s;&CuIzqa&T-4y072H zdSq&_lappOst^W$%OENzasIT4%F&b0YVJ1R9)8}-w@F_T*w%F&ja}W>(A?F%i2I_g zi`ae}t=G{0Hl?bt$JY?z=XQiRG}mGWXZJRAaHWO&)PTL{ z;f!y6HaZtm7LhF;qQBKXFr#SZ_dSHS1NRe#Jp^JeL+@eehZuE&W9$(2aX#9QGn_}y za>z{MQRHzD&v4rLG9@l^?0Xr9@g^SgK|NW(V-3^=xbTP`G2*!kKI98LeUU!MwRt>C z3}qochc7YpLzu<$)Ewb|557V<1(ztT{8d`9TiWj;Sfn)`XEmZDQP@#5L*c&RTe6ZtyE zB<73wZXeU|gJt|wps(f)?2FYb;}-(AaYd$P8NU;tP%N^DH(o_F77o^y@g|cbg|7;{ zcK2Vg@KxFf`Kfu3VFSl#br_9!oSc1v2^hgRpHCrK0`=4y)KhCvPpv^cg?9s}rvj*J znaXSQCrq6~xX#@OHSfd{mTB__t*Qj3Aa#>cm$=f8>bo>qt3W8|Q}A~pui%z~w`2+w gb|*Y^muDgy1$bJfr~~26Q?vzsG6deIbQNy@2l^|idH?_b literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/commands/moderation/WarnCommand.class b/bin/main/com/loki/lochat/commands/moderation/WarnCommand.class new file mode 100644 index 0000000000000000000000000000000000000000..ec27dae190090e05ae0b42d690aa4805ae20d6ec GIT binary patch literal 5479 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 diff --git a/bin/main/com/loki/lochat/commands/rp/TryCommand.class b/bin/main/com/loki/lochat/commands/rp/TryCommand.class index 6f9eebf54a35ad8d494f9dc9201bef909092b941..846743a5ace4055441c1fcb12eabffc122290e9d 100644 GIT binary patch delta 55 zcmaE&eouWuyC82tPGV(hQE+NeS!Qx7BZKzDS+Z;aV6lz+#TXeiC(f3E@;Cotd?g3~ DLbVj2 delta 61 zcmcbo{zQF3yD)!petu3$etBMSYEfBcaw;Q(?!+0g9L`YDjl0Dd8MPiN7@b~U@3#eB_ej3K_1CyfF;mO z;L90Qx3(Rq2L|WpPz!a$o9$Y=3lk%jzuz2jkSinOWliUR>D%-yg*9Q`4j8m$D#WEc zh{T;>iNU#*sYBw3VDkRSI<&y@1~lzr9afR4jgDvL`eHGz^V|%I7C5mWWJaa7o+}AuRZ2XnSsI)OM^?KVC!pr!}rSAms=!09Php7owNKU3{xU1_m*UTz%EautE@M{+f|0kbOPcgSwtj?A9kZ5eEr(WlqW=Ev-Rsw5D~t9V zw1U7K!@5GAG7=$85B|m!iW%+$Bo!U7K-iw^(Ns_dLh!W~f9o`Qgz|jejdp!>iO01D)MK&Fw z&O*d;(4v2LV;BUca8PtR9x<$h#dDHg3}M~nGDRKCZ!cT1qb`9N8d**v4br}7wxfYd zL)-Yq)MZcUa37q;R*1r~rd_$G#u8_rnX?P%0XEbD4IYpTCpoplK7UEEx&xnp5ogNLvH`PvEF z9hS*3?C_UT#XMG~^2JESJT^~-3WpMiC$s@oa20j7Iek^;IwkdPZ^X+@??lscT&ls> zG3YLr4(Ca%!8b+WZtK3Zi#q5dI(&&5Hg(WG-}ii}_+4tb^sK@6g+^CzSFh-yLxpS> zyMbK!BYYhQi3X3-+OFPoZ#Rxi)Nc=(zKbKeT>CS8eS?sQrZC`+dh*>b`8+x5Y4B?T z+0>ox>ysO9w}Y(bi*eGxaT7_XwWchJB9(aYHA(i{p<@|PLu-hV?c(s+Hf4sUf3=St z=}!#K%L%|Fj`b)WF8EH6l*g0?e__y?55vh|b1F5F?LgjgLDIFtlvtg@&{|pF91T-l z8%3S(qR_!4=g$Dn`FRCZnqS#XS4%|iIMjwv)#nApI=7+R>*rNBtS<$s0 z$7Ju^)YaL4DfWyZui$4bnvt=OmJj?%8b2`0v8#kZ=$n&+R+#s4K?AF1wFy>(hgNAo zvE3)POV({uWvv@pB;{d_x7~B#sEV)_$?lj$;V+9vyJa4=(srU?vb(1-YG)X9y zUlUue=oJzoZ92{nPO3eN>R6S=>aqXub{-^|#ui{6M#l{!34R7H6y?s3auVSuUsExh zCDp~)Ln0~V-f&3Chp>n_;JTKdcFZ8Kf&^R2VAVVZRh%SPGlL7BcqxWe5C|)=fU9v9 z#w?@{Li|nOQx#~i8t?0Y3VZ=dhOi#0@OOFp+6O>48f#8MQ~Q0e+PNSN+=dGf1 zv3>?Fy%qY4_nWF`VM`Uv0ydbzJ7CqiS=ht zxb4g%*Ke6SryIDaZkI%)r=Pd>xrVVFy^xw`><{qgXY8 z&9_8-zBwg96C6cZHENB6BX{XUJwjQ~ZcG@S@w6ET$~V{$BYuJ0fI&eGF=ZT6Kx|Y( zJROds$0Bi`!P9jkK9e;KJX|Mc;92FybjFYxB4eJXHs<-p3l+w6#UM)lyjTr!qw!J& zM7I*+CN;#%jaMolHYp)qt%7(B2Ih4Nh|Nlf*Q+6>j9U~CJxYk%&Jvh6C<3!3hExj7 zo7Bdr19N!{q7;}nt08Vwvbt3X@fJ0NIxtr#A#RT#x_AWv=sZW?YP?-hliOn$dX%N_ ztbp+@CB{xA#=GIVsB)>2sdA~!y{Bdd-ghTHR`CucTEk!rqD1Hi&l26c6w%!sLn=l0 zL-66KfbZc29Faq33>voLB-~qG!uw)aC2~H3XO%d!dJ&V_RU_%IorT#d>d)mULmWjM zQH@dCYm}Xq!AIeKi5J<#iTYSmZU#PH&eTDrO`ocO@o6Q-AtlCVBaBX&I3i%5!+^~* z<8Tb36tK@n5Zw}DA$R2qixUqkK#s&f7D{HSXx5i}3?c{}l#2)i54$e|tW)IhwH&5J zER6_W1P{w|Wf6P@@2FpYHOlJ^(!Tn3<7>t@X5d@pQM*27TX70t@NE^qcZ}~T08Ayo z4^#j@G=8iA7*PWJLc!XIW~YEl^7X7|#22ZQxg(UmN+giP7Jkai(TV*fO>p&iNmo CJMt6& literal 6812 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 diff --git a/bin/main/com/loki/lochat/config/ConfigManager.class b/bin/main/com/loki/lochat/config/ConfigManager.class index 6b48d5039b6f9608847c0b4cfe2af34567720924..02d07d725f9dbec35985dedd489896f3a19d5134 100644 GIT binary patch delta 3328 zcmaJ^4R~8s8GgT}OK;Mf{@gTe(spUPwcSY9tgCc&f4EXs#@0b`h#Rua(B5{P`RUW7 zlRZw#BT(^gG7hjYaZH?4T`P1yq0WH}Woki(4yNcf1UD4~)FCJ;%wzC02M>VfbAI|yG=@nvixs8Kz3RriYCXtX!nqhJf6G!PC)2Ev_sNDs%N z{#b7;sMoL816_K*g1f{qrHbwrZ}amWQDUoFeIFsebA2G@?+f|$aNwq(-lbqWp}<() z8wp3x&+Z`PrDmhd!JZEgoEOg3$2Rrp3U-REwldl!Mr|eThX{qW)7$v3p<(f7`htN? zdjDq?>=q|%hBH5^!|;3pn?%5 zzTj1VAlwxRDfpgPWB1adVza%B_KNNHrLa!`nCQJ(!9hYzD8f<&jNJVjBfXt^eP|%Yjw}Vs&o&fdCIyEGu;T_iq2h;lQZy;k zMWO0lJB6bg&x( zwxb>sc3f&a<{6L6FrnZ%5zF0XeO|>c@hefUF0}r}7#oG9P zOv|gW{YS;SIHTY_(UG@cHjzqMI7hJas*cb~x%rgMX(nq&)c`0%@Pkd zAGS@yDpcb}c3EE3Mm}-0$R&o0W{{sp3#n0lu5!(wxpK6YNAtyyYp`lDa?t_@TA|?z zOve(;KpXGLl~{nK{9Gn#ix&^p!NLa;IN;N6%O1xaaeUFIeMS4Kv|Cd%!a9yVId^Yr zZd+<}e`@qK?d#I(LG2sTepCCFw8JTn?-bIkB-S3wV3Kk)%^XOL9!rhB zul<0%#yIjUT*c3iqKKa>xTvq@e6Qge?cj2}77hGnK31{<*P$IBo5dTRl2yqJ?0A85 z$>KMBx=pPX@#idFW#h06t$sZjVzZ~6>+bk7{R)u3>tQ`{;kjII&#xV7Jn zOp57tW5N3L1t;YK?k#M8FTG!u`#3qaS0=}H=Lg5e=@CxjjM(aR75_0ij*H^hB6gLz zEPuwU(&aU49IGdXdbf0W-9&jK6Uu#tODnfNQ_n_83@EH8%lWs-kUP?LP%-5soHe6( zizzOOVyBGaZ8M5{Gexmmy1bKcnetByrz?g^Yp>*9YsR&j3)^TX0pFm6!1i~lPpe}5{InDbi6Uf+Y2wo z(ln?rH84(94zdc{b0`Nywp8FvCYRXdwH9wnhM8XE2n~HPeH<`KH<~xSSFvMv=nm9t+9}w>o1FD aQv+Ltt86a+H`6@+#J^i;J}son;Q1ef65O-^ delta 3075 zcma)8YjjlA75+98CO0>C^0<>b%;YgiNKA0@AW$&0VWmWBd^E5^rHhu*+A&NXtjSE8 z3{-Tvl4`Xg_@GAxqXiKwEu}PojFDO@TCq}Ze|zt<_t|%zYCU$Ddf@#%djYi2tR1RX@q5CtSbTY`Bhnj*B?eS1#;qE@h1CV-X_Hu%RTS7v(8tcZd%C)#{7WeKuGo;}>)Aq>Gxn1cyDB2Alwykxbj1S@qv?`SR&HDA{Fm4^=o8|g8l)eK6Ke5X zg1bKwjfa=2D8^n5KZT#5SzB}CvCf_@6;;@$;Ys*a)M1~B8Rqawb7;ms1<#2$v)8$Q zq2cFvUUX*%#m(BM+=tBNLGg?ha{pSxukf;XTf5c$dkw$AZ^fXekX{$Vp6T?ac+nH0 zzlgJ*Q1Rb2{1wOeqPil9ws=Q4I+v5XH2(FNg11C-P9vQbD{?N%dPl=QaYVs;;*p%D zsra{sb2v*-S*yMGYAb91HeD*$J9&Mjc-0szpNDMBhX?J@u>ckL z9IA0Or)wda@OfO0MdEtjoWW<|0s&MLdJL8N%}LxcjG0nil`)FdNvx2IyY+9U#Cx3k z_qj*WmpWN5wezIHdM9dF9yH5?VduevgU;0k{h<`q!}=pB@lpMIDRHahsg*oiotoS7 zsn+4hc9+BT5vf!pl}4OOJN3tsgLZsL2?|M2(g_-MF80`q9o)cNxRS?C1bAEw4PRnu z2`cerOv6|BErf9uuEiom(7~uabm2yH<2Lj_U>P=`7h4g-yvGs8em;H~12~EVj$=8_ z;5x3xm6V6;sRTDrh*hs*Gn$ntd=_oIx`}@(+|B-TXq{0p9p!jZEb;rJKQW^SBvG~- zl`PdH@f1(flX#k^#w4EMX?7AnU-V09Q2n+3NY9;@!&-Xf(+{oRxy z>p@uwc}{FEE}L@(XKjv!Hb=<^IR3Q-IlNJ+|3`n{u01Eo3!*j{%;eri6KscvQ?{JR zKg13jpXToWQT|d!%HIc598Ob*+22x)+!q`cCrga7%#?$&v&qNd)Ou~qL6yHP3xYlWJ^p3dqcwVt#Bufn68SUvSP7%vRa~(*Ab!lnhbe)BSqXS z#q83;tmFK(cnbwMA+9N%lwE+sj3+YK=r>A@R71FRh5(M)yp486nB`QdoBcmcO`jKQ z%M4ec&AUBKxsuD|Z7Ui}T$9b4Z}NJn*h@i<`okF^#SwV^Sm0&0tyO7a;6Dv)%T;bC zQGQa>G3U!9PPUouNylVJrV6`n+{b;xJAndnv(nZ}rqc^b$VE0}G#zBJgj89O_C4t! lRgTcJ?a*CRZ8k-kLKpL_aNSJhUjt3&mCDj5XeKox@IRH8)-nJ9 diff --git a/bin/main/com/loki/lochat/config/MessagesConfig.class b/bin/main/com/loki/lochat/config/MessagesConfig.class index 2bba3f56c457b5c128cbd022f9f9bc1e4c4fe390..567886837cc58f26b3469b1ef2e3c70c4a52924b 100644 GIT binary patch delta 2356 zcmah~YfKzf6h0TEU01|1&;s?5scSH4Ez}>B-OXkdQ2w+S+oq`rA?v^nxM7)Xmc?ep zW@-CFO=F538>_K}()go4On8^9K$EIfQR|(mRcjxL@Avzyt>@l5JIumZH?R!neCK@U z@!dQ7tLS?7%*FIH0Mw90L++l!)bchp(GUx(aU~InX?LitN>?N8;06LG^WhCBB~W0NYNl|3LCB>* zTU-f8urR-;zP<`Sevda*y9PhOKyxe_4$kiDUHBu@$LT?Ohz`)hv`?l_&|ca{`{_P< zgzlgRzo)j^=dJMuyT!(~ z+X@O)@+2v%vZs-8loJgycB2Oq3Z8Cr=&?8^5GJb<(bXZ|LmDGIf;re7Hg}C78Jj&Q zvxih>5&ydf$3b7-xNr>wu0dgKQR3Q0wMC8GuPR{|yhWhYrmJn}MTv7adQTH*E%(&b zuU%X1n|-QZrh9Q!lQPP6H5=v`p zT}*3@#5-K@UiR_e#GMQgYRN#}C(vH*@p-Ggbw0YEj)*}UyYzF5mlw~~faTCZ^i5Uh z59F;%Nl3RT5=E)ARynB)&J(!K)@CR7hvrCK@Bx8)%1zBQTC4-e`wad*$y)EF{q#Tv z(->md#_Z=K3Xpdvzo4*3=MRA?~We zs+d}Gw%W7QECjS!!{RKSZ?g5%#hh~+{EomXYnG2OHg>G1j6KFqe5hG4LIU~N$6YaY z{b?!+@J9kmZEa|=Lt_Nt>+5sN>cox0#E6}&z#A1))I{;rhm)B!zh&R$itt*g z{?I$FywPt>19Px4n0(BA{heI~*NwTbiv-r`$K9(NnbE$p`5VgCrASQ3f9q1@C%FVR zu>3a(&0d~Gf4H!MRE``iJnPagNXtr|mmV316@&Q7%Yj1tUWBg+fP?z&(o*uGeqJgm zIU&95fEZwi)C`m&elbRPA(uxMtQ>_h>D86v@VZ0P5s(m@FOX0K#rR(idncJ$R^wmB zpDWS%B|U2k%{4=@^HaRWH>I~7*v5Gm?{LJs(isOtnj_9~#5w7L17eyZKIDj5>0<}P zagO+uBR-S9a6p{kh_5)}Yw24D#0*D#&k;XJKRF;ya>Oqj@vHQ^1L71%{K*l2N&h$? hPBTQqC2QJQJ1ZhkL~cz# z47n~8d?12vkt!Ao2zy<-*PU+kA28~CA4=k8?zwZ$%$d3OM`$qY`|xXa0oci+og76_ zmlhNb{r0Cfesbs88BtW~E_ZQhLw78mYVA#QC+ibEz42r`)Rfnob^3E| zaY;p!lMY5*JfKD4Nc8MZww~&Y$Lu^*RGH264o^}37!zjkBi-z&RZsLSPf$J6i=Oek zDHmy8>x6e((X_(3t1sCek4L&wu>vx@vooXTy&+qcS^dp>Sk3G0zG25oa#@9g68**3 zpvrW$KcdPd7V1I&7DqKs)=|WI{n39yZPpb=L~XU$0pp0jPT0MyU>`yDQ^NuM)o4+N zEWSAqaUaD+lmN$A&2c>$*yN}qPyAp~e-AYFho*67xR&L*VvgZxA=UL8io)lN<;f90^`&yx<}}X%ku+URoM*lQnowEy_Zt#Uw{;MnwHm%-bcK zkNU}|olBybl*f=ym28u+W=*tgdAU~#6B}=`ytP>{L(rI$Rnj8Nu{QBY8C;h4zQP&? RtbxpyQ1*8q#d}M=@CV(rt!kRm#IsjqnI^!Y;GU{=wl9c#TVTj|IQ_e~5Q=oPD5 zJ<^dxMm^RYiTS#hCa-v{zb1C1-zZ|cCW<44fzsW=E$Ib& zI@-iKnu)SOA9nVu7Cqb zd)dl9J&+tyhXQ>i>Bo;@GC-0Xn;6s^8@hy6Cp;+WlMVehyeiXEc-ckbRc_X}t@y}H z&Rb$`wO6AQW=+hv5398QtLLR|E6bTM35h*e5{Cs;5YQPqC2!-b{O~9TB=>WH;}k_1 zV_fnTE;7kw`tC4+EvwlzPdK>p5_deI8x>T;pbO|07s-7LH%UX;Hu0#*R!dn sgD+HI)Lp1i{_nXeJ}WXG3T#@kjqEgXjTX7LN;U(^mcL}hhhVz)2MNA-o&W#< diff --git a/bin/main/com/loki/lochat/core/PluginShutdown.class b/bin/main/com/loki/lochat/core/PluginShutdown.class index 7d57a96e5663d655b09a7354f52eb59b706aad66..bf166708dc392fab65a1cb7f87a4082dec1bfbbb 100644 GIT binary patch delta 489 zcmYk2OG{f(5XXNfCg*ya#w2PVm+C?jjm0NgXjIgu4L%l{kSHW-)u=ZySVNlFf~$Uj zuFClWF5L-9pZTq&8KzLe^hUi-5Tm2!P&&#mld-P*_Umg{GU zv_)PK`-3raI`A+@S`nzeD;&5rbEIR@^xyOrF|G*h-7(+x6a%KJ+wUn}>ZCth{ikM% zy2t)s1+63d30|IJt>7$iF`5U5*5juE5H!m3`Zt{&8`xPFUg}OOu zT=~@7oI53XL2gVt`a2ICea5olSOyw|3Z@B5oWv(L3-X#4-rz7pgjo@sqn~-=EHFWe zBpHcW7Uivwqez}zmZ`ByNLXfXgoUwLDzU)SC}Sug-WI^BIE#?xEo&kkXPk95WXlv$ g3T%pnA_2B!R*M;AGi?rSCv?SQOVkvuG(8lr0g44>AsxkwA8V#8wnd;R~n)_36D@BV1Lz5z)t8t#@;#q=Tbs(f$N(&c(t#k5D; zgqqUB`u64ZP0dnYT2@u`NpFEY8@}6rXVDlR_)~eSc2})f2^*ejC-@EIl-&b4UhO z=$5*Q#=7Vvep2+aL4-}AZZXXcOJu~cOPU;6^6bkiae!BnYaJ>+dPNDJWntQcmEn-0 wY>mjzk@y|L8Ik!3&UwLjIOR;%CM7h8rZw>!q}nypBxsOp*MhLl#bT%Y16vSAEdT%j diff --git a/bin/main/com/loki/lochat/core/filter/AdvancedMessageFilter$FilterResult.class b/bin/main/com/loki/lochat/core/filter/AdvancedMessageFilter$FilterResult.class deleted file mode 100644 index e2030588db009b3d1bc7d39a244a4658e5c152c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1769 zcmbtUTT|0O6#lk_rY*tJQoMkI3P@Wn5ycA@QK;jXzWPG#SE~EJ1q;vLa$&=vUE;2-zy?rr2!M zO2XRVB}2HjWSJYg+|u2DUzyHi+g-SJQYsWg-CbHm3{i%tBfSt(#LG1mL%7bclPO+~ znS2NT~0)k+*2`*sU|JE)KVM5IgpjN+~PGstw;&Q zw`IdByG=;s4R>tKIk%|@yeg$*I~K2dR_r)tp(&VS7&~t}t6@5NO=uoB{!JD4G0zYy zompTw4ic%GN3tq3@AeuuD~7Nqic(e^^?Z9~0gVsb`CJ#Tgk84ux+AS1q+oE{4k~=U z-nSbGdcV+Sewh;y%tW!kFz!!bzGeRMfk8_!sPrZ28>H>JX$q?ui?9_uW0?Q%F(4}! zpNQuSljpp5p-{mKhSBah zOi?6mU`p;3SAiA~9i4@iq}OBE4=o590b26802;-Z56Y0I5P6E`rcZIZ8;TkV6Li;Q zVS!{1$#m{JCUYYvP;)aUNakiwFnCP*0FME`89M0?6aQ`42#H9u1i?POOh*1)FM_%rm5auwY^$5EByx)1ELrJ diff --git a/bin/main/com/loki/lochat/core/filter/AdvancedMessageFilter.class b/bin/main/com/loki/lochat/core/filter/AdvancedMessageFilter.class index 33f3934f42b8f26ea7a1b1aac16a1eba2382ed2e..61797f85d8399586ca01c7b14f06afdac6c36df0 100644 GIT binary patch literal 5380 zcmb7Hd3Y3O8UKB`X0uE=whaU*5bh+KWSat^B`p+!ky};XAwH|mM|M%fhf8Tt2l5L*k*=J|o`R2X9_x*kE zyz<|3=K-w6e>4;cG=`m_P}CW;cnJ>}ZYb=;%}}2ebA2wnMrC*@BeJrP1GMto~lsI4-@w8y$n1VFUpc0|(tccXm7j>M-#5A;KMWpYE zm@$-AaKrdq$VgDWJQ*x)t|!-UESl`M?2uP!8(D5$-gC1wHKpCM&CcXduNmKL z^hQmAK)Vw*qFar)C7)A^CGLQg5Lh;5LJAgnvmeEnE900aP+VQJwVZLy*HMZx4GRUP zUtOW?4ioD7u>gy8EWuK?NWbZ-GVd^Kc2AszU0qYa717*DX43m0Xh)*E&Mf zpoTR9OGalUi&|NL@MAUB%1vA+pjEfE_4KHXT(3ie&xcll%07h(4Pm;`Fk~F8kEuZX z*no{1+5|!qgfkttqn+)<+_cGrOs{UwqGh)mx9tArwwg8{I@v1u<}JHE>gC9XE`iy3 zFAe6XKAKLFbZ?W6Zfqt1(b77{iJFF8*ji6bj}L5{K%T;0mCo&qaIAEuWO8BCk8PMC zJ7TAfa`-ju9;c2R;Phh`_R77vPAW2VOoFZ!w?~3JQ}WoP(PwdPfW)&1XuwI!2}kL1J(qtNo6|SyGz~-D_T{_ zDn1F^Q4f4r%%da}ERkn2OItQZrcDJayL>3!lcZ&Rc|9l~did;>hZLb_-L4TH>@Z>; zu4=fOSeVlkg@IZgO@_VhP|QeVPWDjVi9oop+(V(RsBy@QH;->1RI4CnVLuqU;^7G zmcUWrNbA`Ub>(EBl7XdD$=JV?Z*jxPQzz;o{iO)xdG|;JWdkz zy(%^%0;>z{;Y9NDMb<$&Ki<^YkV=pbj|h}{OLF>oRL5c5BO&rJnYE+R`LF0WB@Z&q zh)M)!PEX)#I=+t6>=LCkGi7rdI3^TG*poRYf)Uwo#$7AHvn1_?gt4b|4C82~un2Jp zxqL>)S-Ct_nc84QBBm{0`fx7iRIp;Xx}MeXO(|5agxb2u{Vkp^=^Vt(m}$6Ki71|z z@!sddcXBf2P1k8AA6{Ulyd??$FG>EF1zN`_ra}W#_NZ_5kT6riujG`l9LlqHzt8bh zuE^8kF`4}z>9{Bhq{tcMwhXBc908w-P8Lk<_=|&x8Q1W0Civ=YPLg95!zN(NDmb||NEax~ zfxu0;aU<4Yx&uyxb?^uLNy8sG;ANe(?ETK58B#-u!q!%POE~dE5_B&MJe1otDQ=~3 zgJDOaW@1sh;|wNa1<*UEg!2R?+7)2mnZvI>DVuYi+Mb02*|GjQUN(sdUwu5 z-kl?jcjsK<-8o%&cTNYqmr?`AA6_f?w1(P>c-Lx!;tZxgL9z%ldG&kCl{7IEv-mt+ zv0*l@=aY0mnr8`8S*21YUgv5#-(9FZgE_UqVN{9Ey63Ur7y@f+b~)lMRPFlmZ_~HcxzK> zFwo(XL|HI!2ld_Aqy+<8iX~AQSiZ7&6EZ5$KI5%Jt z&LaVrK2_3-B=*yvAgv#u?o!&HheM>wsBeUAfrm`0bcXmOOL1Vu2LE@AW_*7^8ODGQth&dKqqP1siLWa5Clq@Fnf6JhZ;WR0)!wSB?D>#A|vpPH*7ZhU^8NcT-PUo^Iz5GFzv+gW@csi5$xYx5A zSYB=1+KtTi?cCiCEX18yg-uw`QtiNIQroc?J7A%Q<+hXMvYWfThtp{<9z+-?5y9gy zIY3w}+u6jQKYRIypOCWX+fO~}B-a#mnVLL``csrw88w)61@krh>CQ2%* zYgjov>QwzO*dqRbX{j9l%1hx0ex1cXRjr5FIQI}fk9hGY44jsZho4)o;Wt!SjNejO c5q^i)dG~SU&;0)df8&#!&Hj#mGCwo_560B^^Z)<= literal 19486 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 diff --git a/bin/main/com/loki/lochat/core/filter/CooldownFilter.class b/bin/main/com/loki/lochat/core/filter/CooldownFilter.class index 9550ed0a24a62cfca107b434c89d3b057b563b44..97bbc3dff8b79e18c9fda845b76a0f4e672aece7 100644 GIT binary patch delta 77 zcmew<{!x5FgAi{)PGV(hQE+NeS!Qx7BZIcjD&Z L)NEeQ;>8XCCzKiE delta 104 zcmew;{!@HHgD`(`etu3$etBMSYEfBcaw;Q(?!<|*9L`YDiCgNKf;EsuHb*e(Fso`O i=jZC@%0))+Po3 diff --git a/bin/main/com/loki/lochat/core/filter/FilterResult.class b/bin/main/com/loki/lochat/core/filter/FilterResult.class new file mode 100644 index 0000000000000000000000000000000000000000..1d93e233ca5de65a24bbfeaf766f006ed6cbea02 GIT binary patch literal 2017 zcmb7FZBNrs6n-un>o%&435sus0=gBj;=2w(1rZFWK|&&ay4$X-uIt6N)A*nKpozo; z6Muj|%6M*DSlt!^AKLrU^E~IA=bqF1=kMNc08j8jLk~ltA{(YH>lVE#HSU`g=?e3` zW&6T4mx5tkcum{akYvbx;Gei@b7$LJ7i3vw(70{O&w^%bi=jV6it3v1JiaX$@~bV( z8{f5@?Gi&@nY`841^1-G&?D;%bA?XB;_6YOBhKW4tcolcPU`4Kjv+N`IhH?1ITp4$ zxjT1?n-u@5<%pNfMp?KUyle;Y%L=zQxofF)w5c`r)6;@XRQf45N+)A^PxduQ6pC9J zis`J5UZf~Uzh-$1#&HtMZVUfF&cQ;lGjiqOk`4`NHM^`MgET|hm!aT!I`45ES1=LC zd}K|#cp}ys_i9RN21;&4aTj%5g~2dgIC@&e6;e!mY;v3A7>b|$tMUg?@k_-mMSWez zBus|+V^GKRIpE47ff-d7H-oV0q;qS+ugU6b?(&A9Vy7^&Z)DT02&#HYZXjO#*HsM@ z=Bbpv=ev9-1bbQB$3qPdNd8upyG_Tp8o~^nN0vHDm}SV6_Zb+z$FsJaPqHq|(0Z9W zRapSjfmr2CrR9UDSrBqj{Paz zOJkm4ENc2vTf3KHecjJ6OwR~CqYV0r;|Mp1MR*#XF-)`+J0y7x&xzx3Q>vs;DVl`8 zCCpGCB&dE8$g9SqSzfhAFj8~S*hfv0r8k8}t2<=H)b1~^w@HKj#EEaT?7;xNQ^BVc zQGkpCGn@)YG8pVmkzhs_itK+BLjeWDX`G3oFlkiuy~Ym=e{J)BG6wF&Ih?2O3>R>* z3wWeG_F^haWV%8|+e2(Y2IyC{pVc+;t0d8kJ&XbE zAi7_o&<5xqbS;u14k`fcO2$Exn2JCJ@}zQ18mC literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/core/filter/filters/CharacterFilter.class b/bin/main/com/loki/lochat/core/filter/filters/CharacterFilter.class new file mode 100644 index 0000000000000000000000000000000000000000..ed103630c7bddb835b730f586e2fd1b2ed192a30 GIT binary patch literal 2526 zcmcImZF3V<6n<`-cGK;)O(L|ET8daONne%<_!dMVT8JiqO)c8u%gyFC*=Do5>~0u3 zjx+uY{osFq8L8S2{s70{ zM4f?cGH7l7Xid0+2d3R1QHf#rs%e|SHHORCvg0-MTBp@AC8WJ=HaZ?@I<^EAIsu7d zTwKU)FeHjjU8oqun1&IIGYn>P8%d-X=0cDCW$yrSiK6lR<;W!!8Jtk?8iW3lz#2|s zl0j{V;D+N^g4+y}+45oZ)xe_w3!Ayk5ip$6FofYGPBY9r&nF^A`t!PmS)3tO$aKjj z{`#3Mo*9@7mV{mJOd#3!3go&VO`V3dfuaMmWc@$=Z zVdbUMM3=*`87}l;Dc4I}hN>~IVF8N_Q*GXoc{g})&kUNh^8;=N6w1kLsn1;%%ebgO zr<>h`bOO`TJ<$-4^t(Kug0(fQ;B6{RBEqGbVpw{ zQ$tx6+fc)EIdL!xT<3RF*?ftvVgg5OS0; zoH#Z^m!*G%Ni!wz1CP5E5j35;ui|TLDWLf|e@LlmKXh6`54{T8W0mHy9yh_4P}Z?t_VV)*C(kM@OB961aLjx%-S zxJzTad-^c+{jpiuhu27yLPMoFvZG-YHzyRBDcq!&wq)DFD_Y$51-~Xy$42RE3klKzX7ay7%P(Xm5_?EL z#T0F)hxah^6mw7Lcz_H_6AAhqrC$vb7{h6d;~YsQLlbxt^Ylf=a2^*3fZ;7@2a{|!^WK&55wR}3vYmYaR#fIqNQSzOq|rTjkL1@=&QO!|W{@w)3?beHwQZtYjB5=^AZ^0VcK@Yz4Gze0UZcSCw4MM+XMefsV9{=CoI{oRjW ze*@qU{;MG&P^dWdg5}gq{#51+pZBgT%$Sxh-RR;KMlHvwjs`LfNrCK1W8NrOhCN$2 zQa&jwzCdTiv1iO#fn6oXoh_7`wVJ7%DPhw!eABU&J6WI`IUE-c3FtE_z@%$bYSI0#?CHPejPuh1$zi7|)965#jtsH_9sT)f4LO1BE7`ODO4D_v?H5>cbUbW$b1W;3 zb?90Pfg5yaNU76}I@X|5HNdpxL(O_wx|2rPV$r#hQ!%V*!!^}?Bux5qrl&f7jToxx zLxcrJ+p1@xrr#CVbZX44eu4h0RUJ=G#{hN+ zB%GSS!PT5zOPMnk#VTj8KOf^(z_lDrhINeK0f99}qhYlKHn*wG zH0?@2{?X_#l)-~2YB)^9SBOGB=orKAIQxXP(e!0;=1|$=oCplZTM_P?ILWV!5TzO0 zPeU5U>3Stzuo*Ha;fRi-_&qmF!?CPU)Abmz`%(pQ4@!AL$0Vi%G}C*a-tbv0TX#Y| zIi};0(2bO}z>S6z0@nu-FRz|7j^j}sk0~J5THHm`hSikH{s|pV!r)f%%x6ejTzE!JYt`KC<>$%AGkU@GXer+wSD8kaV1-!GL{dAV?|Cw#uiIMUIc&QvUE zxZzS&oryUKSd^buoOo5oYj~Y?yRz=g6WZ-@lrA+UhDjvi&@`JSYt8=BFN?N}dbw(B z<3j4YGHgsgtX#u8ocWR5WIcito5B9e>9^6s#*Ef$!s;h%7_r zhYh=GNpD-paca#*v=-uTj99aNS3`1VsHa_~1=V8OLCQM%rb#>w|S-hU+ z`~F`#-ohyg{+Y!oeg+k7Te`y*U!Ky_@R`7ES2S$3vNfC$xaI1qoU5=$*vh~i+}jD{ z)Yk@2IraTP>DoY=Mv~_YbpBpP$rQB{yxuVI6|Nt+fOW^e!uoUQ9@udXJzoSSH}N+| zGw_opi7x)<(2e!zrKBe`#s+NU9o+;$IZD`so5K*|MOvxgXLjUnS;CeCK0-)HgwYOsi_+bx{~^G}mv3?WJpLeiIzK%9(+^7aI5YYTxgbtCS3QrJMVvg_o9am} zqP~Qu5(rXann|dF$5^B)FM}JA4a(cd?YIedu&h1UjQ!YxVP414$5+fYz90JVG&NpE zKi)(hr+ED{wf~KsL8A^61-9UFqt2j-c{XSx-oOGa6SRE}EuK1PwSZ^vEHl}|#rqsj zos2V$=kWrOe35<>dZFZxl>CX34Oo-?3HL(NAT(?hBAL{^;G^${we6;_|D!*)pSJXSXC~QM$%hYSvom+jz4zR6zUO<+ zndF22oqG>JFaDt-BG8#D6*5+7%;Y6Es=Jw7$u=_kOv^Rw;O1n8%6ehI6RA)HQv3CT zddAX=Bbn{P`;D9{5YLs0`^*u6^@AmQBr{wY8#ARYDXiGKYnF;qn~|X!qc1G(7HDv& zf48mY#td7aanMs=aZM|;O)qnP-jF#`)ZL102n^KE-W+<|vnSi%9eVCJ4pfX_foJTH zYn#Q9?jC+<@y%w@bbACgHh*lBTlNYleWkpSL==r08juo*Hn;3m(Il|0W={;0UGB)Agw_~LXMVbh#@W^FpI`rmBO%L@79Mcy4^Hb%IVf#-8SWO zAXMB@(-F9S@Z-f$I`0Qq;GR0?X{YIqXLeZnxM2rShLLNzKq;~>yFbY!`y&tv0B9{fh$8qsXJK18mtw#VpMl_7+bMoU&+g17fcLQ!kuZr6k zy;>5G>l$uHA9>9Xl`F21-Pb$pFy;b%VSoH$5>DZZPYBEm6`yVucTjchJTLDX(2qMc zY(bXjIj(K!1%c&t=KInNs{$vxo0xx})G&x`%x%|jXk4!Ol1vD~9;%LWq)ax9>#RFF zY$I>xbk`ta+p$x{4kmxigF4GJT!mc%n**f0x;TfT@Hut+y(4yO*aKFrSgur&xU*Po zP2e7ZWp(5oX2Oom&^Cel2+6wDeFI%xn>JTpt)9K6`rh=D)%T|#ub!-)uAZMhQk~+n z6j#qwC#&yLe2(MO)6Y{l{RD^aRZmSnS3S$A$>}394Ia?&AU?&KPWlmf)?|LrT9E2}erUy3mWldyF*2@Z?abV&@FDdHUE`dtdF8=hBPYOC^`_)63foceIpeNB$Xpq2lKP zt7_ zNlZ1W_%J2k%(QH=$V{^MPG>kO{vxpUA`s_-LdD+%R$h83sp{j#R~2S6-$5d1l5fQv zHOcp6O6PgfB%c@*XuMuQ$sVqb@Sbjc8}nPwW5K;|W8qmWYHd4<#izV2SMu5fh1UhV zHuBSiMOcWXlq~i)V;Pq7iE6w<$xgQ475)+80Cy!gcdV^xlrL= zYL8E#v&S25Ozq&fOX0(!wkdQ8oWo7P6ndg0UjI$8rLidt2(-4Rm2=oCXYPvdG9<7m zzBs<(=@(x?lc(wKrh6xF|0F(ra%rr63d0e|t@}vn| zLURm7ln9BGm2rT;Eyts<;V=Tt)ZlWY@RZ%C;2>dqkZ+t1;}OPioO@)v{JBRt_Zdo- zA)2~?98?BW#kDF93Euk|r{&bm{Py9;htv?``kig<6L|b3#8de<+9q(APlqS*OwWp! zJy9!VFGS@;YQrSHJb@Rv;DxAMH6xh7H@cKmKF#mw3M>Bxl}0`2R(bK=KOo{Hwd+Kl0DS zh-?+UXW|%-mBczKjZ_|bCwW)2OF03pBdw&B?S~bSR^-g_3;$H4{DF>WXIHEv$~mPg zege03q~o1kDz9y6MafU#+wJMNcYh6ubnFC{DCwA7!>^L>Bq;oSXnR`iiXD!Nbllhd zu6K^a?f8Y)JEW|$222l=1?XY!-Acyv;yS+3x1$g1`L47HcirwI_vkEWM`uAh8h|zr&)~Z_&R}Rv!0&mWtsytR?~U3C?g@@!gg@)e z#(C~W{D3mmW70Mx{tMQrABx0$4urUXYmo40b%?Z*6Z}C$4lXe9FP@5eYyJ)OUyPc- zkMQGweEkfVEH7G+({Ds@cde=NhWEAma;V^LSkbJNeNA9X+hy-<|UbCHr~9U zjVJ<&iYzX5p;b{ptSD-W%`{Dg;s%PK6Zc(l-*8`13E#c%B{S2c`O!b@0~649*pxVI$+p*x6DXiJYk!b`;(3I>@p3jLyc+h z3Iul=HyDwGk?N0J-n-L`+5(!HGI|qcOhD`qm~EMvgb_8n(lN96mNZk840RT@du%J7 z>TeO4la*U^8#cF1Wd*7np`0C0M0!k{5|usi{*+pIuk-YMK4!?uffkODh#; zt&6APcB{a)`p&e~AL-2v4#uUQslIrB&NA$HIwd_dBlOU0FCA_vRmdcA{qa;p8GkAL z?^f?E4O<1g?esH%3WRi=f_VZJ^$lA!ED)Hh#FZNxjO+ki31B`#l@M5{BY@d{oGP%u z!8_ZmP-u3c4B#}Jq2Y7}=_KveaVF{n0{y1lmQE*3BPCE>KNa^K4LhVIOLX`!%a3JK zEOEmqg;wa$q)@#;ZMnIwkc_S$VLBfUho$XJI#!~Yk)s(-pam|N#)_$SHB23nCY-Iq zkIDd_R_iziYXtN}+K6pSTd~e~meg8N7U3BJR@E6#nVWLSUenqlX)9Tojv9%rh836Z z?x5Emh?9(~I!`i5BX>B7g$=Vd;))%JUzA%9qckP%4`iU6Ifib>GXPj zmH_CzMr;@1%QoA)$;?)?>v%cVGi4dG&1N>8v!Vp2VVbD9F^Z=n8&jE_46tb=TQt0a zR8yqqOk1w6&$P_gX48n7mWGYw&cs}amNQ+6%h08vlg2BV6c=Dcva+Tfp@XTIVa3{= z_pFXhxLm+@<(7_XFAzA@;Y+E8E7=mj23#Stax+=ux^bM%CsuCQ8bAlONcAfP<`vZ) zi<$d!ew&V~u$|T$nE5q`Z;IB!&deER*lcrJ|}N zb+`{LvvDm94c7_O6st+uvO0PZW#T1`OfkjO?o(Hr$u}X4qo1ihe_}ah09h;hbBL|& z4jp&mE^;tw49YIa@NbLT0}>x5r%fnXeH99&nro zU51t7{%lLrEzQR3k>z=Tv+Z$NO#(6FSa!J2q(FH=Vx<*y+0|DzPGZBktIE0wyIb5I zigYK8AyQ~MPiJZpJX-K%!-|?q*e!m%j{$McTg|;g86)d<{@J(}AJp&xnp{%9%XZdr zAMPh@28?XCX(i*?ECYE0fOZ7%A$(ZFg97z0)i@pd@eu->WAW!Xb|%tG^Ki(>2z*w@=VV6d?g2`jvi+#oFyMzS|%EL#=87{06Hd&<;!c6~Bq5Bc!}=Il~A z^&=fWmYEP>`LXP*tVKjvw&3C#t7VXXrsL;wJKy!Wv~7mq#T|0^S2});-_R>&MurD} zf#%ZKyOnR_)UZ$7EhN2vr{ni{jv-|gKQ0Mf4mR*M@R#Gd%mm0K#=1j6{9Cf*zdHUWdz_bzELr8le}cFxi07Do!lUEcxW8(a5JAbK zvKBM3?$)62Nm;*+XR)VB6Iu{^0zwz3Xkw1QvMFJ4XydfUj)}nBDXIkS`TvK)q-LznPS?q6RbA=bU~Qf7i}_5VYkCF$pl?j2OshS?)@5cjQ6sQ? z3Kr85O%t`;Xl79s1BaDZ%qm&I6SEf~c|+w>NM1tKFK>eCmv?AN>3sHJ4(9|9>s9;? za^wItrG9JpFy@AjV*d8Ss2)a5xG_}g&12Cp7V~jwg?e1h$Fo8Wc{C0q5aS`v`=TV~0*%jw=RQ_wlg;-0Q z XV5bveuKJ!Q7EnX>wra6Zu#mk7DOW6* literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/core/filter/filters/UrlFilter.class b/bin/main/com/loki/lochat/core/filter/filters/UrlFilter.class new file mode 100644 index 0000000000000000000000000000000000000000..92add1d6cdee290c7cb5d8f37d2e3d7db998506a GIT binary patch literal 4978 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 delta 1295 zcmZ8f?Qc_682_F2_TIkS+io4Q!CqR*#KHBU*v4?; ztRjL6i0ttN#23`a7h?j6p&2#$MPuR@qaRI-|AKxs#`v6bO9A`goO6HY{9c~t>93o= zj5Yl9@3n6M?7-&+TmmT)TI$Ns1x@tRK~H3fYghP7wqNS{7rX`;s_ov5x}Ux#M!?( zSG7i3(ao0Y$~Bq0dnXM%B+w;2LgVSWdRibZ)o-g~j_R1qIZh!@q-k`fSenn(8IK6u zE*WjN;0|jyA_g84*dlEjCgzswtUa=1X0_e`9m*s!GX4_+V=8_VMfGO!6y59DMBnJ9 z+rZOQ^u+iaHuvIL`PLTb?V;`N77Dt8tA?g!1-hkUaMx@$U$@NBMRzoQ6XY09BO(yW z-Z~ua@tBO)^uNAI3PpW`t$7?UP!O>8+e>!ep~cxkU)r8CQACLreBaYa|CE`>5fg{; zBG=zT!s?)}dw(EJkZQ?9N%whdKoHOt~?K69g6F4t$u7`dxTj;WRRD0Kp4{4i~(ms(> zK5urh^QUyiN(-HSxBl>Lgbt1Wt(WMBU^o34Y%j;d!&QuA;$LHZs)GBkqCXyfu!^xv z1-sP8!&U57*A+}C;L$236_BjpafLit#okP!f@jp%R29=T!gCB2zOaRR|RS#pT2(en2)2!iz_y0v4K69dd+A=35+7#Ep+-N|ez>AL2yzJe#Ic zwe)u^In*0?TX)*j)i!mNU+Qiyc%UZKrjWMybz#mp{2uY#=aHIUfRcY-QO;M!aW{$8 Iy8^TN51F4$fdBvi delta 417 zcmZvX%}PRH5QZQ1m`RHwP;p@pDZ(i;EYK{CGNMf_YEz4%Qb|cWSz1O5lb|3Xq0=s- z8)zXIZn_5VpxbC|<0)Ll2lMmIJ2THbMB0%?UNNQhDU(mv>~4Lp&)a-b?iI&qQp*Q( z+EG?aGP7R{mWtV2Gh}}@J4XI3eGRJ?a|2)Pr2}LfxRgH24+#WDI_*>`e1Y-6gb%zf)|+DPb%f K!p*j8LHPlLl2s7^ diff --git a/bin/main/com/loki/lochat/core/service/CooldownServiceImpl.class b/bin/main/com/loki/lochat/core/service/CooldownServiceImpl.class deleted file mode 100644 index b8a641e1d54ca0e15504f3c0817243abf222bb3c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2358 zcma)8Yf~Fl7=BJ}3(Im12_RCr*qVew+KO!jL@RA22*oxKEMAvng>5$%lMTubaK<0t z7iaw5FMQA$rBr7ezd7TKpZpX42z7kUX0zE)FJvamIcMMVKF{;M&pG6mzrOzwzzq}> z1OzVS%f+-+erWQM-`DMQzFalZHKTf9=8g1nxoj26pOmtVd8Syg6odr&9_R;p+R{sV z>5bh7M&4Es5tu%mORt#!mP(*^&noZgRuiJel{F`A-8QZCx?Wij=**gXCEc!94S~z; zZx?;jt*y+>g~r_Bv{o+dEi7?=4@I>>L>A4GX)g%`6Um%FXt`W4R0PqdK|{BK0f7rG z68Ul|U$0h;lFdpqhbww*pX^lhqpuSJF%1ej7roL;D`@ovy zk<+WDoV$x5d*7t$D{H6A_J1v)FwLxOl$xa@=frHSKUs~CbE==LPg69RgTUFu<4HxhNi^`lWGCq4OxQYBr3X){eGLmeX zifPO!xY*{f>|xC|iW+8-7U*{yvS}8LbtJboD!1*(HDVdY?Ue|C38gtuxa=ZIs=W5>$*jY#Qfk*eLDhbwp_2~joYTY zB!-)+e7EhabR;HtZiD=W5x{`FZ8+|vuLDpy=bhu5M>ytH#%B*B?mGK`(bQw~rhY*G z{l|!Yhr!k`{*9Apn9l)(_|*99MlZKN!|{l-BKRETe3X9$p2e7({x`;UaP4a9;xW$6 zhekrvPcRw4Pe{y%N5VfN96UNfY9xG&OU58xEMAMmBV(U^@fE_Mqu>#Dh9HsSlqT^N zL@|gMafXoM8;QQcRU)|Pa`Y0vlF?OM!*v!xj$Xz*r7B2cfs8`TGKodJ!n`S}Si-A_ z;59~S+}Vgy8Fhmzy$Jk?Q3X>95`SPg#DLea>xL*9GCpqo`H9=}Oz8v>^3I9cj~S z(+Sp|;H@CGTb59r@+R1tq{DW+vBhH>_1H#TwgQ{jav`$ZE!#7c`WAz9MlOJD4&&~y z1=HiCouQRy>4mfd6>n_ypoUzi01G(5cZ~yZ-?4el1&U1%>>T6WFQH1rM^Gf%*JKcM kY$neJ_&-A0s*yhCrH?uMg<}c+GU{8VBN)ER&;as(16bDnoB#j- diff --git a/bin/main/com/loki/lochat/core/service/IgnoreServiceImpl.class b/bin/main/com/loki/lochat/core/service/IgnoreServiceImpl.class deleted file mode 100644 index ff0f8f615bc7a19659f65c2efe5851500ccdfc5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6464 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 F@jn0*YA65z diff --git a/bin/main/com/loki/lochat/core/service/MessageServiceImpl.class b/bin/main/com/loki/lochat/core/service/MessageServiceImpl.class index c62834f750030372aeb3b53b3cd6214a59c404fe..3ebdad91eaec9bdb0dd4d4d7f084d3207b2760d9 100644 GIT binary patch delta 763 zcmYL_O-~b16o%hBU$>Mgg|?Iy+gcH7J8h{TDh>*Q76k$Mk`KQtG_j-!Ni8HU2)c6J zT;1>o+z=BJ1S$z3E?t^>C>Y{l)?QIAFjIi8r~@Q4j5;BA7t4h+pYI2;@;46#aMvv?Vnjr=D7z&Fw6tRs<2oj2n6ftQ$YM&zG-kvX?VIwDAs}9A0h!~m_{6iD79hu#46s1d@A*R&iRw zd`(J_l(eNJN$HyO-p)8n)la@a%O(^A;6?}r!pI_mF1n7^BGHX=^hM$5!Fd9*P8V>| zM(o9-xDrW}AeyQeu{>CC^J=Qg z3m0#Cp{%k>#7Gs!tDZgk4~SJByozroDqect>+ki~zxR5cIk#L@pMJgH0x*C(91=rB z{9rX=TW-kr4+)17$v>H|ET5ZC7ko|$F zGBDI?;IMPlGjxbq$>aL3-hl>qI2y%w)+u|@BwD3#k9o+aVGk-eS{PgliKRs3K`y-* z8OUZ6k4Dn>a}G4aW!CN2PzDDl=cmk20;MZDDjaBsVR8f+#{Cv&nRqO9dcrus5n-s< zH8uS-97L!1Ecujf2Homhk?WL042Fs9jb$>6$;`uhe|mSI3%#a&AH!tH-fsU}BUYr4 zcp)c6pR!_$VUXjL_@Ef_X`B(;O41(2u!gfZC*o?mZxrJkV+_t+p5a_7o5*Fd8qVW_ zc&_@`q$sK(`vfj&n8LL9sZQ8O5!Y}TSH!sOi*k#hUM$+`WsZ3fo zXinu6OVB8%v05=B{3=rRQuYceWctqN8*u8zI%@Qwv+gb2MbxchpT3Fa_y$_n(Wbva z+a}uML8sr6{l*6L0zwj|OHvm}%RQFXL)zhz_S(8KN^28=fnHrfBcXXv3opH8GmfGK z(`dyJQhg;D+B-%WVOh;ED>{yTYJ}kg{ir4QB30W+iyFaqI5|cb%WVY=F+|mu@VeCk zMi`2ih$?Qy2o-Qq#wt{K%~WO>R(HNCYjj+OiPvCRb`rj2vgjPh=tZolcKBs zp+#{O*9gSiyN(-#W`v3p{ diff --git a/bin/main/com/loki/lochat/core/service/MessagingServiceImpl.class b/bin/main/com/loki/lochat/core/service/MessagingServiceImpl.class new file mode 100644 index 0000000000000000000000000000000000000000..88563d1682e62b0fb3b84f9b4f299ef37f9ac7ca GIT binary patch literal 9899 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

SL)ie{Ar4BrrE5HW6T@ zD1H2_kbc?2@7a9M@HZ=YapXQSYQKIzAP41;JjfBV*!z$?ERP^2t-OJJG+Zr@VYWPh G*#833ZP5b& literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/core/service/MuteServiceImpl$1.class b/bin/main/com/loki/lochat/core/service/MuteServiceImpl$1.class deleted file mode 100644 index 14764f53013ab526ac62787a72eb3fea2284062a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 754 zcmb7CO>fgc5Ph4tabpY(r9k-N(Ry8ppTz+o zao`8=U-%1%S<@3pNbzcC-dOK>MsNJ%*SGHgUgL3!j=+B5Drww>=1?qEkcIOkYx0-6 zAUSOU&Hmt%YGsC_6p6sos41N*4au^0R(kqis0ecYX+?8)K~|s_mbxAuaD&M@9mcFY z)K-ThfzjT2(r0G^$+25d9|G5MY@nB+i|aX3+z{yQMZ(rZTRLs3bMkX_Zb)Ez;tFNX zl-IGodYQ!W1rFAlU^wFOvzcprLGN_@_Re1h?tfI5N}xZ}rB$KvOsU!y|8?_l5@oUp z+Q_L|U6n@JQqF?cwjBR6=0XJ}EB1mFyu&wKj*s}T0dH+#T+ON}g{51hxF_&(ogL+P z5dBPyQwxFo#9H#lM%6X3W1FW`hxe`yc*0xg@}DLKgSd)__@>z8+-LL(9X>$z{KXdz zUy*<2s)H?#y_WkE1IBraGu*^BR~xw1vUYI0&3AB@bAnym$3sRLvtyt5JmTBI?r;7E B%sT)8 diff --git a/bin/main/com/loki/lochat/core/service/MuteServiceImpl.class b/bin/main/com/loki/lochat/core/service/MuteServiceImpl.class index 9797d648bab2f179fb55ff00a8f68fb2fef72db2..8a9dabe0e890f2189a915d89330f6050950eab7c 100644 GIT binary patch literal 14096 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 literal 14154 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 diff --git a/bin/main/com/loki/lochat/core/service/PMServiceImpl.class b/bin/main/com/loki/lochat/core/service/PMServiceImpl.class deleted file mode 100644 index 874241e670293f29ba582a0384f4a035ab4158b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1535 zcmah}TTc@~6#k~IU1=9uS^+PhAcAc{R=lD`P(dTHfC++;w_zRW;&!*$-8SJr`Jjmg z(Zu-dk20Q}4sB^`eAt;eXU_S~cP_KP|9t-m;0YdUh%gjPw_@1t2a6W7%mc%8Jz@C5 zt6QcpUafC6PHUB_ts%ycKHzn3*xcDSHunyM8EA+zOm(90s?`lb-ZuAx71yZ?&*y>V zIt;0jM63mtZLITZk)dzP+IM(R^8~~E>D$FNaeI4hwb&^yk$-&Aa;#v9A)3qYFvM2e zcOro(QaX~*H4HLLw_=*EW7a%RIDtXL)bR!P%S0xDG*W$FIHyCy*^sQ|h>cohPk68S zo=pTZCD-Kk4)?53R?1jVwtR-!QfFEIU0mXw^+izXs?TJuGsjLjzmvo$E^8QL=x-%P z{B>NxIMtzA3m6s@P+RX*`Pw!4270@SDq6Mqq421Gow4aI;NHF<{4A~A^ryS%s|d2; zo7Hd#xJ}ug(35)E+g^rU1F%#Klh5hMqd=KTCMPl5ljxC`hFOMzR>8uc3C!W927?;g zx;SB}V;;8{67Ktrnr%1wpX*W8Uc@QGj3+8?U7Q~OYBx zAN@fhNJt&MDbnFine8Y`Tq>LbB9hV>@GRSblSg|EH6@=k7?kL`?3Q|{)cO%`j rN9kWQ)`qAz5%nhG0v;&DWrdhjhz}$9*g diff --git a/bin/main/com/loki/lochat/core/service/PlayerDataServiceImpl.class b/bin/main/com/loki/lochat/core/service/PlayerDataServiceImpl.class deleted file mode 100644 index ac73544a66b08f6bb2e7a193c2bddd0f2708d0a1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6090 zcmb_g349z?9sXXj$!s>$Hr*z@=#ie}XtHf-IoeuC6AFz}5 z5EQ|SdgB3#RqBBn5mAdIJrFz!9*E+7E8dE?qM``s_ufphlQf0m&;EX$_x2tCNYBb!ca>$%cQdK$_FnkG^+a@MJd36$sTLfXm-Ea`CEbfTxw-*0&de=(sr zw3E$tb!ff7w4~$MDd*~JAPpIIJR8T>@yAJFYEEg?Y3B1r+RT$=hhkIkEIZL@sl(YfIaD|F=S+!^x)CZN@446~^7wa+!e26wmPtyC``@-ZQ-XX3M6WQ+*DWlaRkDM48%@ z3WRVa?_Miia1GCLvWZ@vOzbf-c58T-Q z=INM?`2wYN_1zj43M?<0A_Nn~3lO{5$oJ7kF)YBm3J5ILQ3+kc(o!)V7X*_ zCgrGWukV&*>UB&*m7F!|nC{P-bj1BxLdOh$wn~SFs62O;jtayytQF9e%mMH*oP+Z; zoGY*>1pDN`=~xGXJtb{=GQKv`u}zn8RaZaWmQlPwU`C2Z64Itt-m{YV=D~~|!y23~ zb-94y6S&x@1NbDAUv_z}MbGqA-K3)$H8HBtrsF~he#CZ+lnetaYi=!MdQ5kx(IZ_d z-XT4)+i)%UJ6Mc(eHMde%}GXo?yMnobxod21uiVGEF2gU<7rIp7&@^{!&Z8^!g?&) zbZo~Z0@{FK7tC$FM2Qv+X|6&l&Ro}}V<&boD2!ar9;9(bF;-FETgWC=Ky0!$2h;VH z*o{3JE~U&xpQP*Q*o%E6lrh~jG1?s{5K4pLl;9`EVC9s&!XgJZq?=x*<8th$Wvpz{ zH8V^YE!osXCesPyr9B1%New;Y60r}Njua?cw2#puLrq=S5=EMk?WZPr+0t<(`nhCY zRWNMYv}O`r>i0&G5h%%JqHw6TN=_QPb)Few8U^wize+MSZ)p$Td0!aV=wxcec50X0K7OiO0Ecq-`2*vGz&Nny6`gcO_niS7>-S)5i0tK{{TE>*>lgsWV`X7N|HQ z@DiEwCW&tNUMKY@(Fo;$cT2o0YM=K8Dxg^%`C$uwbI(%9PASma&T!QO6tb zM%t7Twd4o0Nr82zRT7GJALFSqe_;T@|B)uZIX1uTvd zoY3(A9u%0BG5Q%h*`(p^wmfFJEK6h7qi-#r#0KHW4~!eb=kY}iUm&c-{61c89S`A4 zbdswLH%o0MB*=h{^lCwTpK_d#new|jzK8EKQ~PP=x#QWtEYDm&tsMWV!nN$`M2Rj+MMS30 z0=tZ|x^|}3Y2`xjRT|b<^>HCZ)bB70wNNF>t1zE76EPi+hl=aJ2$_&*?~3)~v!KMtG!8{&b1_JtIiB+gGpPfUr5YWB|iIvn}LysUP!L4Quw zdQPp3y4vC+VO_0=ikSj)JSW$bQ}5n=CUsl&EZNC74rld zCSeuiwOXI7VwxvEhW?gp)^uA*ByZ+5v5=CUg!^Q^tBFNyN1pvCGmh`*Sj5mQ<^M9i z!(`5ivdfRlH<=pwDCFK0&PywMj*b-wM?$d{cO`L0tVpF|u>WWT{tHXpMwhu^vU z^&okaa@^l=1ZOndiv@d*pmrFGI9;-*;s}-vqb@XW2+dc7=FJU_!&up{Vi>Erx@J$~ z2+odgID(DCXlWQh>tQ9pW`3LbrUjNVr_ZI}^Kb^{<4nGI&%#2Sk7a1Xa%|zsUexh{ zP>+jvy4DvF7jsAc+pi!IY9>J`p)&1wVW86Eq#fbjfrfjqs4;dF9mlZ)IEL;Bc5|(H z1TVUD#h$GrxZ+MpEye$(!;h8T&T%m?K@cdnnRe$DbQ-J2|UiLpgn~)4Mt^UWi0k2=?WAu5CE`+>X-0T4MkYgSU~`e z;p$R0BAp|6@m<^~QR0*vz-Sdkacwv6DOA6 z{l36Y+8NWJJ4g7Jff%;Z{X5yIZ(}fQXNYVgZkG_D9So04*iv;N&FBAh9B;rbqSVc{ zV>b@rQXFOwNmPB~-W{NNh=1>S7+)q=iDd;!p1^Gy?wdjQ*a`%2MhUWX)N?&R01+5q7eB( z2$6l%ZxoT`loPmusl#C8^e}dk{PRwVc$nCSUhbr+jit2RNYN?ym=6{3!ytkR_>uCV zO5Xc1V<|#f=i^bXm61ZVLM2i}<#M7@Lsa;8Bp($rkx7}^D{wMe^(O~0MH)4}p>zbl zXyCNvD1I}HTP8ZZO?DZk>nxLRj^?~d2`dx1%!YnulLjG&*<{KWW59Gj$I)me!{2w% zdB`=&;ZM7ZT&D6SD14NvS2Gn94PO&bkFsR^m8HNpd{hQewT3lSC9<6en609eGR8HA z?g;)S$%m6gTS#avAX|!ms8dhipB&dwfd8PB&xSIgsaaH13SICo0Ahyv)~N5S(07iS SpCRV+zHD$7h+452bN&m|XVaC7qJT6lMG-|s5flOM1Mvd9P*G7V>i4~w-JMNStbf?gCp&N6%=;bR z-}m?Xy*GXGUxyzB&>>c6C=zInBx2!cV#wl8WSe1!BZ-t5PMfJ=D`JMvjT$3ns@J*e ziY22O`~nr*jbS4kHR6NewOhBF5nDrvK>b)rM$#HzkU&W?ni;g>0&{v2slo8p%+Qc! zhn>l=lHN@f=cp&KG?pD>nGCx0u>XJ%K0^BI&BP^Qm1PLQR+-&;5DU;CO=+ZUb=~!SQecyga#YAsSjQx1v`9zD8J(n~(ixqsLqkBrDFV8(-bFQt zrD)gCCNRr`>G-JVI1Q%@Oc*rn6^3oBA|cHbGor4(Fi-(37pUy#D`6QhD{Whmbn{3o z8pINGNDs~=`Ca~Gqw9!JwCuH07K!ONxI#x2s)KZJm5#Gy82r(M(J!;Wiks(TVq4AB zI%BKkOQ=V(xX(yg^4^{F+uJPix20!nhQ4*KlQFuLUK`J*o#IVC>|-;@HMy&Pv$S-r zj&pGy2~4E20?3EF(**H!2eb_(}QfA&5PaSG^qu`y=u!&YE zF=|@oV-Q<#frhsgl(OEDv~9+8T!@PVCb=|TXT{7lRx~Q{Blo+5*a$<%R{2h8l%o2K zXvQpNLFGT+%c`=;l1a%PpA=M#nmv+KG0I!xg2qrr+~U@2GN1aXPIOU4c9!>nsFAj% zJvrGqe;6svj2g+bNm7Vzfysq5pjwv-6MAhUGDIld`zk_tz8N!E7~+H3IOVZb5jkd{ zyMoAIhlb$-fY%uIHXS=LLd0UmPL`~?uC97IQ8H1)V~Jri4@l)v*4Fn) zjaTcqMpClGSs<{|t2CR%9x4#}<7h_#Tu+%!zX{?-+@#^Xg=AIrLC5>>e!gg4k};yB zNVQja*1yfnq7+G$>UUdAh*aIJ;{&*ru`!a#C?hp1TfkJr2Qu-9Y9(h{tK4C`^!9cg zcVG`A!9qfa$;1M8U5y@Z#}nj@oV!An^}BT3EwifJibn`0`)EhJ|18s%?tf6nhwx#h zz&3Uj*^*Q;b_H;cKv6Olz{lu`>fd}v?qb%dM4Lo%J3E+sa~&s;-O{yB>9|)?N9UTg zsTJmck%^K+<&mgqq#Pl#;LFnXX&vvul_8Gq*YN-zq%o-s_c9ym{?`KQK}uyn0jtl? z#N)Dvw+HbMMl~D|m^rq9%V?#;>_V2YoTiMj$h(e0zpFMCWk;(R-CGs zF18^Q;Uyg};}wA^F=L2DF&;7O4VKMJjB|4nx5*LiD35At-b8nW1@T+_Uc>JQk$PQK zn?k#F`~j~r;5&?z>`7yaTDGEcZz>2?wWGDrHFm@5q?t-{(=|5xt6VdcUN_~4?v41X z>@j~E<42YSN4pl{miVO*{PL$*{*=fc4KFc>nT*9QTpV$Z2>vA7A!m~*bC`QSA#_nB ze6nWmq=pF}Fov z@ljBFU{oF3%MUfdm=5ue+lj24MXWiZM#rD=8oAo5Fp(w@7bmoyqiM5b6r1FkiG-cD zQ$}))X>UvPr-NdKsMW+woNaij_b_DMVvU_h zHYL@UfOIp`ObDNR!!bH6Jd zH&dMyk~Y(tID?iRh5LBK(*#eGGv4}DmK=wrn8l9m<6kj?JS6i-%>7_Up3XT}#&Hqk z;hAUjf_t9S3+{R3R_l1+R#zUk)s=^Bb>)eb*CM$w;I*7Mr6n*>Dgi zHavnEn+~G(0A}TfbMnJ^94^>YdJy#ou+TGZ_KaIRRz-|yScm3~MYCle(gObsnO|1ZXgXhj1~R(7oBEH&ipSOOCR z)Gy}-u}xXI1Qxb4(i&w9F?xPVnT;r7l-of6Prj9PuM8sR9-X=rZ+Bf=PuG0(zf!r@$S@tkWe?*DA2#I46IAf7 zWRk2>*48>pa@JPntgUpd74pR3s_5lvNuntY4`CLWaa9pEa5%>u=G9D8V!N4>XOjy( z9{x?qshZ-dDw1DYDAvi}YgLx@xr(YAL)VSshC2|H8uvky+U}uIc#=@WgNE1p#;wHXogCTJ{F=4Oz(|Uyoprb%rI}^_kjyMG24~%bXP8ByYO+9 zKr#&~pdG;(8g48rD+>nSpj?4{_=FqDF1juwSxpn>HI^!-e9|ZLoSA(e9~5~2HjV+X zleYrx@wWYiFBE(0n4UM&j@fqL{KAQ)?sT>28vyDu!Sj)CE zyQ3b*TXK%KxQ?Gdjy+76id3*kiX#Ziv_H(MsH(Q;p!?F@2U$uVq3%aRk2$}kqxcd> zi+TN;K@>*ulzQ-t^XoodIQH?g>hZJg6{jzbw7al20jZz>LzaoxYjK0jqG$GL?>$g12(=NBLDyZ literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/core/service/PunishmentServiceImpl$1.class b/bin/main/com/loki/lochat/core/service/PunishmentServiceImpl$1.class new file mode 100644 index 0000000000000000000000000000000000000000..7ae9a8c9f3e1b877eccf60e9f525319465568516 GIT binary patch literal 893 zcmbVK+iKfD5It)vR;;SEQd#Dy`LH7$_W`i{GB1sQ>sFO}=R zV4A`FJo|k9KpEu^1zv3|(qQvUAUd*RYD3^giY2rXG;uRUj9UWDjZn}TC_|^S$vIUc zd9F#IH?Xj$1gMI!{UP^)XP!&e{S;d9A zru*-d4;E4M7r&8HS6aV+$Raz(CfC*}Q@kTz+Hs5r0x#!ZU5bZcz>&6cERdcUL)DR% z&XJ4pMBsS-mi>Pv^QNv#E}xR~Z>{rq#%pi#&n~*%u={}ci?PhU&1e@5et~3b`zxDo zNWXB@K!U3AgL2JGfi>d$`X&!YbDAh*6I_1fK9b=Im1>tR(bZ F{RP}p0Eqwq literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/core/service/PunishmentServiceImpl$PunishmentSnapshot.class b/bin/main/com/loki/lochat/core/service/PunishmentServiceImpl$PunishmentSnapshot.class new file mode 100644 index 0000000000000000000000000000000000000000..8828fc96f28adcf6bfbb57d16a9c3dc92ab44a18 GIT binary patch literal 840 zcmbVK%Wl&^6g`uqabpZg!@Gs@5DVynk=Q1x1XZDtLU{zWLaZjvD4t@EqnU9LpTz=I zg0>&PMYge>3J*yr4P?tJ_H@~ literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/core/service/PunishmentServiceImpl.class b/bin/main/com/loki/lochat/core/service/PunishmentServiceImpl.class new file mode 100644 index 0000000000000000000000000000000000000000..a60b788854ffccd26bd45ac1c9a25c869b086912 GIT binary patch literal 11201 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 eKOjH9!9V05;V5Yyj<4D^Ztp6VO@QLyO literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/core/service/SpyServiceImpl.class b/bin/main/com/loki/lochat/core/service/SpyServiceImpl.class deleted file mode 100644 index 7511ba9cccaac380f15df8a4d802fbe2e7a16e94..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3874 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 Cw8-!P literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/data/model/BanRecord.class b/bin/main/com/loki/lochat/data/model/BanRecord.class new file mode 100644 index 0000000000000000000000000000000000000000..161ffa46fb75780f02eadfc96124c93d50ff30a1 GIT binary patch literal 1070 zcma)4%T5$Q6g@T5J;O9Tycq{j-i**Z+Q(931RoIuBo44}SJPdAf_{*mZX^G}M*RU7 zY)l+AF8lyL%6O}0f`}W^Nu66&x6Zle-v0Ib$4>yOxK~7uVb+U7H;9iVE#4tdU7x4i z4P##f?i!C?3olOmA`FJUBmS1V0gn#cSB)d#r3^-vNuO>u^;MS2z}?$hfBJyIItlox zNVa(>7zWz+`e{8)WOP6og+%aX95Hk?coYeL?UcdHBI00R8w@3n5>nx>l1|~FjAZ(V zAwR#cPqI&_JPUaY*yuqoL+_@H#C8@oM6$~pftHLt9_;f(s(t6sNDpO`p|*J)&s5^e z`K!kMH*%emjg5u<5*S8om?)@d%tjGiD!OH()Q%=>SnX)aMmKDAJ7c2^o1t4acSI8M zNJP{Q>cSg_ay{kV@fJVnxGTx#vv(&l5xzw)7i`R8&cY&=i&*++diB$0Dnc78a2fi& zEJ=uUSB7Fs27zi2@zVgTdYmPmcrH~xdoKQk6*X&2kwl(;qXHp$qKGL+8p)4Fa-)&F zXe1|u24&LFXziojeL_ZPI^WR$nSvY!X|)J}eo7v|5Jh%73B#z+P7&OoRS|rmGD?Is zMc^#9P<57C73u~r4x)+?`X(@nIgDxL z7i5drJIX3jc2&tWZCKS=QzO*vsHqVWr#e%%O&uxYMS4R6=~Vq#w*e?SUocrZ`vB88 z&!1CrUfXXAj9;=|?i5hdIjoy_ZeOtNbl9rSSIobMnLqyyM=_s$yh7aW>onCnbBVlq QiQGW0q`G~FgmSq12ZF)4djJ3c literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/data/model/WarnEntry.class b/bin/main/com/loki/lochat/data/model/WarnEntry.class new file mode 100644 index 0000000000000000000000000000000000000000..838af0529d8ee48d09099997b9afb5d08f52608c GIT binary patch literal 635 zcma)&F;5#o5QX2^XB;~RHYPx@LjWm?@JVn5=|D&bkpfvsLF7nu>*E9q=R0Y=L`s>T zL}4MN$PeI0MR{{rP|&2Ad9yqF&Afg1`}+@oBW%`D5qwCBahw%5mLa(^KE5#C#N*;3 z&EoII#QO04NL6Mkj zXq?r0b{P08I}v;w{Er(h)89UQx#P=|^Zu{_!D@&a=2WePsNwP1>Fv|O9w-7~gZwzi0E)7>IB o=n%MsE~Ae5#NCOSjZvjubabzDqYiueFC?f^M>XERXH~(*KPdWey#N3J literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/gradient/GradientModule.class b/bin/main/com/loki/lochat/gradient/GradientModule.class index 2b0936985c8865481368c0d1fa90ae4acd996fde..486569bb5a2bfebcb27a498c321cbaa560691b0c 100644 GIT binary patch literal 6635 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 literal 9162 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

+qYbnLlTY{DSHA7Srl&R;7zL!{DkT_d0#V zI-fV_dDoz|Y^_R)Odw*2J=@7#^Duw=loTl=%2d_LQNT@+@~U5AyH1hU--fyjJ5)Dn{~zy>|40A; diff --git a/bin/main/com/loki/lochat/gradient/commands/GradientAdminCommand.class b/bin/main/com/loki/lochat/gradient/commands/GradientAdminCommand.class index 338bf99fd2d3d79a0fcaa5f13d904b7d10e3c57c..be47dc5f67c089670d9c120f7a223be204f78299 100644 GIT binary patch literal 11330 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 diff --git a/bin/main/com/loki/lochat/gradient/commands/GradientColorCommand.class b/bin/main/com/loki/lochat/gradient/commands/GradientColorCommand.class index 0bff940e60f2f40744d611cacffaf374852fa6ad..43f9bf14b26afbd18de97c8e891df4304fd3a7eb 100644 GIT binary patch delta 35 pcmaEs`8;z&y0Dmha(=FUPJVVKkVwu*EYUA5$;>IVWVTSRT#&iOXvMQ~v}36Id4U(O zi(rd~5{(_9dEq4lw|vf!W14Bj9=Y66VA&_|BKFJs922}R6R5H zanOr{kvZWWv{SwJXnJ5u}k- zZgiDc?1C~#$TwWImP|noa!P0R9Tu0MY|4?3WuHbKc_h2WmPh#-c?qNCQM*?z%k{{H zoJ?Z@70OF;rqmS+8l-r$wFTo7(rj)ZpP<1?ni)!ZQB5yi^}8Y-xi%H+|UAsKEe z7pSEQ!ozZer$DxoLymMWxA+BBQ9wTHKGRYyXgH0K$K9ot(*@Pg8M53nEl@`zH9C_p zs5#bN9f>Un^P{;fn5gbdgd_3lb7GNja0V|0jiS-=Vb8bjdd4z8xHzb5R5x&ztTah( z%Jo|MX|ka6)MOtE)MXhv+s^@Tfh*L|m zIkhxLZp#Z6vC}w+6)ms_yP_$)AC>{^CvWX^I zC}d;DKx!mRkIhy8>3np5o^3X-NOV3$;!ALnaJ74b1$de!Bdu>;#-4%pz zdbiP9M^_8_9bL1vqu?R3+$3lf-7JfW{FYk;-AYnUEgEaPjqcFsc0#WFs3?~!@J{OH z&Y2gEBtjj8k>zqHm*ne3Wwp1_U4rhWGTu$ZR2nmABCQh`u0-Y_=RM8@M}qBhTY^Jt z>~udpsL=y*hZtd^hltCpc&(98B)60lh>DqZJV1{M`Xlwo!^J_#+i1JS z3|a+{wd3c1>K7zTYlrD>|H_Y=@FK_BQNz8IX+N&M+7}a z8yyzL(PdKU=@?oo}?g03*yix5vrjWmOHPBsNw014(T4 zn*6S9Nz5_z@TL(p{49fwpHq;9smMnoe7FD=mD`$W0RQWckLL! zV$iX8bkG+wmP_Elr6|N~#xh6uU70e1iKu5|Y!0h&3%gLORk)d5W-+1^GJ~Q&?f&I7 z74^DPX*jjA>$!mOCyeYKK#^jk5_Y!Q!uCRJv6UHVV-#Wb6JbW$8BG+U5yJ#59AJg@ z>wiF2{{yo6ACT3gk>#_Mu3seWngP^?dtp_1p+K6dsj2tgQ6|?*RlRs+l-X-e;?*SH z=!OXRT~R%R$5EohZzb_wN_-?O9-0<^yq2{m_+6jra<55=zeuV6B_(3P-@IHf-#myC zw);=>Gg#DQbbU9y$)t;W@t^5Q9G5qh=XP;SZ3e7GZl$01$l$dk$($tHT9{2eN~Q#- z)M{>R9UPT@x0a+VeeWr9H>rU9j}y<~Bo%cdTfIxVk)hs0*7LOSn%&wqI(3v)51rVa zwJAx%F6^VzfL^LKb)78Y6)52pB$%{LPTOL5u>?c0l*zsv0bI%HT88oH;uNjm(q4%s zT#bvE>=3Twq+ZVny%AU7Cfv>myOYc6QAi}Q2K#Uu_Os+LNBJr4!sl3ruW%2(!M&8x zh5N{Xb(Dkq$&Uy0TDUlswSiioz(6hVDpjRcD%HEjh%73kU(s1y*Ivq@G3?&Vj#uiH zVqurFc>a!q#`e8*HjU@tRG(c2+xG}?lWAS(*RaUraaA0{2yVP%D4-J<0;>knFjJ!m zN=sMbT^^Erv*DkHCVe;W1Y7$hUCYmOHJZrxKfWoXyXs2rw=t@#Oa0k&j$Q}dFdKWS zl5}o2G6PBabvHEQdT1<}dNNUVJ_oEvCLWO=R{DG@ekCbQ_lJtm0%K;@1Y&>cl2YQ& znLj;TXYQpLzEGX!(LClgcVoE6?9oCwG`+pfmTnOS@|#q(X4A!7t{z)2U8-u;*z==t zR}76^^e~x^!GVoDp*CSKHgk1s;iPSq>-@fPL8e`SI)~;`Gmj4|#!w5d%zUDbLcG%W z%wU?wE1RAaX8brNnoNvYU7=3+JzcG9k;#zeZ>NX>#-3CW$Nby5V34Hvn4-e_G4v$# zr328fVCaSEF8c}c!Hfgg#>1%>*-V$4%czXUub*e_dDwvl9(rm>rv{~5Q|0MW^9yE(t%#9^jG!Km3p70MV0l?r&As%8&8(Z6%=vrD>?#s}RWIFlG6KfHJG#G-N z5AD@g`{*TlSzoUDaP-qKfZ(|h#3zWR`k(#P}(mjPUCIFGLwpN{j=XY>Vq H3Ge>_wn!Om delta 3852 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 I;GbCkAJa+2umAu6 literal 5279 zcmbVP`Fj)B6+L4dYb+07#J<)n0oz1Y3JIh&B`r>HOk{9kho(+wGLpvf;L(hbMh1t{ zW}*9@uIaw7NlQvF2`zNr(zKuXU;1;up7-8pG_ptVwZB+1@7;UucJ967H~(|(27veA zUkS8n7|462jN{GN{F9$H{YN}~SeaI_VNi0LR z0RyWvEbHw%*$EA+4cvi_B-Ubm0_!wvzipfbHejQM&Z6blz1`p27xhE=ew%=W@y!N0 zp(pvY)xb94R?_jzLLGKx)^@GqwNlQiOqe;5iB#6ho6bqIVvG0Su-%`ws~Yak{%NNd>83g18mce8M| zJLPFu+p8Rr>>T$i^kY!ewWDH*jEMmD8@NZL&`?M9B(_)0Syp~64jQ;mrdS-*Z#(Vk zgRaPBK~iE;$00UCy;%k zo22KI8a2-id5%|MK}pdO9Y@L0(4UfdE}fUx!lj1{jNllPK)n%{?HY$X(l-(yi|!K0 zI!2o?Z8yLakz4*)#m-w}Rz(Wy7-Nk>`(<&LE?X6)!Pb-t)|7ots_7VKts>&$CM~rz zthcLU6>)IFwI=M6HCC%|G*vAfEadjZN}g+_eM+Uvftd16dNssU7QC~rj`tBZ)RdW_ zXX;YJs?fQH!jsM6NdFodDQd6+%FV3h7X4`*Pifc`+7J~e%P7*0yw|WYG;`FeluXuP zO}%fTbk&ik?I|hZlfMH2j5-nZvwiSr8K!l@piJ!8N@ zNj0$Q5DoXmP%WmBmLBO~)2$1mZgN$_@^RnH&y1So`dG57!@)i|;f~4d=%DwTLxB22 z+H-^S@iwZJt>tG(x>7(99kUwltZNO_AB4I?Niwih4T09!RrWJ;TJvh5BRb|d!0L+J zhjoE;{fu?8tB+N!tW`AgbB`8ts(d;=pkZ5GXrNZLSZN`Vt`u`=RV_vQao?+$MQdWN zOb&l2O8lz2N&E8ZIgT;0_Lw+QW&Dg|`jIxf}PEhk)-H_ne zXAFE6p9|uUQ&^be8j0aqbaezyt*%di(0hxc`wRF|0$*gjauq~c#=w_hlIXsd_0Do2 zaaM?3`BeiS#mBnvHGCt1ud@`56(Z~y_$I!^p;+}RmdSqD+!Vb!&9FguIhf)n({~Jf z7vE!vd`pC~?>3=K)!e-NkRi_tcHZ=@!7ls&KT6<-oJ9>+nhY8EF@D0vHr=^V)6Y+H z-UVi4&0;XcKIl*-5Nm&NYFObeM!j48Ncb25Ry; z9lWMp3UGGS)!4}*c{*NY6pK`RGnaN`opr}7~2AT@|A=525R&`g_^JZ%0 z!AKzFlK32noHQwl@$mm$DwDU?4Z2KrO<;D&bewUHn8B4D`1PU{qCV`I?r!`+^wOUV z{0f&T_@{2Md5jU)WurM}wQ5xpc#|`DNh0IYk-%G=nYR`tpH=R#oragO^V*RTAK!dx z6JMe7m*2zkH;FF(Zlt7v<@~#XZ4Gb;BHf3`x@|@8jm|0=qW}3VkU@{qQ6_QGh$&| zXyM+_!U5VAp6klOfd*DmW^NC$dLtNmi17}*lbRyS_cA7d(M61%sloPnyz6DYwn3y; zqzc#@f#?b$hVVcDaUg&&B*cT=iFph!#>hl4j1c4D5Tn_REZtZFu|ERQaN~Fg(d@>F z?tL*B2O}5_Hy#Nw4hEqtm!UkmO8a*V$Y2Cyc^Jz?iqlFzPsCf$}v>Zx8E<2-nT|A9|LZ0rhBUY4@j2AVn3BKMWJ+&><{YUJK;0a1&C zcrq5^Yzv5UaS%_(LOjy~;(Q!LAr|67nD}NAeK7T`NH&ue?d|CZMuSApx4?KI_2C!{ zCl-SrZM5`pY3~zq)ZD{&jqgcW@K4U;(~G?+N05?XXMG;UfJy}-h*h$Qi{Qmku7R(- zftNJgbq(JJ-oW?U(3m@Focdf2HCmtLa;Zu59)%E~6AM4Z&xn5qm&VWeBz$~1EW@dQ zygTUgrqnBvyerhREQt)|m3noe)P083JJRd`pHYg%*eN#Zi}QG`kv28*XCp|RxGEF046jqAjd{MtU$OUo YPcQz6zu>R<8&AXs#^3P|{1Y4h3;DRxRR910 diff --git a/bin/main/com/loki/lochat/gradient/gui/GradientConfirmGUI.class b/bin/main/com/loki/lochat/gradient/gui/GradientConfirmGUI.class index 468db56b30d79a01abc5d7633d5138dbbf4c01ea..7389fa6a87752ef1d0db7fd533c29c55d0b5661d 100644 GIT binary patch delta 1221 zcmaJ=TTGi}7=E4>{=Y4MfKgg3-N>M{FiHgm<4^{L;W+JJckl>1AQl9QV1cpG=rGw)ihpt>E)uA#w(IVy)eEl3&|4W#s7WJm*?=kZ@%~cKKwA8 z`O`nk4*@ju3mqn+Oyz53_e0j~b>Uc~Z{X;@NbJZ!Z-m%fJoDI{Kel=-dxosHv4WL4 zHxti`-BtOYDuY$rLS*$v#&=Hi4uvDJ;jz^i;&8EQO07~u_8fK4;k1x725VWTY8)Z0 z-r!a?sFRKsYlzJ{o1WgHzH~Uu2Dhod9EJHW7~IY}B5iCuHZa;U5*>++Wr1un@PO?^ ztvZwARfineIy<>j=MFXLEHm%oZnfato!*uOGuvzoaj(IB%E)nPA--hrW#!Lt7l!z% z!Pjt!$dKAsOKu&B4fl-OB&^F|HxDch=47*ZfPjTVgng^0xT`tP+7sw257ZqvQGIGt zqlLpniqBJ6VZ#bX42~uWv&AB#(VpH&b2MsUOnvv<-js2J6UyP*mU+rRCvwy9gr^Oj z;d_hyuE*4_@w!f>cDeohSRHfEr_We;OOm-U9JX*ydDj=F%oAQU>Go ze>Ti)ZRJI0JN}T0E&80oUuuF@xU=ERz1gH*bfk&tcEHQ(a zgMZZ%jI(hTc}Yb}P%j@#UT zJ1oLoR$!i$xQQ(?+}*1^qYdqHvN6Dea6;ab#XKa)glu|v7%~Rf#B}!XjasBg0gLSA zn@E*HMi>#HCMDR}CrFnw($8@w{W|D$|I90_Hfx7R*ax`fLne2p3N67HL^#3Smux3m3S z3`zn}!K2BptSII&{3iEipn%8mhcrc=KWQzF7ga;igj(@A z)sIC*d#;Gu#A#lYJy(YIhGgGNuCvdqx0Ae?X~N%Wa`7%d;XSzE!3KV&S^2r}iDuIN E1xz#sMF0Q* delta 1253 zcmaJ=TT`1=6kUtJ_a*RIDj~rTL?wh!LJG8$(r_yv7X!t#)@xfSh0p?lU;-73h+=EC zHws5>p)Fc3b$p}Z#KJ?zna-f2Z~g*i93Oq^jL!JrxI>YdzVzXoz0T~l*4gKreeMQ- z3~v49-<7)n>iLb1B%(w;qm|sQvt&NrKH5DH3`GZHt&zw8QCT>%=JcEp94l#wcJ=gy zhDIKuRHd?vJ9X|L%EiRd_6V-Qa%K>gzF5;x*RgP@C!wt{xSN$k`ti_6efLP;$xtvF z>g^jNwyU*Fr;6HaX3}r4iq$G$tJ7)?)^V?zwl!KB_^i&x1oNhCyUAdaGVJ-eEe2az zO{B#}qJ6`Sk#Hm$%K#ZLaEEQG!(OB9iVFG3j5!Px~;h}n8bC<8T)VJrz zc*VrFS~G`<ce3(N7Zjn97ulC;Fv0OG;Dp>Kmgfk z_={5p-{mie(%+ECAb8dETYTxuWk;6=(4(rZ-TWrE{3tSV*XebC+LpXsZ}>XmRGv zBUF?Cd;FSj73IEVrW5n8ThaG5!nr5jgSfH`_F&q7?|4qRt7Zm<~(Y{eDqlIB1B;9AgvR#_!pz6*9pC$fge z1SKI;8nYYH7}!P^dl;%jvJ~($dwCowQb>d+gs4dgR`v-6PEdwnsoqoLOgPB<8 zkmq<_{ZQaniyphuJv(_xRe45D)4Z&fJpTPN!cO8WmBei6>pAhBU!Si(G%u{@O-z&g aXL}v|oL}%OIN-vQ{93c{mgEzMN&62wCJ{pb diff --git a/bin/main/com/loki/lochat/gradient/hooks/GradientLuckPermsHook.class b/bin/main/com/loki/lochat/gradient/hooks/GradientLuckPermsHook.class deleted file mode 100644 index 136622e4ebc5e03d1a95cf87db221c62c15e3589..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5908 zcmcIoXLJm#Ew%*!3iWFBN5}01QN$dr1ai>q~LFRByj?U5UzPaz-?|!$v zH&6X%=sbW;__v0Dz{*H6Zp4y(HvdI>O~>d-n^D_JI7V+W*_Sc4dq2b3NME~^j%Rjo zSwm2u`k>iw8Zk4`W9;cZXhj@>%2-ZHU|u+3Ifh(F$!&(2vW>iw76HvlnB6feDj>Q9 zCakv;wsXC}l7?_H-D7lT`}%ChNX4=}cEV7WcF|nB`rXplDG+Q;My)E8;VK=Km?Tiv z(AZfC;?kj^q6#&bs$q)2)dl)CE44bNVLAnSEXP&YW+u!YD=kpp(CA~nNT{U(GwEsG zm5G>*xfEWT_b0hbT+&z3|_|dv5EXN8B z&l8w=DTO+okChC(oj8yb@FlIY3ahbJ!y17pqm*vFBVwf-JDJdNE!GKC(StTClVQvS zrsiBwne4G&!wOli<2o=SXT;4ut2LR3n9hFN>E*YKVRytq8&Az!GXS0YI zSqd5j(k&D5B6MlEky%%e{zYJQ+=QD2CQ3UA`?jpLlP#+zmozHx_wC!cg{p4FOEkP# zprI7Czj+-dx|tK63j(XWMlh^iBD`~YmYsc7;1-AM0^__kZ^;NfMv#WHB2?xYWw zi6pmG$E)!ga;gvsEcOkzGTsqMrmU#+LdETF9k0XdIZn(dvwe4i&ooin)CNC~+#FT$ z-NHm_%Q{Sn8>~z==Fq^cc%zPc@FszZjM;C=iWF$`vobc<3&%}_Jvp0B%d<*rGM9Z~6J|sXNr9e*D;qUF zkliI!Uik`^^_7it22|l8ykEonI7ZbNQexwURL2MKL9*=hT8T{_*zJYRm)f{&CS;_u zHdDR20JDmOfS$JE$$l$0*XE5g*JM)?_?V71?B)ri5S&uxK7Uv~zmF#X_We!lnxndx zjiG%{r&EZUCt!AXxipqKuSm9gi5hbC@a#YwGovh0bn;A^3s6Nok721#-Ih3#Q^i88 zh?(*3sJUpl9B)2*3wmCcoDZMV@p)WuJ#SCj$u!S6>LRj}2_=R7uR)_~MoseIyVlE> z!h@&}h_Q{Qv9pG^po62TR)X;`|%@8bI!z9+D-gqN^N8<|K4a;*FS zKV)9SN0$Vi{{M3{E`dc4-LMq$Tl83w{~Wx2XUrum7mcoJ{8;wEpXqo4Pci^Mt;Um= zknOC-FQx2nbbJv{P1f*hb*(Dx0Hs-9jo*2Op3(8NGW17fNZ*-ASn1Z7naNlg4S(Z+ zEs2<4Zff|4!2Iz=IVjyk<~h$hpP`QU%Xnc7@Q+t&^^I>J_$fWBRR1UP*JFGx11tE(;X14&##OupuI2sbTII}27iXTs81BWJm4h+71#hJv z^RNeR!`ta(8z$i$cqd~co#qRxif1uPLv*e3{XWn4G$mzj)cg7Vt^h*KLwL80{(B1` zPf}af6Y}+h8!iZMeI8mpK7+&K`x~e#K>J##Netkll9qrqU0*8-cT8J4qh|hA$MjwI}JdR*V zbu3r*-RhWwi}(V!OAF-)%-Sfb1F|i8+8_3qkiG^_(N$FfU-C+z=>nFpQ?LXUG!>P= zZda0Y6cqqhiwj^rJp|iCIp0)GV|~}MF&JzO>xn1K${%eWZF!#BJa!CRrIOrbCx}4# zY7)byeC5P!9+w!#;mbrDz*q29elB7BzDZl(!jJgmC-^yjp}zeJzs2v>w?E)d_%r@O WS$XUEEB=muGPXx}i2K)s%KriMT6^07 diff --git a/bin/main/com/loki/lochat/gradient/listeners/GradientGUIListener.class b/bin/main/com/loki/lochat/gradient/listeners/GradientGUIListener.class index 304ffe2878152292efd35b8abf8d19ba820b2f82..50c186b081d4c5715ac36bd4465ada7af9d76650 100644 GIT binary patch delta 865 zcmZ8gNla5w6g{WYr|qZwj`E>^fHJg{0wP5)2#GRv^(h0WQg0Gha8 zfq|$IFEw3E8FQwaBaTQK-Vu&-BeDMYh0y4DG#VQnO~VJm{Vx~lABn|mNZ^x-&-fym zwTKhnRD8z|!Z6D{ z+{ft>H{BC9?x3zPlL)1*x$5wmn8j?7}IjTA`$c!8%tO!TD&ic zc4^RTAVVrW^kyh5lf*VxrP7aX$yJIDUjb{ygfC#KBXY%6U+%*NpJK2+g8~b{aj}#+ z#rxu#-o!rfTtDCl1-+(8q`BVSgW2_7OzWPiz$1hn<6|%o)XlT_3d~@6n+pVIv7RKh zrsbml0itAZgZ+GV73{K(`!54>Hgzmb0sE_O^ML9<?AlG6&7&CvJvU`z6bXcvoq zFa6@RKfgEU|6*rqJZh@Zxjh~u^W`=J3s@+#Poe;f46>Zz6sq7JR&%ecxuk2i{6{s7 LIwRc2dg#9a`{%4k delta 1094 zcmaJ>TW=I)7=E7FQ+Jr|%+Bt%J5vhV1=>BZu3)v5s- z=RM4q@AJN|cBmg*U40M05ci%|>!GBI3ck_sExsd+S!ZEiqj-3~Q#(>0u2v5d{i(E| zV)q=9r+R)*txzgE^EU?#sJMt91Nec+oiHr%x*6-lPxzTIryJ!;X;-yUtvRLrHD{*$ zI8jZxg8B`q2dbq;#TjrpF&U1FYL4Tssc{20iskTw>=HinSP&Q4t+9tG z@xHm_&uZMtoG3<0{_X4y@HX*bq{nkR?+}+GpLK6DV5lH>XxzljdVqIIVvXCWF^wt7 z-6O6S%)5jZ^Y=I4Gyn9R#mRAx`SR$vH|AZz(&#c?yPkC=o5@VaDXEi<0KyDob*RUhKsnV-orB zlDt@LY?o+OrjIF5#iv#qJH;hyVq^SW99Q6WtTa0gNfAg{zZBNth@*C|YLB0X_te-5 z*NeC9WYj(-V+~kX&jdQrjWiSD3%kosUPA=(E+t))z=&%M#r=aK(GgE=`KOBCZ66cO z@w6sZua76g(_*TlYdZ7a;K_dlhu3!D;Vn`|VV>@0+9c|Ohy5JjT}^a12RX!hgfOg diff --git a/bin/main/com/loki/lochat/gradient/listeners/GradientPlayerListener.class b/bin/main/com/loki/lochat/gradient/listeners/GradientPlayerListener.class index e998c16b955c5d28a6dc9f17d20fbc77a83dbc5c..f70f82b673c119b090f69111e41edf207e46fd23 100644 GIT binary patch delta 35 pcmbOyJzsi5BeR%%a(=FUPJVVKkVwu*EYUA5$;>IB43uqkE$pKD6ATmH4NEgQ=1KCO8p`D z2fX41(SrvM9Q;w1J(~>zNl*{jO!xG>e*LCj?t(5}>Rkj20Gjwe89j=)?m)4$dY#X}6&@>}*!bvbRgd-~q{&{QD z2%-Tk3L4SGa4sow#bY@drrTzB?=6a$^YdQXK3U3yjiO7 zWw66Z%T8+>#Y{#x8r3A6lJ>&nCBu#<|5T82Y>`XH>u22L4nLBie13sk@kljD5S{1> zKs`&?I&ccQ(ZkTP#S8P6Y1yO#Nx3Q!dlj6Qd+nmUwV0ENZOW}a1s8FNj-?H!a!d_- z%!-aMwWKIG@gN2;7{FzQXkB65=!$x;=%|8r3^7cHtLrR#{@}0H(1Lm*?~2XPSN|fY zRUG3jHIQ1Jh4!RaXSPEfHDx=b9Ij`c^1RntQ~yv$%`#e2BAw$-(Wa(-{CAL<>OvB= zdZiU(&;l5zdv%-fNKFM3^0ot_u$av|CDKv%8lvO0?hK)mQ&h@VCTaLoY)x7WT_=WS z$&%A4yJcFz9YhHK#6E3ONS4!mwH9b@@|B(&si8{^F@u!c8M6#%#vH?w z@T`c0e|&mNzqN_6yL{};dgR^IhxbQjX3VnJ-;a2ko^`bnD??}2UMJRLiHOWNuV4X- z3_;tVj?|4g(`2|)gK_oE)u@4RQdZH{jU^$UvF^&qcn{>byi5#%#+Tk%(Cf$K3rODp zSsMLQnP`lDLGx!?_z^eXxvxE4WH=goR;TbF&X%1lNgAn7n@Y=na#( zBjkya$A_3(faKsKI}|&F68nl#hFz*=FB+@l_8-gLPc;k>{~; G%fA71!`rI> literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/gradient/service/PrefixService.class b/bin/main/com/loki/lochat/gradient/service/PrefixService.class new file mode 100644 index 0000000000000000000000000000000000000000..46b5885c886574d304790eddb8522f77503b14fa GIT binary patch literal 4334 zcmcIn`&S&*9sgW*vjfWraT7>3X+X`RJTn?iFs!5rBt+~&XbIRxtq#KkCd|&Pv$Hf* zQ*BGDwJ-Y?`?e4JF17|Cef((8>7UfowEf(f*=3LgwdeG}nLBgu_kJIr@8|oycmMJ4 zTYm#^4u4fpCveumA(Jn>?hJqbAfs9=?=EqI1P&&7uTed!Jn*!nr zl|E!SmVZXz{*L#nwR1`!IpofmX(VumiUu?ZBsw~$8bJS5>_RGy4E8G6BXFnd&1%!- z`FYFN27^IG3tA~SYx?7MdDe19bw{5yJ%Pg=o%xt}F<8|$3FYdsU!WmhiK-C?@Bsw} z1=`GPb*b#9Fqg(lY|zCk1rzG>Wt`xTtn zsTyjMSH%N3NqtplhF!0qvmEx4C0i0!k3NC=XNrO88`7cEDvsi08aWIocxY2$g-KR% z1|JeoJhN!)2Fv1noCrg6x;J4yT{a!V%*E_&ny=xAG<{CRU<=PLFLxt69P!%DNHM%!NZQ}YqD<^W#82F zqNT+ma2Ko@(^Fx9y(7Ws*m{_Y9rolJaTRk4*uf9(Ag02?HPVABCz+;dSL~^&y};8|vrCWmxm&YY2LkEE#ynh{6@ z&22+C0-9Dojpr3S$4<4?>P|aVdVVrL$-^rsQ4-GrNs?)}Y};dcfp;8%AIPKRm@KUysra!xl_bOEYKFJH*Hruz zKNHwj(C1BFybRrc)bi)}R`PYnXZ<`_1J9W0s4dk%nBX{;G0$66%p;zIY+EvWzf|!n z*|zs=YTMy@hJL@s!_q2M(;mA4rYGF8XPCp5ybfln*5V-8a$PwT3%0`__@I!%eim!L>SSllzl=?&Y(E`&+S(fA?^vHQbNGIKnS# z3ha^glF5;kRa%j|M|xKA!DXD`(7TMjt~b%VihDDs`2FB2`o}n)UB)2}L(6Clmxq_3 z_Ht%q4Wlc)D_z}upID)?MBvteAh$Az%(b$_!-oj77hEw5FYoKhB?oFYY{5V9nJMcsR!ULh9DhLBr5C$R;6ha(w zsTs%dbB37=ShX98+vs3C{BOL-2SfP9{}{sSHA49H)(~bGg2@o(7=pzR?Egauzd=t} z9sETms|DC4tK+wP37dv&Hb5~jt%d>-dI^M5zr*h-(M0(-xqdTxzQsA&qSkN=e?j)Y D7Uq6P literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/gradient/util/DisplayNameUtil.class b/bin/main/com/loki/lochat/gradient/util/DisplayNameUtil.class index bcc801dba73064f122fcf77b5eac67766a401a11..653af57e07ea95e22efbd966c1c9397a61c3f500 100644 GIT binary patch delta 2102 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 diff --git a/bin/main/com/loki/lochat/integrations/DiscordIntegration.class b/bin/main/com/loki/lochat/integrations/DiscordIntegration.class index 4f0eda6e478a8444a11af0e036fcc4e44d9b9342..d4ff258e50c5b98139c1aa7af18352ff2f531ba8 100644 GIT binary patch literal 3033 zcmb7GT~`xV6y28u64OB>_*E4QY865xY86{U{V1pfBcNcc)P4+;Aq)%|%}lWPC;F_v zp)al_ajkZ(KK8*M)zy9O3?U$;`Z9Cp-gD31=bU{`^8TMc{sM3dFB51}$XniO-t|@- zKCC4($UD`*uGGxH@v44)-0>~1R-V|}OCYXrVA)(V^R8K~ehz$&u z_CevGfzM#b>ac-!bfmaCYT$FRi3^HCSJA23Q}xvayEZ2z6w*b{GTo9{bL2bPj0a1O zuW)hiKtHTlA%T+$mp(nsDfFRV!r3qMPa8-eDSKZSIE!-%`yBuFX4V+)dJgX4}mub;xh6>S@<7Orl zRSFmJWdfI&=>PI(;4-f00xWt8hXz7mE$G>xM%=<+DS@jB*FGg}+f|jq2)>r|T~nC< z7#;0h*xfrJ6xpbOeMkx5bptn~HVMZsnXXe#;ud>jqPiv@na%vYuosp*Zzc2SVX;3m zfMuSG0Qmd&| zBnrt*?;J@IQOI}FlS$aSM?iz6Bsgf|%d`}>PYwKtpXh`=wMGuj#@bbzY_$>mV&E~p z--i33?gU9#^!uDEF|rMqVx-d@k{h?pV2Oi&4|X3$DtsM<_Oe_S$14UFQDJ)ZPP2#_ zXU_Jw%6e{>=Gx7_IDKYd70g^pQfb$w%vBDP3rnzd9d8gm(E&b*o=UXN? zXCj|n{CZ5gHokkZ?{FX++u&ko1BbE=9C@QJ9pm#bT>{;7-o-{u^KuWL2XS1V>FYRw zUVhP$hF5gT--rn0J@*vPma~7N?@9J84!uKW1E;cFo@(HXpuR=VYck+j0|V*d1}?mz z^@z3>vv>Gv0|l-M4Se&O3?s{%4ScJ4zuDZmEq93)gK)p3gFZs$NBMn>i8_w+WIK$L z{1efSdpN};oW@g}p@h%D!GMm-2pLCl-KD2E+J=X@%kGQf`i*;Wp5zGK^9r4OF`UWQ zhzl7m@4rH_H~K6L<$4>K*^rHdUwh>^JltuG>tr0G@0_GZ6-idgFh&L(LTN*JF zdT55&*GzODT%G9FVA(F3xEneE2q<+;_>;D2K C;-{AY literal 11229 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 n^62yYL*PAr75w49$-DG2 diff --git a/bin/main/com/loki/lochat/integrations/discord/DiscordConfig.class b/bin/main/com/loki/lochat/integrations/discord/DiscordConfig.class new file mode 100644 index 0000000000000000000000000000000000000000..55da43a61d58a1f8a8054d4cc04458487898d66f GIT binary patch literal 5149 zcmb7H`CHu96+L1wGsXhLcz2xyFL4;KnI_&%(v*O4AYdGW?bvQY#LV*`qmibP216Wo z-`B5ulJ0A|IBn9#*mat8Pup}$_q{*&kLcI-yeG{dKoh6_{7Iwx?mh3W=RIEd&$Z_O z^x&UK)M>aa=M=M+GiCCTn>75aY5St!8oue+UN&!fImgXs`+|3`W1lq(Nz`j-IcH28 zSDEAY1rN#wXU&Q=7fg&UMDXakwBA<G{J$BYR}P+`c)8P*BIHRX3GtoJ8PPs8q^|38!6 zNo>%dtK67Qwx7{9;uW|diA@?dUpI+5Zp2L*nhL`2Gkl}pvGT%YK<$~KC}ejE$wsWl z78&?fmZyE;a`2@}IqJKnUFgo7koq_4*p6G6YcXSbK0{>2PRoUijt;d@oXVT7m%bTL;!B`Yl?bMoRQJ+p96P{Ccb4+X|bZLPTRWAqZQrV|#!YFpj z{deowgS~Xjca9qVq=vPXh;3$3^^BA=yZbe)SE;Bj^g5;4k&^80MjXWLNwAq#EmVn)^9JxX^SY>twa8DA06?~P?Lo+#{BGxg4VGWH$ z8y22t6qxzd6}V{X(SAb)3LS4axI{Ycm=RYvQ2Ul&5eeiPIXgk=Y3P?4=n)$7^uEhE_S)jDRE- z>TBqkFRd(mcP7dwF9*9@lE}D@BvPE^z@jpW&D9=O*vx&n&Yd*WNwQp1Rf%*0n^L1f zxVEs0Tx%Njjoj3*Q3@$&x#=CUB_-ukILADS;6vui(oqDrkww4DaV%l*V{J@&PiL4{ zNhK%6#H8a)9d|j$ujQbg55Bv~Qoyo9!j^SR<9yJW0cmL1AA|a`IwW9&(qS@k&Na^ZZKJ}SmgAt)xaLU#_^I(8qIu^sB7Zp8b3G^z&qC<}gePyAoCn zGC7^X*QqO{rdJ6#%}E|10V}CI?w9ae=#aD;Gkr^>Q70`{u^MXAKH=qDQ?YHDcMUGd zky1-4fHdl*u(cu_o80Vz7%rm{%0V3i{@st#*OWK7M18)28s z6AMFv@jc%)O2fjRbn;#!euAGR@ly@k<}+#9)6SI02CP9c=E2f$&vH{Ph z@JmM1&x(nBoA0#A`vD*FNQqu`4z_72{F*5V8%(RTSPH+HZ##{o!zN1McRYxzn)&%2 zO5qPt?XcVb#Qaya{YnRMS_f=fxV@I)dBRKLuNrP%5=>kollZ%a8(&(L0~TZ*n>i#2 z@=+aR&o-!EJp1@w$Cc$g9ys9qIzo*)zMDI)K=0_hjFla8XnjiQujW&Z00w6>qY`coZGW91w+rrWMy^HAp;wmpUvU+pL5 zVrROO;^7@id_bzEcg`VuWasnf+)3%K#Ix9U6$kF4^v=hz?nr>v$IrX3F$T=xSjUd5 zxbHF^c#6AcDZ7`qtOR5Xd+9akbt}DHPk(Q~Mzr&mo8_+qyq({Hn=ptr+HAowY=yur zJdWE{oVytpBM9Sc#r=2?26t%Zoo|A_>WHugIpn$5F~%-%mcI(L*@VQuVodmc}`}WpO^b zV=>-Q#aNiqcQ(X8+!=vbQBB9Y@t)9)ee7LXl_r(a_pZkK=kS3i7Ui@z0@73k`ACh@ zezZQOw1*=Q^L{*11LDy*hlmE})QR zzJYNP$%7<}akWE0fm_0=6Aw>9l8+pI4s;uTev>Q|%y?h8X-l}zw@9sVLz<63w^X59 zLTC+7*F?Nr199>?h;ua&uMjaV!Pa#UuhvBTb`2j(*Fn5iW3ZoLt(I_uufP^_<+D|Y zeX=^NQ83ugwI*MT0hx(_%n$bWYRtp;WzQ_;sD~pEmBCKnhupaiKf;gs-p+&h=k(iH}6d{I4rtDe7C&p+bN)RhPJU+_2l0~`Jag~yuY literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/integrations/discord/DiscordMessageService.class b/bin/main/com/loki/lochat/integrations/discord/DiscordMessageService.class new file mode 100644 index 0000000000000000000000000000000000000000..28a2dd07edefdd2c53e62ab8c45ed7afaa2f6922 GIT binary patch literal 7771 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 literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/listener/ChatEventListener$ChatMode.class b/bin/main/com/loki/lochat/listener/ChatEventListener$ChatMode.class new file mode 100644 index 0000000000000000000000000000000000000000..ed94676f1bf5f5e9d83c70d8df2488bfb31cca96 GIT binary patch literal 1562 zcmbVMT~pIQ6g^vLQ%a1s!2{z^84yLj!5HCMh%i6#s({ zG9%**5B>mul;hnr*hrCacu02l=AL`*y?al7{yP2v;2Bml#2Dr)vToXP-=bHg#(mSa zJYP7%HCO5M@<2F#DYQ%|%et%z4c!cxJ$}GVn>)Madr^^Yl_6<)uWec8HUryX(CWhT z_^x2cmfFsnzH2$V1%|F%o}9W@DPRv6bRBUd7}6!n5gW~VS-4xgYzJsr;r2FnEp-l! z1G#)>P!*?NhlV62Oa$d_(*Udszb32ixXbIp7cS+T+LTSVB3@bQ+J-s?XjV-D0iR3hd%nvXtq5KU z24*x&GmN+A#BDmhRTpNf^w3ksUCc7{lusfs97g+OIR|oIm@V%$?o@5zO_ZeEZ#D{@ zRYfczaO+b;ybxZ+wHm&3qmWgbdtTHKcIHE8kSP0KqZ8g@I)Qu=_oxvkdMSiTNi+1) zlOZ(}9nxSxDm)Dj8I1oQT$;_cR39@;w!3;xgEWvR$If@9Icas1C^m%WuSw4*Tk8ja zIg(!&4QPzSMp~v_R%wye1bvA^$umW#Nm?1LapN0$ztARzOQ93+8GV5T$ECN&34lpX3x~JflegnC*Zngjb literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/listener/ChatEventListener.class b/bin/main/com/loki/lochat/listener/ChatEventListener.class index fb655183841874e8ec0846d049ab39e651d35603..3a17296bd750fb8e011684c589ffae5e8caeb4bb 100644 GIT binary patch delta 3779 zcmY*b33yc175>l6o4GTSmu)gxCLs$UkZphjC747ZA%x8&!~h`#L?_A10+US0OrThG zkh)bA#VfUmZMB=JEhggxXADVvg z{Go#L2dAH#j!vJRe!k%RUDGFc_k^q$9(ya=1g?`iM2U4Xw#Yk0qrDv+0^8(yk!#(K zPWi65D#3}J*d=g1;R=ps$6PczuE7lichheFKL65yf2eQi=J3eCP+zUm@ayP8H^DIw zS{Dd*`2z&%bb(;#=tUnPX?P^qZG=K=2Lhabh_I_fKH^>?Z*%w-be5W;13GqN4}FkBx0CQEGwxfkwH#|Pw1DXsRKaI?S%<#fuG)?09^6sha-2?urDhC_su5u-ae(z7}24-bUG1Kk{9 zRje$xIkKr!9&r!Lw6r3pHfWY?c@u-;`?+Dl50;Rzj|#~6b)3w+~{a@gbIq8wM)JW0qX>6HG2 zwCr|{GN4e6%OoHjCv=>|7iGZF=zJDYf#(R7a&Kz7eBF_kaSEq7lU}oAbD4F>gN`M# z(wUnTt7V%J8V!V*tW$Vi;0&Q8t}ogy7try7ic|Olf#7}>-xR*2<18i!30)l3o;JfD z3J$6LFY95UeWP2Jj$W<`$Mg&n?j)hrFsmn>i7n}DXU#; z?XO{4;B|S2%h!KiSxfIT!ZB{f%{2rA!4Vf;!*_IiS4ntS!erh53$)_O5#olZyuynH#*6~;TO*ZP4j(6}69sk6?Qj ze0Rg(a6q{$)v1iirN_N6e=)({XU-%CE#*Ya4sg*Ts?=#2T_HQ%HK~c*ya$YtxDi6R ztne&w#~-=5wE?zEsNkAqNOqTenY&s+)$$Ebuk#vOC1@p~Q0@{LscU$QsCm7WOQKU9 z)yu*34b&h{rBB(LbZVxSXZB}!$i9uX3+j-!XBAsJX@`6&>rHoi3bpX;q#Jaqq1t3S z?UIjqm!|vU+lEdXXd`d;q>x`8%*a#<{YqhvPMfHePj<`1?A3XLOu7w2L&iu$z#j@3 zJklJqcZ;AAxi`B^X4y@X*lNK^ukBgvsIjY`8>>WFxzB4)7g6+#Lpzx+#*v`5SgF7K3^MgB*$aLeE!}D?i&kOG zzI7zi7k}dXTkUEG=hwXyZk$ zZ}B9awqhJElZV#hyqWJ~dOpJpv_~;Hj;t6%)zgc!B()w_kLPNH&;6B2yllZ3620Pd z1e z}DSJFzg0H-p?opSaOgN8RF9*XBrvCDE8tYKODjcGi($`vCjm5 z7zrx*Iq+qiscJ9ARJBrEw3>2?iyDV6#fWI;?EXrQ#+IRJHa=Ff2+;^S}@7)NLUfEW!Rh#jibagKw6X>T%t)z z6@6&H#6FIFrDwjGUnt8URQI=)-eoCoa8POpu>#+ z{U)gE83Yb@4%A5R9ICx@sP>u&TG@Cf<We zRPlb68SGFMIgwvBlkpH|dW`w>u$jrqn2)(kR?PLL!axl&j==0gE9;ifm9(4_$wL-Z z(F(S!!e5ODPpyjh)Z$^)PJ)UAErNx=1YLEB&BR99<%ibTAyeW4EGaH^zr|C(ms3|E z7x~VieGCb{vuJObfQ`F!an5PFdJMwK(@Z;Fqgju$PHF`CD4^BxUUGAUz%5ASxmdtW zQOKyOsa;@FyolCNBL|du5b|scceUOP*2Vw_UH}(L$>^G`?fwp>1bP z)hOBJW@w&r4z1Z2DT~sz;}9B0#SzUmwVjo~E|l{O6UuDUIp4)_n9ePxOB*a%b;N%7 z#*X`09{0nS-t6pA1+|#hSlfiKiAWZ)Mc)^ lsh9f9)c^%(h=MHTX1$&K$}o&<+DjoCWpeK0eT_};{2#tHAVL5D delta 3521 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 diff --git a/bin/main/com/loki/lochat/listener/ModerationListener.class b/bin/main/com/loki/lochat/listener/ModerationListener.class new file mode 100644 index 0000000000000000000000000000000000000000..6b1248d3dcca815340ffe56336450c51c23d2359 GIT binary patch literal 1935 zcmb7FZBr9h6n<_JSqY1fpdx)ytY{O0UTX_#W7QC_rYy*S(3yUzH=8@*n$2#ydzUtU zrTrClS~~Var{6l$-_&t>?k1EDq11fXmvinp_de%2&z}AB&9A=!s9@bfilO8NZSDmv zMK^a(Xzr;^JLVVDWRCa=e>meO4A zOJDQDB!*lk_EogkChxWk_mwLdxO4nm(NR3=E%VLe31xYG^B8q|Dk8YG}a+_kX8 zaCg`W=eA@Nd)mf5+^0g zYh6Z>Xp*X(!EgJ-(u$5RHE(sP>s&O9jAEJqT7IW_HE4GN-w4Ze_>qmrs1W@P6$#G^ zzGZkZyhaZTc$-trv^kTEKpIs;Q$_kr*G<=Nyj_nO#<#T~21O^C0T!Mz%pD(TCviw$ z?sgD|u6&}*JeeLG_qWWDB01JbwqY`+Bz=w}J!#-7ji)qSXXwhCDMde(6uplkOZPFd zHfTLX&;0x^u;)wpi38+*$HdQ=ewD<|(ES|6Py{lVLJsHg4p|p_S$G%kk!4oj$7R}+ zMgeAN?JD`k$@+kh89Vk&f}TC0Spu>NB{r; literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/listener/PlayerEventListener.class b/bin/main/com/loki/lochat/listener/PlayerEventListener.class index 56f04efae8aa267816e8ff8856822629d52933d8..240a8119a722ff87c9ef04f6d21b1f86c720b80e 100644 GIT binary patch delta 862 zcmZvaOHUI~7>3_7)0t`OL7*23NN-$hJ8hAIR}dA%yG68uccC#2ArT>vkgy`r7}w?O zUHAvoXfe1Tx^ba@LH~gZmoAj}PN~?1ndHoOf1mTsl>N%*{k;8t3&049W$#>b!{Wo@ zYH6uZT3VTVP!dS5i%6_KG$hb8Us_%+&dxoWt(B=)dO&^Gq$mG3QGPk5isGZsFVTpA zfSycES!fYRDIvmv`hi}wqTNE9%4z;}SJ;r)hYs~z>z5G;3vLTBfehO^V!PvOx*iC0 zN_gP4&@GT~iquc7v1z}=9!MvhlxV=-H;?szFc0Feg+prFh^raHL!l!$s=m0g?k)^U z4B_~?@xpalpjCZywW=*+&^+gKoM%VIJ&KDGmvCA2n?3Fz#wD&`LQR{tJBn)(1v+Ij z6pdn1VhR%io~6?K!b+*Ixa!4C+_rE_Ag6xlLG{&)=yzEE+k7Tm>Zui9(@ENhCZ5gw z6i=Y@0dn>``0TVVxPj0I1S<&BiB_Fx1#voERi~?h9y?P(;x(Hzq5ryZ(kJCtj~I6(yiKAglU&L3f;8{!vQEQ~TU zhBN;mQH(hf10*?{r~Czhagv0yo5*XR6{mG|*ScP{64?#h__wJD|FKCRN%a&l)5OdW mI!n|(-Z@uAws&-Pkq0o0yH(=e8nHwI_i&$A7nmlKhM7NHT#fJm delta 1053 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 delta 3948 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 diff --git a/bin/main/com/loki/lochat/managers/CustomCommandManager$CustomCommandData.class b/bin/main/com/loki/lochat/managers/CustomCommandManager$CustomCommandData.class index f0d23a5c7747e669c702981713fbad2cd7576120..fc26f379e2bfaf81d2ec77d995b635b5338954a8 100644 GIT binary patch delta 57 vcmew${6TmFD?1az;>qmnh9JrcLglhsf_N(+yxSmZ$z(nb5VhHnV?84P+4K+o delta 57 vcmew${6TmFD?1aT^<;K-Ll9*Jp>o+RLA(_Z-fa+NGntPAL~VBDSkDLmjqnXE diff --git a/bin/main/com/loki/lochat/managers/CustomCommandManager.class b/bin/main/com/loki/lochat/managers/CustomCommandManager.class index 8a28a2515b6cdd416c66da1f2575615d2bbfd5cf..041150422e438dc4ff8658f783687f7747a8a1df 100644 GIT binary patch delta 1794 zcmZ8hTX0iV6kRJ#a+CD-U0Oolg#t|)3bYh#<s$Hc6=!v>?jc zq8??Wz{rnuP;taE;WhfHBjXJE;f!B8e);3*7mnkoxbFq};hnkX?0xn=`>egr-siIe z#~V@~{Cn>U0JVHmgN5*^WjVf^t1qXxn96jGX+)JQxyyUPLqma3Z?wGaV4x>B5I)!& zjI>3QOBETvbYhbTAT5FPnD!?oiFa?I+xN%Bt^wm+Y=iMhw1_m7s%y0D_E&6rX9)@zbc(84L^tBS3eMl zDNp*Qf*J$JTAk}qDTxh68;6EtOBS0&I`AQBo3^-FF&;ZWaTzs7czkXvluffS9qls7lev6G!T z61iKo<>qyAk8G+BheE-gn2S64g3i50Ajeo_FcxgzvEIRbgsaZvu09;;b#VuKbq3i- zB*wyZ1%{2Zv^|^Ze8}&chmCQn7rGg-6uY z{3|xT=-`B^D_CXWF?FP%CY8tKXfBBcBZqt@F1 zSFyA~&R#F!H^}K5Wn(jHu?d?+_M;V@*n$zXVHDfc4-2l_+Qr+iPLvclOXdDFfTik_ zlF13THO{0m-yNn@+YEV5oY@`K_BiK#BpoDQO#Xx$KUM!K^8))j#k*u)p7`B^Jek+I z?HSCAvtSBqrqO6<#io`yjg^MB;F(rx#9mnDw%y_4X)K)8Cb(o8b3J9o!m?>3&lPOChCMG`)_0k zVgYJA94!^Tl4c--o8xT#w8e7^X>pwQ#&N4Gj<4@=3lL|UnAQ6g*;zO#WqS%)I4x|Q z5h!P+uFt7&D!kmT{;bH`D-Y1q+2JNyogKd0?5MW7tr_f!v)gTz z{JXuznHda7<~?`WZ^bkgv_8dA<}*PY5D@s*o56!>w6dTRZ^HqFbi6BhUy!DKPrB`* zG}4r`&?U6vipk9ylRW<{H~v{}{3at7iBifC!@^BE3OLLW^;>05G9!!%aZUkmVN7MO zDC(15HmXiH3QV$kQgl|1nl6VpCUYTdd|4jkSIqIKIlgL+ubJZso)WKg3EU-Kr{U%q Pp5vs<=Vje0k-Po_EQMHn literal 13746 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 diff --git a/bin/main/com/loki/lochat/managers/commands/MessageSender.class b/bin/main/com/loki/lochat/managers/commands/MessageSender.class new file mode 100644 index 0000000000000000000000000000000000000000..85a72fd7a601d68d7c323faa344faae2187a8ceb GIT binary patch literal 4375 zcmb7H?^hgG8GbJ8GRrbC1V~`pCV_+`EQAa+iKc9t5H^jWE~F78Hm!PwWq=90JKLFA z5^AfhtyZmCY}KYwTYpqhtJQjrk>q%Cj^}7Un&U8-*)ON)u1VTw`%7~&C^(w-s6WE$E zbH->PJ89VC`eeosXi8aWJu{)(rZ*23L+-5U2=u4^PqPvN5r+mQ>4LziuGFf9W3FxH zW)dmOp3x=?b91Jv89CQ<7qmw*`hsC6*6xi@M6nIc3YrA=-fc<2H8YOpDLP}>S>1IF zTSW`D3xxB!?a;Gh71YO5Im6ZF7A)J;^r?ATP_PZnHLkc?(#qzo99I%iv?8XUtqft` zeiirNUV#SJN?Df;JE?n$TDszEjrWA?RIv-YiOH<)%z9KvUWOy+5U8KDEqyAjJFd5L zuZm9WBY(yd1wBLPx0f34vzv%tjNkzG*=(SXM`oIv)^Y&N}Mj_q&$kwIfX3at^p@7sNf`d zl+k*ZVwE{MB%n-N_CtDlR-ip#W*u=pSf^DygfnEB9{Ros94e(WNQe0P`QUMSR>d$L zW&oLvkCP0bi$uWHZHo5NcTI}PsPKw}z&-1Wr%PGCqR>h@CsSxjptG8Y5Kw&sR(#ts z=IZIW5j`&ph=NZD?AbiisM!n0ansEh5qwgh=JBVzY`Cc6F+9#pcJdjMPIgs5ZaDtf zTGu1^v;Zre{3rey#U!Q_qzRKm$u+ZvcDi8eUPP%dFimK!=@B!N@ycCm*TX>~mu*Z0 z@*0*Ka0wX&a{~J+I_LSJNpCvGsmf82MUHNG;waw;+$fjsd$IZ2I;HDwos8!^ct1QL zuzM3DRX8BJG>>)*yj)c>RTCbS_pGsWQ;+CN59U=|#uYDAX06K-Tn6bnrmm_;#3z~{ z@T6BUJ}WR-#eog0gnzBLWU;wI+|fD-;y5o7=7v+4ESW}DrVKW@%yKT!u^C7S1y2iX z@e<^eELlUktn*(|@jSF9)L?)DGkSJ%O7HBGw{1nf%kQ@J3xd}#h`hj#@`hj`ztR+} z=;{|Zu{oR@D_W7o8znks73{Qe#+2Pia5at5Yr{U|Wi79XAuJ}r7gT%)ukzx{t}Y3@S6WemlfLk?Y_>9n)0wiY zxkA1|S5i`S$_|bbsnYG$fAm#ue73a12qY)sVA!``wU)xva~B4v7hBr;auaJWgp@0-y`x42Q`TD>3s$o_yef^ z2EMm&R*w&1D_3|}baHnc*RCF1M&oGrb?kU_Aaot~y~VXT{?@+n>_7gBf} zhi=zsNxl=YP*eML?74>SB@A-1Urw5raF~-WIcZzM{+`|&=&r#M9_U?0--RXgFXL$M zO*{bHz=Ph0_U>gQ2g0%NyVxEJ-@r!&UPD7H>>u>}sg?fhr`@3CVKm|b8)F?!Fwu-B zu^rD~2VUaM`6}*}{vMH+;8lDKDW0Ks7{RE-;402>t%l!U!6P_N+0XFY$N4pkG45Z>@qOG!%@ak${zbezOz#|Qx)8pOM@M^ay^i|c+WVIfZu-~IE>pk zg1_*@-?`fZa9YxOr%2MBB1v}wk|Ovkp2E{4sTJ?xb0jWAU4OvmISNz%?|6fsCml!e zHfcQ1fF8plp2Zhv=WVn}TBDNIBpN>O{*-#|6nXssTln-w|HVZ_`Bu;)pU=rBr=*|% z^PDnyR5iNoee7fuJYS50)*x>dE96bOgtZOx=2&Uo^c3@^aS6M;y!oO>T97X<6gk@< zUwB}ZvONR=I8LBWFkenGUj|`g2v?BA3pmYuIa`t|i$xk1i!>}2bLGV#SFV)hN;8zQ zT=7z+G3r%Xzqn9I4S#@0Pq(ZBtH$}osaYD}7pDlmDpT}D?k`)RMAqR-5*gHYOYE;w zBCB{MiDdx#HV#%JkrDb5?XF?;eUtBY&ad+K8orM=P62-_+Ir4-!*kQ~&?~ literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/managers/commands/PlaceholderProcessor.class b/bin/main/com/loki/lochat/managers/commands/PlaceholderProcessor.class new file mode 100644 index 0000000000000000000000000000000000000000..1354338776c9f04ea7bf483051cc572e7b85d669 GIT binary patch literal 2337 zcmbVN+fy556#sn?$P$)I0~Ba2ZIx=EK(tn@rdA;k0mDUur5A6@<|A3M*-dvhlr-YC z-mi}xADr>2GtM}JFG~Bs_}mx&0cU)z{S%z=a=zVAHj?U$lgaL$-#Op;e&_d{J$viV zo7(`!@MREn45O-J#lnl4*C^_gkZATY$MdevjPjhR*GF5KdrWHhh zp?y_ZQ=+m^l_hS)F76E;*b7P6#~_sp?wXBebf7DUPKKU)<(07?-NY)xojD~>Wp_quqz$1L z2ZK0JC9OwU#(g+M2`o;Vs+3^DUsgP;SSfy~$PJapd~~bu8XHPD%+OL!YqluGAZBhc zM`heE=(ZBw3C|~3OXw#C<;$j}r`@;*WjurkDM4{d0`=y4Y!O@JkHQWsm7_bbLZTi zoE8#4A>&Di9$8cLBA;Jj=!#6&ROczdt+XL_dRoRaRSC~a7#HpME~kkQW-%Yc90|VLsk4fcm9c>585%6JXppvn$i%>mC?F*Y zSQNBpYSN}Rud%#hn%*g$lW|^9lSzsMfl0o`bqN>6S?c21CHJz7S45OJ%Syy+v~`)U z7hM0c3>8G8k^GbE1($h3kXaFAGJ;H^2AMF;8coKk(7QXYDZ2?6lS@THPdzo@8Z26t}^uRa@UMCGsmOe{+`MSMRiQ8B;nq`(}uyVxUSeXw}beU;nWsjVs;GyiUIBn&JBm#kKaI;;Rp1P z(MQIS9peEqjxpT8zz#A*$cQjbY~hr*;`9!5<}Nh01I6z`34*2=wvgPy3*N#P30e|R zCD8@4U-H?nl1+`Z1uo+2M+4Wff4D1f6Io!WD{w-fR|BjoFtq*6U%w4)e~pgea4wu5 z+JY&3*C7krc6ZyPYU?2snxhXPAd#HSq=)!qwP1v_86#^E?Z{vst{{w$DefzD;#+iM z6FvBe?zbrVXB@&W=)1@FAM*UgA)1QB(x51;cdE0l;Z>Xe@JXV>={16XZQlWe*@I_WeWfR literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/renderer/EnhancedChatRenderer.class b/bin/main/com/loki/lochat/renderer/EnhancedChatRenderer.class index e9e0a9d334a06631efe7d155dd5296984c38d75b..47af895030523170c04ec8e2b979aa4b693189a5 100644 GIT binary patch literal 10576 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 literal 15997 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 diff --git a/bin/main/com/loki/lochat/renderer/components/EmojiComponent.class b/bin/main/com/loki/lochat/renderer/components/EmojiComponent.class new file mode 100644 index 0000000000000000000000000000000000000000..4ac2be6db4d75f2198156be10222616fbb2cc428 GIT binary patch literal 2058 zcmb7_T~i!I6o%j41!iCefdLaV37e1@b_d94BnDYxOb8g`Zem=5il4Ky%`)un49?D` zl7C^97k-phd7+i7RAH4B!744i@xq_seg1)E`S!9Di+mX7qPx%Op6=(IKIinG|NQ(b zfGa3!hzXo`gQ{H#${rhc*@^5>`b8Pa(B{Ny;7dQM*|XJP#hclZHM9xzt~hrcyW;pI z`&MB^x{*M~wC8)#D+2LUdO@IVCMe1-#L;P>9UTHaS(zn`=bb`D3Ygizbt(%^ z=qb6WY>Sq?n!rT%@MIZ*_CnpO6b0_2vO!p~3-xl@i)f3#zkg)=;#yBq_1#`3H0M>4Fdw>hdRo@5gZlhD#_?- z5LBe&3mi#hw;h^~LeDQ{7SoFfJcDBzo)sAWe>wxlae^L@)z#>}fR#GHq zrEJx4N&w2T(+1AqIe|`MZUPBR?m@YmJH1<_V}xLCPzgdEX}a+AT;o&rjT%T|OkjGC z&7atU1kT}k4YojfUq>wkVU+>CX*x*^ynyop-AiFmy)N%<8F~3|9RJ-llfVRC(r`iG z?7k>F+;3n67X@yo_K(0_haBLT?Q0bWmg+jLK4f^z0%1Fzx=gN9igF)sxM7}~qIbj)zg)UtHKXiCQ{>(F1{ zeAk87@VbWUJ1W-ZRU5d0H~32^S1XRoa&)cr=4Y0jFfTu;OW&25meY3V_r%Dt6i(G5 zCnUu!Me*=c9k-Qo$qOZwd5#}{`^)1Wzt?d`F}ussEpy^+wfgJNbiCWphC!`H?RyRF z=gJiyC|597v?|^0l*oK$|Df4+wScaYTPXw%xSSnf$zs}-59*;S=R8%c`**$q=hOw@ z6bQ%X1Z-8 z{ed3K?2T{0jN>b4>lj?K%pvpS28QFvTIN|Z)fnbR*I|vVV|)WI#<0eJvv3iiG=kV->e6;cx`k8d(l47cFV;zN8y%svLm$8d-^h;hcs7JesUJ%2-J LoE9i>VGQoSpD-@R literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/renderer/components/GradientBuilder.class b/bin/main/com/loki/lochat/renderer/components/GradientBuilder.class new file mode 100644 index 0000000000000000000000000000000000000000..bb704c2f5c312f8bd6046e491bdaf571f78bf265 GIT binary patch literal 3856 zcmbVOX?GK46n@?`$+XD;r4VXSP_bar0z<)8Bv^$i5KVDu0TH)JGEJvVX5wUmfV*N{ zaNk$l#q}KgqOIrX(Jy}R_>VkCeBQ~#CbW&k4>R-5eV2RhbDw+f`^Vou{Q_VYzEKbm z*p{`6TEU()`O1##uI3n4&TtGz<3!1}49hKRdmTMzauN z**rlTh;J&2E?lf&wZOXn_CduZSi|koGl8A+xX=WNQEr+=Lz;N0ipy|0173C=LoW(k z98c6iTa{|WoRi58vh4~L-B{1q=%rHOxWJ_i>&Gfq)-`QQ+v5+CQCx|Hg1A8EEDjCF zs^~#4ktpgVf$hFS9$a%zYi66kCTZJd!Z^Q8Da&mzYm16LY!y&kdza(rbf$k^?e+T- zN9q_+&6cEss{}S4S(t!hwo_z2R?#om4ngU{fQ;nT0$XY9f?;n6Sht_F$q23iweDz0 zUB-xcp<<_GSGZtUd3QX5L0qrkx*7?+93R%*ag|kLx5V7E7|M7ml}JS(aD$2)u}3Bz z_cQTM#uqkij#C$~bWanP=bS6O-KQdjo9Gt{o3o3=55`l8Aqitz!Z<9@8y{+NXakOf z{}$Y);8uYRi$h+;?HC~}S)0)lxa~qG zv{`kFv8hJ3C=TF=ilw+qKx@WTZ-XrUvwCD(m{YlB6eW&q4DEHL#;f8aYToZqL;f1Y zYXSyg-?A{n8sb~ShULD6JQffsJ|-U$N08oX*~k~!AyVv*#{^pa-%-+P%&}Pndl%L; zhn@lf*Jcs%QpRA?tZM9p$4j+t*nOV2^EMZOB_pn$ogCInUK5SNfvbREB^G4H`8KJj z$Rg0<7&*E90TsvOe<<%5h9xB@R6OW4p-iE|zAo^vibwFMWOzX@vJA`gVqbFbxTO9Q z#GW-G)r2T$#$xp5Ued5}lmz*IlU0?? z_xcUfGsIUrq!$V!rfVdZh4B(gV7*IzN2}lEi`wy~OFY?9EaR$njssXO4-r2j$XVdK-rupLcLp8Q@CjObrg70({;n1H3G09vYzT0) zQI50#e#N%oY4q>!`2pJpf-M7~Gtgt9saPG9+T5Sn%U> zr#H>uPJy#DIOsWh6Xfks_K79DR<^SZQ-~NE76Y_lIY~U<07OvdkCvxuys#j z4NhP!p5i_9ea?TuT@tVpRWP{XZvGJ#+=F`wVjmvDeYlFS^|Pw!$S@*F5;Kb&Eg8f( zPt7qlY21o2^1)V5z6oo~7)qqK@Hno;0 zxdg$b!E@Mzu!3t8tWdB`!2!V-|3w=k^5(>qk`KSprZB&UduDLIz?9lOHo*|eVfRGl z)F8#Ie>JfjO7TFDa$Z7fayj(mp*7QZC>E5j5MR@HOu}B}SO=X3uh^Z4lZFZG^Ujc9J>|g{k^R7TXMk(uPPJt@&aEa@ zrZM#`|La-4xsK&3p5+;6kH}>8&sfi|JR`LFXE?LwS5t&l);kVV^t?BE0Wb2qiYnfq i>@70m18?*pKEbE>jB_2F`y6NR6}fbP_qngbZT|qa6CwKm literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/renderer/components/UrlProcessor.class b/bin/main/com/loki/lochat/renderer/components/UrlProcessor.class new file mode 100644 index 0000000000000000000000000000000000000000..96260fd5430cdeb2497e05897bf73716284f2725 GIT binary patch literal 3470 zcmb_f>r)d~6#rcwSqO_r6oFbS3PN};YSjuT^0HuqqM)<_+9kP=HDNdIZV>vk_FZjj zTkES%KlCwvX=m(XC^*wj`=MX@N&cSJw&!jF1W8hyDKp9Ly*c-s^ZT9G<&VGb{Q{sJ zwRdgcPz%r7B@n zpHPmDo#lR;A@9U--YZZF5s^VaRFqhc#WZ4=`kU zYfds`bs7PlgFFUr(6%F1TeG4JkDMQAXgu>uv(i>EGCVSRaBF2%wYR3WuD)ge_R%^w z@=+jR9Yb*nLo(K*kU_AnZe*fF#wO$tGCQbQ3_E>~$}-U;MPigQ9QOKjZY!Y~ z!_*WtFhwzmnq0B@w5@b5Mv1VjK}fl_EoEQl44CMrB7-3sYq{9zH2!H})`Oo8020 zX7eH`2??Kw=qVZd&`k1{t(qiHdEe_sD-KB5zoMFAc`2g}2N`m$=$PdsD#IpkZ*LOf z1@sO9sgt2Kk?y4gUmLoXJapq}35OUO*LGr&4;ek^WpJsH2z3sIWB*&SCKCRP44=ru z>l z+oLh~uvQ|m3@2p_;aT$3Z;+w%e5_KCCK;C!m=f?5MkEZU1I$H}P&&h!Kh!N=WsKr9 zab{4EsGC1B)Q9ysP@k8fVvNB}{XA|bJ>FGX)`ZtX<0>G7W1O18q#EL#hVEDGALEoD;Wigpy0F(uZnFxMySpxBxg zy=aD^$eXIBWz%GAv`@j#Af-eQAp z45?47Xx}tYJ*($`vWv=M-zL8tL}HRPtm*5%}~P5ii3kQ5uQYRI6*#kGhCeY z&UGQKqEiir2Q-_v6u9ty4z4m3-v4~z;{AII7ZxrpTwJ)^T+SHeUR~#AC-G=;OTu-A z?W-^+Lm}ZN!|iq< zrya7*?jVai0e-Lj4P`fxSNkiRV!e4h_v&nR~Kn zW7p6fjMH;(QTA6TDb1=c61#h6(K3hjd30r9h}MUjU8Py2F52?+&Ee=gPGo?rh@I!= zaVEaF2R}eEbC{UNWCoUwpL0xA-}>ifQ8syEe}RpTrCFR`vQnBgj~5v35|B)Xx6>%5 zcMmbP0fod>39ZWsel-zLL&LNYTZr~@?4jW7!!~rFf~IQ~4YO(lsDYg$saH^oE403b zdfXblC47ghzod)Xf46FBz=)$R*tXf>iRnQ)QIbN18)-9 zJ8%_m;cZHUQEGtikemze()lfr3jP9?zu=qe{2?~zmnF5xmg cMeIMO&nNhlRvy~Fh8ws=2)ha0XRh3T0f-yx3jhEB literal 0 HcmV?d00001 diff --git a/bin/main/com/loki/lochat/util/TimeParser.class b/bin/main/com/loki/lochat/util/TimeParser.class deleted file mode 100644 index 0b8a6ac18a661f7f8594525559d40c02f894c783..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1998 zcmah}TXR!Y6#jN|I|+fbX-J`kHdUx;5W}?{ZLOBj3MB$cbW*4q1zzn=aEU=el% z0|FDKThG_rs?Ei$NI&25?OJ}#u3Hyn)3cfp1O*0f$vZM%lg>u|!ul=C^aaB6wqyGX z0)g!GWr1M9En6`J5RD^*ut1_{JJ$JDecfuV$@Q8gkSw~UtX-B(TkSi-pkJ{)fyv_k zq%kKDYN&(+c4v#-bV`2Hb~fgwS7I2#a0JP|WZT*}QrImJvAv~w!{5{?UsLuYaqPif zfgQeEbic5g1*zhvveT~~U|>u@l$F>1IL0v{U|3GsyJGv5e@W?@fk^=BuWVDlfdZR(th7bOXSr6dN_62LhXW|8w&~Wq9ALvlI>e_i7+l1xIuu#^Pe-~#XSR`s?Cdg8S(s?fzP$( z88vqetg~xm1mb5Mhu5wqJ(mptBYpuJCO%==x`LtqHC}^h{w4yXl$6 z`xwDhK9~}F(ZVRcqUJvK({lokkkNRh+n8Vu^Tg&6F?tsZMC?6{Wn_n#&g|P$j4+od z<1KcuRA?*YtK-oJ-y@tn^?025*B3KHtUen|2Q>|+!$h$@Yov|ngC8)IHniaOQ=A4K zr^9VwSX)zgMhSyTIG7HqNR*wWEb|mABs{KN?lCqO$TA_mnK;Bk-{kv#7*m{&!#KiH zj^Y%K;UbO`z7r@DOf?!O2rN@Z5v!yL+`xHq1N6Uy3*>@~vP!R27B__@d`QmFKnx)m ueaWbyp!$H+FjM)6K?YF58tEvVuMwN;di_MNpXpV?q&`Mn88=bE-oF73UCq$| diff --git a/bin/main/com/loki/lochat/utils/ChatFormatter.class b/bin/main/com/loki/lochat/utils/ChatFormatter.class index 1abd402343b39b8f0cb892e728a042f2aeba86ac..db2f997c7241b0447e32b6cc6eb30e5b9a24d8e5 100644 GIT binary patch literal 7782 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 literal 9665 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 rW3z9CJKl!7Ee?uW>WD1GdHfC!~_8N*$Y$v delta 37 scmX@ewx3Pl)W2Q(7#J8F8Fbhgm>3xpCklv6R4ryw_5m_BuAjsN0MZ2tZ2$lO diff --git a/bin/main/com/loki/lochat/util/FoliaUtil.class b/bin/main/com/loki/lochat/utils/FoliaUtil.class similarity index 92% rename from bin/main/com/loki/lochat/util/FoliaUtil.class rename to bin/main/com/loki/lochat/utils/FoliaUtil.class index 97b9a9dc150007bbc36b0b6bb1884b0a93546265..658c9131064b31786bcbd646aaabadaf1a67cfdc 100644 GIT binary patch delta 43 wcmdlXze`@=)W2Q(7#J8F8K$!{FflU7PZSVgEZ(S6#mJ=S17>gD!pOo003M4AjQ{`u delta 40 wcmdlbze8T&)W2Q(7#J8F8K$!{FflU7OLaOi2`DqA97FU0{}-N2KxX2 delta 17 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 [время|perm] [причина]" + ban_confirm: "⚖C9Игрок ẲE9{player} ⚖C9забанен. &#B798A8Срок: ẲE9{duration}" + unban_confirm: "⚖C9Бан снят с ẲE9{player}" + not_banned: "&#CF6679Игрок не забанен!" + already_banned: "&#CF6679Игрок уже забанен!" + warn_usage: "&#B798A8Использование: ẲE9/warn <ник> [причина]" + silent_warn_usage: "&#B798A8Использование: ẲE9/silentwarn <ник> [причина]" + ban_kick_message: "═══════ ДОСТУП ЗАПРЕЩЁН ═══════\nВы заблокированы на этом сервере.\nПричина: {reason}\nМодератор: {moderator}\nСрок: <#7858E9>{duration}\n════════════════════════" diff --git a/bin/main/messages.yml b/bin/main/messages.yml index dfb3252..452cc4d 100644 --- a/bin/main/messages.yml +++ b/bin/main/messages.yml @@ -1,20 +1,19 @@ # ╔══════════════════════════════════════════════════════════════════════════════╗ -# ║ LoChat Messages ║ -# ║ Цветовая гамма: Spacelegacy ║ -# ║ #B798A8 (роза) → #7858E9 (фиолет) ║ +# ║ LoChat — messages.yml ║ +# ║ Default palette (Spacelegacy): #B798A8 (dusty rose) → #7858E9 (violet) ║ # ╚══════════════════════════════════════════════════════════════════════════════╝ - -# Роли цветов: -# &#B798A8 — основной текст / лейблы (пыльная роза, светлый) -# ⚖C9 — акцент / разделители (средний фиолет) -# ẲE9 — важные данные / значения (насыщенный фиолет, тёмный) -# &f / #E8E0F0 — белый текст сообщений -# &#CF6679 — ошибки (приглушённый красный, вписывается в гамму) +# +# Color roles (reference when editing strings below): +# &#B798A8 — primary labels / body (dusty rose) +# ⚖C9 — accents / separators (mid violet) +# ẲE9 — emphasis / values (deep violet) +# &f / #E8E0F0 — message body white +# &#CF6679 — errors (muted red, fits the palette) prefix: "&#B798A8[⚖C9LB6BD6oẲE9CⅵDChB6BD6a⚖C9t&#B798A8]" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ГЛОБАЛЬНЫЙ ЧАТ │ +# │ Global chat (toggle messages, server-disabled notice) │ # └─────────────────────────────────────────────────────────────────────────────┘ global: @@ -23,7 +22,7 @@ global: disabled: "&#CF6679Глобальный чат отключён на этом сервере" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ЛОКАЛЬНЫЙ ЧАТ │ +# │ Local chat (no recipients nearby) │ # └─────────────────────────────────────────────────────────────────────────────┘ local: @@ -31,7 +30,7 @@ local: nobody-heard: "⚖C9Вас никто не услышал &#B798A8— рядом нет игроков" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ЛИЧНЫЕ СООБЩЕНИЯ │ +# │ Private messages (/msg, /reply) │ # └─────────────────────────────────────────────────────────────────────────────┘ pm: @@ -46,7 +45,7 @@ pm: self: "&#CF6679Нельзя писать самому себе" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ИГНОРИРОВАНИЕ │ +# │ Ignore list │ # └─────────────────────────────────────────────────────────────────────────────┘ ignore: @@ -60,14 +59,14 @@ ignore: list-footer: "&#B798A8Всего: ẲE9{count} &#B798A8игроков" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ОБЪЯВЛЕНИЯ │ +# │ Announcements │ # └─────────────────────────────────────────────────────────────────────────────┘ announce: reloaded: "⚖C9Конфигурация ẲE9перезагружена" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ШПИОН РЕЖИМ │ +# │ Chat spy (moderation) │ # └─────────────────────────────────────────────────────────────────────────────┘ spy: @@ -77,7 +76,7 @@ spy: chat-format: "&#B798A8[⚖C9SPY&#B798A8] &#B798A8{type} ẲE9{sender}&#B798A8: &f{message}" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ФИЛЬТРЫ │ +# │ Message filters │ # └─────────────────────────────────────────────────────────────────────────────┘ filter: @@ -85,14 +84,14 @@ filter: warning: "&#B798A8Предупреждение" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ КУЛДАУНЫ │ +# │ Cooldowns │ # └─────────────────────────────────────────────────────────────────────────────┘ cooldown: wait: "&#B798A8Подождите ẲE9{time} &#B798A8сек" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ АНТИ-СПАМ │ +# │ Anti-spam (caps, repeat, similar messages) │ # └─────────────────────────────────────────────────────────────────────────────┘ antispam: @@ -101,14 +100,14 @@ antispam: similar: "&#CF6679Не отправляйте одинаковые сообщения" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ОЧИСТКА ЧАТА │ +# │ Clear chat │ # └─────────────────────────────────────────────────────────────────────────────┘ clearchat: cleared: "⚖C9Чат очищен &#B798A8администратором ẲE9{player}" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ОШИБКИ │ +# │ Command / usage errors │ # └─────────────────────────────────────────────────────────────────────────────┘ errors: @@ -117,7 +116,7 @@ errors: invalid-usage: "&#B798A8Использование: ẲE9{usage}" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ОБЩИЕ СООБЩЕНИЯ │ +# │ General (permissions, reload, hub, etc.) │ # └─────────────────────────────────────────────────────────────────────────────┘ general: @@ -132,7 +131,7 @@ hub: connecting: "⚖C9Подключение к ẲE9{server}⚖C9..." # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ СИСТЕМА МУТОВ │ +# │ Mutes │ # └─────────────────────────────────────────────────────────────────────────────┘ mute: @@ -144,7 +143,7 @@ mute: permanent: "&#CF6679Вы замучены навсегда!" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ГРАДИЕНТНЫЕ НИКИ │ +# │ Gradient nick module (LoPreff) │ # └─────────────────────────────────────────────────────────────────────────────┘ gradient: @@ -191,7 +190,7 @@ gradient: info-prefix-purchased: "&#B798A8Куплен: &f{purchased}" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ КАСТОМНЫЕ НИКИ │ +# │ Custom nicknames (/nick) │ # └─────────────────────────────────────────────────────────────────────────────┘ nick: @@ -211,7 +210,7 @@ nick: error: "&#CF6679Не удалось установить ник!" # ┌─────────────────────────────────────────────────────────────────────────────┐ -# │ ROLEPLAY КОМАНДЫ │ +# │ Roleplay commands (/me, /try, /do, /roll) │ # └─────────────────────────────────────────────────────────────────────────────┘ rp: diff --git a/bin/main/plugin.yml b/bin/main/plugin.yml index ca91aaa..b6c8071 100644 --- a/bin/main/plugin.yml +++ b/bin/main/plugin.yml @@ -36,7 +36,7 @@ commands: description: Send private message usage: /msg permission: chat.pm.use - aliases: [ tell, w, pm, m, ьып, ееддж, цж, зьж, ьж ] + aliases: [ tell, pm, m, ьып, ееддж, цж, зьж, ьж ] reply: description: Reply to last message @@ -146,6 +146,36 @@ commands: permission: lochat.muteblame aliases: [ muteblame, мутблейм, ьгеуидфьу ] + warn: + description: Warn a player (visible to them) + usage: /warn [reason] + permission: lochat.warn + aliases: [ w, варн ] + + silentwarn: + description: Silent warn (logged; staff with lochat.warn.notify see) + usage: /silentwarn [reason] + permission: lochat.warn.silent + aliases: [ siw ] + + lban: + description: Ban a player + usage: /lban [time|perm] [reason] + permission: lochat.ban + aliases: [ ban, бан ] + + lunban: + description: Unban a player + usage: /lunban + permission: lochat.unban + aliases: [ unban, разбан ] + + playerinfo: + description: Player info and moderation stats + usage: /playerinfo + permission: lochat.playerinfo + aliases: [ pi, инфо ] + # ═══════════════════════════════════════════════════════════════════════════════ # КАСТОМНЫЕ КОМАНДЫ # ═══════════════════════════════════════════════════════════════════════════════ @@ -284,6 +314,34 @@ permissions: lochat.mute.dur.perm: description: Can mute permanently default: op + + lochat.warn: + description: Issue a visible warning + default: op + lochat.warn.silent: + description: Issue a silent warning + default: op + lochat.warn.notify: + description: See silent warn notifications + default: op + lochat.ban: + description: Ban players + default: op + lochat.ban.override: + description: Ban players with lochat.bypass.ban + default: op + lochat.unban: + description: Unban players + default: op + lochat.bypass.warn: + description: Immune to /warn + default: false + lochat.bypass.ban: + description: Immune to /lban (unless staff has lochat.ban.override) + default: false + lochat.playerinfo: + description: View player moderation stats + default: op lochat.clearchat.config: description: Can configure clear chat message default: op diff --git a/src/main/java/com/loki/lochat/api/service/CooldownService.java b/src/main/java/com/loki/lochat/api/service/CooldownService.java deleted file mode 100644 index 651861d..0000000 --- a/src/main/java/com/loki/lochat/api/service/CooldownService.java +++ /dev/null @@ -1,16 +0,0 @@ -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); -} diff --git a/src/main/java/com/loki/lochat/api/service/IgnoreService.java b/src/main/java/com/loki/lochat/api/service/IgnoreService.java deleted file mode 100644 index 56a073e..0000000 --- a/src/main/java/com/loki/lochat/api/service/IgnoreService.java +++ /dev/null @@ -1,53 +0,0 @@ -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 getIgnoredPlayers(UUID player); - - /** - * Получает количество игнорируемых игроков - */ - int getIgnoredCount(UUID player); - - /** - * Очищает список игнорируемых для игрока - */ - void clearIgnores(UUID player); - - /** - * Сохраняет данные на диск - */ - void save(); - - /** - * Загружает данные с диска - */ - void load(); -} diff --git a/src/main/java/com/loki/lochat/api/service/MessagingService.java b/src/main/java/com/loki/lochat/api/service/MessagingService.java new file mode 100644 index 0000000..2344c44 --- /dev/null +++ b/src/main/java/com/loki/lochat/api/service/MessagingService.java @@ -0,0 +1,111 @@ +package com.loki.lochat.api.service; + +import net.kyori.adventure.text.Component; +import org.bukkit.entity.Player; + +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +/** + * Объединённый сервис для управления общением между игроками + * Включает: личные сообщения (PM), шпионаж (Spy), игнорирование (Ignore) + */ +public interface MessagingService { + + // ========== PM (Private Messages) ========== + + /** + * Устанавливает последнего собеседника для игрока + */ + void setLastConversation(UUID player, UUID target); + + /** + * Получает последнего собеседника игрока + */ + Optional getLastConversation(UUID player); + + /** + * Удаляет информацию о последнем разговоре + */ + void removeConversation(UUID player); + + /** + * Проверяет, есть ли у игрока активный разговор + */ + boolean hasConversation(UUID player); + + // ========== Spy (Шпионаж) ========== + + /** + * Переключает режим шпиона для игрока + * @return true если шпион включен, false если выключен + */ + boolean toggleSpy(UUID player); + + /** + * Проверяет, находится ли игрок в режиме шпиона + */ + boolean isSpying(UUID player); + + /** + * Отправляет сообщение всем шпионам (для обычного чата) + */ + void sendToSpies(Player sender, Component message, boolean isGlobal); + + /** + * Отправляет информацию о личном сообщении шпионам + */ + void broadcastPM(Player sender, Player receiver, String message); + + /** + * Удаляет игрока из списка шпионов + */ + void removeSpy(UUID player); + + // ========== Ignore (Игнорирование) ========== + + /** + * Проверяет, игнорирует ли игрок другого + */ + boolean isIgnoring(UUID player, UUID target); + + /** + * Добавляет игрока в список игнорируемых + * @return true если успешно добавлен + */ + boolean addIgnore(UUID player, UUID target); + + /** + * Удаляет игрока из списка игнорируемых + * @return true если успешно удалён + */ + boolean removeIgnore(UUID player, UUID target); + + /** + * Получает список всех игнорируемых игроков + */ + Set getIgnoredPlayers(UUID player); + + /** + * Получает количество игнорируемых игроков + */ + int getIgnoredCount(UUID player); + + /** + * Очищает список игнорируемых для игрока + */ + void clearIgnores(UUID player); + + // ========== Persistence ========== + + /** + * Сохраняет данные на диск + */ + void save(); + + /** + * Загружает данные с диска + */ + void load(); +} diff --git a/src/main/java/com/loki/lochat/api/service/PMService.java b/src/main/java/com/loki/lochat/api/service/PMService.java deleted file mode 100644 index 2ed3421..0000000 --- a/src/main/java/com/loki/lochat/api/service/PMService.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.loki.lochat.api.service; - -import java.util.Optional; -import java.util.UUID; - -/** - * Сервис для управления личными сообщениями - */ -public interface PMService { - /** - * Устанавливает последнего собеседника для игрока - */ - void setLastConversation(UUID player, UUID target); - - /** - * Получает последнего собеседника игрока - */ - Optional getLastConversation(UUID player); - - /** - * Удаляет информацию о последнем разговоре - */ - void removeConversation(UUID player); - - /** - * Проверяет, есть ли у игрока активный разговор - */ - boolean hasConversation(UUID player); -} diff --git a/src/main/java/com/loki/lochat/api/service/PlayerDataService.java b/src/main/java/com/loki/lochat/api/service/PlayerDataService.java deleted file mode 100644 index d8b0d50..0000000 --- a/src/main/java/com/loki/lochat/api/service/PlayerDataService.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.loki.lochat.api.service; - -import java.util.UUID; - -/** - * Сервис для работы с данными игроков - */ -public interface PlayerDataService { - void clearPlayerData(UUID player); - - void saveAll(); -} diff --git a/src/main/java/com/loki/lochat/api/service/PlayerService.java b/src/main/java/com/loki/lochat/api/service/PlayerService.java new file mode 100644 index 0000000..71b4f48 --- /dev/null +++ b/src/main/java/com/loki/lochat/api/service/PlayerService.java @@ -0,0 +1,56 @@ +package com.loki.lochat.api.service; + +import java.util.UUID; + +/** + * Объединённый сервис для управления данными игроков + * Включает: кулдауны (Cooldown), статистику (PlayerData) + */ +public interface PlayerService { + + // ========== Cooldown ========== + + /** + * Проверяет, находится ли игрок на кулдауне + */ + 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); + + // ========== Player Statistics ========== + + /** + * Записывает отправленное сообщение в статистику + * @param player UUID игрока + * @param chatType тип чата: "global", "local", "pm" + */ + void recordMessage(UUID player, String chatType); + + /** + * Получает количество сообщений игрока за текущую сессию + */ + long getPlayerMessages(UUID player); + + /** + * Очищает данные игрока (кулдауны + сохраняет статистику) + */ + void clearPlayerData(UUID player); + + /** + * Сохраняет всю статистику на диск + */ + void saveAll(); +} diff --git a/src/main/java/com/loki/lochat/api/service/SpyService.java b/src/main/java/com/loki/lochat/api/service/SpyService.java deleted file mode 100644 index 78d0d8a..0000000 --- a/src/main/java/com/loki/lochat/api/service/SpyService.java +++ /dev/null @@ -1,36 +0,0 @@ -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); -} diff --git a/src/main/java/com/loki/lochat/commands/admin/ChatSpyCommand.java b/src/main/java/com/loki/lochat/commands/admin/ChatSpyCommand.java index 47a8436..af48020 100644 --- a/src/main/java/com/loki/lochat/commands/admin/ChatSpyCommand.java +++ b/src/main/java/com/loki/lochat/commands/admin/ChatSpyCommand.java @@ -1,7 +1,7 @@ package com.loki.lochat.commands.admin; import com.loki.lochat.LoChat; -import com.loki.lochat.api.service.SpyService; +import com.loki.lochat.api.service.MessagingService; import com.loki.lochat.utils.ChatFormatter; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -12,11 +12,11 @@ import org.jetbrains.annotations.NotNull; public class ChatSpyCommand implements CommandExecutor { private final LoChat plugin; - private final SpyService spyService; + private final MessagingService messagingService; public ChatSpyCommand(LoChat plugin) { this.plugin = plugin; - this.spyService = plugin.getServiceRegistry().get(SpyService.class); + this.messagingService = plugin.getServiceRegistry().get(MessagingService.class); } @Override @@ -25,7 +25,7 @@ public class ChatSpyCommand implements CommandExecutor { sender.sendMessage(plugin.getConfigManager().getMessagesConfig().getPlayerOnly()); return true; } - boolean enabled = spyService.toggleSpy(player.getUniqueId()); + boolean enabled = messagingService.toggleSpy(player.getUniqueId()); player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get(enabled ? "spy.enabled" : "spy.disabled"))); return true; } diff --git a/src/main/java/com/loki/lochat/commands/messaging/IgnoreCommand.java b/src/main/java/com/loki/lochat/commands/messaging/IgnoreCommand.java index 4bcdd87..78612c6 100644 --- a/src/main/java/com/loki/lochat/commands/messaging/IgnoreCommand.java +++ b/src/main/java/com/loki/lochat/commands/messaging/IgnoreCommand.java @@ -1,7 +1,7 @@ package com.loki.lochat.commands.messaging; import com.loki.lochat.LoChat; -import com.loki.lochat.api.service.IgnoreService; +import com.loki.lochat.api.service.MessagingService; import com.loki.lochat.utils.ChatFormatter; import org.bukkit.Bukkit; import org.bukkit.command.Command; @@ -13,11 +13,11 @@ import org.jetbrains.annotations.NotNull; public class IgnoreCommand implements CommandExecutor { private final LoChat plugin; - private final IgnoreService ignoreService; + private final MessagingService messagingService; public IgnoreCommand(LoChat plugin) { this.plugin = plugin; - this.ignoreService = plugin.getServiceRegistry().get(IgnoreService.class); + this.messagingService = plugin.getServiceRegistry().get(MessagingService.class); } @Override @@ -28,9 +28,9 @@ public class IgnoreCommand implements CommandExecutor { Player target = Bukkit.getPlayer(args[0]); if (target == null) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getPlayerNotFound())); return true; } if (target.equals(player)) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.self"))); return true; } - if (ignoreService.isIgnoring(player.getUniqueId(), target.getUniqueId())) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.already-ignored"))); return true; } + if (messagingService.isIgnoring(player.getUniqueId(), target.getUniqueId())) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.already-ignored"))); return true; } - ignoreService.addIgnore(player.getUniqueId(), target.getUniqueId()); + messagingService.addIgnore(player.getUniqueId(), target.getUniqueId()); player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.added", "{player}", target.getName()))); return true; } diff --git a/src/main/java/com/loki/lochat/commands/messaging/IgnoreListCommand.java b/src/main/java/com/loki/lochat/commands/messaging/IgnoreListCommand.java index 409dddd..e674248 100644 --- a/src/main/java/com/loki/lochat/commands/messaging/IgnoreListCommand.java +++ b/src/main/java/com/loki/lochat/commands/messaging/IgnoreListCommand.java @@ -1,7 +1,7 @@ package com.loki.lochat.commands.messaging; import com.loki.lochat.LoChat; -import com.loki.lochat.api.service.IgnoreService; +import com.loki.lochat.api.service.MessagingService; import com.loki.lochat.utils.ChatFormatter; import com.loki.lochat.utils.PlayerUtil; import org.bukkit.command.Command; @@ -16,18 +16,18 @@ import java.util.UUID; public class IgnoreListCommand implements CommandExecutor { private final LoChat plugin; - private final IgnoreService ignoreService; + private final MessagingService messagingService; public IgnoreListCommand(LoChat plugin) { this.plugin = plugin; - this.ignoreService = plugin.getServiceRegistry().get(IgnoreService.class); + this.messagingService = plugin.getServiceRegistry().get(MessagingService.class); } @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { if (!(sender instanceof Player player)) { sender.sendMessage(plugin.getConfigManager().getMessagesConfig().getPlayerOnly()); return true; } - Set ignored = ignoreService.getIgnoredPlayers(player.getUniqueId()); + Set ignored = messagingService.getIgnoredPlayers(player.getUniqueId()); if (ignored.isEmpty()) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.list-empty"))); return true; } player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.list-header"))); diff --git a/src/main/java/com/loki/lochat/commands/messaging/MsgCommand.java b/src/main/java/com/loki/lochat/commands/messaging/MsgCommand.java index 6bbb0d8..dca89af 100644 --- a/src/main/java/com/loki/lochat/commands/messaging/MsgCommand.java +++ b/src/main/java/com/loki/lochat/commands/messaging/MsgCommand.java @@ -1,9 +1,7 @@ package com.loki.lochat.commands.messaging; 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.api.service.MessagingService; import com.loki.lochat.utils.ChatFormatter; import com.loki.lochat.utils.PlayerUtil; import org.bukkit.Bukkit; @@ -17,15 +15,11 @@ import org.jetbrains.annotations.NotNull; public class MsgCommand implements CommandExecutor { private final LoChat plugin; - private final PMService pmService; - private final IgnoreService ignoreService; - private final SpyService spyService; + private final MessagingService messagingService; 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); + this.messagingService = plugin.getServiceRegistry().get(MessagingService.class); } @Override @@ -43,8 +37,8 @@ public class MsgCommand implements CommandExecutor { Player target = Bukkit.getPlayer(args[0]); if (target == null) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.player-offline"))); return true; } if (target.equals(player)) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.self"))); return true; } - if (ignoreService.isIgnoring(target.getUniqueId(), player.getUniqueId())) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.ignored"))); return true; } - if (ignoreService.isIgnoring(player.getUniqueId(), target.getUniqueId())) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.ignored"))); return true; } + if (messagingService.isIgnoring(target.getUniqueId(), player.getUniqueId())) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.ignored"))); return true; } + if (messagingService.isIgnoring(player.getUniqueId(), target.getUniqueId())) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.ignored"))); return true; } String message = String.join(" ", java.util.Arrays.copyOfRange(args, 1, args.length)); sendPm(player, target, message); @@ -58,9 +52,9 @@ public class MsgCommand implements CommandExecutor { Sound sound = PlayerUtil.parseSound(plugin.getConfigManager().getPmSoundType(), null); if (sound != null) target.playSound(target.getLocation(), sound, 1.0f, 1.0f); } - spyService.broadcastPM(sender, target, message); - pmService.setLastConversation(sender.getUniqueId(), target.getUniqueId()); - pmService.setLastConversation(target.getUniqueId(), sender.getUniqueId()); + messagingService.broadcastPM(sender, target, message); + messagingService.setLastConversation(sender.getUniqueId(), target.getUniqueId()); + messagingService.setLastConversation(target.getUniqueId(), sender.getUniqueId()); if (plugin.getConfigManager().isPmLogEnabled()) plugin.getLogger().info("[PM] " + sender.getName() + " -> " + target.getName() + ": " + message); } diff --git a/src/main/java/com/loki/lochat/commands/messaging/ReplyCommand.java b/src/main/java/com/loki/lochat/commands/messaging/ReplyCommand.java index fb1e154..70bc232 100644 --- a/src/main/java/com/loki/lochat/commands/messaging/ReplyCommand.java +++ b/src/main/java/com/loki/lochat/commands/messaging/ReplyCommand.java @@ -1,9 +1,7 @@ package com.loki.lochat.commands.messaging; 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.api.service.MessagingService; import com.loki.lochat.utils.ChatFormatter; import com.loki.lochat.utils.PlayerUtil; import org.bukkit.Bukkit; @@ -20,15 +18,11 @@ import java.util.UUID; public class ReplyCommand implements CommandExecutor { private final LoChat plugin; - private final PMService pmService; - private final IgnoreService ignoreService; - private final SpyService spyService; + private final MessagingService messagingService; 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); + this.messagingService = plugin.getServiceRegistry().get(MessagingService.class); } @Override @@ -43,12 +37,12 @@ public class ReplyCommand implements CommandExecutor { return true; } - Optional lastUuid = pmService.getLastConversation(player.getUniqueId()); + Optional lastUuid = messagingService.getLastConversation(player.getUniqueId()); if (lastUuid.isEmpty()) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.no-reply"))); return true; } Player target = Bukkit.getPlayer(lastUuid.get()); if (target == null) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.player-offline"))); return true; } - if (ignoreService.isIgnoring(target.getUniqueId(), player.getUniqueId())) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.ignored"))); return true; } + if (messagingService.isIgnoring(target.getUniqueId(), player.getUniqueId())) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("pm.ignored"))); return true; } String message = String.join(" ", args); player.sendMessage(ChatFormatter.formatPmSentNew(plugin.getMessageConfig().getPmFormatSent(), player, target, message)); @@ -57,8 +51,8 @@ public class ReplyCommand implements CommandExecutor { Sound sound = PlayerUtil.parseSound(plugin.getConfigManager().getPmSoundType(), null); if (sound != null) target.playSound(target.getLocation(), sound, 1.0f, 1.0f); } - spyService.broadcastPM(player, target, message); - pmService.setLastConversation(target.getUniqueId(), player.getUniqueId()); + messagingService.broadcastPM(player, target, message); + messagingService.setLastConversation(target.getUniqueId(), player.getUniqueId()); if (plugin.getConfigManager().isPmLogEnabled()) plugin.getLogger().info("[PM] " + player.getName() + " -> " + target.getName() + ": " + message); return true; diff --git a/src/main/java/com/loki/lochat/commands/messaging/UnignoreCommand.java b/src/main/java/com/loki/lochat/commands/messaging/UnignoreCommand.java index 00ee1dc..23d210d 100644 --- a/src/main/java/com/loki/lochat/commands/messaging/UnignoreCommand.java +++ b/src/main/java/com/loki/lochat/commands/messaging/UnignoreCommand.java @@ -1,7 +1,7 @@ package com.loki.lochat.commands.messaging; import com.loki.lochat.LoChat; -import com.loki.lochat.api.service.IgnoreService; +import com.loki.lochat.api.service.MessagingService; import com.loki.lochat.utils.ChatFormatter; import org.bukkit.Bukkit; import org.bukkit.command.Command; @@ -13,11 +13,11 @@ import org.jetbrains.annotations.NotNull; public class UnignoreCommand implements CommandExecutor { private final LoChat plugin; - private final IgnoreService ignoreService; + private final MessagingService messagingService; public UnignoreCommand(LoChat plugin) { this.plugin = plugin; - this.ignoreService = plugin.getServiceRegistry().get(IgnoreService.class); + this.messagingService = plugin.getServiceRegistry().get(MessagingService.class); } @Override @@ -27,9 +27,9 @@ public class UnignoreCommand implements CommandExecutor { Player target = Bukkit.getPlayer(args[0]); if (target == null) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getPlayerNotFound())); return true; } - if (!ignoreService.isIgnoring(player.getUniqueId(), target.getUniqueId())) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.not-ignored"))); return true; } + if (!messagingService.isIgnoring(player.getUniqueId(), target.getUniqueId())) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.not-ignored"))); return true; } - ignoreService.removeIgnore(player.getUniqueId(), target.getUniqueId()); + messagingService.removeIgnore(player.getUniqueId(), target.getUniqueId()); player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().get("ignore.removed", "{player}", target.getName()))); return true; } diff --git a/src/main/java/com/loki/lochat/commands/rp/DoCommand.java b/src/main/java/com/loki/lochat/commands/rp/DoCommand.java index 6cd2a7e..83bd81c 100644 --- a/src/main/java/com/loki/lochat/commands/rp/DoCommand.java +++ b/src/main/java/com/loki/lochat/commands/rp/DoCommand.java @@ -1,7 +1,7 @@ package com.loki.lochat.commands.rp; import com.loki.lochat.LoChat; -import com.loki.lochat.api.service.CooldownService; +import com.loki.lochat.api.service.PlayerService; import com.loki.lochat.commands.base.PlayerCommand; import com.loki.lochat.utils.ChatFormatter; import net.kyori.adventure.text.Component; @@ -16,11 +16,11 @@ import org.jetbrains.annotations.NotNull; */ public class DoCommand extends PlayerCommand { - private final CooldownService cooldownService; + private final PlayerService playerService; public DoCommand(LoChat plugin) { super(plugin); - this.cooldownService = plugin.getServiceRegistry().get(CooldownService.class); + this.playerService = plugin.getServiceRegistry().get(PlayerService.class); } @Override @@ -29,8 +29,8 @@ public class DoCommand extends PlayerCommand { if (!requirePermission(player, "lochat.rp.do")) return true; int cooldown = plugin.getConfigManager().getInt("rp.cooldowns.do", 0); - if (cooldown > 0 && cooldownService.isOnCooldown(player.getUniqueId(), "rp_do", cooldown)) { - int remaining = cooldownService.getRemainingCooldown(player.getUniqueId(), "rp_do", cooldown); + if (cooldown > 0 && playerService.isOnCooldown(player.getUniqueId(), "rp_do", cooldown)) { + int remaining = playerService.getRemainingCooldown(player.getUniqueId(), "rp_do", cooldown); String msg = plugin.getConfigManager().getMessagesConfig().getCooldownMessage().replace("{remaining}", String.valueOf(remaining)); player.sendMessage(ChatFormatter.parse(msg)); return true; @@ -54,7 +54,7 @@ public class DoCommand extends PlayerCommand { int radius = plugin.getConfigManager().getInt("rp.radius", 100); RpUtil.sendToRadius(player, message, radius); - if (cooldown > 0) cooldownService.setCooldown(player.getUniqueId(), "rp_do"); + if (cooldown > 0) playerService.setCooldown(player.getUniqueId(), "rp_do"); plugin.getLogger().info("[DO] " + player.getName() + ": " + description); return true; } diff --git a/src/main/java/com/loki/lochat/commands/rp/MeCommand.java b/src/main/java/com/loki/lochat/commands/rp/MeCommand.java index 80253cf..c4a1bd8 100644 --- a/src/main/java/com/loki/lochat/commands/rp/MeCommand.java +++ b/src/main/java/com/loki/lochat/commands/rp/MeCommand.java @@ -1,7 +1,7 @@ package com.loki.lochat.commands.rp; import com.loki.lochat.LoChat; -import com.loki.lochat.api.service.CooldownService; +import com.loki.lochat.api.service.PlayerService; import com.loki.lochat.commands.base.PlayerCommand; import com.loki.lochat.utils.ChatFormatter; import net.kyori.adventure.text.Component; @@ -16,11 +16,11 @@ import org.jetbrains.annotations.NotNull; */ public class MeCommand extends PlayerCommand { - private final CooldownService cooldownService; + private final PlayerService playerService; public MeCommand(LoChat plugin) { super(plugin); - this.cooldownService = plugin.getServiceRegistry().get(CooldownService.class); + this.playerService = plugin.getServiceRegistry().get(PlayerService.class); } @Override @@ -29,8 +29,8 @@ public class MeCommand extends PlayerCommand { if (!requirePermission(player, "lochat.rp.me")) return true; int cooldown = plugin.getConfigManager().getInt("rp.cooldowns.me", 0); - if (cooldown > 0 && cooldownService.isOnCooldown(player.getUniqueId(), "rp_me", cooldown)) { - int remaining = cooldownService.getRemainingCooldown(player.getUniqueId(), "rp_me", cooldown); + if (cooldown > 0 && playerService.isOnCooldown(player.getUniqueId(), "rp_me", cooldown)) { + int remaining = playerService.getRemainingCooldown(player.getUniqueId(), "rp_me", cooldown); String msg = plugin.getConfigManager().getMessagesConfig().getCooldownMessage().replace("{remaining}", String.valueOf(remaining)); player.sendMessage(ChatFormatter.parse(msg)); return true; @@ -54,7 +54,7 @@ public class MeCommand extends PlayerCommand { int radius = plugin.getConfigManager().getInt("rp.radius", 100); RpUtil.sendToRadius(player, message, radius); - if (cooldown > 0) cooldownService.setCooldown(player.getUniqueId(), "rp_me"); + if (cooldown > 0) playerService.setCooldown(player.getUniqueId(), "rp_me"); plugin.getLogger().info("[ME] " + player.getName() + ": " + action); return true; } diff --git a/src/main/java/com/loki/lochat/commands/rp/RpUtil.java b/src/main/java/com/loki/lochat/commands/rp/RpUtil.java index d675aad..dd17b4a 100644 --- a/src/main/java/com/loki/lochat/commands/rp/RpUtil.java +++ b/src/main/java/com/loki/lochat/commands/rp/RpUtil.java @@ -1,7 +1,7 @@ package com.loki.lochat.commands.rp; import com.loki.lochat.LoChat; -import com.loki.lochat.util.DistanceUtil; +import com.loki.lochat.utils.DistanceUtil; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.Bukkit; diff --git a/src/main/java/com/loki/lochat/commands/rp/TryCommand.java b/src/main/java/com/loki/lochat/commands/rp/TryCommand.java index 1cd997c..81fef88 100644 --- a/src/main/java/com/loki/lochat/commands/rp/TryCommand.java +++ b/src/main/java/com/loki/lochat/commands/rp/TryCommand.java @@ -1,7 +1,7 @@ package com.loki.lochat.commands.rp; import com.loki.lochat.LoChat; -import com.loki.lochat.api.service.CooldownService; +import com.loki.lochat.api.service.PlayerService; import com.loki.lochat.commands.base.PlayerCommand; import com.loki.lochat.utils.ChatFormatter; import net.kyori.adventure.text.Component; @@ -19,11 +19,11 @@ import java.util.Random; public class TryCommand extends PlayerCommand { private static final Random RANDOM = new Random(); - private final CooldownService cooldownService; + private final PlayerService playerService; public TryCommand(LoChat plugin) { super(plugin); - this.cooldownService = plugin.getServiceRegistry().get(CooldownService.class); + this.playerService = plugin.getServiceRegistry().get(PlayerService.class); } @Override @@ -32,8 +32,8 @@ public class TryCommand extends PlayerCommand { if (!requirePermission(player, "lochat.rp.try")) return true; int cooldown = plugin.getConfigManager().getInt("rp.cooldowns.try", 0); - if (cooldown > 0 && cooldownService.isOnCooldown(player.getUniqueId(), "rp_try", cooldown)) { - int remaining = cooldownService.getRemainingCooldown(player.getUniqueId(), "rp_try", cooldown); + if (cooldown > 0 && playerService.isOnCooldown(player.getUniqueId(), "rp_try", cooldown)) { + int remaining = playerService.getRemainingCooldown(player.getUniqueId(), "rp_try", cooldown); String msg = plugin.getConfigManager().getMessagesConfig().getCooldownMessage().replace("{remaining}", String.valueOf(remaining)); player.sendMessage(ChatFormatter.parse(msg)); return true; @@ -64,7 +64,7 @@ public class TryCommand extends PlayerCommand { int radius = plugin.getConfigManager().getInt("rp.radius", 100); RpUtil.sendToRadius(player, message, radius); - if (cooldown > 0) cooldownService.setCooldown(player.getUniqueId(), "rp_try"); + if (cooldown > 0) playerService.setCooldown(player.getUniqueId(), "rp_try"); plugin.getLogger().info("[TRY] " + player.getName() + ": " + action + " -> " + (success ? "SUCCESS" : "FAIL")); return true; } diff --git a/src/main/java/com/loki/lochat/config/AppearanceConfig.java b/src/main/java/com/loki/lochat/config/AppearanceConfig.java index d9eae7d..02dc56c 100644 --- a/src/main/java/com/loki/lochat/config/AppearanceConfig.java +++ b/src/main/java/com/loki/lochat/config/AppearanceConfig.java @@ -44,237 +44,66 @@ public class AppearanceConfig extends BaseConfig { // ========== ПРЕФИКСЫ ========== - /** - * Получить эмодзи для глобального чата - */ - public String getGlobalEmoji() { - return config.getString("prefixes.global.emoji", "🌍"); + private String getPrefixValue(String chatType, String key, String defaultValue) { + return config.getString("prefixes." + chatType + "." + key, defaultValue); } - /** - * Получить текст префикса глобального чата - */ - public String getGlobalText() { - return config.getString("prefixes.global.text", "GLOBAL"); - } + public String getGlobalEmoji() { return getPrefixValue("global", "emoji", "🌍"); } + public String getGlobalText() { return getPrefixValue("global", "text", "GLOBAL"); } + public List getGlobalColors() { return config.getStringList("prefixes.global.colors"); } + public String getGlobalSeparatorText() { return getPrefixValue("global", "separator.text", " ▶ "); } + public String getGlobalSeparatorColor() { return getPrefixValue("global", "separator.color", "#FFD700"); } + public String getGlobalMessageColor() { return getPrefixValue("global", "message-color", "#FFFFFF"); } - /** - * Получить цвета градиента для глобального чата - */ - public List getGlobalColors() { - return config.getStringList("prefixes.global.colors"); - } - - /** - * Получить разделитель для глобального чата - */ - public String getGlobalSeparatorText() { - return config.getString("prefixes.global.separator.text", " ▶ "); - } - - /** - * Получить цвет разделителя для глобального чата - */ - public String getGlobalSeparatorColor() { - return config.getString("prefixes.global.separator.color", "#FFD700"); - } - - /** - * Получить эмодзи для локального чата - */ - public String getLocalEmoji() { - return config.getString("prefixes.local.emoji", ""); - } - - /** - * Получить текст префикса локального чата - */ - public String getLocalText() { - return config.getString("prefixes.local.text", "LOCAL"); - } - - /** - * Получить цвета градиента для локального чата - */ - public List getLocalColors() { - return config.getStringList("prefixes.local.colors"); - } - - /** - * Получить разделитель для локального чата - */ - public String getLocalSeparatorText() { - return config.getString("prefixes.local.separator.text", " ▶ "); - } - - /** - * Получить цвет разделителя для локального чата - */ - public String getLocalSeparatorColor() { - return config.getString("prefixes.local.separator.color", "#FFFFFF"); - } - - /** - * Получить цвет текста сообщений в глобальном чате (дефолт) - */ - public String getGlobalMessageColor() { - return config.getString("prefixes.global.message-color", "#FFFFFF"); - } - - /** - * Получить цвет текста сообщений в локальном чате (дефолт) - */ - public String getLocalMessageColor() { - return config.getString("prefixes.local.message-color", "#FFFFFF"); - } + public String getLocalEmoji() { return getPrefixValue("local", "emoji", ""); } + public String getLocalText() { return getPrefixValue("local", "text", "LOCAL"); } + public List getLocalColors() { return config.getStringList("prefixes.local.colors"); } + public String getLocalSeparatorText() { return getPrefixValue("local", "separator.text", " ▶ "); } + public String getLocalSeparatorColor() { return getPrefixValue("local", "separator.color", "#FFFFFF"); } + public String getLocalMessageColor() { return getPrefixValue("local", "message-color", "#FFFFFF"); } // ========== ФОРМАТ СООБЩЕНИЙ ========== - /** - * Получить формат сообщения для глобального чата - */ public String getGlobalChatFormat() { return config.getString("chat-format.global", "{emoji} {prefix} {separator} {player} : {message}"); } - /** - * Получить формат сообщения для локального чата - */ public String getLocalChatFormat() { return config.getString("chat-format.local", "{emoji} {prefix} {separator} {player} : {message}"); } // ========== HOVER ЭФФЕКТЫ ========== - /** - * Включены ли hover эффекты - */ - public boolean isHoverEnabled() { - return config.getBoolean("hover.enabled", true); - } - - /** - * Получить формат hover подсказки - */ - public List getHoverFormat() { - return config.getStringList("hover.format"); - } + public boolean isHoverEnabled() { return config.getBoolean("hover.enabled", true); } + public List getHoverFormat() { return config.getStringList("hover.format"); } // ========== ЭМОДЗИ ========== - /** - * Получить все эмодзи замены - */ - public Map getEmojis() { - return new HashMap<>(emojiCache); - } - - /** - * Получить замену для эмодзи - */ - public String getEmoji(String key) { - return emojiCache.get(key); - } + public Map getEmojis() { return new HashMap<>(emojiCache); } + public String getEmoji(String key) { return emojiCache.get(key); } // ========== НАСТРОЙКИ ЧАТА ========== - /** - * Получить радиус локального чата - */ - public int getLocalRadius() { - return config.getInt("chat.local_radius", 100); + public int getLocalRadius() { return config.getInt("chat.local_radius", 100); } + public int getClearLines() { return config.getInt("chat.clear_lines", 100); } + public int getMinMessageLength() { return config.getInt("chat.min_message_length", 1); } + public int getMaxMessageLength() { return config.getInt("chat.max_message_length", 2000); } + + private String getChatColor(String type, String defaultColor) { + return config.getString("chat.colors." + type, defaultColor); } - /** - * Получить количество строк для очистки чата - */ - public int getClearLines() { - return config.getInt("chat.clear_lines", 100); - } - - /** - * Получить минимальную длину сообщения - */ - public int getMinMessageLength() { - return config.getInt("chat.min_message_length", 1); - } - - /** - * Получить максимальную длину сообщения - */ - public int getMaxMessageLength() { - return config.getInt("chat.max_message_length", 2000); - } - - /** - * Получить цвет "никто не услышал" - */ - public String getNobodyHeardColor() { - return config.getString("chat.colors.nobody_heard", "#FFA726"); - } - - /** - * Получить цвет ошибки - */ - public String getErrorColor() { - return config.getString("chat.colors.error", "#FF6B6B"); - } - - /** - * Получить цвет успеха - */ - public String getSuccessColor() { - return config.getString("chat.colors.success", "#6BCB77"); - } - - /** - * Получить цвет предупреждения - */ - public String getWarningColor() { - return config.getString("chat.colors.warning", "#FFA726"); - } - - /** - * Получить цвет информации - */ - public String getInfoColor() { - return config.getString("chat.colors.info", "#87CEEB"); - } + public String getNobodyHeardColor() { return getChatColor("nobody_heard", "#FFA726"); } + public String getErrorColor() { return getChatColor("error", "#FF6B6B"); } + public String getSuccessColor() { return getChatColor("success", "#6BCB77"); } + public String getWarningColor() { return getChatColor("warning", "#FFA726"); } + public String getInfoColor() { return getChatColor("info", "#87CEEB"); } // ========== DISCORD ========== - /** - * Получить эмодзи для Discord событий - */ - public String getDiscordEmoji(String event) { - return config.getString("discord.emojis." + event, ""); - } - - /** - * Получить URL аватара по умолчанию - */ - public String getDefaultAvatarUrl() { - return config.getString("discord.avatar_urls.default", "https://mc-heads.net/avatar/minecraft/64"); - } - - /** - * Получить URL аватара игрока - */ - public String getPlayerAvatarUrl() { - return config.getString("discord.avatar_urls.player", "https://mc-heads.net/avatar/{player}/64"); - } - - /** - * Получить цвет embed по умолчанию - */ - public String getDefaultEmbedColor() { - return config.getString("discord.default_embed_color", "5865F2"); - } - - /** - * Получить заголовок события Discord - */ - public String getDiscordEventTitle(String event) { - return config.getString("discord.event_titles." + event, ""); - } + public String getDiscordEmoji(String event) { return config.getString("discord.emojis." + event, ""); } + public String getDiscordEventTitle(String event) { return config.getString("discord.event_titles." + event, ""); } + public String getDefaultAvatarUrl() { return config.getString("discord.avatar_urls.default", "https://mc-heads.net/avatar/minecraft/64"); } + public String getPlayerAvatarUrl() { return config.getString("discord.avatar_urls.player", "https://mc-heads.net/avatar/{player}/64"); } + public String getDefaultEmbedColor() { return config.getString("discord.default_embed_color", "5865F2"); } } diff --git a/src/main/java/com/loki/lochat/config/ConfigManager.java b/src/main/java/com/loki/lochat/config/ConfigManager.java index 49a0787..775ad98 100644 --- a/src/main/java/com/loki/lochat/config/ConfigManager.java +++ b/src/main/java/com/loki/lochat/config/ConfigManager.java @@ -6,12 +6,12 @@ import org.bukkit.configuration.file.FileConfiguration; import java.util.List; +/** + * Менеджер конфигураций - упрощенная версия + */ public class ConfigManager { private final LoChat plugin; - - // ✅ THREAD-SAFETY: volatile для безопасного reload из разных потоков - // volatile гарантирует что все потоки видят актуальную версию конфига после reload private volatile FileConfiguration config; private volatile AppearanceConfig appearanceConfig; private volatile MessagesConfig messagesConfig; @@ -24,55 +24,42 @@ public class ConfigManager { plugin.saveDefaultConfig(); this.config = plugin.getConfig(); - // Инициализируем новые конфигурации + // Инициализируем конфигурации this.appearanceConfig = new AppearanceConfig(plugin); this.messagesConfig = new MessagesConfig(plugin); this.muteConfig = new MuteConfig(plugin); this.filtersConfig = new FiltersConfig(plugin); this.soundsConfig = new SoundsConfig(plugin); - // Инициализируем их после создания this.appearanceConfig.init(); - // Проверяем версию конфига и обновляем при необходимости updateConfig(); } - /** - * Получить конфигурацию внешнего вида - */ + // ========== Геттеры конфигураций ========== + public AppearanceConfig getAppearanceConfig() { return appearanceConfig; } - /** - * Получить конфигурацию системных сообщений - */ public MessagesConfig getMessagesConfig() { return messagesConfig; } - /** - * Получить конфигурацию мутов - */ public MuteConfig getMuteConfig() { return muteConfig; } - /** - * Получить конфигурацию фильтров - */ public FiltersConfig getFiltersConfig() { return filtersConfig; } - /** - * Получить конфигурацию звуков - */ public SoundsConfig getSoundsConfig() { return soundsConfig; } + // ========== Обновление и перезагрузка ========== + private void updateConfig() { int currentVersion = config.getInt("config-version", 1); int requiredVersion = 5; @@ -87,57 +74,37 @@ public class ConfigManager { } private void addMissingConfigOptions() { - if (!config.contains("announcements")) { - config.set("announcements.show-title", true); - config.set("announcements.title-header", "&#FFD700ОБЪЯВЛЕНИЕ"); - config.set("announcements.show-actionbar", true); - config.set("announcements.title-duration", 3); - } + setIfMissing("announcements.show-title", true); + setIfMissing("announcements.title-header", "&#FFD700ОБЪЯВЛЕНИЕ"); + setIfMissing("announcements.show-actionbar", true); + setIfMissing("announcements.title-duration", 3); + setIfMissing("chat.pm.enabled", true); + setIfMissing("mentions.enabled", true); + setIfMissing("mentions.sound", true); + setIfMissing("mentions.sound-type", "BLOCK_NOTE_BLOCK_PLING"); + setIfMissing("mentions.highlight", "&#FFFF00@{player}"); + setIfMissing("mentions.self-highlight", "&#FFD700{player}"); + setIfMissing("automessages.enabled", true); + setIfMissing("automessages.interval", 300); + setIfMissing("automessages.random", false); + setIfMissing("clearchat.message.enabled", true); + setIfMissing("clearchat.message.text", "CADFЧат очищен администратором {player}"); + setIfMissing("lopreff.enabled", true); + setIfMissing("lopreff.use-gradient-name", true); + setIfMissing("lopreff.use-custom-prefix", true); + setIfMissing("moderation.voice-mute-console-command", ""); + } - if (!config.contains("chat.pm.enabled")) { - config.set("chat.pm.enabled", true); - } - - if (!config.contains("mentions.enabled")) { - config.set("mentions.enabled", true); - config.set("mentions.sound", true); - config.set("mentions.sound-type", "BLOCK_NOTE_BLOCK_PLING"); - config.set("mentions.highlight", "&#FFFF00@{player}"); - config.set("mentions.self-highlight", "&#FFD700{player}"); - } - - if (!config.contains("automessages.enabled")) { - config.set("automessages.enabled", true); - config.set("automessages.interval", 300); - config.set("automessages.random", false); - } - - if (!config.contains("clearchat.message.enabled")) { - config.set("clearchat.message.enabled", true); - config.set("clearchat.message.text", "CADFЧат очищен администратором {player}"); - } - - if (!config.contains("lopreff.enabled")) { - config.set("lopreff.enabled", true); - config.set("lopreff.use-gradient-name", true); - config.set("lopreff.use-custom-prefix", true); - } - - if (!config.contains("moderation.voice-mute-console-command")) { - config.set("moderation.voice-mute-console-command", ""); + private void setIfMissing(String path, Object value) { + if (!config.contains(path)) { + config.set(path, value); } } - /** - * Перезагрузить все конфигурации - * ✅ THREAD-SAFE: использует volatile для безопасного обновления - */ public synchronized void reload() { plugin.reloadConfig(); this.config = plugin.getConfig(); - // Перезагружаем новые конфигурации - // volatile гарантирует что изменения видны всем потокам this.appearanceConfig.reload(); this.messagesConfig.reload(); this.muteConfig.reload(); @@ -147,6 +114,8 @@ public class ConfigManager { plugin.getLogger().info("Все конфигурации успешно перезагружены"); } + // ========== Базовые методы доступа ========== + public String getString(String path, String defaultValue) { return config.getString(path, defaultValue); } @@ -167,7 +136,8 @@ public class ConfigManager { return config.getStringList(path); } - // Global chat + // ========== Настройки чата ========== + public boolean isGlobalEnabled() { return config.getBoolean("chat.global.enabled", true); } @@ -176,7 +146,6 @@ public class ConfigManager { return config.getInt("chat.global.cooldown", 3); } - // Local chat public boolean isLocalEnabled() { return config.getBoolean("chat.local.enabled", true); } @@ -189,7 +158,8 @@ public class ConfigManager { return config.getInt("chat.local.cooldown", 1); } - // PM + // ========== PM ========== + public boolean isPmEnabled() { return config.getBoolean("chat.pm.enabled", true); } @@ -199,28 +169,29 @@ public class ConfigManager { } public boolean isPmSoundEnabled() { - // ✅ Используем SoundsConfig если доступен - return soundsConfig != null ? soundsConfig.isPmSoundEnabled() : config.getBoolean("chat.pm.sound", true); + return soundsConfig != null ? soundsConfig.isPmSoundEnabled() : + config.getBoolean("chat.pm.sound", true); } public String getPmSoundType() { - // ✅ Используем SoundsConfig если доступен - return soundsConfig != null ? soundsConfig.getPmSound() : config.getString("chat.pm.sound-type", "ENTITY_EXPERIENCE_ORB_PICKUP"); + return soundsConfig != null ? soundsConfig.getPmSound() : + config.getString("chat.pm.sound-type", "ENTITY_EXPERIENCE_ORB_PICKUP"); } - // Mentions + // ========== Mentions ========== + public boolean isMentionsEnabled() { return config.getBoolean("mentions.enabled", true); } public boolean isMentionSoundEnabled() { - // ✅ Используем SoundsConfig если доступен - return soundsConfig != null ? soundsConfig.isMentionSoundEnabled() : config.getBoolean("mentions.sound", true); + return soundsConfig != null ? soundsConfig.isMentionSoundEnabled() : + config.getBoolean("mentions.sound", true); } public String getMentionSoundType() { - // ✅ Используем SoundsConfig если доступен - return soundsConfig != null ? soundsConfig.getMentionSound() : config.getString("mentions.sound-type", "BLOCK_NOTE_BLOCK_PLING"); + return soundsConfig != null ? soundsConfig.getMentionSound() : + config.getString("mentions.sound-type", "BLOCK_NOTE_BLOCK_PLING"); } public String getMentionHighlight() { @@ -231,7 +202,8 @@ public class ConfigManager { return ChatFormatter.convertAllColors(config.getString("mentions.self-highlight", "&#FFD700{player}")); } - // Clear chat + // ========== Clear chat ========== + public boolean isClearChatMessageEnabled() { return config.getBoolean("clearchat.message.enabled", true); } @@ -250,7 +222,8 @@ public class ConfigManager { plugin.saveConfig(); } - // Auto messages + // ========== Auto messages ========== + public boolean isAutoMessagesEnabled() { return config.getBoolean("automessages.enabled", true); } @@ -263,18 +236,22 @@ public class ConfigManager { return config.getBoolean("automessages.random", false); } - // Formats + // ========== Formats ========== + public String getAnnouncementFormat() { - return ChatFormatter.convertAllColors(config.getString("formats.announcement", "&#FFD700[ОБЪЯВЛЕНИЕ] ")); + return ChatFormatter.convertAllColors(config.getString("formats.announcement", + "&#FFD700[ОБЪЯВЛЕНИЕ] ")); } - // Announcements + // ========== Announcements ========== + public boolean isAnnouncementTitleEnabled() { return config.getBoolean("announcements.show-title", true); } public String getAnnouncementTitleHeader() { - return ChatFormatter.convertAllColors(config.getString("announcements.title-header", "&#FFD700ОБЪЯВЛЕНИЕ")); + return ChatFormatter.convertAllColors(config.getString("announcements.title-header", + "&#FFD700ОБЪЯВЛЕНИЕ")); } public boolean isAnnouncementActionBarEnabled() { @@ -285,7 +262,8 @@ public class ConfigManager { return config.getInt("announcements.title-duration", 3); } - // LoPreff integration + // ========== LoPreff integration ========== + public boolean isLopreffEnabled() { return config.getBoolean("lopreff.enabled", true); } diff --git a/src/main/java/com/loki/lochat/core/PluginShutdown.java b/src/main/java/com/loki/lochat/core/PluginShutdown.java index 877ddf6..ca0f72c 100644 --- a/src/main/java/com/loki/lochat/core/PluginShutdown.java +++ b/src/main/java/com/loki/lochat/core/PluginShutdown.java @@ -1,11 +1,11 @@ package com.loki.lochat.core; import com.loki.lochat.LoChat; -import com.loki.lochat.api.service.IgnoreService; +import com.loki.lochat.api.service.MessagingService; import com.loki.lochat.api.service.MuteService; import com.loki.lochat.api.service.PunishmentService; import com.loki.lochat.api.service.NickService; -import com.loki.lochat.api.service.PlayerDataService; +import com.loki.lochat.api.service.PlayerService; import com.loki.lochat.core.registry.ServiceRegistry; import com.loki.lochat.gradient.GradientModule; import com.loki.lochat.integrations.DiscordIntegration; @@ -62,15 +62,15 @@ public class PluginShutdown { if (serviceRegistry == null) return; // Статистика игроков - PlayerDataService playerDataService = serviceRegistry.get(PlayerDataService.class); - if (playerDataService != null) { - playerDataService.saveAll(); + PlayerService playerService = serviceRegistry.get(PlayerService.class); + if (playerService != null) { + playerService.saveAll(); } - // Игнорирования - IgnoreService ignoreService = serviceRegistry.get(IgnoreService.class); - if (ignoreService != null) { - ignoreService.save(); + // Messaging (Ignore + PM + Spy) + MessagingService messagingService = serviceRegistry.get(MessagingService.class); + if (messagingService != null) { + messagingService.save(); } // Муты diff --git a/src/main/java/com/loki/lochat/core/filter/AdvancedMessageFilter.java b/src/main/java/com/loki/lochat/core/filter/AdvancedMessageFilter.java index 561202c..5410401 100644 --- a/src/main/java/com/loki/lochat/core/filter/AdvancedMessageFilter.java +++ b/src/main/java/com/loki/lochat/core/filter/AdvancedMessageFilter.java @@ -1,55 +1,24 @@ package com.loki.lochat.core.filter; -import com.google.gson.Gson; -import com.google.gson.JsonObject; -import com.google.gson.JsonArray; +import com.loki.lochat.core.filter.filters.*; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; -import java.io.File; -import java.io.FileReader; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.UUID; /** - * Главный фильтр сообщений. - * Порядок: mute (в MessageService) → caps → swear → advertising → ip → repeat → flood → spam + * Главный фильтр сообщений - координирует работу всех фильтров */ public class AdvancedMessageFilter { - // ── URL / IP / повторы ───────────────────────────────────────────────────── - private static final Pattern URL_PATTERN = Pattern.compile( - "(https?://)?([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?", Pattern.CASE_INSENSITIVE); - private static final Pattern IP_PATTERN = Pattern.compile( - "\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b"); - private static final Pattern REPEAT_PATTERN = Pattern.compile("(.)\\1{3,}"); - private static final Pattern HIDDEN_URL_PATTERN = Pattern.compile( - "(?i)(h\\s*t\\s*t\\s*p|w\\s*w\\s*w|d\\s*o\\s*t\\s*c\\s*o\\s*m)"); - private static final Pattern DOMAIN_PATTERN = Pattern.compile( - "\\b([a-zA-Z0-9-]+\\.(com|net|org|ru|gg|me|io|xyz|tk|ml|ga|cf|gq))\\b", - Pattern.CASE_INSENSITIVE); - private final FileConfiguration config; - private final List whitelistedDomains; - private final List blacklistedDomains; private final CapsFilter capsFilter; - - // ── Word filter ──────────────────────────────────────────────────────────── - private final Set badWords = new HashSet<>(); - private final Set badFragments = new HashSet<>(); - private final char replacementChar; - private final boolean checkFragments; - private final boolean ignoreCase; - - // ── Anti-flood: сколько сообщений за период ──────────────────────────────── - // UUID → список timestamp'ов - private final Map> floodTracker = new ConcurrentHashMap<>(); - - // ── Anti-spam: последние N сообщений игрока ──────────────────────────────── - private final Map> spamTracker = new ConcurrentHashMap<>(); + private final SwearFilter swearFilter; + private final FloodFilter floodFilter; + private final SpamFilter spamFilter; + private final UrlFilter urlFilter; + private final CharacterFilter characterFilter; public AdvancedMessageFilter(FileConfiguration config) { this(config, null); @@ -58,419 +27,105 @@ public class AdvancedMessageFilter { public AdvancedMessageFilter(FileConfiguration config, JavaPlugin plugin) { this.config = config; - // ✅ Используем FiltersConfig если доступен + // Инициализация CapsFilter com.loki.lochat.config.FiltersConfig filtersConfig = null; if (plugin instanceof com.loki.lochat.LoChat loChat) { filtersConfig = loChat.getConfigManager().getFiltersConfig(); } - // Читаем настройки из FiltersConfig или fallback на config.yml if (filtersConfig != null) { - this.whitelistedDomains = filtersConfig.getWhitelistedDomains(); - this.blacklistedDomains = filtersConfig.getBlacklistedDomains(); - int maxCapsPercent = filtersConfig.getCapsMaxPercent(); int minLength = filtersConfig.getCapsMinLength(); boolean autoLower = filtersConfig.isCapsAutoLowercase(); boolean blockCaps = filtersConfig.isCapsBlock(); this.capsFilter = new CapsFilter(maxCapsPercent, minLength, autoLower, blockCaps); - - this.replacementChar = filtersConfig.getSwearReplacementChar().charAt(0); - this.checkFragments = filtersConfig.isSwearCheckFragments(); - this.ignoreCase = filtersConfig.isSwearIgnoreCase(); } else { - // Fallback на config.yml - this.whitelistedDomains = config.getStringList("filters.advertising.whitelist"); - this.blacklistedDomains = config.getStringList("filters.advertising.blacklist"); - int maxCapsPercent = config.getInt("filters.caps.max-percent", 70); int minLength = config.getInt("filters.caps.min-length", 5); boolean autoLower = config.getBoolean("filters.caps.auto-lowercase", true); boolean blockCaps = config.getBoolean("filters.caps.block", false); this.capsFilter = new CapsFilter(maxCapsPercent, minLength, autoLower, blockCaps); - - this.replacementChar = config.getString("filters.swear.replacement-char", "*").charAt(0); - this.checkFragments = config.getBoolean("filters.swear.check-fragments", true); - this.ignoreCase = config.getBoolean("filters.swear.ignore-case", true); } - - loadWordFilter(plugin); + + // Инициализация остальных фильтров + this.swearFilter = new SwearFilter(config, plugin); + this.floodFilter = new FloodFilter(config); + this.spamFilter = new SpamFilter(config); + this.urlFilter = new UrlFilter(config); + this.characterFilter = new CharacterFilter(config); } - // ────────────────────────────────────────────────────────────────────────── - // Загрузка словаря - // ────────────────────────────────────────────────────────────────────────── - - private void loadWordFilter(JavaPlugin plugin) { - if (!config.getBoolean("filters.swear.enabled", true)) return; - - // Встроенные слова из config - List builtIn = config.getStringList("filters.swear.words"); - for (String w : builtIn) { - if (!w.isBlank() && !w.startsWith("плохое")) { - badWords.add(ignoreCase ? w.toLowerCase() : w); - } - } - - // Внешний файл filter-words.json - if (!config.getBoolean("filters.swear.use-external-file", true)) return; - if (plugin == null) return; - - File jsonFile = new File(plugin.getDataFolder(), "filter-words.json"); - if (!jsonFile.exists()) { - // Копируем из ресурсов - plugin.saveResource("data/filter-words.json", false); - jsonFile = new File(plugin.getDataFolder(), "data/filter-words.json"); - } - if (!jsonFile.exists()) return; - - try (FileReader reader = new FileReader(jsonFile)) { - JsonObject root = new Gson().fromJson(reader, JsonObject.class); - - JsonArray words = root.getAsJsonArray("words"); - if (words != null) { - for (var el : words) { - String w = el.getAsString(); - badWords.add(ignoreCase ? w.toLowerCase() : w); - } - } - - JsonArray fragments = root.getAsJsonArray("fragments"); - if (fragments != null && checkFragments) { - for (var el : fragments) { - String f = el.getAsString(); - badFragments.add(ignoreCase ? f.toLowerCase() : f); - } - } - - plugin.getLogger().info("[LoChat] Word filter loaded: " - + badWords.size() + " words, " + badFragments.size() + " fragments"); - } catch (Exception e) { - if (plugin != null) - plugin.getLogger().warning("[LoChat] Failed to load filter-words.json: " + e.getMessage()); - } + /** + * Проверяет, может ли игрок обойти фильтр + */ + private boolean canBypassFilter(Player player, String filterType) { + return player.hasPermission("lochat.bypass." + filterType) + || player.hasPermission("lochat.bypass.filter"); } - - // ────────────────────────────────────────────────────────────────────────── - // Главный метод - // ────────────────────────────────────────────────────────────────────────── - + + /** + * Главный метод фильтрации + */ public FilterResult filterMessage(Player player, String message) { - + // 1. Капс if (config.getBoolean("filters.caps.enabled", true)) { String filtered = capsFilter.filter(player, message); if (filtered == null) { - return blocked("&#CF6679Не кричите в чате"); + return FilterResult.blocked("&#CF6679Не кричите в чате"); } message = filtered; } // 2. Мат - if (config.getBoolean("filters.swear.enabled", true) - && !player.hasPermission("lochat.bypass.swear") - && !player.hasPermission("lochat.bypass.filter")) { - FilterResult swear = filterSwear(player, message); + if (config.getBoolean("filters.swear.enabled", true) && !canBypassFilter(player, "swear")) { + FilterResult swear = swearFilter.filter(player, message); if (!swear.allowed()) return swear; message = swear.filteredMessage(); } - // 3. Реклама / URL + // 3. URL фильтры if (config.getBoolean("filters.advertising.enabled", true)) { - FilterResult r = filterUrls(player, message); + FilterResult r = urlFilter.filterUrls(player, message); if (!r.allowed()) return r; message = r.filteredMessage(); - r = filterHiddenUrls(player, message); - if (!r.allowed()) return r; - - r = filterDomains(player, message); + r = urlFilter.filterHiddenUrls(player, message); if (!r.allowed()) return r; } // 4. IP if (config.getBoolean("filters.ip.enabled", true)) { - FilterResult r = filterIPs(player, message); + FilterResult r = urlFilter.filterIPs(player, message); if (!r.allowed()) return r; message = r.filteredMessage(); } // 5. Повторяющиеся символы if (config.getBoolean("filters.repeat.enabled", true)) { - message = filterRepeatingChars(message); + message = characterFilter.filter(message); } - // 6. Anti-flood (количество сообщений в период) - if (config.getBoolean("filters.flood.enabled", true) - && !player.hasPermission("lochat.bypass.flood") - && !player.hasPermission("lochat.bypass.filter")) { - FilterResult r = filterFlood(player); + // 6. Anti-flood + if (config.getBoolean("filters.flood.enabled", true) && !canBypassFilter(player, "flood")) { + FilterResult r = floodFilter.filter(player); if (!r.allowed()) return r; } - // 7. Anti-spam (похожие сообщения) - if (config.getBoolean("filters.spam.enabled", true) - && !player.hasPermission("lochat.bypass.spam") - && !player.hasPermission("lochat.bypass.filter")) { - FilterResult r = filterSpam(player, message); + // 7. Anti-spam + if (config.getBoolean("filters.spam.enabled", true) && !canBypassFilter(player, "spam")) { + FilterResult r = spamFilter.filter(player, message); if (!r.allowed()) return r; } - return new FilterResult(true, message, null); - } - - // ────────────────────────────────────────────────────────────────────────── - // Фильтр мата - // ────────────────────────────────────────────────────────────────────────── - - private FilterResult filterSwear(Player player, String message) { - String action = config.getString("filters.swear.action", "replace"); - String check = ignoreCase ? message.toLowerCase() : message; - - // Проверка полных слов - String[] tokens = check.split("\\s+"); - boolean hasBad = false; - for (String token : tokens) { - // убираем пунктуацию по краям - String clean = token.replaceAll("^[^а-яёa-z]+|[^а-яёa-z]+$", ""); - if (badWords.contains(clean)) { - hasBad = true; - break; - } - } - - // Проверка фрагментов (подстрок) - if (!hasBad && checkFragments) { - for (String frag : badFragments) { - if (check.contains(frag)) { - hasBad = true; - break; - } - } - } - - if (!hasBad) return new FilterResult(true, message, null); - - return switch (action) { - case "block" -> blocked(config.getString("filters.swear.block-message", - "&#CF6679Мат запрещён")); - case "replace" -> { - String replaced = replaceSwear(message); - yield new FilterResult(true, replaced, null); - } - default -> new FilterResult(true, message, null); - }; + return FilterResult.ok(message); } /** - * Заменяет матные слова звёздочками, сохраняя длину слова. + * Очистка данных игрока при выходе */ - private String replaceSwear(String message) { - String[] words = message.split("(?<=\\s)|(?=\\s)"); // разбиваем с сохранением пробелов - StringBuilder result = new StringBuilder(); - for (String word : words) { - String check = ignoreCase ? word.toLowerCase() : word; - String cleanCheck = check.replaceAll("^[^а-яёa-z]+|[^а-яёa-z]+$", ""); - - boolean isBad = badWords.contains(cleanCheck); - if (!isBad && checkFragments) { - for (String frag : badFragments) { - if (check.contains(frag)) { isBad = true; break; } - } - } - - if (isBad) { - result.append(String.valueOf(replacementChar).repeat(word.length())); - } else { - result.append(word); - } - } - return result.toString(); + public void clearPlayer(UUID uuid) { + floodFilter.clearPlayer(uuid); + spamFilter.clearPlayer(uuid); } - - // ────────────────────────────────────────────────────────────────────────── - // Anti-flood - // ────────────────────────────────────────────────────────────────────────── - - private FilterResult filterFlood(Player player) { - int maxMessages = config.getInt("filters.flood.max-messages", 5); - int timePeriod = config.getInt("filters.flood.time-period", 10); // секунды - long now = System.currentTimeMillis(); - long windowMs = timePeriod * 1000L; - - Deque timestamps = floodTracker.computeIfAbsent( - player.getUniqueId(), k -> new ArrayDeque<>()); - - // Удаляем старые timestamp'ы за пределами окна - while (!timestamps.isEmpty() && now - timestamps.peekFirst() > windowMs) { - timestamps.pollFirst(); - } - - if (timestamps.size() >= maxMessages) { - return blocked(config.getString("filters.flood.block-message", - "&#CF6679Не флудите!")); - } - - timestamps.addLast(now); - return new FilterResult(true, null, null); - } - - // ────────────────────────────────────────────────────────────────────────── - // Anti-spam (похожие сообщения) - // ────────────────────────────────────────────────────────────────────────── - - private FilterResult filterSpam(Player player, String message) { - int maxSimilar = config.getInt("filters.spam.max-similar-messages", 3); - int threshold = config.getInt("filters.spam.similarity-threshold", 80); - - Deque history = spamTracker.computeIfAbsent( - player.getUniqueId(), k -> new ArrayDeque<>()); - - // Считаем сколько прошлых сообщений похожи - long similarCount = history.stream() - .filter(prev -> similarity(prev, message) >= threshold) - .count(); - - if (similarCount >= maxSimilar) { - return blocked(config.getString("filters.spam.block-message", - "&#CF6679Не отправляйте одинаковые сообщения")); - } - - // Добавляем в историю (храним последние 10) - history.addLast(message); - if (history.size() > 10) history.pollFirst(); - - return new FilterResult(true, null, null); - } - - /** - * Простая метрика схожести: процент общих символов (0-100). - */ - private int similarity(String a, String b) { - if (a.equals(b)) return 100; - int maxLen = Math.max(a.length(), b.length()); - if (maxLen == 0) return 100; - int dist = levenshtein( - a.toLowerCase().substring(0, Math.min(a.length(), 50)), - b.toLowerCase().substring(0, Math.min(b.length(), 50))); - return 100 - (dist * 100 / maxLen); - } - - private int levenshtein(String a, String b) { - int[] dp = new int[b.length() + 1]; - for (int j = 0; j <= b.length(); j++) dp[j] = j; - for (int i = 1; i <= a.length(); i++) { - int prev = dp[0]; - dp[0] = i; - for (int j = 1; j <= b.length(); j++) { - int temp = dp[j]; - dp[j] = a.charAt(i - 1) == b.charAt(j - 1) - ? prev - : 1 + Math.min(prev, Math.min(dp[j], dp[j - 1])); - prev = temp; - } - } - return dp[b.length()]; - } - - // ────────────────────────────────────────────────────────────────────────── - // URL / IP / повторы — прежняя логика - // ────────────────────────────────────────────────────────────────────────── - - private FilterResult filterUrls(Player player, String message) { - if (player.hasPermission("lochat.bypass.urlfilter")) return ok(message); - Matcher matcher = URL_PATTERN.matcher(message); - while (matcher.find()) { - String domain = extractDomain(matcher.group()); - if (!whitelistedDomains.isEmpty()) { - boolean ok = whitelistedDomains.stream() - .anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase())); - if (!ok) return blocked(config.getString("filters.url.blocked-message", - "&#CF6679Ссылки запрещены!")); - } - if (!blacklistedDomains.isEmpty()) { - boolean bad = blacklistedDomains.stream() - .anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase())); - if (bad) return blocked(config.getString("filters.url.blocked-message", - "&#CF6679Эта ссылка запрещена!")); - } - } - return ok(message); - } - - private FilterResult filterIPs(Player player, String message) { - if (player.hasPermission("lochat.bypass.ipfilter")) return ok(message); - Matcher matcher = IP_PATTERN.matcher(message); - if (matcher.find()) { - if (config.getBoolean("filters.ip.block", false)) - return blocked(config.getString("filters.ip.blocked-message", - "&#CF6679IP адреса запрещены!")); - return ok(matcher.replaceAll(config.getString("filters.ip.replacement", "[IP скрыт]"))); - } - return ok(message); - } - - private FilterResult filterHiddenUrls(Player player, String message) { - if (!config.getBoolean("filters.advertising.block-hidden-urls", true)) return ok(message); - if (player.hasPermission("lochat.bypass.hiddenurls")) return ok(message); - if (HIDDEN_URL_PATTERN.matcher(message).find()) - return blocked(config.getString("filters.advertising.blocked-message", - "&#CF6679Реклама запрещена!")); - return ok(message); - } - - private FilterResult filterDomains(Player player, String message) { - if (!config.getBoolean("filters.advertising.block-domains", true)) return ok(message); - if (player.hasPermission("lochat.bypass.domains")) return ok(message); - Matcher matcher = DOMAIN_PATTERN.matcher(message); - while (matcher.find()) { - String domain = matcher.group(); - if (!whitelistedDomains.isEmpty()) { - boolean ok = whitelistedDomains.stream() - .anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase())); - if (!ok) return blocked(config.getString("filters.advertising.blocked-message", - "&#CF6679Реклама запрещена!")); - } - if (!blacklistedDomains.isEmpty()) { - boolean bad = blacklistedDomains.stream() - .anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase())); - if (bad) return blocked(config.getString("filters.advertising.blocked-message", - "&#CF6679Реклама запрещена!")); - } - } - return ok(message); - } - - private String filterRepeatingChars(String message) { - int max = config.getInt("filters.repeat.max-repeats", 3); - Matcher m = REPEAT_PATTERN.matcher(message); - StringBuffer sb = new StringBuffer(); - while (m.find()) { - m.appendReplacement(sb, Matcher.quoteReplacement( - m.group(1).repeat(Math.min(max, m.group().length())))); - } - m.appendTail(sb); - return sb.toString(); - } - - private String extractDomain(String url) { - url = url.replaceFirst("^https?://", ""); - int slash = url.indexOf('/'); - return slash != -1 ? url.substring(0, slash) : url; - } - - // ── Утилиты ──────────────────────────────────────────────────────────────── - - private static FilterResult ok(String msg) { return new FilterResult(true, msg, null); } - private static FilterResult blocked(String reason) { return new FilterResult(false, null, reason); } - - /** - * Очищает трекеры для игрока при выходе (вызывать из PlayerEventListener). - */ - public void clearPlayer(java.util.UUID uuid) { - floodTracker.remove(uuid); - spamTracker.remove(uuid); - } - - public record FilterResult(boolean allowed, String filteredMessage, String blockReason) {} } diff --git a/src/main/java/com/loki/lochat/core/filter/CooldownFilter.java b/src/main/java/com/loki/lochat/core/filter/CooldownFilter.java index eab9d72..c44a557 100644 --- a/src/main/java/com/loki/lochat/core/filter/CooldownFilter.java +++ b/src/main/java/com/loki/lochat/core/filter/CooldownFilter.java @@ -1,7 +1,7 @@ package com.loki.lochat.core.filter; import com.loki.lochat.api.filter.MessageFilter; -import com.loki.lochat.api.service.CooldownService; +import com.loki.lochat.api.service.PlayerService; import com.loki.lochat.data.model.ChatMessage; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; @@ -11,12 +11,12 @@ import org.bukkit.plugin.java.JavaPlugin; * Фильтр кулдаунов */ public class CooldownFilter implements MessageFilter { - private final CooldownService cooldownService; + private final PlayerService playerService; private final FileConfiguration config; private final JavaPlugin plugin; - public CooldownFilter(CooldownService cooldownService, JavaPlugin plugin) { - this.cooldownService = cooldownService; + public CooldownFilter(PlayerService playerService, JavaPlugin plugin) { + this.playerService = playerService; this.config = plugin.getConfig(); this.plugin = plugin; } @@ -31,8 +31,8 @@ public class CooldownFilter implements MessageFilter { int cooldown = config.getInt("chat." + chatType + ".cooldown", 0); if (cooldown > 0) { - if (cooldownService.isOnCooldown(player.getUniqueId(), chatType, cooldown)) { - int remaining = cooldownService.getRemainingCooldown( + if (playerService.isOnCooldown(player.getUniqueId(), chatType, cooldown)) { + int remaining = playerService.getRemainingCooldown( player.getUniqueId(), chatType, cooldown); // Получаем сообщение из конфигурации @@ -45,7 +45,7 @@ public class CooldownFilter implements MessageFilter { } } - cooldownService.setCooldown(player.getUniqueId(), chatType); + playerService.setCooldown(player.getUniqueId(), chatType); return true; } } diff --git a/src/main/java/com/loki/lochat/core/filter/FilterResult.java b/src/main/java/com/loki/lochat/core/filter/FilterResult.java new file mode 100644 index 0000000..a1030ac --- /dev/null +++ b/src/main/java/com/loki/lochat/core/filter/FilterResult.java @@ -0,0 +1,20 @@ +package com.loki.lochat.core.filter; + +public record FilterResult(boolean allowed, String filteredMessage, String blockReason) { + + public static FilterResult ok(String message) { + return new FilterResult(true, message, null); + } + + public static FilterResult blocked(String reason) { + return new FilterResult(false, null, reason); + } + + public boolean isAllowed() { + return allowed; + } + + public String getMessage() { + return filteredMessage; + } +} diff --git a/src/main/java/com/loki/lochat/core/filter/filters/CharacterFilter.java b/src/main/java/com/loki/lochat/core/filter/filters/CharacterFilter.java new file mode 100644 index 0000000..ade733a --- /dev/null +++ b/src/main/java/com/loki/lochat/core/filter/filters/CharacterFilter.java @@ -0,0 +1,27 @@ +package com.loki.lochat.core.filter.filters; + +import org.bukkit.configuration.file.FileConfiguration; + +import java.util.regex.Pattern; + +public class CharacterFilter { + private final boolean enabled; + private final int maxRepeating; + + public CharacterFilter(FileConfiguration config) { + this.enabled = config.getBoolean("filters.repeating-chars.enabled", true); + this.maxRepeating = config.getInt("filters.repeating-chars.max", 3); + } + + public String filter(String message) { + if (!enabled || maxRepeating <= 0) { + return message; + } + + Pattern pattern = Pattern.compile("(.)\\1{" + maxRepeating + ",}"); + return pattern.matcher(message).replaceAll(m -> { + String repeated = m.group(1); + return repeated.repeat(maxRepeating); + }); + } +} diff --git a/src/main/java/com/loki/lochat/core/filter/filters/FloodFilter.java b/src/main/java/com/loki/lochat/core/filter/filters/FloodFilter.java new file mode 100644 index 0000000..fccf0dc --- /dev/null +++ b/src/main/java/com/loki/lochat/core/filter/filters/FloodFilter.java @@ -0,0 +1,51 @@ +package com.loki.lochat.core.filter.filters; + +import com.loki.lochat.core.filter.FilterResult; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class FloodFilter { + private final FileConfiguration config; + private final Map> floodTracker = new ConcurrentHashMap<>(); + + public FloodFilter(FileConfiguration config) { + this.config = config; + } + + public FilterResult filter(Player player) { + if (player.hasPermission("lochat.bypass.flood")) { + return FilterResult.ok(null); + } + + int maxMessages = config.getInt("filters.flood.max-messages", 5); + int timePeriod = config.getInt("filters.flood.time-period", 10); + long now = System.currentTimeMillis(); + long windowMs = timePeriod * 1000L; + + Deque timestamps = floodTracker.computeIfAbsent( + player.getUniqueId(), k -> new ArrayDeque<>()); + + // Удаляем старые timestamp'ы + while (!timestamps.isEmpty() && now - timestamps.peekFirst() > windowMs) { + timestamps.pollFirst(); + } + + if (timestamps.size() >= maxMessages) { + return FilterResult.blocked(config.getString("filters.flood.block-message", + "&#CF6679Не флудите!")); + } + + timestamps.addLast(now); + return FilterResult.ok(null); + } + + public void clearPlayer(UUID uuid) { + floodTracker.remove(uuid); + } +} diff --git a/src/main/java/com/loki/lochat/core/filter/filters/SpamFilter.java b/src/main/java/com/loki/lochat/core/filter/filters/SpamFilter.java new file mode 100644 index 0000000..e3e3df0 --- /dev/null +++ b/src/main/java/com/loki/lochat/core/filter/filters/SpamFilter.java @@ -0,0 +1,83 @@ +package com.loki.lochat.core.filter.filters; + +import com.loki.lochat.core.filter.FilterResult; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class SpamFilter { + private final FileConfiguration config; + private final Map> spamTracker = new ConcurrentHashMap<>(); + + public SpamFilter(FileConfiguration config) { + this.config = config; + } + + public FilterResult filter(Player player, String message) { + if (player.hasPermission("lochat.bypass.spam")) { + return FilterResult.ok(message); + } + + int maxSimilar = config.getInt("filters.spam.max-similar-messages", 3); + int threshold = config.getInt("filters.spam.similarity-threshold", 80); + + Deque history = spamTracker.computeIfAbsent( + player.getUniqueId(), k -> new ArrayDeque<>()); + + // Считаем похожие сообщения + long similarCount = history.stream() + .filter(prev -> similarity(prev, message) >= threshold) + .count(); + + if (similarCount >= maxSimilar) { + return FilterResult.blocked(config.getString("filters.spam.block-message", + "&#CF6679Не отправляйте одинаковые сообщения")); + } + + // Добавляем в историю (храним последние 10) + history.addLast(message); + if (history.size() > 10) history.pollFirst(); + + return FilterResult.ok(null); + } + + private int similarity(String a, String b) { + if (a.equals(b)) return 100; + int maxLen = Math.max(a.length(), b.length()); + if (maxLen == 0) return 100; + + int dist = levenshtein( + a.toLowerCase().substring(0, Math.min(a.length(), 50)), + b.toLowerCase().substring(0, Math.min(b.length(), 50))); + + return 100 - (dist * 100 / maxLen); + } + + private int levenshtein(String a, String b) { + int[] dp = new int[b.length() + 1]; + for (int j = 0; j <= b.length(); j++) dp[j] = j; + + for (int i = 1; i <= a.length(); i++) { + int prev = dp[0]; + dp[0] = i; + for (int j = 1; j <= b.length(); j++) { + int temp = dp[j]; + dp[j] = a.charAt(i - 1) == b.charAt(j - 1) + ? prev + : 1 + Math.min(prev, Math.min(dp[j], dp[j - 1])); + prev = temp; + } + } + + return dp[b.length()]; + } + + public void clearPlayer(UUID uuid) { + spamTracker.remove(uuid); + } +} diff --git a/src/main/java/com/loki/lochat/core/filter/filters/SwearFilter.java b/src/main/java/com/loki/lochat/core/filter/filters/SwearFilter.java new file mode 100644 index 0000000..af3b5fb --- /dev/null +++ b/src/main/java/com/loki/lochat/core/filter/filters/SwearFilter.java @@ -0,0 +1,72 @@ +package com.loki.lochat.core.filter.filters; + +import com.loki.lochat.core.filter.FilterResult; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Pattern; + +public class SwearFilter { + private final boolean enabled; + private final boolean replaceMode; + private final String replacement; + private final Set swearPatterns = new HashSet<>(); + + public SwearFilter(FileConfiguration config, JavaPlugin plugin) { + this.enabled = config.getBoolean("filters.swear.enabled", true); + this.replaceMode = config.getBoolean("filters.swear.replace", false); + this.replacement = config.getString("filters.swear.replacement", "***"); + + if (enabled) { + loadWordList(plugin); + } + } + + private void loadWordList(JavaPlugin plugin) { + try (InputStream is = plugin.getResource("badwords.txt"); + BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + + reader.lines() + .map(String::trim) + .filter(line -> !line.isEmpty() && !line.startsWith("#")) + .forEach(word -> swearPatterns.add( + Pattern.compile("\\b" + Pattern.quote(word) + "\\b", Pattern.CASE_INSENSITIVE) + )); + } catch (Exception e) { + plugin.getLogger().warning("Failed to load badwords.txt: " + e.getMessage()); + } + } + + public FilterResult filter(Player player, String message) { + if (!enabled || player.hasPermission("lochat.bypass.swear")) { + return FilterResult.ok(message); + } + + for (Pattern pattern : swearPatterns) { + if (pattern.matcher(message).find()) { + if (replaceMode) { + return FilterResult.ok(replaceSwear(message)); + } + return FilterResult.blocked("Сообщение содержит запрещенные слова"); + } + } + + return FilterResult.ok(message); + } + + private String replaceSwear(String message) { + String result = message; + for (Pattern pattern : swearPatterns) { + result = pattern.matcher(result).replaceAll(replacement); + } + return result; + } +} diff --git a/src/main/java/com/loki/lochat/core/filter/filters/UrlFilter.java b/src/main/java/com/loki/lochat/core/filter/filters/UrlFilter.java new file mode 100644 index 0000000..6515475 --- /dev/null +++ b/src/main/java/com/loki/lochat/core/filter/filters/UrlFilter.java @@ -0,0 +1,106 @@ +package com.loki.lochat.core.filter.filters; + +import com.loki.lochat.core.filter.FilterResult; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.entity.Player; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class UrlFilter { + private static final Pattern URL_PATTERN = Pattern.compile( + "(https?://)?([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?", Pattern.CASE_INSENSITIVE + ); + + private static final Pattern IP_PATTERN = Pattern.compile( + "\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b" + ); + + private static final Pattern HIDDEN_URL_PATTERN = Pattern.compile( + "(?i)(h\\s*t\\s*t\\s*p|w\\s*w\\s*w|d\\s*o\\s*t\\s*c\\s*o\\s*m)" + ); + + private final FileConfiguration config; + private final Set whitelistedDomains; + private final Set blacklistedDomains; + + public UrlFilter(FileConfiguration config) { + this.config = config; + this.whitelistedDomains = new HashSet<>(config.getStringList("filters.advertising.whitelist")); + this.blacklistedDomains = new HashSet<>(config.getStringList("filters.advertising.blacklist")); + } + + public FilterResult filterUrls(Player player, String message) { + if (player.hasPermission("lochat.bypass.urlfilter")) { + return FilterResult.ok(message); + } + + Matcher matcher = URL_PATTERN.matcher(message); + while (matcher.find()) { + String domain = extractDomain(matcher.group()); + + if (!whitelistedDomains.isEmpty()) { + boolean ok = whitelistedDomains.stream() + .anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase())); + if (!ok) { + return FilterResult.blocked(config.getString("filters.url.blocked-message", + "&#CF6679Ссылки запрещены!")); + } + } + + if (!blacklistedDomains.isEmpty()) { + boolean bad = blacklistedDomains.stream() + .anyMatch(d -> domain.toLowerCase().contains(d.toLowerCase())); + if (bad) { + return FilterResult.blocked(config.getString("filters.url.blocked-message", + "&#CF6679Эта ссылка запрещена!")); + } + } + } + + return FilterResult.ok(message); + } + + public FilterResult filterIPs(Player player, String message) { + if (player.hasPermission("lochat.bypass.ipfilter")) { + return FilterResult.ok(message); + } + + Matcher matcher = IP_PATTERN.matcher(message); + if (matcher.find()) { + if (config.getBoolean("filters.ip.block", false)) { + return FilterResult.blocked(config.getString("filters.ip.blocked-message", + "&#CF6679IP адреса запрещены!")); + } + return FilterResult.ok(matcher.replaceAll( + config.getString("filters.ip.replacement", "[IP скрыт]"))); + } + + return FilterResult.ok(message); + } + + public FilterResult filterHiddenUrls(Player player, String message) { + if (!config.getBoolean("filters.advertising.block-hidden-urls", true)) { + return FilterResult.ok(message); + } + if (player.hasPermission("lochat.bypass.hiddenurls")) { + return FilterResult.ok(message); + } + + if (HIDDEN_URL_PATTERN.matcher(message).find()) { + return FilterResult.blocked(config.getString("filters.advertising.blocked-message", + "&#CF6679Реклама запрещена!")); + } + + return FilterResult.ok(message); + } + + private String extractDomain(String url) { + url = url.replaceFirst("^https?://", ""); + int slash = url.indexOf('/'); + return slash != -1 ? url.substring(0, slash) : url; + } +} diff --git a/src/main/java/com/loki/lochat/core/registry/ServiceRegistry.java b/src/main/java/com/loki/lochat/core/registry/ServiceRegistry.java index 746967b..967722e 100644 --- a/src/main/java/com/loki/lochat/core/registry/ServiceRegistry.java +++ b/src/main/java/com/loki/lochat/core/registry/ServiceRegistry.java @@ -27,19 +27,16 @@ public class ServiceRegistry { register(ChatService.class, new ChatServiceImpl(plugin, this)); register(MuteService.class, new MuteServiceImpl(plugin)); - // CooldownService регистрируем раньше PlayerDataService, чтобы передать его как зависимость - CooldownServiceImpl cooldownService = new CooldownServiceImpl(); - register(CooldownService.class, cooldownService); + // ✅ NEW: Объединённый PlayerService (Cooldown + PlayerData) + PlayerService playerService = new PlayerServiceImpl(plugin); + register(PlayerService.class, playerService); register(MessageService.class, new MessageServiceImpl(plugin, this)); - // Передаём тот же экземпляр CooldownService, а не новый — иначе очистка при выходе не работает - register(PlayerDataService.class, new PlayerDataServiceImpl(plugin, cooldownService)); - - register(PMService.class, new PMServiceImpl()); - register(IgnoreService.class, new IgnoreServiceImpl(plugin)); - SpyService spyService = new SpyServiceImpl(messageConfig); - register(SpyService.class, spyService); + // ✅ NEW: Объединённый MessagingService (PM + Spy + Ignore) + MessagingService messagingService = new MessagingServiceImpl(plugin, messageConfig); + register(MessagingService.class, messagingService); + register(MentionService.class, new MentionServiceImpl(configManager)); register(NickService.class, new NickServiceImpl(plugin)); register(PunishmentService.class, new PunishmentServiceImpl(plugin, configManager.getMessagesConfig())); @@ -48,6 +45,14 @@ public class ServiceRegistry { public void register(Class serviceClass, T implementation) { services.put(serviceClass, implementation); } + + /** + * Регистрирует сервис с возможностью приведения типов (для обратной совместимости) + */ + @SuppressWarnings("unchecked") + public void registerCompat(Class serviceClass, Object implementation) { + services.put(serviceClass, implementation); + } @SuppressWarnings("unchecked") public T get(Class serviceClass) { diff --git a/src/main/java/com/loki/lochat/core/service/ChatServiceImpl.java b/src/main/java/com/loki/lochat/core/service/ChatServiceImpl.java index 8b4acbd..cfcbce1 100644 --- a/src/main/java/com/loki/lochat/core/service/ChatServiceImpl.java +++ b/src/main/java/com/loki/lochat/core/service/ChatServiceImpl.java @@ -3,8 +3,8 @@ package com.loki.lochat.core.service; import com.loki.lochat.api.service.ChatService; import com.loki.lochat.core.registry.ServiceRegistry; import com.loki.lochat.renderer.EnhancedChatRenderer; -import com.loki.lochat.util.DistanceUtil; -import com.loki.lochat.util.FoliaUtil; +import com.loki.lochat.utils.DistanceUtil; +import com.loki.lochat.utils.FoliaUtil; import com.loki.lochat.utils.ChatFormatter; import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; diff --git a/src/main/java/com/loki/lochat/core/service/CooldownServiceImpl.java b/src/main/java/com/loki/lochat/core/service/CooldownServiceImpl.java deleted file mode 100644 index b2afcd3..0000000 --- a/src/main/java/com/loki/lochat/core/service/CooldownServiceImpl.java +++ /dev/null @@ -1,52 +0,0 @@ -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 globalCooldowns = new ConcurrentHashMap<>(); - private final Map localCooldowns = new ConcurrentHashMap<>(); - - @Override - public boolean isOnCooldown(UUID player, String type, int cooldownSeconds) { - Map 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 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 getCooldownMap(String type) { - return type.equals("global") ? globalCooldowns : localCooldowns; - } -} diff --git a/src/main/java/com/loki/lochat/core/service/IgnoreServiceImpl.java b/src/main/java/com/loki/lochat/core/service/IgnoreServiceImpl.java deleted file mode 100644 index e5cf033..0000000 --- a/src/main/java/com/loki/lochat/core/service/IgnoreServiceImpl.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.loki.lochat.core.service; - -import com.loki.lochat.api.service.IgnoreService; -import com.loki.lochat.util.FoliaUtil; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.plugin.java.JavaPlugin; - -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Реализация сервиса игнорирования - */ -public class IgnoreServiceImpl implements IgnoreService { - private final JavaPlugin plugin; - private final File ignoreFile; - private final Map> 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 ignoredList = config.getStringList(key); - Set 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> entry : ignoreMap.entrySet()) { - List 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 ignored = ignoreMap.get(player); - return ignored != null && ignored.contains(target); - } - - @Override - public boolean addIgnore(UUID player, UUID target) { - Set 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 ignored = ignoreMap.get(player); - if (ignored == null) return false; - boolean removed = ignored.remove(target); - if (removed) saveAsync(); - return removed; - } - - @Override - public Set getIgnoredPlayers(UUID player) { - return ignoreMap.getOrDefault(player, Collections.emptySet()); - } - - @Override - public int getIgnoredCount(UUID player) { - Set 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); - } -} diff --git a/src/main/java/com/loki/lochat/core/service/MessageServiceImpl.java b/src/main/java/com/loki/lochat/core/service/MessageServiceImpl.java index d736209..71b6ea5 100644 --- a/src/main/java/com/loki/lochat/core/service/MessageServiceImpl.java +++ b/src/main/java/com/loki/lochat/core/service/MessageServiceImpl.java @@ -1,7 +1,7 @@ package com.loki.lochat.core.service; import com.loki.lochat.api.filter.MessageFilter; -import com.loki.lochat.api.service.CooldownService; +import com.loki.lochat.api.service.PlayerService; import com.loki.lochat.api.service.MessageService; import com.loki.lochat.api.service.MuteService; import com.loki.lochat.core.filter.CooldownFilter; @@ -26,7 +26,7 @@ public class MessageServiceImpl implements MessageService { // Регистрируем фильтры в порядке применения // MuteFilter теперь получает plugin для доступа к HardcodedMessages filters.add(new MuteFilter(registry.get(MuteService.class), plugin)); - filters.add(new CooldownFilter(registry.get(CooldownService.class), plugin)); + filters.add(new CooldownFilter(registry.get(PlayerService.class), plugin)); } @Override diff --git a/src/main/java/com/loki/lochat/core/service/MessagingServiceImpl.java b/src/main/java/com/loki/lochat/core/service/MessagingServiceImpl.java new file mode 100644 index 0000000..aed7c78 --- /dev/null +++ b/src/main/java/com/loki/lochat/core/service/MessagingServiceImpl.java @@ -0,0 +1,219 @@ +package com.loki.lochat.core.service; + +import com.loki.lochat.api.service.MessagingService; +import com.loki.lochat.config.MessageConfig; +import com.loki.lochat.utils.FoliaUtil; +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.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Объединённая реализация сервиса общения + * Включает: PM, Spy, Ignore + */ +public class MessagingServiceImpl implements MessagingService { + + private final JavaPlugin plugin; + private final MessageConfig messageConfig; + private final File ignoreFile; + + // PM state + private final Map lastConversation = new ConcurrentHashMap<>(); + + // Spy state + private final Set spyEnabled = ConcurrentHashMap.newKeySet(); + + // Ignore state + private final Map> ignoreMap = new ConcurrentHashMap<>(); + + public MessagingServiceImpl(JavaPlugin plugin, MessageConfig messageConfig) { + this.plugin = plugin; + this.messageConfig = messageConfig; + this.ignoreFile = new File(plugin.getDataFolder(), "ignores.yml"); + load(); + } + + // ========== PM Implementation ========== + + @Override + public void setLastConversation(UUID player, UUID target) { + lastConversation.put(player, target); + } + + @Override + public Optional 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); + } + + // ========== Spy Implementation ========== + + @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); + } + + // ========== Ignore Implementation ========== + + @Override + public boolean isIgnoring(UUID player, UUID target) { + Set ignored = ignoreMap.get(player); + return ignored != null && ignored.contains(target); + } + + @Override + public boolean addIgnore(UUID player, UUID target) { + Set 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 ignored = ignoreMap.get(player); + if (ignored == null) return false; + boolean removed = ignored.remove(target); + if (removed) saveAsync(); + return removed; + } + + @Override + public Set getIgnoredPlayers(UUID player) { + return ignoreMap.getOrDefault(player, Collections.emptySet()); + } + + @Override + public int getIgnoredCount(UUID player) { + Set ignored = ignoreMap.get(player); + return ignored == null ? 0 : ignored.size(); + } + + @Override + public void clearIgnores(UUID player) { + ignoreMap.remove(player); + saveAsync(); + } + + // ========== Persistence ========== + + @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 ignoredList = config.getStringList(key); + Set ignored = ConcurrentHashMap.newKeySet(); + + for (String ignoredStr : ignoredList) { + try { + ignored.add(UUID.fromString(ignoredStr)); + } catch (Exception e) { + // Ignore invalid UUID + } + } + + if (!ignored.isEmpty()) { + ignoreMap.put(uuid, ignored); + } + } catch (Exception e) { + // Ignore invalid entry + } + } + } + + @Override + public void save() { + YamlConfiguration config = new YamlConfiguration(); + + for (Map.Entry> entry : ignoreMap.entrySet()) { + List 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()); + } + } + + private void saveAsync() { + FoliaUtil.runAsync(plugin, this::save); + } +} diff --git a/src/main/java/com/loki/lochat/core/service/MuteServiceImpl.java b/src/main/java/com/loki/lochat/core/service/MuteServiceImpl.java index 434a7f0..ed8a748 100644 --- a/src/main/java/com/loki/lochat/core/service/MuteServiceImpl.java +++ b/src/main/java/com/loki/lochat/core/service/MuteServiceImpl.java @@ -1,52 +1,66 @@ 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.util.FoliaUtil; +import com.loki.lochat.utils.FoliaUtil; import com.loki.lochat.utils.TimeFormatter; +import org.bukkit.Bukkit; 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; +/** + * Сервис мутов с интеграцией Simple Voice Chat + */ public class MuteServiceImpl implements MuteService { private final JavaPlugin plugin; - private final Map mutes = new ConcurrentHashMap<>(); - private final MuteHistoryManager historyManager; - private final File dataFile; - private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + private final Map mutes = new HashMap<>(); + private final Map> history = new HashMap<>(); + private Object voicechatService; // Используем Object для опциональной зависимости public MuteServiceImpl(JavaPlugin plugin) { this.plugin = plugin; - this.dataFile = new File(plugin.getDataFolder(), "mutes.json"); - this.historyManager = new MuteHistoryManager(new File(plugin.getDataFolder(), "mute-history.json")); + initVoiceChat(); load(); } + private void initVoiceChat() { + if (Bukkit.getPluginManager().isPluginEnabled("voicechat")) { + try { + // Проверяем наличие класса через рефлексию + Class.forName("de.maxhenkel.voicechat.api.BukkitVoicechatService"); + voicechatService = Bukkit.getServicesManager().load( + Class.forName("de.maxhenkel.voicechat.api.BukkitVoicechatService") + ); + if (voicechatService != null) { + plugin.getLogger().info("Simple Voice Chat integration enabled!"); + } + } catch (ClassNotFoundException e) { + plugin.getLogger().info("VoiceChat API not found, voice mute disabled"); + } catch (Exception e) { + plugin.getLogger().warning("Failed to hook into Simple Voice Chat: " + e.getMessage()); + } + } + } + @Override public void mute(UUID uuid, String playerName, long duration, String reason, String mutedBy) { - if (uuid == null) { - plugin.getLogger().warning("Attempted to mute player with null UUID"); - return; - } - - if (playerName == null || playerName.isEmpty()) { - plugin.getLogger().warning("Attempted to mute player with null or empty name"); + if (uuid == null || playerName == null || playerName.isEmpty()) { + plugin.getLogger().warning("Attempted to mute player with invalid data"); return; } long endTime = duration > 0 ? System.currentTimeMillis() + duration : 0; MuteData data = new MuteData(uuid, playerName, endTime, reason, mutedBy); + mutes.put(uuid, data); - - historyManager.addEntry(uuid, playerName, duration, reason, mutedBy); + addToHistory(uuid, playerName, duration, reason, mutedBy); + + // Мутим в голосовом чате + muteVoiceChat(uuid, true); + saveAsync(); plugin.getLogger().info("Player " + playerName + " muted by " + mutedBy + @@ -63,7 +77,11 @@ public class MuteServiceImpl implements MuteService { MuteData removed = mutes.remove(uuid); if (removed != null) { - historyManager.updateUnmute(uuid, unmutedBy); + updateUnmuteInHistory(uuid, unmutedBy); + + // Размутим в голосовом чате + muteVoiceChat(uuid, false); + saveAsync(); plugin.getLogger().info("Player " + removed.getPlayerName() + " unmuted by " + unmutedBy); return true; @@ -80,13 +98,10 @@ public class MuteServiceImpl implements MuteService { if (!isExpired(data)) return true; - // Атомарно удаляем только если значение не изменилось между get() и remove(). - // ConcurrentHashMap.remove(key, value) удаляет запись лишь когда она точно та же. - boolean removed = mutes.remove(uuid, data); - if (removed) { - saveAsync(); - plugin.getLogger().info("Mute expired for player " + data.getPlayerName()); - } + mutes.remove(uuid); + muteVoiceChat(uuid, false); + saveAsync(); + plugin.getLogger().info("Mute expired for player " + data.getPlayerName()); return false; } @@ -98,12 +113,8 @@ public class MuteServiceImpl implements MuteService { @Override public long getRemainingTime(UUID uuid) { MuteData data = mutes.get(uuid); - if (data == null) { - return 0; - } - if (data.getEndTime() == 0) { - return -1; - } + if (data == null) return 0; + if (data.getEndTime() == 0) return -1; return Math.max(0, data.getEndTime() - System.currentTimeMillis()); } @@ -119,9 +130,7 @@ public class MuteServiceImpl implements MuteService { @Override public long getMaxDuration(Player player) { - if (player.hasPermission("lochat.mute.dur.perm")) { - return 0; - } + 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) { @@ -129,54 +138,44 @@ public class MuteServiceImpl implements MuteService { 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; - } + 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; - } + if (maxDuration == -1) return false; + if (maxDuration == 0) return true; return duration <= maxDuration; } @Override public List getPlayerHistory(UUID uuid) { - return historyManager.getHistory(uuid); + return history.getOrDefault(uuid, new ArrayList<>()); } @Override public List getMutesByOperator(String operatorName) { List result = new ArrayList<>(); - mutes.keySet().forEach(uuid -> { - historyManager.getHistory(uuid).stream() - .filter(entry -> entry.mutedBy != null && entry.mutedBy.equalsIgnoreCase(operatorName)) - .forEach(result::add); - }); + history.values().forEach(entries -> + entries.stream() + .filter(e -> e.mutedBy != null && e.mutedBy.equalsIgnoreCase(operatorName)) + .forEach(result::add) + ); result.sort((a, b) -> Long.compare(b.mutedAt, a.mutedAt)); return result; } @Override public UUID getUUIDByName(String name) { - for (UUID uuid : mutes.keySet()) { - for (MuteData.MuteHistoryEntry entry : historyManager.getHistory(uuid)) { - if (entry.playerName != null && entry.playerName.equalsIgnoreCase(name)) { - return uuid; + for (Map.Entry> entry : history.entrySet()) { + for (MuteData.MuteHistoryEntry e : entry.getValue()) { + if (e.playerName != null && e.playerName.equalsIgnoreCase(name)) { + return entry.getKey(); } } } @@ -185,7 +184,7 @@ public class MuteServiceImpl implements MuteService { @Override public Map getActiveMutes() { - mutes.entrySet().removeIf(entry -> isExpired(entry.getValue())); + mutes.entrySet().removeIf(e -> isExpired(e.getValue())); return new HashMap<>(mutes); } @@ -200,54 +199,60 @@ public class MuteServiceImpl implements MuteService { @Override public void save() { + // TODO: Implement save to file + } + + // ========== Voice Chat Integration ========== + + private void muteVoiceChat(UUID uuid, boolean mute) { + if (voicechatService == null) return; + try { - ensureDataFolderExists(); - saveMutes(); - historyManager.save(); - } catch (IOException e) { - plugin.getLogger().warning("Error saving mutes: " + e.getMessage()); + Player player = Bukkit.getPlayer(uuid); + if (player != null && player.isOnline()) { + // Используем рефлексию для вызова метода + voicechatService.getClass() + .getMethod("getVoicechat") + .invoke(voicechatService) + .getClass() + .getMethod("setMuted", UUID.class, boolean.class) + .invoke(voicechatService.getClass().getMethod("getVoicechat").invoke(voicechatService), + player.getUniqueId(), mute); + } + } catch (Exception e) { + plugin.getLogger().warning("Failed to " + (mute ? "mute" : "unmute") + + " player in voice chat: " + e.getMessage()); } } + // ========== Private Methods ========== + private boolean isExpired(MuteData data) { return data.getEndTime() > 0 && System.currentTimeMillis() > data.getEndTime(); } - private void ensureDataFolderExists() { - if (!dataFile.getParentFile().exists()) { - dataFile.getParentFile().mkdirs(); - } + private void addToHistory(UUID uuid, String playerName, long duration, String reason, String mutedBy) { + List entries = history.computeIfAbsent(uuid, k -> new ArrayList<>()); + entries.add(new MuteData.MuteHistoryEntry( + playerName, duration, reason, mutedBy, System.currentTimeMillis(), false, null, 0 + )); } - private void saveMutes() throws IOException { - Map toSave = new ConcurrentHashMap<>(); - mutes.forEach((uuid, data) -> toSave.put(uuid.toString(), data)); - - try (Writer writer = new FileWriter(dataFile)) { - gson.toJson(toSave, writer); + private void updateUnmuteInHistory(UUID uuid, String unmutedBy) { + List entries = history.get(uuid); + if (entries != null && !entries.isEmpty()) { + MuteData.MuteHistoryEntry last = entries.get(entries.size() - 1); + if (!last.unmuted) { + entries.set(entries.size() - 1, new MuteData.MuteHistoryEntry( + last.playerName, last.duration, last.reason, last.mutedBy, last.mutedAt, + true, unmutedBy, System.currentTimeMillis() + )); + } } } private void load() { - if (!dataFile.exists()) { - return; - } - - try (Reader reader = new FileReader(dataFile)) { - Type type = new TypeToken>() { - }.getType(); - Map 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("Error loading mutes: " + e.getMessage()); - } + // TODO: Implement load from file } private void saveAsync() { diff --git a/src/main/java/com/loki/lochat/core/service/PMServiceImpl.java b/src/main/java/com/loki/lochat/core/service/PMServiceImpl.java deleted file mode 100644 index 84a2f71..0000000 --- a/src/main/java/com/loki/lochat/core/service/PMServiceImpl.java +++ /dev/null @@ -1,35 +0,0 @@ -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 lastConversation = new ConcurrentHashMap<>(); - - @Override - public void setLastConversation(UUID player, UUID target) { - lastConversation.put(player, target); - } - - @Override - public Optional 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); - } -} diff --git a/src/main/java/com/loki/lochat/core/service/PlayerDataServiceImpl.java b/src/main/java/com/loki/lochat/core/service/PlayerServiceImpl.java similarity index 57% rename from src/main/java/com/loki/lochat/core/service/PlayerDataServiceImpl.java rename to src/main/java/com/loki/lochat/core/service/PlayerServiceImpl.java index 35de771..060f846 100644 --- a/src/main/java/com/loki/lochat/core/service/PlayerDataServiceImpl.java +++ b/src/main/java/com/loki/lochat/core/service/PlayerServiceImpl.java @@ -1,8 +1,7 @@ package com.loki.lochat.core.service; -import com.loki.lochat.api.service.CooldownService; -import com.loki.lochat.api.service.PlayerDataService; -import com.loki.lochat.util.FoliaUtil; +import com.loki.lochat.api.service.PlayerService; +import com.loki.lochat.utils.FoliaUtil; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.java.JavaPlugin; @@ -14,61 +13,100 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; /** - * Реализация сервиса данных игроков. - * Хранит и сохраняет статистику сообщений в statistics.yml. + * Объединённая реализация сервиса данных игроков + * Включает: Cooldown, PlayerData (статистика) */ -public class PlayerDataServiceImpl implements PlayerDataService { - +public class PlayerServiceImpl implements PlayerService { + private final JavaPlugin plugin; - private final CooldownService cooldownService; - - // Счётчики сообщений per-player (сбрасываются при выходе, сохраняются в файл) + + // Cooldown state + private final Map globalCooldowns = new ConcurrentHashMap<>(); + private final Map localCooldowns = new ConcurrentHashMap<>(); + + // Statistics state private final Map playerMessages = new ConcurrentHashMap<>(); - // Глобальные счётчики по типу чата - private final AtomicLong globalChatCount = new AtomicLong(0); - private final AtomicLong localChatCount = new AtomicLong(0); - private final AtomicLong pmCount = new AtomicLong(0); - private final AtomicLong totalMessages = new AtomicLong(0); - + private final AtomicLong globalChatCount = new AtomicLong(0); + private final AtomicLong localChatCount = new AtomicLong(0); + private final AtomicLong pmCount = new AtomicLong(0); + private final AtomicLong totalMessages = new AtomicLong(0); + private final File statsFile; private YamlConfiguration stats; - - public PlayerDataServiceImpl(JavaPlugin plugin, CooldownService cooldownService) { + + public PlayerServiceImpl(JavaPlugin plugin) { this.plugin = plugin; - this.cooldownService = cooldownService; this.statsFile = new File(plugin.getDataFolder(), "data/statistics.yml"); load(); } - - // ── Публичные методы статистики ──────────────────────────────────────────── - - /** Вызывать при каждом сообщении в чате */ + + // ========== Cooldown Implementation ========== + + @Override + public boolean isOnCooldown(UUID player, String type, int cooldownSeconds) { + Map 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 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 getCooldownMap(String type) { + return type.equals("global") ? globalCooldowns : localCooldowns; + } + + // ========== Statistics Implementation ========== + + @Override public void recordMessage(UUID player, String chatType) { playerMessages.compute(player, (k, v) -> (v == null ? 0L : v) + 1L); totalMessages.incrementAndGet(); switch (chatType) { case "global" -> globalChatCount.incrementAndGet(); - case "local" -> localChatCount.incrementAndGet(); - case "pm" -> pmCount.incrementAndGet(); + case "local" -> localChatCount.incrementAndGet(); + case "pm" -> pmCount.incrementAndGet(); } } - + + @Override public long getPlayerMessages(UUID player) { return playerMessages.getOrDefault(player, 0L); } - - // ── PlayerDataService ────────────────────────────────────────────────────── - + @Override public void clearPlayerData(UUID player) { - cooldownService.removeCooldown(player); + removeCooldown(player); // Сохраняем статистику игрока в файл при выходе long msgs = playerMessages.getOrDefault(player, 0L); if (msgs > 0) { FoliaUtil.runAsync(plugin, () -> persistPlayerMessages(player, msgs)); } } - + @Override public void saveAll() { try { @@ -88,9 +126,9 @@ public class PlayerDataServiceImpl implements PlayerDataService { plugin.getLogger().warning("[LoChat] Failed to save statistics: " + e.getMessage()); } } - - // ── Внутренние методы ────────────────────────────────────────────────────── - + + // ========== Private Methods ========== + private void persistPlayerMessages(UUID uuid, long count) { try { String path = "top-players.by-messages." + uuid; @@ -103,7 +141,7 @@ public class PlayerDataServiceImpl implements PlayerDataService { plugin.getLogger().warning("[LoChat] Failed to persist player stats: " + e.getMessage()); } } - + private void load() { ensureDir(); if (!statsFile.exists()) { @@ -111,7 +149,7 @@ public class PlayerDataServiceImpl implements PlayerDataService { } stats = YamlConfiguration.loadConfiguration(statsFile); } - + private void ensureDir() { File dir = statsFile.getParentFile(); if (!dir.exists()) dir.mkdirs(); diff --git a/src/main/java/com/loki/lochat/core/service/PunishmentServiceImpl.java b/src/main/java/com/loki/lochat/core/service/PunishmentServiceImpl.java index b6e9aa3..030b86c 100644 --- a/src/main/java/com/loki/lochat/core/service/PunishmentServiceImpl.java +++ b/src/main/java/com/loki/lochat/core/service/PunishmentServiceImpl.java @@ -7,7 +7,7 @@ import com.loki.lochat.api.service.PunishmentService; import com.loki.lochat.config.MessagesConfig; import com.loki.lochat.data.model.BanRecord; import com.loki.lochat.data.model.WarnEntry; -import com.loki.lochat.util.FoliaUtil; +import com.loki.lochat.utils.FoliaUtil; import com.loki.lochat.utils.ChatFormatter; import com.loki.lochat.utils.TimeFormatter; import net.kyori.adventure.text.Component; diff --git a/src/main/java/com/loki/lochat/core/service/SpyServiceImpl.java b/src/main/java/com/loki/lochat/core/service/SpyServiceImpl.java deleted file mode 100644 index 65c48ee..0000000 --- a/src/main/java/com/loki/lochat/core/service/SpyServiceImpl.java +++ /dev/null @@ -1,80 +0,0 @@ -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 java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; - -/** - * Реализация сервиса шпионажа за чатом - */ -public class SpyServiceImpl implements SpyService { - private final MessageConfig messageConfig; - private final Set spyEnabled = ConcurrentHashMap.newKeySet(); - - public SpyServiceImpl(MessageConfig messageConfig) { - 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); - } -} diff --git a/src/main/java/com/loki/lochat/core/service/mute/MuteDataStorage.java b/src/main/java/com/loki/lochat/core/service/mute/MuteDataStorage.java new file mode 100644 index 0000000..2986777 --- /dev/null +++ b/src/main/java/com/loki/lochat/core/service/mute/MuteDataStorage.java @@ -0,0 +1,117 @@ +package com.loki.lochat.core.service.mute; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.loki.lochat.data.model.MuteData; +import com.loki.lochat.utils.FoliaUtil; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.*; +import java.lang.reflect.Type; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Хранилище данных о мутах + */ +public class MuteDataStorage { + + private final JavaPlugin plugin; + private final Map mutes = new ConcurrentHashMap<>(); + private final File dataFile; + private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + public MuteDataStorage(JavaPlugin plugin) { + this.plugin = plugin; + this.dataFile = new File(plugin.getDataFolder(), "mutes.json"); + } + + public void addMute(UUID uuid, MuteData data) { + mutes.put(uuid, data); + saveAsync(); + } + + public MuteData removeMute(UUID uuid) { + MuteData removed = mutes.remove(uuid); + if (removed != null) { + saveAsync(); + } + return removed; + } + + public boolean isMuted(UUID uuid) { + if (uuid == null) return false; + + MuteData data = mutes.get(uuid); + if (data == null) return false; + + if (!isExpired(data)) return true; + + boolean removed = mutes.remove(uuid, data); + if (removed) { + saveAsync(); + plugin.getLogger().info("Mute expired for player " + data.getPlayerName()); + } + return false; + } + + public MuteData getMute(UUID uuid) { + return mutes.get(uuid); + } + + public Map getAllMutes() { + mutes.entrySet().removeIf(entry -> isExpired(entry.getValue())); + return new HashMap<>(mutes); + } + + private boolean isExpired(MuteData data) { + return data.getEndTime() > 0 && System.currentTimeMillis() > data.getEndTime(); + } + + public void load() { + if (!dataFile.exists()) { + return; + } + + try (Reader reader = new FileReader(dataFile)) { + Type type = new TypeToken>() {}.getType(); + Map 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("Error loading mutes: " + e.getMessage()); + } + } + + public void save() { + try { + ensureDataFolderExists(); + + Map 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("Error saving mutes: " + e.getMessage()); + } + } + + private void saveAsync() { + FoliaUtil.runAsync(plugin, this::save); + } + + private void ensureDataFolderExists() { + if (!dataFile.getParentFile().exists()) { + dataFile.getParentFile().mkdirs(); + } + } +} diff --git a/src/main/java/com/loki/lochat/core/service/mute/MutePermissionChecker.java b/src/main/java/com/loki/lochat/core/service/mute/MutePermissionChecker.java new file mode 100644 index 0000000..e66a53f --- /dev/null +++ b/src/main/java/com/loki/lochat/core/service/mute/MutePermissionChecker.java @@ -0,0 +1,48 @@ +package com.loki.lochat.core.service.mute; + +import com.loki.lochat.utils.TimeFormatter; +import org.bukkit.entity.Player; + +/** + * Проверка прав на муты + */ +public class MutePermissionChecker { + + private static final String[] DURATIONS = { + "30d", "14d", "7d", "3d", "1d", "12h", "6h", "1h", "30m", "10m" + }; + + public long getMaxDuration(Player player) { + if (player.hasPermission("lochat.mute.dur.perm")) { + return 0; // Permanent + } + + for (String dur : DURATIONS) { + if (player.hasPermission("lochat.mute.dur." + dur)) { + return TimeFormatter.parse(dur); + } + } + + return -1; // No permission + } + + public boolean canMuteForDuration(Player player, long duration) { + if (player.hasPermission("lochat.mute.dur.perm")) { + return true; + } + + if (duration == 0) { + return false; // Permanent mute requires special permission + } + + long maxDuration = getMaxDuration(player); + if (maxDuration == -1) { + return false; // No mute permission + } + if (maxDuration == 0) { + return true; // Can mute permanently + } + + return duration <= maxDuration; + } +} diff --git a/src/main/java/com/loki/lochat/gradient/GradientModule.java b/src/main/java/com/loki/lochat/gradient/GradientModule.java index 29b1f34..4e89c86 100644 --- a/src/main/java/com/loki/lochat/gradient/GradientModule.java +++ b/src/main/java/com/loki/lochat/gradient/GradientModule.java @@ -7,10 +7,10 @@ import com.loki.lochat.gradient.config.GradientConfig; import com.loki.lochat.gradient.config.GradientMessages; import com.loki.lochat.gradient.data.GradientDataManager; 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.GradientUtil; +import com.loki.lochat.gradient.service.GradientService; +import com.loki.lochat.gradient.service.PrefixService; import org.black_ixx.playerpoints.PlayerPoints; import org.black_ixx.playerpoints.PlayerPointsAPI; import org.bukkit.Bukkit; @@ -18,7 +18,7 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; /** - * Модуль градиентных ников (интегрированный LoPreff) + * Модуль градиентных ников - упрощенная версия */ public class GradientModule { @@ -26,21 +26,15 @@ public class GradientModule { private GradientConfig config; private GradientMessages messages; private GradientDataManager dataManager; - private GradientLuckPermsHook luckPermsHook; + private PrefixService prefixService; private PlayerPointsAPI playerPointsAPI; private boolean enabled; public GradientModule(JavaPlugin plugin) { this.plugin = plugin; this.enabled = false; - this.luckPermsHook = null; // Явно инициализируем как null } - /** - * Инициализация модуля - * - * @return true если модуль успешно инициализирован - */ public boolean init() { // Загружаем конфиги config = new GradientConfig(plugin); @@ -51,7 +45,11 @@ public class GradientModule { return false; } - // Проверяем PlayerPoints (опционально теперь) + // Инициализация сервисов + dataManager = new GradientDataManager(plugin, config); + prefixService = new PrefixService(config); + + // PlayerPoints (опционально) if (Bukkit.getPluginManager().isPluginEnabled("PlayerPoints")) { playerPointsAPI = PlayerPoints.getInstance().getAPI(); plugin.getLogger().info("Gradient: PlayerPoints подключен!"); @@ -59,22 +57,13 @@ public class GradientModule { plugin.getLogger().warning("Gradient: PlayerPoints не найден. Покупки будут бесплатными."); } - // Инициализация хранилища - dataManager = new GradientDataManager(plugin, config); - - // Подключение LuckPerms (опционально) - try { - if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) { - luckPermsHook = new GradientLuckPermsHook(plugin); - plugin.getLogger().info("Gradient: LuckPerms подключен!"); - } else { - plugin.getLogger().info("Gradient: LuckPerms не найден. Префиксы LuckPerms будут недоступны."); - } - } catch (NoClassDefFoundError e) { + // LuckPerms проверка + if (prefixService.isLuckPermsAvailable()) { + plugin.getLogger().info("Gradient: LuckPerms подключен!"); + } else { plugin.getLogger().info("Gradient: LuckPerms не найден. Префиксы LuckPerms будут недоступны."); } - // Регистрация команд registerCommands(); @@ -107,106 +96,37 @@ public class GradientModule { } } - /** - * Выключение модуля - */ public void shutdown() { if (dataManager != null) { dataManager.saveAll(); } } - /** - * Перезагрузка модуля - */ public void reload() { config.reload(); messages.reload(); } + // ========== Публичные методы для получения отформатированных имен ========== + /** - * Получает отформатированное имя игрока с градиентом и LuckPerms префиксом - * Градиент применяется на всю строку целиком (префикс + ник) - * Использует формат &#RRGGBB + * Получает отформатированное имя игрока (префикс + ник с градиентом) */ public String getFormattedName(Player player) { - return getFormattedName(player, false); + if (!enabled) return player.getName(); + + GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId()); + return prefixService.getFullName(player, data); } /** * Получает отформатированное имя для TAB плагина - * Использует формат §x§R§R§G§G§B§B */ public String getFormattedNameForTab(Player player) { - return getFormattedName(player, true); - } - - /** - * Получает отформатированное имя игрока с градиентом и LuckPerms префиксом - * - * @param useTabFormat true для §x§... формата (TAB), false для &#RRGGBB - */ - private String getFormattedName(Player player, boolean useTabFormat) { if (!enabled) return player.getName(); - + GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId()); - - // Собираем полную строку: префикс + ник - StringBuilder fullText = new StringBuilder(); - - // LuckPerms префикс - String lpPrefix = null; - if (luckPermsHook != null && luckPermsHook.isEnabled()) { - lpPrefix = luckPermsHook.getActivePrefix(player); - } - - // Определяем какой префикс использовать - if (data.hasPrefix() && data.isPrefixEnabled()) { - // Кастомный префикс - fullText.append(config.getPrefixFormat().replace("{prefix}", data.getPrefix())); - } else if (lpPrefix != null && !lpPrefix.isEmpty()) { - // LuckPerms префикс (убираем его цвета если будем применять градиент) - if (data.hasColors() && data.isColorEnabled() && config.isGradientOnLuckPermsPrefix()) { - fullText.append(stripColors(lpPrefix)); - } else { - // LP префикс со своими цветами, ник отдельно - String nick = data.hasColors() && data.isColorEnabled() - ? applyGradientWithFormat(player.getName(), data.getColors(), useTabFormat) - : player.getName(); - return lpPrefix + nick; - } - } - - // Добавляем ник - fullText.append(player.getName()); - - // Применяем единый градиент на всю строку - if (data.hasColors() && data.isColorEnabled()) { - return applyGradientWithFormat(fullText.toString(), data.getColors(), useTabFormat); - } - - return fullText.toString(); - } - - /** - * Применяет градиент с нужным форматом - */ - private String applyGradientWithFormat(String text, java.util.List colors, boolean useTabFormat) { - if (useTabFormat) { - // Формат §x§R§R§G§G§B§B для TAB - return GradientUtil.applyGradientTabFormat(text, colors); - } else { - // Формат &#RRGGBB для GUI и чата - return GradientUtil.applyGradient(text, colors, true); - } - } - - /** - * Убирает цветовые коды из строки - */ - private String stripColors(String text) { - if (text == null) return ""; - return text.replaceAll("(?i)(§x(§[0-9a-f]){6}|§[0-9a-fk-or]|&[0-9a-fk-or]|&#[0-9a-f]{6}|<[^>]+>)", ""); + return prefixService.getFullNameForTab(player, data); } /** @@ -216,56 +136,31 @@ public class GradientModule { if (!enabled) return player.getName(); GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId()); - if (!data.hasColors() || !data.isColorEnabled()) { - return player.getName(); - } - - return GradientUtil.applyGradient(player.getName(), data.getColors(), config.isUseLegacyRgbFormat()); + return GradientService.applyGradient(player, player.getName(), data); } /** * Получает префикс игрока (кастомный или LuckPerms) с градиентом - * Градиент применяется только на префикс */ public String getPrefix(Player player) { if (!enabled) return ""; - + GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId()); - - // Кастомный префикс - if (data.hasPrefix() && data.isPrefixEnabled()) { - String prefix = config.getPrefixFormat().replace("{prefix}", data.getPrefix()); - if (data.hasColors() && data.isColorEnabled() && config.isGradientOnPrefix()) { - return GradientUtil.applyGradient(prefix, data.getColors(), config.isUseLegacyRgbFormat()); - } - return prefix; - } - - // LuckPerms префикс - if (luckPermsHook != null && luckPermsHook.isEnabled()) { - String lpPrefix = luckPermsHook.getActivePrefix(player); - if (lpPrefix != null && !lpPrefix.isEmpty()) { - if (data.hasColors() && data.isColorEnabled() && config.isGradientOnLuckPermsPrefix()) { - String cleanPrefix = stripColors(lpPrefix); - return GradientUtil.applyGradient(cleanPrefix, data.getColors(), config.isUseLegacyRgbFormat()); - } - return lpPrefix; - } - } - - return ""; + return prefixService.getPrefix(player, data); } /** * Получает только LuckPerms префикс */ public String getLuckPermsPrefix(Player player) { - if (!enabled || luckPermsHook == null || !luckPermsHook.isEnabled()) return ""; - String prefix = luckPermsHook.getActivePrefix(player); - return prefix != null ? prefix : ""; + if (!enabled) return ""; + + GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId()); + return prefixService.getLuckPermsPrefix(player, data); } - // Геттеры + // ========== Геттеры ========== + public JavaPlugin getPlugin() { return plugin; } @@ -282,8 +177,8 @@ public class GradientModule { return dataManager; } - public GradientLuckPermsHook getLuckPermsHook() { - return luckPermsHook; + public PrefixService getPrefixService() { + return prefixService; } public PlayerPointsAPI getPlayerPointsAPI() { diff --git a/src/main/java/com/loki/lochat/gradient/commands/GradientAdminCommand.java b/src/main/java/com/loki/lochat/gradient/commands/GradientAdminCommand.java index cec9059..fb31a20 100644 --- a/src/main/java/com/loki/lochat/gradient/commands/GradientAdminCommand.java +++ b/src/main/java/com/loki/lochat/gradient/commands/GradientAdminCommand.java @@ -1,12 +1,12 @@ package com.loki.lochat.gradient.commands; import com.loki.lochat.gradient.GradientModule; -import com.loki.lochat.gradient.config.GradientConfig; +import com.loki.lochat.gradient.commands.handlers.ColorCommandHandler; +import com.loki.lochat.gradient.commands.handlers.PrefixCommandHandler; import com.loki.lochat.gradient.config.GradientMessages; import com.loki.lochat.gradient.data.GradientPlayerData; import com.loki.lochat.gradient.util.DisplayNameUtil; -import com.loki.lochat.util.FoliaUtil; -import com.loki.lochat.gradient.util.GradientUtil; +import com.loki.lochat.utils.FoliaUtil; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.command.Command; @@ -22,17 +22,22 @@ import java.util.Arrays; import java.util.List; /** - * Админ команда /aprefix для управления градиентами + * Админ команда /aprefix - упрощенная версия с делегированием */ public class GradientAdminCommand implements CommandExecutor, TabCompleter { private static final List PRESET_COLORS = Arrays.asList( - "#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3" + "#FF0000", "#FF7F00", "#FFFF00", "#00FF00", "#0000FF", "#4B0082", "#9400D3" ); + private final GradientModule module; + private final ColorCommandHandler colorHandler; + private final PrefixCommandHandler prefixHandler; public GradientAdminCommand(GradientModule module) { this.module = module; + this.colorHandler = new ColorCommandHandler(module); + this.prefixHandler = new PrefixCommandHandler(module); } @Override @@ -64,29 +69,15 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter { } handleInfo(sender, args[1], msg); } - case "setcolor" -> { - if (args.length < 3) { - sender.sendMessage("§cИспользование: /aprefix setcolor <игрок> <цвет1> [цвет2] ..."); - return true; - } - handleSetColor(sender, args, msg); - } - case "setprefix" -> { - if (args.length < 3) { - sender.sendMessage("§cИспользование: /aprefix setprefix <игрок> <префикс>"); - return true; - } - handleSetPrefix(sender, args, msg); - } - case "coloron" -> handleColorToggle(sender, args, true, msg); - case "coloroff" -> handleColorToggle(sender, args, false, msg); - case "prefixon" -> handlePrefixToggle(sender, args, true, msg); - case "prefixoff" -> handlePrefixToggle(sender, args, false, msg); - case "resetcolor" -> handleResetColor(sender, args, msg); - case "resetprefix" -> handleResetPrefix(sender, args, msg); - case "updateall" -> { - handleUpdateAll(sender, msg); - } + case "setcolor" -> colorHandler.handleSetColor(sender, args, msg); + case "setprefix" -> prefixHandler.handleSetPrefix(sender, args, msg); + case "coloron" -> colorHandler.handleColorToggle(sender, args, true, msg); + case "coloroff" -> colorHandler.handleColorToggle(sender, args, false, msg); + case "prefixon" -> prefixHandler.handlePrefixToggle(sender, args, true, msg); + case "prefixoff" -> prefixHandler.handlePrefixToggle(sender, args, false, msg); + case "resetcolor" -> colorHandler.handleResetColor(sender, args, msg); + case "resetprefix" -> prefixHandler.handleResetPrefix(sender, args, msg); + case "updateall" -> handleUpdateAll(sender, msg); default -> sendHelp(sender); } @@ -104,7 +95,6 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter { sender.sendMessage("§e/aprefix resetcolor <игрок> §7— сбросить цвет"); sender.sendMessage("§e/aprefix resetprefix <игрок> §7— сбросить префикс"); sender.sendMessage("§e/aprefix updateall §7— обновить display names всех игроков"); - sender.sendMessage("§e/aprefix toggledisplay §7— переключить TextDisplay/обычный режим"); } private void handleInfo(CommandSender sender, String playerName, GradientMessages msg) { @@ -124,220 +114,17 @@ public class GradientAdminCommand implements CommandExecutor, TabCompleter { msg.send(sender, "info-prefix-purchased", "purchased", data.isPrefixPurchased() ? "да" : "нет"); } - private void handleSetColor(CommandSender sender, String[] args, GradientMessages msg) { - OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); - if (!target.hasPlayedBefore() && !target.isOnline()) { - msg.send(sender, "player-not-found"); - return; - } - - List colors = new ArrayList<>(); - for (int i = 2; i < args.length; i++) { - String color = args[i].startsWith("#") ? args[i] : "#" + args[i]; - if (!GradientUtil.isValidHex(color)) { - msg.send(sender, "invalid-color", "color", args[i]); - return; - } - colors.add(color.toUpperCase()); - } - - GradientConfig cfg = module.getConfig(); - if (colors.size() > cfg.getMaxColors()) { - msg.send(sender, "too-many-colors", "max", String.valueOf(cfg.getMaxColors())); - return; - } - - GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId()); - data.setColors(colors); - data.setColorEnabled(true); - - Player onlineTarget = target.getPlayer(); - if (onlineTarget != null) { - FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget, - () -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data)); - } - - FoliaUtil.runAsync(module.getPlugin(), - () -> module.getDataManager().savePlayerData(target.getUniqueId())); - - sender.sendMessage("§aЦвет для игрока §f" + args[1] + " §aустановлен: §f" + String.join(" ", colors)); - } - - private void handleSetPrefix(CommandSender sender, String[] args, GradientMessages msg) { - OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); - if (!target.hasPlayedBefore() && !target.isOnline()) { - msg.send(sender, "player-not-found"); - return; - } - - StringBuilder prefixBuilder = new StringBuilder(); - for (int i = 2; i < args.length; i++) { - if (i > 2) prefixBuilder.append(" "); - prefixBuilder.append(args[i]); - } - String prefix = prefixBuilder.toString(); - - GradientConfig cfg = module.getConfig(); - if (prefix.length() > cfg.getMaxPrefixLength()) { - msg.send(sender, "prefix-too-long", "max", String.valueOf(cfg.getMaxPrefixLength())); - return; - } - - GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId()); - data.setPrefix(prefix); - data.setPrefixEnabled(true); - data.setPrefixPurchased(true); - - if (module.getLuckPermsHook() != null) { - module.getLuckPermsHook().setPrefix(target.getUniqueId(), DisplayNameUtil.buildColoredPrefix(module, data)); - } - - Player onlineTarget = target.getPlayer(); - if (onlineTarget != null) { - FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget, - () -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data)); - } - - FoliaUtil.runAsync(module.getPlugin(), - () -> module.getDataManager().savePlayerData(target.getUniqueId())); - - sender.sendMessage("§aПрефикс для игрока §f" + args[1] + " §aустановлен: §f" + prefix); - } - - private void handleColorToggle(CommandSender sender, String[] args, boolean enable, GradientMessages msg) { - if (args.length < 2) { - sender.sendMessage("§cИспользование: /aprefix " + (enable ? "coloron" : "coloroff") + " <игрок>"); - return; - } - - OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); - if (!target.hasPlayedBefore() && !target.isOnline()) { - msg.send(sender, "player-not-found"); - return; - } - - GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId()); - data.setColorEnabled(enable); - - Player onlineTarget = target.getPlayer(); - if (onlineTarget != null) { - FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget, - () -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data)); - } - - FoliaUtil.runAsync(module.getPlugin(), - () -> module.getDataManager().savePlayerData(target.getUniqueId())); - - sender.sendMessage("§aЦвет для игрока §f" + args[1] + (enable ? " §aвключён" : " §cвыключен")); - } - - private void handlePrefixToggle(CommandSender sender, String[] args, boolean enable, GradientMessages msg) { - if (args.length < 2) { - sender.sendMessage("§cИспользование: /aprefix " + (enable ? "prefixon" : "prefixoff") + " <игрок>"); - return; - } - - OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); - if (!target.hasPlayedBefore() && !target.isOnline()) { - msg.send(sender, "player-not-found"); - return; - } - - GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId()); - data.setPrefixEnabled(enable); - - if (module.getLuckPermsHook() != null) { - if (enable && data.hasPrefix()) { - module.getLuckPermsHook().setPrefix(target.getUniqueId(), DisplayNameUtil.buildColoredPrefix(module, data)); - } else { - module.getLuckPermsHook().removePrefix(target.getUniqueId()); - } - } - - Player onlineTarget = target.getPlayer(); - if (onlineTarget != null) { - FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget, - () -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data)); - } - - FoliaUtil.runAsync(module.getPlugin(), - () -> module.getDataManager().savePlayerData(target.getUniqueId())); - - sender.sendMessage("§aПрефикс для игрока §f" + args[1] + (enable ? " §aвключён" : " §cвыключен")); - } - - private void handleResetColor(CommandSender sender, String[] args, GradientMessages msg) { - if (args.length < 2) { - sender.sendMessage("§cИспользование: /aprefix resetcolor <игрок>"); - return; - } - - OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); - if (!target.hasPlayedBefore() && !target.isOnline()) { - msg.send(sender, "player-not-found"); - return; - } - - GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId()); - data.setColors(new ArrayList<>()); - data.setColorEnabled(false); - - Player onlineTarget = target.getPlayer(); - if (onlineTarget != null) { - FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget, - () -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data)); - } - - FoliaUtil.runAsync(module.getPlugin(), - () -> module.getDataManager().savePlayerData(target.getUniqueId())); - - sender.sendMessage("§aЦвет для игрока §f" + args[1] + " §aсброшен"); - } - - private void handleResetPrefix(CommandSender sender, String[] args, GradientMessages msg) { - if (args.length < 2) { - sender.sendMessage("§cИспользование: /aprefix resetprefix <игрок>"); - return; - } - - OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); - if (!target.hasPlayedBefore() && !target.isOnline()) { - msg.send(sender, "player-not-found"); - return; - } - - GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId()); - data.setPrefix(null); - data.setPrefixEnabled(false); - - if (module.getLuckPermsHook() != null) { - module.getLuckPermsHook().removePrefix(target.getUniqueId()); - } - - Player onlineTarget = target.getPlayer(); - if (onlineTarget != null) { - FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget, - () -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data)); - } - - FoliaUtil.runAsync(module.getPlugin(), - () -> module.getDataManager().savePlayerData(target.getUniqueId())); - - sender.sendMessage("§aПрефикс для игрока §f" + args[1] + " §aсброшен"); - } - private void handleUpdateAll(CommandSender sender, GradientMessages msg) { int count = 0; for (Player player : Bukkit.getOnlinePlayers()) { GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId()); FoliaUtil.runEntityTask(module.getPlugin(), player, - () -> DisplayNameUtil.updateDisplayName(module, player, data)); + () -> DisplayNameUtil.updateDisplayName(module, player, data)); count++; } sender.sendMessage("§aОбновлены display names для §f" + count + " §aигроков"); } - @Override public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) { diff --git a/src/main/java/com/loki/lochat/gradient/commands/GradientColorCommand.java b/src/main/java/com/loki/lochat/gradient/commands/GradientColorCommand.java index 0529e26..cc54351 100644 --- a/src/main/java/com/loki/lochat/gradient/commands/GradientColorCommand.java +++ b/src/main/java/com/loki/lochat/gradient/commands/GradientColorCommand.java @@ -6,7 +6,7 @@ import com.loki.lochat.gradient.config.GradientMessages; import com.loki.lochat.gradient.data.GradientPlayerData; import com.loki.lochat.gradient.gui.GradientConfirmGUI; import com.loki.lochat.gradient.util.DisplayNameUtil; -import com.loki.lochat.util.FoliaUtil; +import com.loki.lochat.utils.FoliaUtil; import com.loki.lochat.gradient.util.GradientConstants; import com.loki.lochat.gradient.util.GradientUtil; import org.bukkit.Bukkit; diff --git a/src/main/java/com/loki/lochat/gradient/commands/GradientPrefixCommand.java b/src/main/java/com/loki/lochat/gradient/commands/GradientPrefixCommand.java index b5ee511..d383b50 100644 --- a/src/main/java/com/loki/lochat/gradient/commands/GradientPrefixCommand.java +++ b/src/main/java/com/loki/lochat/gradient/commands/GradientPrefixCommand.java @@ -6,7 +6,7 @@ import com.loki.lochat.gradient.config.GradientMessages; import com.loki.lochat.gradient.data.GradientPlayerData; import com.loki.lochat.gradient.gui.GradientConfirmGUI; import com.loki.lochat.gradient.util.DisplayNameUtil; -import com.loki.lochat.util.FoliaUtil; +import com.loki.lochat.utils.FoliaUtil; import com.loki.lochat.gradient.util.GradientConstants; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -83,9 +83,6 @@ public class GradientPrefixCommand implements CommandExecutor, TabCompleter { } data.setPrefixEnabled(true); - if (module.getLuckPermsHook() != null) { - module.getLuckPermsHook().setPrefix(player, DisplayNameUtil.buildColoredPrefix(module, data)); - } saveAndUpdate(player, data); msg.send(player, "prefix-enabled"); return true; @@ -94,9 +91,6 @@ public class GradientPrefixCommand implements CommandExecutor, TabCompleter { private boolean handleOff(Player player, String[] args) { GradientPlayerData data = module.getDataManager().getPlayerData(player.getUniqueId()); data.setPrefixEnabled(false); - if (module.getLuckPermsHook() != null) { - module.getLuckPermsHook().removePrefix(player); - } saveAndUpdate(player, data); module.getMessages().send(player, "prefix-disabled"); return true; @@ -113,9 +107,6 @@ public class GradientPrefixCommand implements CommandExecutor, TabCompleter { data.setPrefix(null); data.setPrefixEnabled(false); - if (module.getLuckPermsHook() != null) { - module.getLuckPermsHook().removePrefix(player); - } saveAndUpdate(player, data); msg.send(player, "prefix-reset-success"); return true; diff --git a/src/main/java/com/loki/lochat/gradient/commands/handlers/ColorCommandHandler.java b/src/main/java/com/loki/lochat/gradient/commands/handlers/ColorCommandHandler.java new file mode 100644 index 0000000..79036f1 --- /dev/null +++ b/src/main/java/com/loki/lochat/gradient/commands/handlers/ColorCommandHandler.java @@ -0,0 +1,129 @@ +package com.loki.lochat.gradient.commands.handlers; + +import com.loki.lochat.gradient.GradientModule; +import com.loki.lochat.gradient.config.GradientMessages; +import com.loki.lochat.gradient.data.GradientPlayerData; +import com.loki.lochat.gradient.util.DisplayNameUtil; +import com.loki.lochat.gradient.util.GradientUtil; +import com.loki.lochat.utils.FoliaUtil; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; + +/** + * Обработчик команд для управления цветами + */ +public class ColorCommandHandler { + + private final GradientModule module; + + public ColorCommandHandler(GradientModule module) { + this.module = module; + } + + /** + * Установить цвет игроку + */ + public void handleSetColor(CommandSender sender, String[] args, GradientMessages msg) { + if (args.length < 3) { + sender.sendMessage("§cИспользование: /aprefix setcolor <игрок> <цвет1> [цвет2] ..."); + return; + } + + OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); + if (!target.hasPlayedBefore() && !target.isOnline()) { + msg.send(sender, "player-not-found"); + return; + } + + List colors = new ArrayList<>(); + for (int i = 2; i < args.length; i++) { + String color = args[i].startsWith("#") ? args[i] : "#" + args[i]; + if (!GradientUtil.isValidHex(color)) { + msg.send(sender, "invalid-color", "color", args[i]); + return; + } + colors.add(color.toUpperCase()); + } + + if (colors.size() > module.getConfig().getMaxColors()) { + msg.send(sender, "too-many-colors", "max", String.valueOf(module.getConfig().getMaxColors())); + return; + } + + GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId()); + data.setColors(colors); + data.setColorEnabled(true); + + updatePlayerDisplay(target, data); + savePlayerData(target); + + sender.sendMessage("§aЦвет для игрока §f" + args[1] + " §aустановлен: §f" + String.join(" ", colors)); + } + + /** + * Включить/выключить цвет + */ + public void handleColorToggle(CommandSender sender, String[] args, boolean enable, GradientMessages msg) { + if (args.length < 2) { + sender.sendMessage("§cИспользование: /aprefix " + (enable ? "coloron" : "coloroff") + " <игрок>"); + return; + } + + OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); + if (!target.hasPlayedBefore() && !target.isOnline()) { + msg.send(sender, "player-not-found"); + return; + } + + GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId()); + data.setColorEnabled(enable); + + updatePlayerDisplay(target, data); + savePlayerData(target); + + sender.sendMessage("§aЦвет для игрока §f" + args[1] + (enable ? " §aвключён" : " §cвыключен")); + } + + /** + * Сбросить цвет + */ + public void handleResetColor(CommandSender sender, String[] args, GradientMessages msg) { + if (args.length < 2) { + sender.sendMessage("§cИспользование: /aprefix resetcolor <игрок>"); + return; + } + + OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); + if (!target.hasPlayedBefore() && !target.isOnline()) { + msg.send(sender, "player-not-found"); + return; + } + + GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId()); + data.setColors(new ArrayList<>()); + data.setColorEnabled(false); + + updatePlayerDisplay(target, data); + savePlayerData(target); + + sender.sendMessage("§aЦвет для игрока §f" + args[1] + " §aсброшен"); + } + + private void updatePlayerDisplay(OfflinePlayer target, GradientPlayerData data) { + Player onlineTarget = target.getPlayer(); + if (onlineTarget != null) { + FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget, + () -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data)); + } + } + + private void savePlayerData(OfflinePlayer target) { + FoliaUtil.runAsync(module.getPlugin(), + () -> module.getDataManager().savePlayerData(target.getUniqueId())); + } +} diff --git a/src/main/java/com/loki/lochat/gradient/commands/handlers/PrefixCommandHandler.java b/src/main/java/com/loki/lochat/gradient/commands/handlers/PrefixCommandHandler.java new file mode 100644 index 0000000..fca72d6 --- /dev/null +++ b/src/main/java/com/loki/lochat/gradient/commands/handlers/PrefixCommandHandler.java @@ -0,0 +1,123 @@ +package com.loki.lochat.gradient.commands.handlers; + +import com.loki.lochat.gradient.GradientModule; +import com.loki.lochat.gradient.config.GradientMessages; +import com.loki.lochat.gradient.data.GradientPlayerData; +import com.loki.lochat.gradient.util.DisplayNameUtil; +import com.loki.lochat.utils.FoliaUtil; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +/** + * Обработчик команд для управления префиксами + */ +public class PrefixCommandHandler { + + private final GradientModule module; + + public PrefixCommandHandler(GradientModule module) { + this.module = module; + } + + /** + * Установить префикс игроку + */ + public void handleSetPrefix(CommandSender sender, String[] args, GradientMessages msg) { + if (args.length < 3) { + sender.sendMessage("§cИспользование: /aprefix setprefix <игрок> <префикс>"); + return; + } + + OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); + if (!target.hasPlayedBefore() && !target.isOnline()) { + msg.send(sender, "player-not-found"); + return; + } + + StringBuilder prefixBuilder = new StringBuilder(); + for (int i = 2; i < args.length; i++) { + if (i > 2) prefixBuilder.append(" "); + prefixBuilder.append(args[i]); + } + String prefix = prefixBuilder.toString(); + + if (prefix.length() > module.getConfig().getMaxPrefixLength()) { + msg.send(sender, "prefix-too-long", "max", String.valueOf(module.getConfig().getMaxPrefixLength())); + return; + } + + GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId()); + data.setPrefix(prefix); + data.setPrefixEnabled(true); + data.setPrefixPurchased(true); + + updatePlayerDisplay(target, data); + savePlayerData(target); + + sender.sendMessage("§aПрефикс для игрока §f" + args[1] + " §aустановлен: §f" + prefix); + } + + /** + * Включить/выключить префикс + */ + public void handlePrefixToggle(CommandSender sender, String[] args, boolean enable, GradientMessages msg) { + if (args.length < 2) { + sender.sendMessage("§cИспользование: /aprefix " + (enable ? "prefixon" : "prefixoff") + " <игрок>"); + return; + } + + OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); + if (!target.hasPlayedBefore() && !target.isOnline()) { + msg.send(sender, "player-not-found"); + return; + } + + GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId()); + data.setPrefixEnabled(enable); + + updatePlayerDisplay(target, data); + savePlayerData(target); + + sender.sendMessage("§aПрефикс для игрока §f" + args[1] + (enable ? " §aвключён" : " §cвыключен")); + } + + /** + * Сбросить префикс + */ + public void handleResetPrefix(CommandSender sender, String[] args, GradientMessages msg) { + if (args.length < 2) { + sender.sendMessage("§cИспользование: /aprefix resetprefix <игрок>"); + return; + } + + OfflinePlayer target = Bukkit.getOfflinePlayer(args[1]); + if (!target.hasPlayedBefore() && !target.isOnline()) { + msg.send(sender, "player-not-found"); + return; + } + + GradientPlayerData data = module.getDataManager().getPlayerData(target.getUniqueId()); + data.setPrefix(null); + data.setPrefixEnabled(false); + + updatePlayerDisplay(target, data); + savePlayerData(target); + + sender.sendMessage("§aПрефикс для игрока §f" + args[1] + " §aсброшен"); + } + + private void updatePlayerDisplay(OfflinePlayer target, GradientPlayerData data) { + Player onlineTarget = target.getPlayer(); + if (onlineTarget != null) { + FoliaUtil.runEntityTask(module.getPlugin(), onlineTarget, + () -> DisplayNameUtil.updateDisplayName(module, onlineTarget, data)); + } + } + + private void savePlayerData(OfflinePlayer target) { + FoliaUtil.runAsync(module.getPlugin(), + () -> module.getDataManager().savePlayerData(target.getUniqueId())); + } +} diff --git a/src/main/java/com/loki/lochat/gradient/gui/GradientConfirmGUI.java b/src/main/java/com/loki/lochat/gradient/gui/GradientConfirmGUI.java index 6163663..1c6ba5f 100644 --- a/src/main/java/com/loki/lochat/gradient/gui/GradientConfirmGUI.java +++ b/src/main/java/com/loki/lochat/gradient/gui/GradientConfirmGUI.java @@ -128,8 +128,8 @@ public final class GradientConfirmGUI implements InventoryHolder { boolean useLegacyForPreview = true; // Если нет кастомного префикса - используем LuckPerms префикс - if ((prefixToUse == null || prefixToUse.isEmpty()) && module.getLuckPermsHook() != null && module.getLuckPermsHook().isEnabled()) { - String lpPrefix = module.getLuckPermsHook().getActivePrefix(player); + if ((prefixToUse == null || prefixToUse.isEmpty())) { + String lpPrefix = module.getPrefixService().getLuckPermsPrefix(player, data); if (lpPrefix != null && !lpPrefix.isEmpty()) { // Конвертируем LP префикс в legacy формат lpPrefix = convertToLegacy(lpPrefix); diff --git a/src/main/java/com/loki/lochat/gradient/hooks/GradientLuckPermsHook.java b/src/main/java/com/loki/lochat/gradient/hooks/GradientLuckPermsHook.java deleted file mode 100644 index 7f05069..0000000 --- a/src/main/java/com/loki/lochat/gradient/hooks/GradientLuckPermsHook.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.loki.lochat.gradient.hooks; - -import com.loki.lochat.gradient.util.GradientConstants; -import net.luckperms.api.LuckPerms; -import net.luckperms.api.LuckPermsProvider; -import net.luckperms.api.cacheddata.CachedMetaData; -import net.luckperms.api.model.user.User; -import net.luckperms.api.node.types.PrefixNode; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; - -import java.util.UUID; - -/** - * Хук для LuckPerms - */ -public class GradientLuckPermsHook { - - private LuckPerms luckPerms; - private boolean enabled; - - public GradientLuckPermsHook(JavaPlugin plugin) { - this.enabled = false; - - if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) { - try { - this.luckPerms = LuckPermsProvider.get(); - this.enabled = true; - plugin.getLogger().info("Gradient: LuckPerms подключен!"); - } catch (Exception e) { - plugin.getLogger().warning("Gradient: Не удалось подключить LuckPerms: " + e.getMessage()); - } - } - } - - public boolean isEnabled() { - return enabled && luckPerms != null; - } - - public void setPrefix(Player player, String prefix) { - if (!isEnabled()) return; - - User user = luckPerms.getUserManager().getUser(player.getUniqueId()); - if (user == null) return; - - user.data().clear(node -> - node instanceof PrefixNode && ((PrefixNode) node).getPriority() == GradientConstants.LUCKPERMS_PREFIX_PRIORITY - ); - - if (prefix != null && !prefix.isEmpty()) { - PrefixNode prefixNode = PrefixNode.builder(prefix, GradientConstants.LUCKPERMS_PREFIX_PRIORITY).build(); - user.data().add(prefixNode); - } - - luckPerms.getUserManager().saveUser(user); - } - - public void setPrefix(UUID uuid, String prefix) { - if (!isEnabled()) return; - - luckPerms.getUserManager().loadUser(uuid).thenAccept(user -> { - if (user == null) return; - - user.data().clear(node -> - node instanceof PrefixNode && ((PrefixNode) node).getPriority() == GradientConstants.LUCKPERMS_PREFIX_PRIORITY - ); - - if (prefix != null && !prefix.isEmpty()) { - PrefixNode prefixNode = PrefixNode.builder(prefix, GradientConstants.LUCKPERMS_PREFIX_PRIORITY).build(); - user.data().add(prefixNode); - } - - luckPerms.getUserManager().saveUser(user); - }); - } - - public void removePrefix(Player player) { - setPrefix(player, null); - } - - public void removePrefix(UUID uuid) { - setPrefix(uuid, null); - } - - public String getActivePrefix(Player player) { - if (!isEnabled()) return null; - - User user = luckPerms.getUserManager().getUser(player.getUniqueId()); - if (user == null) return null; - - CachedMetaData metaData = user.getCachedData().getMetaData(); - return metaData.getPrefix(); - } -} diff --git a/src/main/java/com/loki/lochat/gradient/listeners/GradientGUIListener.java b/src/main/java/com/loki/lochat/gradient/listeners/GradientGUIListener.java index 5110902..feee460 100644 --- a/src/main/java/com/loki/lochat/gradient/listeners/GradientGUIListener.java +++ b/src/main/java/com/loki/lochat/gradient/listeners/GradientGUIListener.java @@ -5,7 +5,7 @@ import com.loki.lochat.gradient.config.GradientMessages; import com.loki.lochat.gradient.data.GradientPlayerData; import com.loki.lochat.gradient.gui.GradientConfirmGUI; import com.loki.lochat.gradient.util.DisplayNameUtil; -import com.loki.lochat.util.FoliaUtil; +import com.loki.lochat.utils.FoliaUtil; import com.loki.lochat.gradient.util.GradientConstants; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -81,9 +81,6 @@ public class GradientGUIListener implements Listener { data.setColors(gui.getColors()); data.setColorEnabled(true); } - if (module.getLuckPermsHook() != null) { - module.getLuckPermsHook().setPrefix(player, DisplayNameUtil.buildColoredPrefix(module, data)); - } msg.send(player, price > 0 ? "prefix-success" : "prefix-success-free", "price", String.valueOf(price)); } diff --git a/src/main/java/com/loki/lochat/gradient/listeners/GradientPlayerListener.java b/src/main/java/com/loki/lochat/gradient/listeners/GradientPlayerListener.java index 4f2ed44..a125a28 100644 --- a/src/main/java/com/loki/lochat/gradient/listeners/GradientPlayerListener.java +++ b/src/main/java/com/loki/lochat/gradient/listeners/GradientPlayerListener.java @@ -1,7 +1,7 @@ package com.loki.lochat.gradient.listeners; import com.loki.lochat.gradient.GradientModule; -import com.loki.lochat.util.FoliaUtil; +import com.loki.lochat.utils.FoliaUtil; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; diff --git a/src/main/java/com/loki/lochat/gradient/service/GradientService.java b/src/main/java/com/loki/lochat/gradient/service/GradientService.java new file mode 100644 index 0000000..46bb02b --- /dev/null +++ b/src/main/java/com/loki/lochat/gradient/service/GradientService.java @@ -0,0 +1,57 @@ +package com.loki.lochat.gradient.service; + +import com.loki.lochat.gradient.data.GradientPlayerData; +import com.loki.lochat.gradient.util.GradientUtil; +import org.bukkit.entity.Player; + +import java.util.List; + +/** + * Сервис для применения градиентов - единая точка входа + */ +public class GradientService { + + /** + * Применяет градиент к тексту если у игрока есть цвета + */ + public static String applyGradient(Player player, String text, GradientPlayerData data) { + if (data == null || !data.hasColors() || !data.isColorEnabled()) { + return text; + } + + return GradientUtil.applyGradient(text, data.getColors(), true); + } + + /** + * Применяет градиент к тексту с указанными цветами + */ + public static String applyGradient(String text, List colors) { + if (colors == null || colors.isEmpty()) { + return text; + } + + return GradientUtil.applyGradient(text, colors, true); + } + + /** + * Применяет градиент в формате для TAB плагина + */ + public static String applyGradientForTab(String text, List colors) { + if (colors == null || colors.isEmpty()) { + return text; + } + + return GradientUtil.applyGradientTabFormat(text, colors); + } + + /** + * Убирает все цветовые коды из текста + */ + public static String stripColors(String text) { + if (text == null || text.isEmpty()) { + return ""; + } + + return text.replaceAll("(?i)(§x(§[0-9a-f]){6}|§[0-9a-fk-or]|&[0-9a-fk-or]|&#[0-9a-f]{6}|<[^>]+>)", ""); + } +} diff --git a/src/main/java/com/loki/lochat/gradient/service/PrefixService.java b/src/main/java/com/loki/lochat/gradient/service/PrefixService.java new file mode 100644 index 0000000..dbd278e --- /dev/null +++ b/src/main/java/com/loki/lochat/gradient/service/PrefixService.java @@ -0,0 +1,114 @@ +package com.loki.lochat.gradient.service; + +import com.loki.lochat.gradient.config.GradientConfig; +import com.loki.lochat.gradient.data.GradientPlayerData; +import net.luckperms.api.LuckPerms; +import net.luckperms.api.LuckPermsProvider; +import net.luckperms.api.cacheddata.CachedMetaData; +import net.luckperms.api.model.user.User; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +/** + * Сервис для работы с префиксами - упрощенная интеграция с LuckPerms + */ +public class PrefixService { + + private final GradientConfig config; + private final boolean luckPermsAvailable; + + public PrefixService(GradientConfig config) { + this.config = config; + this.luckPermsAvailable = Bukkit.getPluginManager().isPluginEnabled("LuckPerms"); + } + + /** + * Получает префикс игрока (кастомный или из LuckPerms) + */ + public String getPrefix(Player player, GradientPlayerData data) { + // Приоритет: кастомный префикс > LuckPerms префикс + if (data.hasPrefix() && data.isPrefixEnabled()) { + String prefix = config.getPrefixFormat().replace("{prefix}", data.getPrefix()); + + // Применяем градиент если нужно + if (data.hasColors() && data.isColorEnabled() && config.isGradientOnPrefix()) { + return GradientService.applyGradient(prefix, data.getColors()); + } + + return prefix; + } + + // Пробуем получить из LuckPerms + return getLuckPermsPrefix(player, data); + } + + /** + * Получает префикс из LuckPerms + */ + public String getLuckPermsPrefix(Player player, GradientPlayerData data) { + if (!luckPermsAvailable) { + return ""; + } + + try { + LuckPerms luckPerms = LuckPermsProvider.get(); + User user = luckPerms.getUserManager().getUser(player.getUniqueId()); + + if (user == null) { + return ""; + } + + CachedMetaData metaData = user.getCachedData().getMetaData(); + String prefix = metaData.getPrefix(); + + if (prefix == null || prefix.isEmpty()) { + return ""; + } + + // Применяем градиент если нужно + if (data != null && data.hasColors() && data.isColorEnabled() && config.isGradientOnLuckPermsPrefix()) { + String cleanPrefix = GradientService.stripColors(prefix); + return GradientService.applyGradient(cleanPrefix, data.getColors()); + } + + return prefix; + + } catch (Exception e) { + return ""; + } + } + + /** + * Получает полное имя игрока: префикс + ник с градиентом + */ + public String getFullName(Player player, GradientPlayerData data) { + String prefix = getPrefix(player, data); + String nick = player.getName(); + + // Применяем градиент к нику если есть цвета + if (data.hasColors() && data.isColorEnabled()) { + nick = GradientService.applyGradient(nick, data.getColors()); + } + + return prefix.isEmpty() ? nick : prefix + " " + nick; + } + + /** + * Получает полное имя для TAB плагина + */ + public String getFullNameForTab(Player player, GradientPlayerData data) { + String prefix = getPrefix(player, data); + String nick = player.getName(); + + // Применяем градиент в формате TAB + if (data.hasColors() && data.isColorEnabled()) { + nick = GradientService.applyGradientForTab(nick, data.getColors()); + } + + return prefix.isEmpty() ? nick : prefix + " " + nick; + } + + public boolean isLuckPermsAvailable() { + return luckPermsAvailable; + } +} diff --git a/src/main/java/com/loki/lochat/gradient/util/DisplayNameUtil.java b/src/main/java/com/loki/lochat/gradient/util/DisplayNameUtil.java index 209068c..2bad26c 100644 --- a/src/main/java/com/loki/lochat/gradient/util/DisplayNameUtil.java +++ b/src/main/java/com/loki/lochat/gradient/util/DisplayNameUtil.java @@ -6,7 +6,6 @@ import com.loki.lochat.gradient.data.GradientPlayerData; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.entity.Player; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -25,46 +24,10 @@ public final class DisplayNameUtil { } public static void updateDisplayName(GradientModule module, Player player, GradientPlayerData data) { - GradientConfig cfg = module.getConfig(); - String prefix = null; - String prefixFormat = cfg.getPrefixFormat(); - - // Определяем префикс: сначала кастомный, потом LuckPerms - if (data.isPrefixEnabled() && data.hasPrefix()) { - // Есть кастомный префикс - prefix = data.getPrefix(); - } else if (module.getLuckPermsHook() != null && module.getLuckPermsHook().isEnabled()) { - // Нет кастомного префикса, проверяем LuckPerms - String lpPrefix = module.getLuckPermsHook().getActivePrefix(player); - if (lpPrefix != null && !lpPrefix.isEmpty()) { - String displayName = buildWithLuckPermsPrefix( - lpPrefix, - player.getName(), - data.isColorEnabled() ? data.getColors() : null, - cfg.isUseLegacyRgbFormat(), - cfg.isContinuousGradient() - ); - // Для display name нужен §x формат, конвертируем MiniMessage в legacy - displayName = convertMiniMessageToLegacy(displayName); - var component = SERIALIZER.deserialize(displayName); - player.displayName(component); - player.playerListName(component); - return; - } - } - - // Строим display name с кастомным префиксом или без префикса - String displayName = GradientUtil.buildDisplayName( - prefix, - player.getName(), - data.isColorEnabled() ? data.getColors() : null, - cfg.isGradientOnPrefix(), - cfg.isContinuousGradient(), - prefixFormat, - cfg.isUseLegacyRgbFormat() - ); - - // Для display name нужен §x формат, конвертируем MiniMessage в legacy + // Используем новый PrefixService для получения полного имени + String displayName = module.getFormattedName(player); + + // Конвертируем в legacy формат для display name displayName = convertMiniMessageToLegacy(displayName); var component = SERIALIZER.deserialize(displayName); player.displayName(component); @@ -95,28 +58,6 @@ public final class DisplayNameUtil { return sb.toString(); } - private static String buildWithLuckPermsPrefix(String lpPrefix, String nick, - List colors, - boolean useLegacyFormat, - boolean continuousGradient) { - if (colors == null || colors.isEmpty()) { - return lpPrefix + nick; - } - - if (continuousGradient) { - String cleanPrefix = stripColors(lpPrefix); - String fullText = cleanPrefix + nick; - return GradientUtil.applyGradient(fullText, colors, useLegacyFormat); - } else { - return lpPrefix + GradientUtil.applyGradient(nick, colors, useLegacyFormat); - } - } - - private static String stripColors(String text) { - if (text == null) return null; - return text.replaceAll("(?i)(§x(§[0-9a-f]){6}|§[0-9a-fk-or]|&[0-9a-fk-or]|&#[0-9a-f]{6})", ""); - } - public static String buildColoredPrefix(GradientModule module, GradientPlayerData data) { if (!data.hasPrefix() || !data.isPrefixEnabled()) return null; diff --git a/src/main/java/com/loki/lochat/integrations/DiscordIntegration.java b/src/main/java/com/loki/lochat/integrations/DiscordIntegration.java index f1683c1..a0e5cb8 100644 --- a/src/main/java/com/loki/lochat/integrations/DiscordIntegration.java +++ b/src/main/java/com/loki/lochat/integrations/DiscordIntegration.java @@ -1,87 +1,36 @@ package com.loki.lochat.integrations; -import com.loki.lochat.util.FoliaUtil; -import org.bukkit.configuration.file.YamlConfiguration; +import com.loki.lochat.integrations.discord.DiscordConfig; +import com.loki.lochat.integrations.discord.DiscordMessageService; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; -import java.io.File; -import java.util.regex.Pattern; - /** - * Интеграция с Discord через вебхуки + * Интеграция с Discord - упрощенная версия */ public class DiscordIntegration { private final JavaPlugin plugin; - private YamlConfiguration config; - private DiscordWebhook webhook; + private DiscordConfig config; + private DiscordMessageService messageService; private boolean enabled = false; - - // Паттерны для очистки сообщений - private static final Pattern MENTION_PATTERN = Pattern.compile("@(everyone|here)", Pattern.CASE_INSENSITIVE); - private static final Pattern COLOR_CODE_PATTERN = Pattern.compile("§[0-9a-fk-or]", Pattern.CASE_INSENSITIVE); public DiscordIntegration(JavaPlugin plugin) { this.plugin = plugin; - loadConfig(); - initWebhook(); + init(); } - /** - * Загрузить конфигурацию - */ - private void loadConfig() { - // Создаём папку config если её нет - File configFolder = new File(plugin.getDataFolder(), "config"); - if (!configFolder.exists()) { - configFolder.mkdirs(); - } - - File configFile = new File(configFolder, "discord.yml"); - if (!configFile.exists()) { - try { - // Копируем из resources/config/discord.yml - java.io.InputStream in = plugin.getResource("config/discord.yml"); - if (in != null) { - java.nio.file.Files.copy(in, configFile.toPath()); - in.close(); - } - } catch (Exception e) { - plugin.getLogger().warning("Failed to copy config/discord.yml: " + e.getMessage()); - } - } - - config = YamlConfiguration.loadConfiguration(configFile); - enabled = config.getBoolean("enabled", false); + private void init() { + config = new DiscordConfig(plugin); + enabled = config.isEnabled(); if (!enabled) { plugin.getLogger().info("Discord integration disabled in config"); return; } - } - - /** - * Инициализировать вебхук - */ - private void initWebhook() { - if (!enabled) return; - // Получаем конфигурацию внешнего вида для дефолтных значений - com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin; - com.loki.lochat.config.AppearanceConfig appearanceConfig = loChat.getConfigManager().getAppearanceConfig(); + messageService = new DiscordMessageService(plugin, config); - String webhookUrl = config.getString("webhook.url", ""); - String username = config.getString("webhook.username", "Minecraft Server"); - String avatarUrl = config.getString("webhook.avatar_url", appearanceConfig.getDefaultAvatarUrl()); - int timeout = config.getInt("performance.timeout", 10); - int retryAttempts = config.getInt("performance.retry_attempts", 3); - long retryDelay = config.getLong("performance.retry_delay", 1000); - - // Логируем только факт наличия URL, но не его содержимое (security) - - webhook = new DiscordWebhook(plugin, webhookUrl, username, avatarUrl, timeout, retryAttempts, retryDelay); - - if (!webhook.isValid()) { + if (!messageService.isValid()) { plugin.getLogger().severe("Invalid Discord webhook URL! Please check config/discord.yml"); enabled = false; return; @@ -90,205 +39,47 @@ public class DiscordIntegration { plugin.getLogger().info("Discord integration initialized successfully"); } - /** - * Отправить сообщение чата в Discord - */ public void sendChatMessage(Player player, String message, boolean isGlobal) { - if (!enabled || !config.getBoolean("chat.enabled", true)) return; + if (!enabled || !config.isChatEnabled()) return; - // Проверяем фильтр глобального чата - if (config.getBoolean("chat.global_only", true) && !isGlobal) { + if (config.isGlobalOnly() && !isGlobal) { return; } - // Фильтрация сообщения - String cleanMessage = sanitizeMessage(message); - if (cleanMessage.length() < config.getInt("filter.min_message_length", 1)) { - return; - } - - // Обрезаем если слишком длинное - int maxLength = config.getInt("filter.max_message_length", 2000); - if (cleanMessage.length() > maxLength) { - cleanMessage = cleanMessage.substring(0, maxLength - 3) + "..."; - } - - String playerName = player.getName(); - String format = config.getString("chat.format", "**{player}**: {message}"); - String formattedMessage = format - .replace("{player}", playerName) - .replace("{message}", cleanMessage); - - // Отправляем асинхронно - if (config.getBoolean("performance.async", true)) { - FoliaUtil.runAsync(plugin, () -> sendChatMessageInternal(formattedMessage, playerName, isGlobal)); - } else { - sendChatMessageInternal(formattedMessage, playerName, isGlobal); - } + messageService.sendChatMessage(player, message, isGlobal); } - /** - * Внутренний метод отправки сообщения чата - */ - private void sendChatMessageInternal(String message, String playerName, boolean isGlobal) { - if (config.getBoolean("chat.use_embed", true)) { - // Получаем заголовки из конфигурации внешнего вида - com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin; - com.loki.lochat.config.AppearanceConfig appearanceConfig = loChat.getConfigManager().getAppearanceConfig(); - - String title = isGlobal ? - appearanceConfig.getDiscordEventTitle("global_chat") : - appearanceConfig.getDiscordEventTitle("local_chat"); - - // Если заголовок не найден в новой конфигурации, используем старый способ - if (title.isEmpty()) { - title = isGlobal ? "🌍 Глобальный чат" : "📍 Локальный чат"; - } - - String color = config.getString("chat.embed_color", appearanceConfig.getDefaultEmbedColor()); - String thumbnail = appearanceConfig.getPlayerAvatarUrl().replace("{player}", playerName); - - webhook.sendEmbed(title, message, color, thumbnail); - } else { - webhook.sendMessage(message); - } - } - - /** - * Отправить событие входа игрока - */ public void sendPlayerJoin(Player player) { - if (!enabled || !config.getBoolean("events.enabled", true) || - !config.getBoolean("events.join.enabled", true)) return; - - // Получаем конфигурацию внешнего вида - com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin; - com.loki.lochat.config.AppearanceConfig appearanceConfig = loChat.getConfigManager().getAppearanceConfig(); - - String playerName = player.getName(); - String title = config.getString("events.join.title", appearanceConfig.getDiscordEventTitle("join")); - String description = config.getString("events.join.description", "**{player}** присоединился к серверу") - .replace("{player}", playerName); - String color = config.getString("events.join.color", "00FF00"); - String thumbnail = config.getString("events.join.thumbnail", appearanceConfig.getPlayerAvatarUrl()) - .replace("{player}", playerName); - - if (config.getBoolean("performance.async", true)) { - FoliaUtil.runAsync(plugin, () -> webhook.sendEmbed(title, description, color, thumbnail)); - } else { - webhook.sendEmbed(title, description, color, thumbnail); - } + if (!enabled || !config.isEventEnabled("join")) return; + messageService.sendPlayerJoin(player); } - /** - * Отправить событие выхода игрока - */ public void sendPlayerQuit(Player player) { - if (!enabled || !config.getBoolean("events.enabled", true) || - !config.getBoolean("events.quit.enabled", true)) return; - - // Получаем конфигурацию внешнего вида - com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin; - com.loki.lochat.config.AppearanceConfig appearanceConfig = loChat.getConfigManager().getAppearanceConfig(); - - String playerName = player.getName(); - String title = config.getString("events.quit.title", appearanceConfig.getDiscordEventTitle("quit")); - String description = config.getString("events.quit.description", "**{player}** покинул сервер") - .replace("{player}", playerName); - String color = config.getString("events.quit.color", "FF0000"); - String thumbnail = config.getString("events.quit.thumbnail", appearanceConfig.getPlayerAvatarUrl()) - .replace("{player}", playerName); - - if (config.getBoolean("performance.async", true)) { - FoliaUtil.runAsync(plugin, () -> webhook.sendEmbed(title, description, color, thumbnail)); - } else { - webhook.sendEmbed(title, description, color, thumbnail); - } + if (!enabled || !config.isEventEnabled("quit")) return; + messageService.sendPlayerQuit(player); } - /** - * Отправить событие смерти игрока - */ public void sendPlayerDeath(Player player, String deathMessage) { - if (!enabled || !config.getBoolean("events.enabled", true) || - !config.getBoolean("events.death.enabled", true)) return; - - // Получаем конфигурацию внешнего вида - com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin; - com.loki.lochat.config.AppearanceConfig appearanceConfig = loChat.getConfigManager().getAppearanceConfig(); - - String playerName = player.getName(); - String title = config.getString("events.death.title", appearanceConfig.getDiscordEventTitle("death")); - String description = config.getString("events.death.description", "**{player}** {death_message}") - .replace("{player}", playerName) - .replace("{death_message}", sanitizeMessage(deathMessage)); - String color = config.getString("events.death.color", "800080"); - String thumbnail = config.getString("events.death.thumbnail", appearanceConfig.getPlayerAvatarUrl()) - .replace("{player}", playerName); - - if (config.getBoolean("performance.async", true)) { - FoliaUtil.runAsync(plugin, () -> webhook.sendEmbed(title, description, color, thumbnail)); - } else { - webhook.sendEmbed(title, description, color, thumbnail); - } + if (!enabled || !config.isEventEnabled("death")) return; + messageService.sendPlayerDeath(player, deathMessage); } - /** - * Очистить сообщение от нежелательных элементов - */ - private String sanitizeMessage(String message) { - if (message == null) return ""; - - // Удаляем цветовые коды Minecraft - message = COLOR_CODE_PATTERN.matcher(message).replaceAll(""); - - // Заменяем опасные упоминания - if (config.getBoolean("filter.sanitize_mentions", true)) { - message = MENTION_PATTERN.matcher(message).replaceAll("@\u200B$1"); // Добавляем невидимый символ - } - - // Экранируем Discord markdown - message = message.replace("*", "\\*") - .replace("_", "\\_") - .replace("`", "\\`") - .replace("~", "\\~"); - - return message.trim(); + public void sendTestMessage(String message, String senderName) { + if (!enabled) return; + messageService.sendTestMessage(message, senderName); } - /** - * Перезагрузить конфигурацию - */ public void reload() { - loadConfig(); - initWebhook(); + init(); plugin.getLogger().info("Discord integration reloaded"); } - /** - * Отправить тестовое сообщение - */ - public void sendTestMessage(String message, String senderName) { - if (!enabled) return; - - String title = "🧪 Тестовое сообщение"; - String description = "**Отправитель:** " + senderName + "\n**Сообщение:** " + message; - String color = "FFD700"; // золотой цвет - String thumbnail = "https://mc-heads.net/avatar/" + senderName + "/64"; - - webhook.sendEmbed(title, description, color, thumbnail); - } - - /** - * Остановить executor при выключении плагина - */ public void shutdown() { - if (webhook != null) webhook.shutdown(); + if (messageService != null) { + messageService.shutdown(); + } } - /** - * Проверить включена ли интеграция - */ public boolean isEnabled() { return enabled; } diff --git a/src/main/java/com/loki/lochat/integrations/PlaceholderAPIHook.java b/src/main/java/com/loki/lochat/integrations/PlaceholderAPIHook.java index 71fa31e..ec66a15 100644 --- a/src/main/java/com/loki/lochat/integrations/PlaceholderAPIHook.java +++ b/src/main/java/com/loki/lochat/integrations/PlaceholderAPIHook.java @@ -2,9 +2,7 @@ package com.loki.lochat.integrations; import com.loki.lochat.LoChat; import com.loki.lochat.api.service.ChatService; -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.api.service.MessagingService; import com.loki.lochat.gradient.GradientModule; import me.clip.placeholderapi.expansion.PlaceholderExpansion; import org.bukkit.Bukkit; @@ -19,17 +17,13 @@ import java.util.UUID; public class PlaceholderAPIHook extends PlaceholderExpansion { private final LoChat plugin; - private final IgnoreService ignoreService; + private final MessagingService messagingService; 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.messagingService = plugin.getServiceRegistry().get(MessagingService.class); this.chatService = plugin.getServiceRegistry().get(ChatService.class); - this.pmService = plugin.getServiceRegistry().get(PMService.class); - this.spyService = plugin.getServiceRegistry().get(SpyService.class); } @Override @@ -60,15 +54,15 @@ public class PlaceholderAPIHook extends PlaceholderExpansion { GradientModule gradient = plugin.getGradientModule(); return switch (params.toLowerCase()) { - case "ignored_count" -> String.valueOf(ignoreService.getIgnoredCount(uuid)); + case "ignored_count" -> String.valueOf(messagingService.getIgnoredCount(uuid)); case "global_enabled" -> String.valueOf(!chatService.isGlobalChatDisabled(uuid)); case "last_pm" -> { - Optional lastOpt = pmService.getLastConversation(uuid); + Optional lastOpt = messagingService.getLastConversation(uuid); if (lastOpt.isEmpty()) yield ""; var lastPlayer = Bukkit.getOfflinePlayer(lastOpt.get()); yield lastPlayer.getName() != null ? lastPlayer.getName() : ""; } - case "spy_enabled" -> String.valueOf(spyService.isSpying(uuid)); + case "spy_enabled" -> String.valueOf(messagingService.isSpying(uuid)); // Gradient placeholders (совместимость с LoPreff) // Используем TAB формат §x§R§R§G§G§B§B для совместимости с TAB плагином diff --git a/src/main/java/com/loki/lochat/integrations/discord/DiscordConfig.java b/src/main/java/com/loki/lochat/integrations/discord/DiscordConfig.java new file mode 100644 index 0000000..8594f59 --- /dev/null +++ b/src/main/java/com/loki/lochat/integrations/discord/DiscordConfig.java @@ -0,0 +1,126 @@ +package com.loki.lochat.integrations.discord; + +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; + +/** + * Конфигурация Discord интеграции + */ +public class DiscordConfig { + private final JavaPlugin plugin; + private YamlConfiguration config; + + public DiscordConfig(JavaPlugin plugin) { + this.plugin = plugin; + load(); + } + + private void load() { + File configFolder = new File(plugin.getDataFolder(), "config"); + if (!configFolder.exists()) { + configFolder.mkdirs(); + } + + File configFile = new File(configFolder, "discord.yml"); + if (!configFile.exists()) { + try { + java.io.InputStream in = plugin.getResource("config/discord.yml"); + if (in != null) { + java.nio.file.Files.copy(in, configFile.toPath()); + in.close(); + } + } catch (Exception e) { + plugin.getLogger().warning("Failed to copy config/discord.yml: " + e.getMessage()); + } + } + + config = YamlConfiguration.loadConfiguration(configFile); + } + + public boolean isEnabled() { + return config.getBoolean("enabled", false); + } + + public String getWebhookUrl() { + return config.getString("webhook.url", ""); + } + + public String getUsername() { + return config.getString("webhook.username", "Minecraft Server"); + } + + public String getAvatarUrl() { + return config.getString("webhook.avatar_url", ""); + } + + public int getTimeout() { + return config.getInt("performance.timeout", 10); + } + + public int getRetryAttempts() { + return config.getInt("performance.retry_attempts", 3); + } + + public long getRetryDelay() { + return config.getLong("performance.retry_delay", 1000); + } + + public boolean isAsync() { + return config.getBoolean("performance.async", true); + } + + public boolean isChatEnabled() { + return config.getBoolean("chat.enabled", true); + } + + public boolean isGlobalOnly() { + return config.getBoolean("chat.global_only", true); + } + + public boolean useEmbed() { + return config.getBoolean("chat.use_embed", true); + } + + public String getChatFormat() { + return config.getString("chat.format", "**{player}**: {message}"); + } + + public String getEmbedColor() { + return config.getString("chat.embed_color", "5865F2"); + } + + public boolean isEventEnabled(String event) { + return config.getBoolean("events.enabled", true) && + config.getBoolean("events." + event + ".enabled", true); + } + + public String getEventTitle(String event) { + return config.getString("events." + event + ".title", ""); + } + + public String getEventDescription(String event) { + return config.getString("events." + event + ".description", ""); + } + + public String getEventColor(String event) { + return config.getString("events." + event + ".color", "5865F2"); + } + + public String getEventThumbnail(String event) { + return config.getString("events." + event + ".thumbnail", ""); + } + + public int getMinMessageLength() { + return config.getInt("filter.min_message_length", 1); + } + + public int getMaxMessageLength() { + return config.getInt("filter.max_message_length", 2000); + } + + public boolean sanitizeMentions() { + return config.getBoolean("filter.sanitize_mentions", true); + } +} diff --git a/src/main/java/com/loki/lochat/integrations/discord/DiscordMessageService.java b/src/main/java/com/loki/lochat/integrations/discord/DiscordMessageService.java new file mode 100644 index 0000000..54ba337 --- /dev/null +++ b/src/main/java/com/loki/lochat/integrations/discord/DiscordMessageService.java @@ -0,0 +1,166 @@ +package com.loki.lochat.integrations.discord; + +import com.loki.lochat.integrations.DiscordWebhook; +import com.loki.lochat.utils.FoliaUtil; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.regex.Pattern; + +/** + * Сервис для отправки сообщений в Discord + */ +public class DiscordMessageService { + private static final Pattern MENTION_PATTERN = Pattern.compile("@(everyone|here)", Pattern.CASE_INSENSITIVE); + private static final Pattern COLOR_CODE_PATTERN = Pattern.compile("§[0-9a-fk-or]", Pattern.CASE_INSENSITIVE); + + private final JavaPlugin plugin; + private final DiscordConfig config; + private final DiscordWebhook webhook; + + public DiscordMessageService(JavaPlugin plugin, DiscordConfig config) { + this.plugin = plugin; + this.config = config; + this.webhook = new DiscordWebhook( + plugin, + config.getWebhookUrl(), + config.getUsername(), + getAvatarUrl(), + config.getTimeout(), + config.getRetryAttempts(), + config.getRetryDelay() + ); + } + + private String getAvatarUrl() { + String url = config.getAvatarUrl(); + if (url.isEmpty()) { + com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin; + return loChat.getConfigManager().getAppearanceConfig().getDefaultAvatarUrl(); + } + return url; + } + + public void sendChatMessage(Player player, String message, boolean isGlobal) { + String cleanMessage = sanitizeMessage(message); + + if (cleanMessage.length() < config.getMinMessageLength()) { + return; + } + + if (cleanMessage.length() > config.getMaxMessageLength()) { + cleanMessage = cleanMessage.substring(0, config.getMaxMessageLength() - 3) + "..."; + } + + String formatted = config.getChatFormat() + .replace("{player}", player.getName()) + .replace("{message}", cleanMessage); + + if (config.isAsync()) { + FoliaUtil.runAsync(plugin, () -> sendFormatted(formatted, player, isGlobal)); + } else { + sendFormatted(formatted, player, isGlobal); + } + } + + private void sendFormatted(String message, Player player, boolean isGlobal) { + if (config.useEmbed()) { + com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin; + com.loki.lochat.config.AppearanceConfig appearanceConfig = loChat.getConfigManager().getAppearanceConfig(); + + String title = isGlobal ? + appearanceConfig.getDiscordEventTitle("global_chat") : + appearanceConfig.getDiscordEventTitle("local_chat"); + + if (title.isEmpty()) { + title = isGlobal ? "🌍 Глобальный чат" : "📍 Локальный чат"; + } + + String color = config.getEmbedColor(); + String thumbnail = appearanceConfig.getPlayerAvatarUrl().replace("{player}", player.getName()); + + webhook.sendEmbed(title, message, color, thumbnail); + } else { + webhook.sendMessage(message); + } + } + + public void sendPlayerJoin(Player player) { + sendEvent(player, "join", null); + } + + public void sendPlayerQuit(Player player) { + sendEvent(player, "quit", null); + } + + public void sendPlayerDeath(Player player, String deathMessage) { + sendEvent(player, "death", deathMessage); + } + + private void sendEvent(Player player, String event, String extraMessage) { + com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin; + com.loki.lochat.config.AppearanceConfig appearanceConfig = loChat.getConfigManager().getAppearanceConfig(); + + String title = config.getEventTitle(event); + if (title.isEmpty()) { + title = appearanceConfig.getDiscordEventTitle(event); + } + + String description = config.getEventDescription(event) + .replace("{player}", player.getName()); + + if (extraMessage != null) { + description = description.replace("{death_message}", sanitizeMessage(extraMessage)); + } + + String color = config.getEventColor(event); + String thumbnail = config.getEventThumbnail(event); + if (thumbnail.isEmpty()) { + thumbnail = appearanceConfig.getPlayerAvatarUrl(); + } + final String finalThumbnail = thumbnail.replace("{player}", player.getName()); + final String finalTitle = title; + final String finalDescription = description; + final String finalColor = color; + + if (config.isAsync()) { + FoliaUtil.runAsync(plugin, () -> webhook.sendEmbed(finalTitle, finalDescription, finalColor, finalThumbnail)); + } else { + webhook.sendEmbed(title, description, color, finalThumbnail); + } + } + + public void sendTestMessage(String message, String senderName) { + String title = "🧪 Тестовое сообщение"; + String description = "**Отправитель:** " + senderName + "\n**Сообщение:** " + message; + String color = "FFD700"; + String thumbnail = "https://mc-heads.net/avatar/" + senderName + "/64"; + + webhook.sendEmbed(title, description, color, thumbnail); + } + + private String sanitizeMessage(String message) { + if (message == null) return ""; + + message = COLOR_CODE_PATTERN.matcher(message).replaceAll(""); + + if (config.sanitizeMentions()) { + message = MENTION_PATTERN.matcher(message).replaceAll("@\u200B$1"); + } + + message = message.replace("*", "\\*") + .replace("_", "\\_") + .replace("`", "\\`") + .replace("~", "\\~"); + + return message.trim(); + } + + public boolean isValid() { + return webhook.isValid(); + } + + public void shutdown() { + webhook.shutdown(); + } +} diff --git a/src/main/java/com/loki/lochat/listener/ChatEventListener.java b/src/main/java/com/loki/lochat/listener/ChatEventListener.java index 1b0e113..9b5a11a 100644 --- a/src/main/java/com/loki/lochat/listener/ChatEventListener.java +++ b/src/main/java/com/loki/lochat/listener/ChatEventListener.java @@ -3,6 +3,7 @@ package com.loki.lochat.listener; import com.loki.lochat.LoChat; import com.loki.lochat.api.service.MessageService; import com.loki.lochat.core.filter.AdvancedMessageFilter; +import com.loki.lochat.core.filter.FilterResult; import com.loki.lochat.core.registry.ServiceRegistry; import com.loki.lochat.renderer.EnhancedChatRenderer; import com.loki.lochat.utils.ChatFormatter; @@ -29,66 +30,81 @@ public class ChatEventListener implements Listener { @EventHandler(priority = EventPriority.HIGHEST) public void onChat(AsyncChatEvent event) { Player sender = event.getPlayer(); - - // plain нужен только для фильтров — цвета игрока обрабатываем ниже отдельно String rawMessage = PlainTextComponentSerializer.plainText().serialize(event.message()); - - // ✅ РЕЖИМ ЕДИНОГО ЧАТА: если локальный отключен, весь чат = глобальный LoChat loChat = (LoChat) plugin; - boolean localEnabled = loChat.getConfigManager().isLocalEnabled(); - - boolean isGlobal; - String plainMessage; - - if (localEnabled) { - // Старая логика: "!" = глобальный, без "!" = локальный - isGlobal = rawMessage.startsWith("!"); - plainMessage = isGlobal ? rawMessage.substring(1).stripLeading() : rawMessage; - } else { - // Новая логика: весь чат глобальный, "!" не нужен - isGlobal = true; - plainMessage = rawMessage; + + // Определяем режим чата (глобальный/локальный) + ChatMode mode = determineChatMode(loChat, rawMessage); + if (mode == null) { + event.setCancelled(true); + sender.sendMessage(ChatFormatter.parse("&#CF6679Этот режим чата отключен!")); + return; } - // ✅ Проверка: включен ли глобальный/локальный чат + String plainMessage = mode.message; + boolean isGlobal = mode.isGlobal; + + // Проверки: мут, кулдаун, фильтры + if (!processFilters(event, sender, plainMessage, isGlobal)) { + return; + } + + // Обработка локального чата (радиус) + if (!isGlobal) { + handleLocalChat(event, sender, loChat); + } + + // Рендеринг и интеграции + event.renderer(new EnhancedChatRenderer(plugin, isGlobal)); + loChat.getDiscordIntegration().sendChatMessage(sender, plainMessage, isGlobal); + recordStatistics(loChat, sender, isGlobal); + } + + private ChatMode determineChatMode(LoChat loChat, String rawMessage) { + boolean localEnabled = loChat.getConfigManager().isLocalEnabled(); + boolean isGlobal; + String message; + + if (localEnabled) { + isGlobal = rawMessage.startsWith("!"); + message = isGlobal ? rawMessage.substring(1).stripLeading() : rawMessage; + } else { + isGlobal = true; + message = rawMessage; + } + + // Проверка: включен ли режим if (isGlobal && !loChat.getConfigManager().isGlobalEnabled()) { - event.setCancelled(true); - sender.sendMessage(ChatFormatter.parse("&#CF6679Глобальный чат отключен!")); - return; + return null; } - if (!isGlobal && !localEnabled) { - event.setCancelled(true); - sender.sendMessage(ChatFormatter.parse("&#CF6679Локальный чат отключен!")); - return; + return null; } + return new ChatMode(isGlobal, message); + } + + private boolean processFilters(AsyncChatEvent event, Player sender, String plainMessage, boolean isGlobal) { + LoChat loChat = (LoChat) plugin; + // Мут, кулдаун if (!messageService.processMessage(sender, plainMessage)) { event.setCancelled(true); - return; + return false; } // Капс, реклама, повторы - AdvancedMessageFilter.FilterResult filterResult = advancedFilter.filterMessage(sender, plainMessage); + FilterResult filterResult = advancedFilter.filterMessage(sender, plainMessage); if (!filterResult.allowed()) { event.setCancelled(true); sender.sendMessage(ChatFormatter.parse(filterResult.blockReason())); - return; + return false; } String filteredMessage = filterResult.filteredMessage(); - // ── Цвет текста сообщения ({message}) ───────────────────────────────── - // Если стиль не задан явно, Paper может унаследовать цвет последнего символа градиента - // префикса в строке чата (например у LOCAL последняя буква давала синеватый #4169E1) — - // из‑за этого весь текст сообщения «подтягивался» под этот тон. Намеренный цвет текста - // задаётся в appearance.yml: prefixes.global.message-color / prefixes.local.message-color (#E8E0F0 по умолчанию). - // Решение: всегда выставлять стиль тела сообщения: - // • lochat.chat.colors → MiniMessage / &# / & из текста - // • иначе → parseWithDefaultMessageColor(..., message-color) + экранирование < (см. ChatFormatter) + // Цвет текста сообщения Component messageComponent; - if (sender.hasPermission("lochat.chat.colors")) { messageComponent = ChatFormatter.parse(filteredMessage); } else { @@ -99,46 +115,41 @@ public class ChatEventListener implements Listener { } event.message(messageComponent); + return true; + } - // ✅ FIX: Радиус для локального чата - кэшируем локации в async потоке - // Paper позволяет читать Location в async, но не модифицировать World - if (!isGlobal) { - int radius = loChat.getConfigManager().getAppearanceConfig().getLocalRadius(); - - // Безопасно: AsyncChatEvent.viewers() thread-safe, Location.distance() тоже - event.viewers().removeIf(v -> { - if (!(v instanceof Player p)) return false; - - // Проверяем мир и расстояние (чтение Location безопасно в async) - try { - return !com.loki.lochat.util.DistanceUtil.isInRange(sender, p, radius); - } catch (Exception e) { - // На всякий случай ловим исключения - return false; - } - }); + private void handleLocalChat(AsyncChatEvent event, Player sender, LoChat loChat) { + int radius = loChat.getConfigManager().getAppearanceConfig().getLocalRadius(); - long recipients = event.viewers().stream() - .filter(v -> v instanceof Player p && !p.equals(sender)) - .count(); - - if (recipients == 0) { - String msg = loChat.getConfigManager().getMessagesConfig().getNobodyHeard(); - sender.getScheduler().run(plugin, t -> - sender.sendMessage(ChatFormatter.parse(msg)), null - ); + event.viewers().removeIf(v -> { + if (!(v instanceof Player p)) return false; + try { + return !com.loki.lochat.utils.DistanceUtil.isInRange(sender, p, radius); + } catch (Exception e) { + return false; } + }); + + long recipients = event.viewers().stream() + .filter(v -> v instanceof Player p && !p.equals(sender)) + .count(); + + if (recipients == 0) { + String msg = loChat.getConfigManager().getMessagesConfig().getNobodyHeard(); + sender.getScheduler().run(plugin, t -> + sender.sendMessage(ChatFormatter.parse(msg)), null + ); } + } - event.renderer(new EnhancedChatRenderer(plugin, isGlobal)); - - loChat.getDiscordIntegration().sendChatMessage(sender, filteredMessage, isGlobal); - - // Статистика - com.loki.lochat.api.service.PlayerDataService pds = - loChat.getServiceRegistry().get(com.loki.lochat.api.service.PlayerDataService.class); - if (pds instanceof com.loki.lochat.core.service.PlayerDataServiceImpl impl) { + private void recordStatistics(LoChat loChat, Player sender, boolean isGlobal) { + com.loki.lochat.api.service.PlayerService playerService = + loChat.getServiceRegistry().get(com.loki.lochat.api.service.PlayerService.class); + if (playerService instanceof com.loki.lochat.core.service.PlayerServiceImpl impl) { impl.recordMessage(sender.getUniqueId(), isGlobal ? "global" : "local"); } } + + private record ChatMode(boolean isGlobal, String message) { + } } diff --git a/src/main/java/com/loki/lochat/listener/PlayerEventListener.java b/src/main/java/com/loki/lochat/listener/PlayerEventListener.java index 0792af5..3c4ba6a 100644 --- a/src/main/java/com/loki/lochat/listener/PlayerEventListener.java +++ b/src/main/java/com/loki/lochat/listener/PlayerEventListener.java @@ -1,9 +1,8 @@ package com.loki.lochat.listener; +import com.loki.lochat.api.service.MessagingService; import com.loki.lochat.api.service.NickService; -import com.loki.lochat.api.service.PlayerDataService; -import com.loki.lochat.api.service.PMService; -import com.loki.lochat.api.service.SpyService; +import com.loki.lochat.api.service.PlayerService; import com.loki.lochat.core.filter.AdvancedMessageFilter; import com.loki.lochat.core.registry.ServiceRegistry; import org.bukkit.entity.Player; @@ -16,17 +15,15 @@ import org.bukkit.event.player.PlayerQuitEvent; * Слушатель событий игрока */ public class PlayerEventListener implements Listener { - private final PlayerDataService playerDataService; + private final PlayerService playerService; + private final MessagingService messagingService; private final NickService nickService; - private final PMService pmService; - private final SpyService spyService; private final AdvancedMessageFilter advancedFilter; public PlayerEventListener(ServiceRegistry registry, AdvancedMessageFilter advancedFilter) { - this.playerDataService = registry.get(PlayerDataService.class); - this.nickService = registry.get(NickService.class); - this.pmService = registry.get(PMService.class); - this.spyService = registry.get(SpyService.class); + this.playerService = registry.get(PlayerService.class); + this.messagingService = registry.get(MessagingService.class); + this.nickService = registry.get(NickService.class); this.advancedFilter = advancedFilter; } @@ -43,9 +40,9 @@ public class PlayerEventListener implements Listener { Player player = event.getPlayer(); java.util.UUID uuid = player.getUniqueId(); - playerDataService.clearPlayerData(uuid); - pmService.removeConversation(uuid); - spyService.removeSpy(uuid); + playerService.clearPlayerData(uuid); + messagingService.removeConversation(uuid); + messagingService.removeSpy(uuid); // Очищаем flood / spam трекеры — иначе данные висят в памяти вечно advancedFilter.clearPlayer(uuid); diff --git a/src/main/java/com/loki/lochat/managers/AutoMessageManager.java b/src/main/java/com/loki/lochat/managers/AutoMessageManager.java index 582bc75..4781b03 100644 --- a/src/main/java/com/loki/lochat/managers/AutoMessageManager.java +++ b/src/main/java/com/loki/lochat/managers/AutoMessageManager.java @@ -1,7 +1,7 @@ package com.loki.lochat.managers; import com.loki.lochat.LoChat; -import com.loki.lochat.util.FoliaUtil; +import com.loki.lochat.utils.FoliaUtil; import com.loki.lochat.utils.ChatFormatter; import io.papermc.paper.threadedregions.scheduler.ScheduledTask; import net.kyori.adventure.text.Component; diff --git a/src/main/java/com/loki/lochat/managers/CustomCommandManager.java b/src/main/java/com/loki/lochat/managers/CustomCommandManager.java index 006f5f0..c8980de 100644 --- a/src/main/java/com/loki/lochat/managers/CustomCommandManager.java +++ b/src/main/java/com/loki/lochat/managers/CustomCommandManager.java @@ -2,9 +2,9 @@ package com.loki.lochat.managers; import com.loki.lochat.LoChat; import com.loki.lochat.commands.CustomCommand; +import com.loki.lochat.managers.commands.MessageSender; +import com.loki.lochat.managers.commands.PlaceholderProcessor; import com.loki.lochat.utils.ChatFormatter; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.title.Title; import org.bukkit.Bukkit; import org.bukkit.command.CommandMap; import org.bukkit.configuration.ConfigurationSection; @@ -13,7 +13,6 @@ import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import java.io.File; -import java.time.Duration; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -41,11 +40,8 @@ public class CustomCommandManager { } FileConfiguration commandsConfig = YamlConfiguration.loadConfiguration(commandsFile); - - // Очищаем старые команды commands.clear(); - // Загружаем команды из конфига for (String commandName : commandsConfig.getKeys(false)) { ConfigurationSection section = commandsConfig.getConfigurationSection(commandName); if (section == null) continue; @@ -61,8 +57,6 @@ public class CustomCommandManager { ); commands.put(commandName, data); - - // Регистрируем команду registerCommand(data); } @@ -71,14 +65,11 @@ public class CustomCommandManager { private void registerCommand(CustomCommandData data) { try { - // Получаем CommandMap через Paper API (безопаснее чем рефлексия) CommandMap commandMap = Bukkit.getCommandMap(); - // Создаем и регистрируем команду CustomCommand command = new CustomCommand(data.name(), this); commandMap.register("lochat", command); - // Регистрируем алиасы for (String alias : data.aliases()) { CustomCommand aliasCommand = new CustomCommand(alias, this); commandMap.register("lochat", aliasCommand); @@ -97,7 +88,6 @@ public class CustomCommandManager { public boolean executeCustomCommand(String commandName, Player player, String[] args) { CustomCommandData data = commands.get(commandName); if (data == null) { - // Проверяем алиасы for (CustomCommandData cmd : commands.values()) { if (cmd.aliases().contains(commandName)) { data = cmd; @@ -108,173 +98,32 @@ public class CustomCommandManager { if (data == null) return false; - // Проверяем включена ли команда if (!data.enabled()) { - // Не показываем ошибку, просто игнорируем команду return true; } - // Проверяем права if (data.permission() != null && !player.hasPermission(data.permission())) { player.sendMessage(ChatFormatter.parse(plugin.getMessageConfig().getNoPermission())); return true; } - // Обрабатываем плейсхолдеры - String message = processPlaceholders(data.message(), player, args); + String message = PlaceholderProcessor.process(data.message(), player, args); - // Выполняем команду в зависимости от типа switch (data.type().toLowerCase()) { - case "broadcast" -> sendBroadcast(data.target(), message); - case "title" -> sendTitle(data.target(), player, message); - case "actionbar" -> sendActionBar(data.target(), player, message); + case "title" -> MessageSender.sendTitle(data.target(), player, message); + case "actionbar" -> MessageSender.sendActionBar(data.target(), player, message); case "bungee" -> sendBungeeCommand(player, data); - default -> sendChatMessage(data.target(), player, message); + default -> MessageSender.sendChat(data.target(), player, message); } return true; } - private String processPlaceholders(String message, Player player, String[] args) { - String result = message; - - // Базовые плейсхолдеры - result = result.replace("{player}", player.getName()); - result = result.replace("{x}", String.valueOf(player.getLocation().getBlockX())); - result = result.replace("{y}", String.valueOf(player.getLocation().getBlockY())); - result = result.replace("{z}", String.valueOf(player.getLocation().getBlockZ())); - result = result.replace("{world}", player.getWorld().getName()); - result = result.replace("{ping}", String.valueOf(player.getPing())); - - // Аргументы команды - for (int i = 0; i < args.length; i++) { - result = result.replace("{arg" + i + "}", args[i]); - } - result = result.replace("{args}", String.join(" ", args)); - - // PlaceholderAPI если доступен - if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) { - try { - // Используем безопасный способ вызова PlaceholderAPI - Class papiClass = Class.forName("me.clip.placeholderapi.PlaceholderAPI"); - java.lang.reflect.Method setPlaceholdersMethod = papiClass.getMethod("setPlaceholders", org.bukkit.entity.Player.class, String.class); - result = (String) setPlaceholdersMethod.invoke(null, player, result); - } catch (Exception e) { - // Если PlaceholderAPI недоступен или произошла ошибка, просто пропускаем - plugin.getLogger().warning("Ошибка при обработке PlaceholderAPI плейсхолдеров: " + e.getMessage()); - } - } - - return result; - } - - private void sendChatMessage(String target, Player sender, String message) { - Component component = ChatFormatter.parse(message); - - switch (target.toLowerCase()) { - case "sender" -> sender.sendMessage(component); - case "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 { - sender.sendMessage(component); - } - } - } - } - - private void sendBroadcast(String target, String message) { - Component component = ChatFormatter.parse(message); - - if (target.equalsIgnoreCase("all")) { - for (Player player : Bukkit.getOnlinePlayers()) { - 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); - } - } - } - } - } - - private void sendTitle(String target, Player sender, String message) { - String[] parts = message.split("\\n", 2); - Component title = ChatFormatter.parse(parts[0]); - Component subtitle = parts.length > 1 ? ChatFormatter.parse(parts[1]) : Component.empty(); - - Title titleObj = Title.title(title, subtitle, - Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(500))); - - switch (target.toLowerCase()) { - case "sender" -> sender.showTitle(titleObj); - case "all" -> { - for (Player player : Bukkit.getOnlinePlayers()) { - player.showTitle(titleObj); - } - } - default -> { - if (target.startsWith("permission:")) { - String permission = target.substring(11); - for (Player player : Bukkit.getOnlinePlayers()) { - if (player.hasPermission(permission)) { - player.showTitle(titleObj); - } - } - } else { - sender.showTitle(titleObj); - } - } - } - } - - private void sendActionBar(String target, Player sender, String message) { - Component component = ChatFormatter.parse(message); - - switch (target.toLowerCase()) { - case "sender" -> sender.sendActionBar(component); - case "all" -> { - for (Player player : Bukkit.getOnlinePlayers()) { - player.sendActionBar(component); - } - } - default -> { - if (target.startsWith("permission:")) { - String permission = target.substring(11); - for (Player player : Bukkit.getOnlinePlayers()) { - if (player.hasPermission(permission)) { - player.sendActionBar(component); - } - } - } else { - sender.sendActionBar(component); - } - } - } - } - private void sendBungeeCommand(Player player, CustomCommandData data) { - // Отправляем сообщение игроку if (!data.message().isEmpty()) { - Component component = ChatFormatter.parse(data.message()); - player.sendMessage(component); + player.sendMessage(ChatFormatter.parse(data.message())); } - // Подключаем к серверу через BungeeCord String serverName = getServerFromConfig(data); if (serverName != null && !serverName.isEmpty()) { connectToServer(player, serverName); @@ -282,7 +131,6 @@ public class CustomCommandManager { } private String getServerFromConfig(CustomCommandData data) { - // Ищем параметр server в конфиге команды try { File commandsFile = new File(plugin.getDataFolder(), "custom-commands.yml"); FileConfiguration config = YamlConfiguration.loadConfiguration(commandsFile); diff --git a/src/main/java/com/loki/lochat/managers/commands/MessageSender.java b/src/main/java/com/loki/lochat/managers/commands/MessageSender.java new file mode 100644 index 0000000..492933b --- /dev/null +++ b/src/main/java/com/loki/lochat/managers/commands/MessageSender.java @@ -0,0 +1,73 @@ +package com.loki.lochat.managers.commands; + +import com.loki.lochat.utils.ChatFormatter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.title.Title; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.time.Duration; + +public class MessageSender { + + public static void sendChat(String target, Player sender, String message) { + Component formatted = ChatFormatter.parse(message); + + switch (target.toLowerCase()) { + case "player" -> sender.sendMessage(formatted); + case "broadcast" -> Bukkit.broadcast(formatted); + case "server" -> Bukkit.getOnlinePlayers().forEach(p -> p.sendMessage(formatted)); + default -> { + Player targetPlayer = Bukkit.getPlayer(target); + if (targetPlayer != null && targetPlayer.isOnline()) { + targetPlayer.sendMessage(formatted); + } + } + } + } + + public static void sendTitle(String target, Player sender, String message) { + String[] parts = message.split("\\|", 2); + String titleText = parts.length > 0 ? parts[0] : ""; + String subtitleText = parts.length > 1 ? parts[1] : ""; + + Component title = ChatFormatter.parse(titleText); + Component subtitle = ChatFormatter.parse(subtitleText); + + Title titleObj = Title.title( + title, + subtitle, + Title.Times.times( + Duration.ofMillis(500), + Duration.ofMillis(3000), + Duration.ofMillis(500) + ) + ); + + switch (target.toLowerCase()) { + case "player" -> sender.showTitle(titleObj); + case "broadcast", "server" -> Bukkit.getOnlinePlayers().forEach(p -> p.showTitle(titleObj)); + default -> { + Player targetPlayer = Bukkit.getPlayer(target); + if (targetPlayer != null && targetPlayer.isOnline()) { + targetPlayer.showTitle(titleObj); + } + } + } + } + + public static void sendActionBar(String target, Player sender, String message) { + Component formatted = ChatFormatter.parse(message); + + switch (target.toLowerCase()) { + case "player" -> sender.sendActionBar(formatted); + case "broadcast", "server" -> Bukkit.getOnlinePlayers().forEach(p -> p.sendActionBar(formatted)); + default -> { + Player targetPlayer = Bukkit.getPlayer(target); + if (targetPlayer != null && targetPlayer.isOnline()) { + targetPlayer.sendActionBar(formatted); + } + } + } + } +} diff --git a/src/main/java/com/loki/lochat/managers/commands/PlaceholderProcessor.java b/src/main/java/com/loki/lochat/managers/commands/PlaceholderProcessor.java new file mode 100644 index 0000000..55ab7b1 --- /dev/null +++ b/src/main/java/com/loki/lochat/managers/commands/PlaceholderProcessor.java @@ -0,0 +1,36 @@ +package com.loki.lochat.managers.commands; + +import org.bukkit.entity.Player; + +public class PlaceholderProcessor { + + public static String process(String message, Player player, String[] args) { + String result = message; + + // Базовые плейсхолдеры + result = result.replace("{player}", player.getName()); + result = result.replace("{displayname}", player.getDisplayName()); + result = result.replace("{world}", player.getWorld().getName()); + result = result.replace("{x}", String.valueOf(player.getLocation().getBlockX())); + result = result.replace("{y}", String.valueOf(player.getLocation().getBlockY())); + result = result.replace("{z}", String.valueOf(player.getLocation().getBlockZ())); + result = result.replace("{health}", String.valueOf(Math.round(player.getHealth()))); + result = result.replace("{food}", String.valueOf(player.getFoodLevel())); + result = result.replace("{level}", String.valueOf(player.getLevel())); + result = result.replace("{exp}", String.valueOf(player.getExp())); + + // Аргументы команды + for (int i = 0; i < args.length; i++) { + result = result.replace("{arg" + i + "}", args[i]); + } + + // Все аргументы вместе + if (args.length > 0) { + result = result.replace("{args}", String.join(" ", args)); + } else { + result = result.replace("{args}", ""); + } + + return result; + } +} diff --git a/src/main/java/com/loki/lochat/renderer/EnhancedChatRenderer.java b/src/main/java/com/loki/lochat/renderer/EnhancedChatRenderer.java index 197049d..2b4798d 100644 --- a/src/main/java/com/loki/lochat/renderer/EnhancedChatRenderer.java +++ b/src/main/java/com/loki/lochat/renderer/EnhancedChatRenderer.java @@ -1,5 +1,6 @@ package com.loki.lochat.renderer; +import com.loki.lochat.renderer.components.*; import com.loki.lochat.utils.MentionHandler; import com.loki.lochat.utils.TextFormatter; import io.papermc.paper.chat.ChatRenderer; @@ -9,16 +10,13 @@ import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.HoverEvent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextColor; -import net.kyori.adventure.text.format.TextDecoration; -import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import java.util.List; /** - * Рендерер чата — глобальный и локальный режимы. - * Цвета PREFIX и текста сообщения полностью настраиваются через chat-appearance.yml. + * Рендерер чата — глобальный и локальный режимы */ public class EnhancedChatRenderer implements ChatRenderer { @@ -43,14 +41,14 @@ public class EnhancedChatRenderer implements ChatRenderer { // Обрабатываем сообщение Component processed = message; processed = TextFormatter.formatMarkdown(processed); - processed = processUrls(processed); - processed = processEmojis(processed); + processed = UrlProcessor.process(processed); + processed = EmojiComponent.processEmojis(processed); if (viewer instanceof Player vp) { processed = mentionHandler.processMentions(processed, source, vp); } - // ✅ Получаем кастомный формат из конфига + // Получаем формат из конфига com.loki.lochat.config.AppearanceConfig appearanceCfg = cfg.getAppearanceConfig(); String format = isGlobal ? appearanceCfg.getGlobalChatFormat() : appearanceCfg.getLocalChatFormat(); @@ -61,13 +59,9 @@ public class EnhancedChatRenderer implements ChatRenderer { Component playerPrefix = buildPlayerPrefix(source); Component player = buildPlayerComponent(source); - // Парсим формат и заменяем плейсхолдеры return buildFormattedMessage(format, emoji, prefix, separator, playerPrefix, player, processed); } - /** - * Строит финальное сообщение по формату с плейсхолдерами - */ private Component buildFormattedMessage(String format, Component emoji, Component prefix, Component separator, Component playerPrefix, Component player, Component message) { @@ -78,12 +72,10 @@ public class EnhancedChatRenderer implements ChatRenderer { int lastEnd = 0; while (matcher.find()) { - // Добавляем текст до плейсхолдера if (matcher.start() > lastEnd) { result = result.append(Component.text(format.substring(lastEnd, matcher.start()))); } - // Заменяем плейсхолдер String placeholder = matcher.group(1); switch (placeholder) { case "emoji" -> result = result.append(emoji); @@ -98,7 +90,6 @@ public class EnhancedChatRenderer implements ChatRenderer { lastEnd = matcher.end(); } - // Добавляем оставшийся текст if (lastEnd < format.length()) { result = result.append(Component.text(format.substring(lastEnd))); } @@ -106,115 +97,35 @@ public class EnhancedChatRenderer implements ChatRenderer { return result; } - /** - * Строит компонент эмодзи - */ private Component buildEmojiComponent() { com.loki.lochat.config.AppearanceConfig appearanceCfg = cfg.getAppearanceConfig(); String emoji = isGlobal ? appearanceCfg.getGlobalEmoji() : appearanceCfg.getLocalEmoji(); return emoji.isEmpty() ? Component.empty() : Component.text(emoji); } - /** - * Строит префикс игрока из LuckPerms/градиента - */ private Component buildPlayerPrefix(Player player) { - // Получаем градиентный модуль com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin; com.loki.lochat.gradient.GradientModule gradientModule = loChat.getGradientModule(); - if (gradientModule != null) { - com.loki.lochat.gradient.data.GradientPlayerData data = - gradientModule.getDataManager().getPlayerData(player.getUniqueId()); - - if (data != null && data.isPrefixEnabled() && data.hasPrefix()) { - String prefixText = data.getPrefix(); - if (data.hasColors() && data.isColorEnabled()) { - // Применяем градиент к префиксу - return buildGradientText(prefixText, data.getColors()); - } - return Component.text(prefixText); + if (gradientModule != null && gradientModule.isEnabled()) { + String prefix = gradientModule.getPrefix(player); + if (!prefix.isEmpty()) { + return com.loki.lochat.utils.ChatFormatter.parse(prefix); } } return Component.empty(); } - // ────────────────────────────────────────────────────────────────────────── - // Префикс - // ────────────────────────────────────────────────────────────────────────── - private Component buildPrefix() { - // ✅ FIX: Получаем AppearanceConfig через ConfigManager com.loki.lochat.config.AppearanceConfig appearanceCfg = cfg.getAppearanceConfig(); String text = isGlobal ? appearanceCfg.getGlobalText() : appearanceCfg.getLocalText(); List colors = isGlobal ? appearanceCfg.getGlobalColors() : appearanceCfg.getLocalColors(); - return buildGradientText(text, colors); + return GradientBuilder.build(text, colors); } - /** - * Строит компонент с плавным градиентом. - * colors — список hex-цветов (минимум 1). - * Если цветов меньше чем букв — интерполируем плавно между соседними цветами. - * Если цветов больше чем букв — берём по одному на букву. - */ - private Component buildGradientText(String text, List colors) { - if (text.isEmpty()) return Component.empty(); - - if (colors.isEmpty()) { - return Component.text(text, FALLBACK_COLOR); - } - - // Один цвет — просто красим весь текст - if (colors.size() == 1) { - return Component.text(text, parseColor(colors.get(0))); - } - - char[] chars = text.toCharArray(); - int len = chars.length; - Component result = Component.empty(); - - for (int i = 0; i < len; i++) { - // Нормализованная позиция 0..1 - float t = len == 1 ? 0f : (float) i / (len - 1); - - // Какой сегмент градиента - float scaled = t * (colors.size() - 1); - int seg = (int) scaled; - float frac = scaled - seg; - - // Зажимаем в пределах списка - if (seg >= colors.size() - 1) { - seg = colors.size() - 2; - frac = 1f; - } - - TextColor from = parseColor(colors.get(seg)); - TextColor to = parseColor(colors.get(seg + 1)); - TextColor blended = blend(from, to, frac); - - result = result.append(Component.text(String.valueOf(chars[i]), blended)); - } - - return result; - } - - /** Линейная интерполяция между двумя TextColor */ - private static TextColor blend(TextColor from, TextColor to, float t) { - int r = Math.round(from.red() + t * (to.red() - from.red())); - int g = Math.round(from.green() + t * (to.green() - from.green())); - int b = Math.round(from.blue() + t * (to.blue() - from.blue())); - return TextColor.color(clamp(r), clamp(g), clamp(b)); - } - - private static int clamp(int v) { return Math.max(0, Math.min(255, v)); } - - // ────────────────────────────────────────────────────────────────────────── - // Разделитель - // ────────────────────────────────────────────────────────────────────────── - private Component buildSeparator() { com.loki.lochat.config.AppearanceConfig appearanceCfg = cfg.getAppearanceConfig(); String text = isGlobal ? appearanceCfg.getGlobalSeparatorText() : appearanceCfg.getLocalSeparatorText(); @@ -222,14 +133,9 @@ public class EnhancedChatRenderer implements ChatRenderer { return Component.text(text, parseColor(color)); } - // ────────────────────────────────────────────────────────────────────────── - // Ник игрока с hover - // ────────────────────────────────────────────────────────────────────────── - private Component buildPlayerComponent(Player player) { Component displayName = player.displayName(); - // ✅ FIX: Получаем AppearanceConfig для проверки hover com.loki.lochat.config.AppearanceConfig appearanceCfg = cfg.getAppearanceConfig(); if (!appearanceCfg.isHoverEnabled()) { @@ -271,42 +177,6 @@ public class EnhancedChatRenderer implements ChatRenderer { .clickEvent(ClickEvent.suggestCommand("/msg " + player.getName() + " ")); } - // ────────────────────────────────────────────────────────────────────────── - // URL и эмодзи - // ────────────────────────────────────────────────────────────────────────── - - private Component processUrls(Component message) { - String plain = PlainTextComponentSerializer.plainText().serialize(message); - java.util.regex.Pattern pattern = - java.util.regex.Pattern.compile("(https?://[\\w\\-.~:/?#\\[\\]@!$&'()*+,;=%]+)"); - java.util.regex.Matcher matcher = pattern.matcher(plain); - - Component result = message; - while (matcher.find()) { - String url = matcher.group(1); - Component urlComp = Component.text(url, NamedTextColor.AQUA, TextDecoration.UNDERLINED) - .hoverEvent(HoverEvent.showText(Component.text("Открыть: " + url, NamedTextColor.GRAY))) - .clickEvent(ClickEvent.openUrl(url)); - result = result.replaceText(b -> b.matchLiteral(url).replacement(urlComp)); - } - return result; - } - - private Component processEmojis(Component message) { - // ✅ FIX: Получаем AppearanceConfig для эмодзи - com.loki.lochat.config.AppearanceConfig appearanceCfg = cfg.getAppearanceConfig(); - - Component result = message; - for (java.util.Map.Entry e : appearanceCfg.getEmojis().entrySet()) { - result = result.replaceText(b -> b.matchLiteral(e.getKey()).replacement(e.getValue())); - } - return result; - } - - // ────────────────────────────────────────────────────────────────────────── - // Утилиты - // ────────────────────────────────────────────────────────────────────────── - private TextColor parseColor(String hex) { if (hex == null || hex.isBlank()) return FALLBACK_COLOR; String n = hex.startsWith("#") ? hex : "#" + hex; diff --git a/src/main/java/com/loki/lochat/renderer/components/EmojiComponent.java b/src/main/java/com/loki/lochat/renderer/components/EmojiComponent.java new file mode 100644 index 0000000..9ae30a5 --- /dev/null +++ b/src/main/java/com/loki/lochat/renderer/components/EmojiComponent.java @@ -0,0 +1,36 @@ +package com.loki.lochat.renderer.components; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.TextColor; +import org.bukkit.configuration.ConfigurationSection; + +public class EmojiComponent { + + public static Component build(ConfigurationSection emojiSection) { + if (emojiSection == null || !emojiSection.getBoolean("enabled", false)) { + return Component.empty(); + } + + String text = emojiSection.getString("text", ""); + String color = emojiSection.getString("color", "#FFFFFF"); + + return Component.text(text) + .color(TextColor.fromHexString(color)); + } + + public static Component processEmojis(Component message) { + if (!(message instanceof TextComponent textComp)) { + return message; + } + + String content = textComp.content(); + content = content.replace(":heart:", "❤") + .replace(":star:", "⭐") + .replace(":fire:", "🔥") + .replace(":check:", "✓") + .replace(":cross:", "✗"); + + return textComp.content(content); + } +} diff --git a/src/main/java/com/loki/lochat/renderer/components/GradientBuilder.java b/src/main/java/com/loki/lochat/renderer/components/GradientBuilder.java new file mode 100644 index 0000000..8e7e79a --- /dev/null +++ b/src/main/java/com/loki/lochat/renderer/components/GradientBuilder.java @@ -0,0 +1,48 @@ +package com.loki.lochat.renderer.components; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.TextColor; + +import java.util.List; + +public class GradientBuilder { + + public static Component build(String text, List colors) { + if (colors == null || colors.size() < 2) { + return Component.text(text); + } + + TextColor[] gradientColors = colors.stream() + .map(TextColor::fromHexString) + .toArray(TextColor[]::new); + + TextComponent.Builder builder = Component.text(); + int len = text.length(); + + for (int i = 0; i < len; i++) { + float t = (float) i / (len - 1); + int segmentIndex = (int) (t * (gradientColors.length - 1)); + float segmentT = (t * (gradientColors.length - 1)) - segmentIndex; + + TextColor from = gradientColors[segmentIndex]; + TextColor to = gradientColors[Math.min(segmentIndex + 1, gradientColors.length - 1)]; + TextColor blended = blend(from, to, segmentT); + + builder.append(Component.text(text.charAt(i)).color(blended)); + } + + return builder.build(); + } + + private static TextColor blend(TextColor from, TextColor to, float t) { + int r = (int) (from.red() * (1 - t) + to.red() * t); + int g = (int) (from.green() * (1 - t) + to.green() * t); + int b = (int) (from.blue() * (1 - t) + to.blue() * t); + return TextColor.color(clamp(r), clamp(g), clamp(b)); + } + + private static int clamp(int v) { + return Math.max(0, Math.min(255, v)); + } +} diff --git a/src/main/java/com/loki/lochat/renderer/components/UrlProcessor.java b/src/main/java/com/loki/lochat/renderer/components/UrlProcessor.java new file mode 100644 index 0000000..f84b311 --- /dev/null +++ b/src/main/java/com/loki/lochat/renderer/components/UrlProcessor.java @@ -0,0 +1,53 @@ +package com.loki.lochat.renderer.components; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.TextColor; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class UrlProcessor { + private static final Pattern URL_PATTERN = Pattern.compile( + "https?://[\\w\\-._~:/?#\\[\\]@!$&'()*+,;=%]+" + ); + + public static Component process(Component message) { + if (!(message instanceof TextComponent textComp)) { + return message; + } + + String content = textComp.content(); + Matcher matcher = URL_PATTERN.matcher(content); + + if (!matcher.find()) { + return message; + } + + TextComponent.Builder builder = Component.text(); + int lastEnd = 0; + + matcher.reset(); + while (matcher.find()) { + if (matcher.start() > lastEnd) { + builder.append(Component.text(content.substring(lastEnd, matcher.start()))); + } + + String url = matcher.group(); + builder.append(Component.text(url) + .color(TextColor.fromHexString("#5DADE2")) + .clickEvent(ClickEvent.openUrl(url)) + .hoverEvent(HoverEvent.showText(Component.text("Открыть: " + url)))); + + lastEnd = matcher.end(); + } + + if (lastEnd < content.length()) { + builder.append(Component.text(content.substring(lastEnd))); + } + + return builder.build(); + } +} diff --git a/src/main/java/com/loki/lochat/util/TimeParser.java b/src/main/java/com/loki/lochat/util/TimeParser.java deleted file mode 100644 index e6c5499..0000000 --- a/src/main/java/com/loki/lochat/util/TimeParser.java +++ /dev/null @@ -1,57 +0,0 @@ -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 + "с"; - } - } -} diff --git a/src/main/java/com/loki/lochat/utils/ChatFormatter.java b/src/main/java/com/loki/lochat/utils/ChatFormatter.java index a653670..47e4616 100644 --- a/src/main/java/com/loki/lochat/utils/ChatFormatter.java +++ b/src/main/java/com/loki/lochat/utils/ChatFormatter.java @@ -1,146 +1,49 @@ package com.loki.lochat.utils; +import com.loki.lochat.utils.color.ColorConverter; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; import org.bukkit.entity.Player; +/** + * Утилита для форматирования сообщений - упрощенная версия + */ public final class ChatFormatter { private static final MiniMessage MM = MiniMessage.miniMessage(); - private static final PlainTextComponentSerializer PLAIN = - PlainTextComponentSerializer.plainText(); + private static final PlainTextComponentSerializer PLAIN = PlainTextComponentSerializer.plainText(); private ChatFormatter() { } - /* ===================== BASIC ===================== */ + // ========== Базовые методы ========== public static Component parse(String message) { if (message == null) return Component.empty(); - // Конвертируем legacy форматы в MiniMessage перед парсингом - String converted = convertLegacyFormats(message); + String converted = ColorConverter.convertLegacyFormats(message); return MM.deserialize(converted); } - /** - * Default message color for players without {@code lochat.chat.colors}. - * Escapes {@code \} and {@code <} in the raw text so a literal {@code } (or any {@code <}) - * cannot close the outer MiniMessage wrapper early — which used to leave a stray {@code } - * visible in-game and in the server console log. - *

- * Legacy hex ({@code &#RRGGBB}) and ampersand color codes in the message are still applied after escaping - * (see {@link #convertLegacyFormats} inside {@link #parse}). - */ public static Component parseWithDefaultMessageColor(String message, String hexCode) { if (message == null) message = ""; - String hex = (hexCode == null || hexCode.isBlank()) - ? "#FFFFFF" - : (hexCode.startsWith("#") ? hexCode : "#" + hexCode); - String escaped = escapeForMiniMessageOuterWrap(message); + String hex = (hexCode == null || hexCode.isBlank()) ? "#FFFFFF" : + (hexCode.startsWith("#") ? hexCode : "#" + hexCode); + String escaped = ColorConverter.escapeForMiniMessage(message); return parse("" + escaped + ""); } - /** - * Escape user text before it is embedded in a MiniMessage template with outer tags. - * MiniMessage treats {@code \<} as a literal angle bracket. - */ - public static String escapeForMiniMessageOuterWrap(String text) { - if (text == null) return ""; - return text.replace("\\", "\\\\").replace("<", "\\<"); - } - public static Component parse(String message, String... replacements) { if (message == null) return Component.empty(); String result = message; for (int i = 0; i + 1 < replacements.length; i += 2) { result = result.replace(replacements[i], replacements[i + 1]); } - // Конвертируем legacy форматы в MiniMessage перед парсингом - String converted = convertLegacyFormats(result); + String converted = ColorConverter.convertLegacyFormats(result); return MM.deserialize(converted); } - /** - * Конвертирует legacy форматы цветов в MiniMessage формат - */ - private static String convertLegacyFormats(String message) { - if (message == null) return ""; - - // Конвертируем &#RRGGBB в - java.util.regex.Pattern pattern1 = java.util.regex.Pattern.compile("&#([0-9a-fA-F]{6})"); - java.util.regex.Matcher matcher1 = pattern1.matcher(message); - StringBuffer sb1 = new StringBuffer(); - while (matcher1.find()) { - String hex = matcher1.group(1).toLowerCase(); - matcher1.appendReplacement(sb1, ""); - } - matcher1.appendTail(sb1); - message = sb1.toString(); - - // Конвертируем #RRGGBB в (только если не внутри тегов) - java.util.regex.Pattern pattern2 = java.util.regex.Pattern.compile("(?)"); - java.util.regex.Matcher matcher2 = pattern2.matcher(message); - StringBuffer sb2 = new StringBuffer(); - while (matcher2.find()) { - String hex = matcher2.group(1).toLowerCase(); - matcher2.appendReplacement(sb2, ""); - } - matcher2.appendTail(sb2); - message = sb2.toString(); - - // Конвертируем §[0-9a-fk-or] в соответствующие MiniMessage теги - message = message.replaceAll("§0", ""); - message = message.replaceAll("§1", ""); - message = message.replaceAll("§2", ""); - message = message.replaceAll("§3", ""); - message = message.replaceAll("§4", ""); - message = message.replaceAll("§5", ""); - message = message.replaceAll("§6", ""); - message = message.replaceAll("§7", ""); - message = message.replaceAll("§8", ""); - message = message.replaceAll("§9", ""); - message = message.replaceAll("§a", ""); - message = message.replaceAll("§b", ""); - message = message.replaceAll("§c", ""); - message = message.replaceAll("§d", ""); - message = message.replaceAll("§e", ""); - message = message.replaceAll("§f", ""); - message = message.replaceAll("§k", ""); - message = message.replaceAll("§l", ""); - message = message.replaceAll("§m", ""); - message = message.replaceAll("§n", ""); - message = message.replaceAll("§o", ""); - message = message.replaceAll("§r", ""); - - // Конвертируем &[0-9a-fk-or] в соответствующие MiniMessage теги - message = message.replaceAll("&0", ""); - message = message.replaceAll("&1", ""); - message = message.replaceAll("&2", ""); - message = message.replaceAll("&3", ""); - message = message.replaceAll("&4", ""); - message = message.replaceAll("&5", ""); - message = message.replaceAll("&6", ""); - message = message.replaceAll("&7", ""); - message = message.replaceAll("&8", ""); - message = message.replaceAll("&9", ""); - message = message.replaceAll("&a", ""); - message = message.replaceAll("&b", ""); - message = message.replaceAll("&c", ""); - message = message.replaceAll("&d", ""); - message = message.replaceAll("&e", ""); - message = message.replaceAll("&f", ""); - - // Конвертируем форматирование - message = message.replaceAll("&k", ""); - message = message.replaceAll("&l", ""); - message = message.replaceAll("&m", ""); - message = message.replaceAll("&n", ""); - message = message.replaceAll("&o", ""); - message = message.replaceAll("&r", ""); - - return message; - } + // ========== Утилиты ========== public static String stripTags(String message) { if (message == null) return ""; @@ -159,63 +62,24 @@ public final class ChatFormatter { return obj.toString(); } - /** - * Отправляет сообщение игроку - */ public static void sendMessage(Player player, Component component) { if (player != null && component != null) { player.sendMessage(component); } } - /* ===================== COLORS ===================== */ - - /** - * Конвертирует различные форматы цветов в MiniMessage формат - */ public static String convertAllColors(String message) { if (message == null) return ""; - return convertLegacyFormats(message); + return ColorConverter.convertLegacyFormats(message); } - /* ===================== EMOJIS (REMOVED) ===================== */ - - public static String replaceEmojis(String message) { - return message; // Эмодзи удалены - } - - public static String replaceEmojis(String message, Player player) { - return message; // Эмодзи удалены - } - - /* ===================== CHAT ===================== */ - - /** - * Legacy API для ChatManager - */ - public static String processPlayerMessage( - String message, - Player player, - boolean hasColorPermission - ) { - return message; - } - - /* ===================== ANNOUNCE ===================== */ + // ========== Форматирование сообщений ========== public static Component formatAnnouncement(String format, String message) { return parse(format.replace("", message)); } - /* ===================== PM ===================== */ - - public static Component formatPmSentNew( - String format, - Player sender, - Player receiver, - String message - ) { - // Получаем отображаемые имена игроков + public static Component formatPmSentNew(String format, Player sender, Player receiver, String message) { String senderDisplay = getPlayerDisplayName(sender); String receiverDisplay = getPlayerDisplayName(receiver); @@ -227,13 +91,7 @@ public final class ChatFormatter { .replace("{message}", message)); } - public static Component formatPmReceivedNew( - String format, - Player sender, - Player receiver, - String message - ) { - // Получаем отображаемые имена игроков + public static Component formatPmReceivedNew(String format, Player sender, Player receiver, String message) { String senderDisplay = getPlayerDisplayName(sender); String receiverDisplay = getPlayerDisplayName(receiver); @@ -245,40 +103,45 @@ public final class ChatFormatter { .replace("{message}", message)); } - /** - * Получает отображаемое имя игрока с градиентом - */ - private static String getPlayerDisplayName(Player player) { - try { - // Получаем экземпляр плагина - com.loki.lochat.LoChat plugin = com.loki.lochat.LoChat.getInstance(); - - // Если градиентный модуль включен, используем его - if (plugin.getGradientModule() != null && plugin.getGradientModule().isEnabled()) { - return plugin.getGradientModule().getFormattedName(player); - } - } catch (Exception e) { - // Если произошла ошибка, логируем и возвращаем обычное имя - com.loki.lochat.LoChat.getInstance().getLogger().warning( - "Ошибка при получении градиентного имени для " + player.getName() + ": " + e.getMessage() - ); - } - - // Иначе просто имя игрока - return player.getName(); - } - - /* ===================== FORMAT (Component-based) ===================== */ - - public static Component format( - String format, - Component name, - Component prefix, - Component message - ) { + public static Component format(String format, Component name, Component prefix, Component message) { return MM.deserialize(format) .replaceText(b -> b.matchLiteral("{name}").replacement(name)) .replaceText(b -> b.matchLiteral("{prefix}").replacement(prefix)) .replaceText(b -> b.matchLiteral("{message}").replacement(message)); } + + // ========== Приватные методы ========== + + private static String getPlayerDisplayName(Player player) { + try { + com.loki.lochat.LoChat plugin = com.loki.lochat.LoChat.getInstance(); + + if (plugin.getGradientModule() != null && plugin.getGradientModule().isEnabled()) { + return plugin.getGradientModule().getFormattedName(player); + } + } catch (Exception e) { + com.loki.lochat.LoChat.getInstance().getLogger().warning( + "Ошибка при получении градиентного имени для " + player.getName() + ": " + e.getMessage() + ); + } + + return player.getName(); + } + + // ========== Deprecated методы для обратной совместимости ========== + + @Deprecated + public static String replaceEmojis(String message) { + return message; + } + + @Deprecated + public static String replaceEmojis(String message, Player player) { + return message; + } + + @Deprecated + public static String processPlayerMessage(String message, Player player, boolean hasColorPermission) { + return message; + } } diff --git a/src/main/java/com/loki/lochat/util/DistanceUtil.java b/src/main/java/com/loki/lochat/utils/DistanceUtil.java similarity index 93% rename from src/main/java/com/loki/lochat/util/DistanceUtil.java rename to src/main/java/com/loki/lochat/utils/DistanceUtil.java index 5491d9e..0e4e34b 100644 --- a/src/main/java/com/loki/lochat/util/DistanceUtil.java +++ b/src/main/java/com/loki/lochat/utils/DistanceUtil.java @@ -1,4 +1,4 @@ -package com.loki.lochat.util; +package com.loki.lochat.utils; import org.bukkit.entity.Player; diff --git a/src/main/java/com/loki/lochat/util/FoliaUtil.java b/src/main/java/com/loki/lochat/utils/FoliaUtil.java similarity index 98% rename from src/main/java/com/loki/lochat/util/FoliaUtil.java rename to src/main/java/com/loki/lochat/utils/FoliaUtil.java index e451744..3f93ae7 100644 --- a/src/main/java/com/loki/lochat/util/FoliaUtil.java +++ b/src/main/java/com/loki/lochat/utils/FoliaUtil.java @@ -1,4 +1,4 @@ -package com.loki.lochat.util; +package com.loki.lochat.utils; import org.bukkit.Bukkit; import org.bukkit.entity.Entity; diff --git a/src/main/java/com/loki/lochat/utils/MentionHandler.java b/src/main/java/com/loki/lochat/utils/MentionHandler.java index c4e053e..e60c659 100644 --- a/src/main/java/com/loki/lochat/utils/MentionHandler.java +++ b/src/main/java/com/loki/lochat/utils/MentionHandler.java @@ -1,6 +1,6 @@ package com.loki.lochat.utils; -import com.loki.lochat.util.FoliaUtil; +import com.loki.lochat.utils.FoliaUtil; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.TextDecoration; diff --git a/src/main/java/com/loki/lochat/utils/color/ColorConverter.java b/src/main/java/com/loki/lochat/utils/color/ColorConverter.java new file mode 100644 index 0000000..fb5de3c --- /dev/null +++ b/src/main/java/com/loki/lochat/utils/color/ColorConverter.java @@ -0,0 +1,144 @@ +package com.loki.lochat.utils.color; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Конвертер различных форматов цветов в MiniMessage + */ +public class ColorConverter { + + private static final Pattern HEX_PATTERN_1 = Pattern.compile("&#([0-9a-fA-F]{6})"); + private static final Pattern HEX_PATTERN_2 = Pattern.compile("(?)"); + + // Мапа для быстрой конвертации цветов (вместо 32 вызовов replaceAll) + private static final Map COLOR_MAP = Map.ofEntries( + // § коды + Map.entry("§0", ""), + Map.entry("§1", ""), + Map.entry("§2", ""), + Map.entry("§3", ""), + Map.entry("§4", ""), + Map.entry("§5", ""), + Map.entry("§6", ""), + Map.entry("§7", ""), + Map.entry("§8", ""), + Map.entry("§9", ""), + Map.entry("§a", ""), + Map.entry("§b", ""), + Map.entry("§c", ""), + Map.entry("§d", ""), + Map.entry("§e", ""), + Map.entry("§f", ""), + Map.entry("§k", ""), + Map.entry("§l", ""), + Map.entry("§m", ""), + Map.entry("§n", ""), + Map.entry("§o", ""), + Map.entry("§r", ""), + // & коды + Map.entry("&0", ""), + Map.entry("&1", ""), + Map.entry("&2", ""), + Map.entry("&3", ""), + Map.entry("&4", ""), + Map.entry("&5", ""), + Map.entry("&6", ""), + Map.entry("&7", ""), + Map.entry("&8", ""), + Map.entry("&9", ""), + Map.entry("&a", ""), + Map.entry("&b", ""), + Map.entry("&c", ""), + Map.entry("&d", ""), + Map.entry("&e", ""), + Map.entry("&f", ""), + Map.entry("&k", ""), + Map.entry("&l", ""), + Map.entry("&m", ""), + Map.entry("&n", ""), + Map.entry("&o", ""), + Map.entry("&r", "") + ); + + /** + * Конвертирует legacy форматы цветов в MiniMessage формат + */ + public static String convertLegacyFormats(String message) { + if (message == null) return ""; + + // Конвертируем &#RRGGBB в + message = convertHexFormat1(message); + + // Конвертируем #RRGGBB в + message = convertHexFormat2(message); + + // Конвертируем § и & коды через Map (быстрее чем 32 replaceAll) + message = convertLegacyCodes(message); + + return message; + } + + private static String convertHexFormat1(String message) { + Matcher matcher = HEX_PATTERN_1.matcher(message); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + String hex = matcher.group(1).toLowerCase(); + matcher.appendReplacement(sb, ""); + } + matcher.appendTail(sb); + return sb.toString(); + } + + private static String convertHexFormat2(String message) { + Matcher matcher = HEX_PATTERN_2.matcher(message); + StringBuffer sb = new StringBuffer(); + while (matcher.find()) { + String hex = matcher.group(1).toLowerCase(); + matcher.appendReplacement(sb, ""); + } + matcher.appendTail(sb); + return sb.toString(); + } + + /** + * Конвертирует legacy коды (§ и &) через Map - быстрее чем 32 replaceAll + */ + private static String convertLegacyCodes(String message) { + StringBuilder result = new StringBuilder(message.length()); + int i = 0; + + while (i < message.length()) { + if (i + 1 < message.length()) { + char current = message.charAt(i); + char next = message.charAt(i + 1); + + // Проверяем § или & + if (current == '§' || current == '&') { + String code = String.valueOf(current) + next; + String replacement = COLOR_MAP.get(code); + + if (replacement != null) { + result.append(replacement); + i += 2; + continue; + } + } + } + + result.append(message.charAt(i)); + i++; + } + + return result.toString(); + } + + /** + * Экранирует текст для безопасного использования внутри MiniMessage тегов + */ + public static String escapeForMiniMessage(String text) { + if (text == null) return ""; + return text.replace("\\", "\\\\").replace("<", "\\<"); + } +}