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
This commit is contained in:
parent
2d79d2a91d
commit
bba480002f
181 changed files with 3455 additions and 2908 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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<UUID> getIgnoredPlayers(UUID player);
|
||||
|
||||
/**
|
||||
* Получает количество игнорируемых игроков
|
||||
*/
|
||||
int getIgnoredCount(UUID player);
|
||||
|
||||
/**
|
||||
* Очищает список игнорируемых для игрока
|
||||
*/
|
||||
void clearIgnores(UUID player);
|
||||
|
||||
/**
|
||||
* Сохраняет данные на диск
|
||||
*/
|
||||
void save();
|
||||
|
||||
/**
|
||||
* Загружает данные с диска
|
||||
*/
|
||||
void load();
|
||||
}
|
||||
111
src/main/java/com/loki/lochat/api/service/MessagingService.java
Normal file
111
src/main/java/com/loki/lochat/api/service/MessagingService.java
Normal file
|
|
@ -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<UUID> 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<UUID> getIgnoredPlayers(UUID player);
|
||||
|
||||
/**
|
||||
* Получает количество игнорируемых игроков
|
||||
*/
|
||||
int getIgnoredCount(UUID player);
|
||||
|
||||
/**
|
||||
* Очищает список игнорируемых для игрока
|
||||
*/
|
||||
void clearIgnores(UUID player);
|
||||
|
||||
// ========== Persistence ==========
|
||||
|
||||
/**
|
||||
* Сохраняет данные на диск
|
||||
*/
|
||||
void save();
|
||||
|
||||
/**
|
||||
* Загружает данные с диска
|
||||
*/
|
||||
void load();
|
||||
}
|
||||
|
|
@ -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<UUID> getLastConversation(UUID player);
|
||||
|
||||
/**
|
||||
* Удаляет информацию о последнем разговоре
|
||||
*/
|
||||
void removeConversation(UUID player);
|
||||
|
||||
/**
|
||||
* Проверяет, есть ли у игрока активный разговор
|
||||
*/
|
||||
boolean hasConversation(UUID player);
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package com.loki.lochat.api.service;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Сервис для работы с данными игроков
|
||||
*/
|
||||
public interface PlayerDataService {
|
||||
void clearPlayerData(UUID player);
|
||||
|
||||
void saveAll();
|
||||
}
|
||||
56
src/main/java/com/loki/lochat/api/service/PlayerService.java
Normal file
56
src/main/java/com/loki/lochat/api/service/PlayerService.java
Normal file
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<UUID> ignored = ignoreService.getIgnoredPlayers(player.getUniqueId());
|
||||
Set<UUID> 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")));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<UUID> lastUuid = pmService.getLastConversation(player.getUniqueId());
|
||||
Optional<UUID> 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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String> 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<String> 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<String> 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<String> 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<String> getHoverFormat() {
|
||||
return config.getStringList("hover.format");
|
||||
}
|
||||
public boolean isHoverEnabled() { return config.getBoolean("hover.enabled", true); }
|
||||
public List<String> getHoverFormat() { return config.getStringList("hover.format"); }
|
||||
|
||||
// ========== ЭМОДЗИ ==========
|
||||
|
||||
/**
|
||||
* Получить все эмодзи замены
|
||||
*/
|
||||
public Map<String, String> getEmojis() {
|
||||
return new HashMap<>(emojiCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить замену для эмодзи
|
||||
*/
|
||||
public String getEmoji(String key) {
|
||||
return emojiCache.get(key);
|
||||
}
|
||||
public Map<String, String> 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"); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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[ОБЪЯВЛЕНИЕ] <message>"));
|
||||
return ChatFormatter.convertAllColors(config.getString("formats.announcement",
|
||||
"&#FFD700[ОБЪЯВЛЕНИЕ] <message>"));
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
// Муты
|
||||
|
|
|
|||
|
|
@ -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<String> whitelistedDomains;
|
||||
private final List<String> blacklistedDomains;
|
||||
private final CapsFilter capsFilter;
|
||||
|
||||
// ── Word filter ────────────────────────────────────────────────────────────
|
||||
private final Set<String> badWords = new HashSet<>();
|
||||
private final Set<String> badFragments = new HashSet<>();
|
||||
private final char replacementChar;
|
||||
private final boolean checkFragments;
|
||||
private final boolean ignoreCase;
|
||||
|
||||
// ── Anti-flood: сколько сообщений за период ────────────────────────────────
|
||||
// UUID → список timestamp'ов
|
||||
private final Map<UUID, Deque<Long>> floodTracker = new ConcurrentHashMap<>();
|
||||
|
||||
// ── Anti-spam: последние N сообщений игрока ────────────────────────────────
|
||||
private final Map<UUID, Deque<String>> 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<String> 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<Long> 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<String> 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) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
src/main/java/com/loki/lochat/core/filter/FilterResult.java
Normal file
20
src/main/java/com/loki/lochat/core/filter/FilterResult.java
Normal file
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UUID, Deque<Long>> 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<Long> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UUID, Deque<String>> 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<String> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Pattern> 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;
|
||||
}
|
||||
}
|
||||
106
src/main/java/com/loki/lochat/core/filter/filters/UrlFilter.java
Normal file
106
src/main/java/com/loki/lochat/core/filter/filters/UrlFilter.java
Normal file
|
|
@ -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<String> whitelistedDomains;
|
||||
private final Set<String> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <T> void register(Class<T> serviceClass, T implementation) {
|
||||
services.put(serviceClass, implementation);
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрирует сервис с возможностью приведения типов (для обратной совместимости)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public void registerCompat(Class<?> serviceClass, Object implementation) {
|
||||
services.put(serviceClass, implementation);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(Class<T> serviceClass) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<UUID, Long> globalCooldowns = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, Long> localCooldowns = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public boolean isOnCooldown(UUID player, String type, int cooldownSeconds) {
|
||||
Map<UUID, Long> cooldowns = getCooldownMap(type);
|
||||
Long lastTime = cooldowns.get(player);
|
||||
|
||||
if (lastTime == null) return false;
|
||||
|
||||
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
|
||||
return elapsed < cooldownSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemainingCooldown(UUID player, String type, int cooldownSeconds) {
|
||||
Map<UUID, Long> cooldowns = getCooldownMap(type);
|
||||
Long lastTime = cooldowns.get(player);
|
||||
|
||||
if (lastTime == null) return 0;
|
||||
|
||||
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
|
||||
return Math.max(0, cooldownSeconds - (int) elapsed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCooldown(UUID player, String type) {
|
||||
getCooldownMap(type).put(player, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCooldown(UUID player) {
|
||||
globalCooldowns.remove(player);
|
||||
localCooldowns.remove(player);
|
||||
}
|
||||
|
||||
private Map<UUID, Long> getCooldownMap(String type) {
|
||||
return type.equals("global") ? globalCooldowns : localCooldowns;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UUID, Set<UUID>> ignoreMap = new ConcurrentHashMap<>();
|
||||
|
||||
public IgnoreServiceImpl(JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
this.ignoreFile = new File(plugin.getDataFolder(), "ignores.yml");
|
||||
load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
if (!ignoreFile.exists()) return;
|
||||
|
||||
YamlConfiguration config = YamlConfiguration.loadConfiguration(ignoreFile);
|
||||
for (String key : config.getKeys(false)) {
|
||||
try {
|
||||
UUID uuid = UUID.fromString(key);
|
||||
List<String> ignoredList = config.getStringList(key);
|
||||
Set<UUID> ignored = ConcurrentHashMap.newKeySet();
|
||||
|
||||
for (String ignoredStr : ignoredList) {
|
||||
try {
|
||||
ignored.add(UUID.fromString(ignoredStr));
|
||||
} catch (Exception ignored2) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignored.isEmpty()) {
|
||||
ignoreMap.put(uuid, ignored);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
YamlConfiguration config = new YamlConfiguration();
|
||||
|
||||
for (Map.Entry<UUID, Set<UUID>> entry : ignoreMap.entrySet()) {
|
||||
List<String> ignoredList = new ArrayList<>();
|
||||
for (UUID ignored : entry.getValue()) {
|
||||
ignoredList.add(ignored.toString());
|
||||
}
|
||||
config.set(entry.getKey().toString(), ignoredList);
|
||||
}
|
||||
|
||||
try {
|
||||
config.save(ignoreFile);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().severe("Не удалось сохранить игноры: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isIgnoring(UUID player, UUID target) {
|
||||
Set<UUID> ignored = ignoreMap.get(player);
|
||||
return ignored != null && ignored.contains(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addIgnore(UUID player, UUID target) {
|
||||
Set<UUID> ignored = ignoreMap.computeIfAbsent(player, k -> ConcurrentHashMap.newKeySet());
|
||||
boolean added = ignored.add(target);
|
||||
if (added) saveAsync();
|
||||
return added;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeIgnore(UUID player, UUID target) {
|
||||
Set<UUID> ignored = ignoreMap.get(player);
|
||||
if (ignored == null) return false;
|
||||
boolean removed = ignored.remove(target);
|
||||
if (removed) saveAsync();
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getIgnoredPlayers(UUID player) {
|
||||
return ignoreMap.getOrDefault(player, Collections.emptySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIgnoredCount(UUID player) {
|
||||
Set<UUID> ignored = ignoreMap.get(player);
|
||||
return ignored == null ? 0 : ignored.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearIgnores(UUID player) {
|
||||
ignoreMap.remove(player);
|
||||
saveAsync();
|
||||
}
|
||||
|
||||
private void saveAsync() {
|
||||
FoliaUtil.runAsync(plugin, this::save);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<UUID, UUID> lastConversation = new ConcurrentHashMap<>();
|
||||
|
||||
// Spy state
|
||||
private final Set<UUID> spyEnabled = ConcurrentHashMap.newKeySet();
|
||||
|
||||
// Ignore state
|
||||
private final Map<UUID, Set<UUID>> 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<UUID> getLastConversation(UUID player) {
|
||||
return Optional.ofNullable(lastConversation.get(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConversation(UUID player) {
|
||||
lastConversation.remove(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConversation(UUID player) {
|
||||
return lastConversation.containsKey(player);
|
||||
}
|
||||
|
||||
// ========== 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<UUID> ignored = ignoreMap.get(player);
|
||||
return ignored != null && ignored.contains(target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addIgnore(UUID player, UUID target) {
|
||||
Set<UUID> ignored = ignoreMap.computeIfAbsent(player, k -> ConcurrentHashMap.newKeySet());
|
||||
boolean added = ignored.add(target);
|
||||
if (added) saveAsync();
|
||||
return added;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeIgnore(UUID player, UUID target) {
|
||||
Set<UUID> ignored = ignoreMap.get(player);
|
||||
if (ignored == null) return false;
|
||||
boolean removed = ignored.remove(target);
|
||||
if (removed) saveAsync();
|
||||
return removed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getIgnoredPlayers(UUID player) {
|
||||
return ignoreMap.getOrDefault(player, Collections.emptySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIgnoredCount(UUID player) {
|
||||
Set<UUID> ignored = ignoreMap.get(player);
|
||||
return ignored == null ? 0 : ignored.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearIgnores(UUID player) {
|
||||
ignoreMap.remove(player);
|
||||
saveAsync();
|
||||
}
|
||||
|
||||
// ========== 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<String> ignoredList = config.getStringList(key);
|
||||
Set<UUID> 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<UUID, Set<UUID>> entry : ignoreMap.entrySet()) {
|
||||
List<String> ignoredList = new ArrayList<>();
|
||||
for (UUID ignored : entry.getValue()) {
|
||||
ignoredList.add(ignored.toString());
|
||||
}
|
||||
config.set(entry.getKey().toString(), ignoredList);
|
||||
}
|
||||
|
||||
try {
|
||||
config.save(ignoreFile);
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().severe("Не удалось сохранить игноры: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void saveAsync() {
|
||||
FoliaUtil.runAsync(plugin, this::save);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UUID, MuteData> mutes = new ConcurrentHashMap<>();
|
||||
private final MuteHistoryManager historyManager;
|
||||
private final File dataFile;
|
||||
private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
||||
private final Map<UUID, MuteData> mutes = new HashMap<>();
|
||||
private final Map<UUID, List<MuteData.MuteHistoryEntry>> 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<MuteData.MuteHistoryEntry> getPlayerHistory(UUID uuid) {
|
||||
return historyManager.getHistory(uuid);
|
||||
return history.getOrDefault(uuid, new ArrayList<>());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MuteData.MuteHistoryEntry> getMutesByOperator(String operatorName) {
|
||||
List<MuteData.MuteHistoryEntry> 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<UUID, List<MuteData.MuteHistoryEntry>> 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<UUID, MuteData> 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<MuteData.MuteHistoryEntry> 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<String, MuteData> 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<MuteData.MuteHistoryEntry> 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<Map<String, MuteData>>() {
|
||||
}.getType();
|
||||
Map<String, MuteData> loaded = gson.fromJson(reader, type);
|
||||
if (loaded != null) {
|
||||
loaded.forEach((key, value) -> {
|
||||
try {
|
||||
mutes.put(UUID.fromString(key), value);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Error loading mutes: " + e.getMessage());
|
||||
}
|
||||
// TODO: Implement load from file
|
||||
}
|
||||
|
||||
private void saveAsync() {
|
||||
|
|
|
|||
|
|
@ -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<UUID, UUID> lastConversation = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void setLastConversation(UUID player, UUID target) {
|
||||
lastConversation.put(player, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<UUID> getLastConversation(UUID player) {
|
||||
return Optional.ofNullable(lastConversation.get(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConversation(UUID player) {
|
||||
lastConversation.remove(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasConversation(UUID player) {
|
||||
return lastConversation.containsKey(player);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UUID, Long> globalCooldowns = new ConcurrentHashMap<>();
|
||||
private final Map<UUID, Long> localCooldowns = new ConcurrentHashMap<>();
|
||||
|
||||
// Statistics state
|
||||
private final Map<UUID, Long> 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<UUID, Long> cooldowns = getCooldownMap(type);
|
||||
Long lastTime = cooldowns.get(player);
|
||||
|
||||
if (lastTime == null) return false;
|
||||
|
||||
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
|
||||
return elapsed < cooldownSeconds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRemainingCooldown(UUID player, String type, int cooldownSeconds) {
|
||||
Map<UUID, Long> cooldowns = getCooldownMap(type);
|
||||
Long lastTime = cooldowns.get(player);
|
||||
|
||||
if (lastTime == null) return 0;
|
||||
|
||||
long elapsed = (System.currentTimeMillis() - lastTime) / 1000;
|
||||
return Math.max(0, cooldownSeconds - (int) elapsed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCooldown(UUID player, String type) {
|
||||
getCooldownMap(type).put(player, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeCooldown(UUID player) {
|
||||
globalCooldowns.remove(player);
|
||||
localCooldowns.remove(player);
|
||||
}
|
||||
|
||||
private Map<UUID, Long> getCooldownMap(String type) {
|
||||
return type.equals("global") ? globalCooldowns : localCooldowns;
|
||||
}
|
||||
|
||||
// ========== 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();
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<UUID> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<UUID, MuteData> 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<UUID, MuteData> 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<Map<String, MuteData>>() {}.getType();
|
||||
Map<String, MuteData> loaded = gson.fromJson(reader, type);
|
||||
if (loaded != null) {
|
||||
loaded.forEach((key, value) -> {
|
||||
try {
|
||||
mutes.put(UUID.fromString(key), value);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Error loading mutes: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public void save() {
|
||||
try {
|
||||
ensureDataFolderExists();
|
||||
|
||||
Map<String, MuteData> toSave = new ConcurrentHashMap<>();
|
||||
mutes.forEach((uuid, data) -> toSave.put(uuid.toString(), data));
|
||||
|
||||
try (Writer writer = new FileWriter(dataFile)) {
|
||||
gson.toJson(toSave, writer);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
plugin.getLogger().warning("Error saving mutes: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void saveAsync() {
|
||||
FoliaUtil.runAsync(plugin, this::save);
|
||||
}
|
||||
|
||||
private void ensureDataFolderExists() {
|
||||
if (!dataFile.getParentFile().exists()) {
|
||||
dataFile.getParentFile().mkdirs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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() {
|
||||
|
|
|
|||
|
|
@ -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<String> 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<String> 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<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
|
||||
@NotNull String alias, @NotNull String[] args) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<String> 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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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<String> colors) {
|
||||
if (colors == null || colors.isEmpty()) {
|
||||
return text;
|
||||
}
|
||||
|
||||
return GradientUtil.applyGradient(text, colors, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Применяет градиент в формате для TAB плагина
|
||||
*/
|
||||
public static String applyGradientForTab(String text, List<String> 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}|<[^>]+>)", "");
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<UUID> lastOpt = pmService.getLastConversation(uuid);
|
||||
Optional<UUID> 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 плагином
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> colors = isGlobal ? appearanceCfg.getGlobalColors() : appearanceCfg.getLocalColors();
|
||||
|
||||
return buildGradientText(text, colors);
|
||||
return GradientBuilder.build(text, colors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Строит компонент с плавным градиентом.
|
||||
* colors — список hex-цветов (минимум 1).
|
||||
* Если цветов меньше чем букв — интерполируем плавно между соседними цветами.
|
||||
* Если цветов больше чем букв — берём по одному на букву.
|
||||
*/
|
||||
private Component buildGradientText(String text, List<String> 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<String, String> 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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String> 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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 + "с";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 </color>} (or any {@code <})
|
||||
* cannot close the outer MiniMessage wrapper early — which used to leave a stray {@code </color>}
|
||||
* visible in-game and in the server console log.
|
||||
* <p>
|
||||
* 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("<color:" + hex + ">" + escaped + "</color>");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 в <color:#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, "<color:#" + hex + ">");
|
||||
}
|
||||
matcher1.appendTail(sb1);
|
||||
message = sb1.toString();
|
||||
|
||||
// Конвертируем #RRGGBB в <color:#RRGGBB> (только если не внутри тегов)
|
||||
java.util.regex.Pattern pattern2 = java.util.regex.Pattern.compile("(?<!<)#([0-9a-fA-F]{6})(?![^<]*>)");
|
||||
java.util.regex.Matcher matcher2 = pattern2.matcher(message);
|
||||
StringBuffer sb2 = new StringBuffer();
|
||||
while (matcher2.find()) {
|
||||
String hex = matcher2.group(1).toLowerCase();
|
||||
matcher2.appendReplacement(sb2, "<color:#" + hex + ">");
|
||||
}
|
||||
matcher2.appendTail(sb2);
|
||||
message = sb2.toString();
|
||||
|
||||
// Конвертируем §[0-9a-fk-or] в соответствующие MiniMessage теги
|
||||
message = message.replaceAll("§0", "<black>");
|
||||
message = message.replaceAll("§1", "<dark_blue>");
|
||||
message = message.replaceAll("§2", "<dark_green>");
|
||||
message = message.replaceAll("§3", "<dark_aqua>");
|
||||
message = message.replaceAll("§4", "<dark_red>");
|
||||
message = message.replaceAll("§5", "<dark_purple>");
|
||||
message = message.replaceAll("§6", "<gold>");
|
||||
message = message.replaceAll("§7", "<gray>");
|
||||
message = message.replaceAll("§8", "<dark_gray>");
|
||||
message = message.replaceAll("§9", "<blue>");
|
||||
message = message.replaceAll("§a", "<green>");
|
||||
message = message.replaceAll("§b", "<aqua>");
|
||||
message = message.replaceAll("§c", "<red>");
|
||||
message = message.replaceAll("§d", "<light_purple>");
|
||||
message = message.replaceAll("§e", "<yellow>");
|
||||
message = message.replaceAll("§f", "<white>");
|
||||
message = message.replaceAll("§k", "<obfuscated>");
|
||||
message = message.replaceAll("§l", "<bold>");
|
||||
message = message.replaceAll("§m", "<strikethrough>");
|
||||
message = message.replaceAll("§n", "<underlined>");
|
||||
message = message.replaceAll("§o", "<italic>");
|
||||
message = message.replaceAll("§r", "<reset>");
|
||||
|
||||
// Конвертируем &[0-9a-fk-or] в соответствующие MiniMessage теги
|
||||
message = message.replaceAll("&0", "<black>");
|
||||
message = message.replaceAll("&1", "<dark_blue>");
|
||||
message = message.replaceAll("&2", "<dark_green>");
|
||||
message = message.replaceAll("&3", "<dark_aqua>");
|
||||
message = message.replaceAll("&4", "<dark_red>");
|
||||
message = message.replaceAll("&5", "<dark_purple>");
|
||||
message = message.replaceAll("&6", "<gold>");
|
||||
message = message.replaceAll("&7", "<gray>");
|
||||
message = message.replaceAll("&8", "<dark_gray>");
|
||||
message = message.replaceAll("&9", "<blue>");
|
||||
message = message.replaceAll("&a", "<green>");
|
||||
message = message.replaceAll("&b", "<aqua>");
|
||||
message = message.replaceAll("&c", "<red>");
|
||||
message = message.replaceAll("&d", "<light_purple>");
|
||||
message = message.replaceAll("&e", "<yellow>");
|
||||
message = message.replaceAll("&f", "<white>");
|
||||
|
||||
// Конвертируем форматирование
|
||||
message = message.replaceAll("&k", "<obfuscated>");
|
||||
message = message.replaceAll("&l", "<bold>");
|
||||
message = message.replaceAll("&m", "<strikethrough>");
|
||||
message = message.replaceAll("&n", "<underlined>");
|
||||
message = message.replaceAll("&o", "<italic>");
|
||||
message = message.replaceAll("&r", "<reset>");
|
||||
|
||||
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>", 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.loki.lochat.util;
|
||||
package com.loki.lochat.utils;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.loki.lochat.util;
|
||||
package com.loki.lochat.utils;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Entity;
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
144
src/main/java/com/loki/lochat/utils/color/ColorConverter.java
Normal file
144
src/main/java/com/loki/lochat/utils/color/ColorConverter.java
Normal file
|
|
@ -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("(?<!<)#([0-9a-fA-F]{6})(?![^<]*>)");
|
||||
|
||||
// Мапа для быстрой конвертации цветов (вместо 32 вызовов replaceAll)
|
||||
private static final Map<String, String> COLOR_MAP = Map.ofEntries(
|
||||
// § коды
|
||||
Map.entry("§0", "<black>"),
|
||||
Map.entry("§1", "<dark_blue>"),
|
||||
Map.entry("§2", "<dark_green>"),
|
||||
Map.entry("§3", "<dark_aqua>"),
|
||||
Map.entry("§4", "<dark_red>"),
|
||||
Map.entry("§5", "<dark_purple>"),
|
||||
Map.entry("§6", "<gold>"),
|
||||
Map.entry("§7", "<gray>"),
|
||||
Map.entry("§8", "<dark_gray>"),
|
||||
Map.entry("§9", "<blue>"),
|
||||
Map.entry("§a", "<green>"),
|
||||
Map.entry("§b", "<aqua>"),
|
||||
Map.entry("§c", "<red>"),
|
||||
Map.entry("§d", "<light_purple>"),
|
||||
Map.entry("§e", "<yellow>"),
|
||||
Map.entry("§f", "<white>"),
|
||||
Map.entry("§k", "<obfuscated>"),
|
||||
Map.entry("§l", "<bold>"),
|
||||
Map.entry("§m", "<strikethrough>"),
|
||||
Map.entry("§n", "<underlined>"),
|
||||
Map.entry("§o", "<italic>"),
|
||||
Map.entry("§r", "<reset>"),
|
||||
// & коды
|
||||
Map.entry("&0", "<black>"),
|
||||
Map.entry("&1", "<dark_blue>"),
|
||||
Map.entry("&2", "<dark_green>"),
|
||||
Map.entry("&3", "<dark_aqua>"),
|
||||
Map.entry("&4", "<dark_red>"),
|
||||
Map.entry("&5", "<dark_purple>"),
|
||||
Map.entry("&6", "<gold>"),
|
||||
Map.entry("&7", "<gray>"),
|
||||
Map.entry("&8", "<dark_gray>"),
|
||||
Map.entry("&9", "<blue>"),
|
||||
Map.entry("&a", "<green>"),
|
||||
Map.entry("&b", "<aqua>"),
|
||||
Map.entry("&c", "<red>"),
|
||||
Map.entry("&d", "<light_purple>"),
|
||||
Map.entry("&e", "<yellow>"),
|
||||
Map.entry("&f", "<white>"),
|
||||
Map.entry("&k", "<obfuscated>"),
|
||||
Map.entry("&l", "<bold>"),
|
||||
Map.entry("&m", "<strikethrough>"),
|
||||
Map.entry("&n", "<underlined>"),
|
||||
Map.entry("&o", "<italic>"),
|
||||
Map.entry("&r", "<reset>")
|
||||
);
|
||||
|
||||
/**
|
||||
* Конвертирует legacy форматы цветов в MiniMessage формат
|
||||
*/
|
||||
public static String convertLegacyFormats(String message) {
|
||||
if (message == null) return "";
|
||||
|
||||
// Конвертируем &#RRGGBB в <color:#RRGGBB>
|
||||
message = convertHexFormat1(message);
|
||||
|
||||
// Конвертируем #RRGGBB в <color:#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, "<color:#" + hex + ">");
|
||||
}
|
||||
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, "<color:#" + hex + ">");
|
||||
}
|
||||
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("<", "\\<");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue