diff --git a/src/main/java/com/loki/lochat/LoChat.java b/src/main/java/com/loki/lochat/LoChat.java index 85ca1b7..07bcb31 100644 --- a/src/main/java/com/loki/lochat/LoChat.java +++ b/src/main/java/com/loki/lochat/LoChat.java @@ -29,6 +29,7 @@ public final class LoChat extends JavaPlugin { private CustomCommandManager customCommandManager; private GradientModule gradientModule; private LibertyBansHook libertyBansHook; + private MuteManager muteManager; @Override public void onEnable() { @@ -47,6 +48,7 @@ public final class LoChat extends JavaPlugin { mentionManager = new MentionManager(this); spyManager = new SpyManager(this); customCommandManager = new CustomCommandManager(this); + muteManager = new MuteManager(this); chatManager = new ChatManager(this); autoMessageManager = new AutoMessageManager(this); @@ -88,6 +90,9 @@ public final class LoChat extends JavaPlugin { if (gradientModule != null) { gradientModule.shutdown(); } + if (muteManager != null) { + muteManager.save(); + } getLogger().info("LoChat отключен!"); } @@ -106,6 +111,15 @@ public final class LoChat extends JavaPlugin { LoChatCommand loChatCommand = new LoChatCommand(this); getCommand("lochat").setExecutor(loChatCommand); getCommand("lochat").setTabCompleter(loChatCommand); + + // Команды мутов + MuteCommand muteCommand = new MuteCommand(this); + getCommand("lmute").setExecutor(muteCommand); + getCommand("lmute").setTabCompleter(muteCommand); + + UnmuteCommand unmuteCommand = new UnmuteCommand(this); + getCommand("lunmute").setExecutor(unmuteCommand); + getCommand("lunmute").setTabCompleter(unmuteCommand); } public void reload() { @@ -183,4 +197,8 @@ public final class LoChat extends JavaPlugin { return libertyBansHook; } + public MuteManager getMuteManager() { + return muteManager; + } + } diff --git a/src/main/java/com/loki/lochat/commands/MuteCommand.java b/src/main/java/com/loki/lochat/commands/MuteCommand.java new file mode 100644 index 0000000..d61b80d --- /dev/null +++ b/src/main/java/com/loki/lochat/commands/MuteCommand.java @@ -0,0 +1,160 @@ +package com.loki.lochat.commands; + +import com.loki.lochat.LoChat; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Команда /lmute nick time [-s] причина + */ +public class MuteCommand implements CommandExecutor, TabCompleter { + + private final LoChat plugin; + + public MuteCommand(LoChat plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, + @NotNull String label, @NotNull String[] args) { + if (!sender.hasPermission("lochat.mute")) { + sender.sendMessage(plugin.getMessageConfig().getComponent("errors.no-permission")); + return true; + } + + // /lmute nick time [-s] причина + if (args.length < 2) { + sender.sendMessage("§cИспользование: /lmute <ник> <время> [-s] [причина]"); + sender.sendMessage("§7Время: 1d, 2h, 30m, 60s или 0 для перманентного"); + return true; + } + + String targetName = args[0]; + String timeStr = args[1]; + + // Парсим время + long duration = plugin.getMuteManager().parseTime(timeStr); + if (duration < 0) { + sender.sendMessage("§cНеверный формат времени! Используйте: 1d, 2h, 30m, 60s"); + return true; + } + + // Проверяем флаг -s (silent) и собираем причину + boolean silent = false; + StringBuilder reasonBuilder = new StringBuilder(); + + for (int i = 2; i < args.length; i++) { + if (args[i].equalsIgnoreCase("-s")) { + silent = true; + } else { + if (reasonBuilder.length() > 0) reasonBuilder.append(" "); + reasonBuilder.append(args[i]); + } + } + + String reason = reasonBuilder.length() > 0 ? reasonBuilder.toString() : "Не указана"; + + // Ищем игрока (онлайн или оффлайн) + Player target = Bukkit.getPlayer(targetName); + if (target == null) { + // Пробуем найти оффлайн игрока + @SuppressWarnings("deprecation") + var offlinePlayer = Bukkit.getOfflinePlayer(targetName); + if (!offlinePlayer.hasPlayedBefore() && !offlinePlayer.isOnline()) { + sender.sendMessage(plugin.getMessageConfig().getComponent("errors.player-not-found")); + return true; + } + + // Мутим оффлайн игрока + plugin.getMuteManager().mute(offlinePlayer.getUniqueId(), duration, reason, sender.getName()); + + String timeDisplay = duration == 0 ? "навсегда" : plugin.getMuteManager().formatTime(duration); + sender.sendMessage("§aИгрок §e" + targetName + " §aзамучен на §e" + timeDisplay); + sender.sendMessage("§7Причина: §f" + reason); + + if (!silent) { + Bukkit.broadcast(plugin.getMessageConfig().getComponent("mute.muted", + "{player}", targetName, "{time}", timeDisplay)); + } + return true; + } + + // Проверяем, не пытается ли замутить себя + if (sender instanceof Player && ((Player) sender).getUniqueId().equals(target.getUniqueId())) { + sender.sendMessage("§cВы не можете замутить себя!"); + return true; + } + + // Проверяем, не замучен ли уже + if (plugin.getMuteManager().isMuted(target.getUniqueId())) { + sender.sendMessage("§cИгрок уже замучен!"); + return true; + } + + // Мутим игрока + plugin.getMuteManager().mute(target.getUniqueId(), duration, reason, sender.getName()); + + String timeDisplay = duration == 0 ? "навсегда" : plugin.getMuteManager().formatTime(duration); + + // Уведомляем отправителя + sender.sendMessage("§aИгрок §e" + target.getName() + " §aзамучен на §e" + timeDisplay); + sender.sendMessage("§7Причина: §f" + reason); + + // Уведомляем замученного + if (duration == 0) { + target.sendMessage(plugin.getMessageConfig().getComponent("mute.permanent")); + } else { + target.sendMessage(plugin.getMessageConfig().getComponent("mute.you-muted", + "{time}", timeDisplay)); + } + + // Broadcast если не silent + if (!silent) { + Bukkit.broadcast(plugin.getMessageConfig().getComponent("mute.muted", + "{player}", target.getName(), "{time}", timeDisplay)); + } + + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, + @NotNull String alias, @NotNull String[] args) { + List completions = new ArrayList<>(); + + if (args.length == 1) { + // Ники игроков + String prefix = args[0].toLowerCase(); + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.getName().toLowerCase().startsWith(prefix)) { + completions.add(player.getName()); + } + } + } else if (args.length == 2) { + // Время + completions.add("10m"); + completions.add("30m"); + completions.add("1h"); + completions.add("6h"); + completions.add("12h"); + completions.add("1d"); + completions.add("7d"); + completions.add("30d"); + completions.add("0"); + } else if (args.length == 3) { + completions.add("-s"); + } + + return completions; + } +} diff --git a/src/main/java/com/loki/lochat/commands/UnmuteCommand.java b/src/main/java/com/loki/lochat/commands/UnmuteCommand.java new file mode 100644 index 0000000..d13e885 --- /dev/null +++ b/src/main/java/com/loki/lochat/commands/UnmuteCommand.java @@ -0,0 +1,102 @@ +package com.loki.lochat.commands; + +import com.loki.lochat.LoChat; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Команда /lunmute nick [-s] + */ +public class UnmuteCommand implements CommandExecutor, TabCompleter { + + private final LoChat plugin; + + public UnmuteCommand(LoChat plugin) { + this.plugin = plugin; + } + + @Override + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, + @NotNull String label, @NotNull String[] args) { + if (!sender.hasPermission("lochat.unmute")) { + sender.sendMessage(plugin.getMessageConfig().getComponent("errors.no-permission")); + return true; + } + + if (args.length < 1) { + sender.sendMessage("§cИспользование: /lunmute <ник> [-s]"); + return true; + } + + String targetName = args[0]; + boolean silent = args.length > 1 && args[1].equalsIgnoreCase("-s"); + + // Ищем игрока + Player target = Bukkit.getPlayer(targetName); + if (target == null) { + // Пробуем найти оффлайн игрока + @SuppressWarnings("deprecation") + var offlinePlayer = Bukkit.getOfflinePlayer(targetName); + if (!offlinePlayer.hasPlayedBefore() && !offlinePlayer.isOnline()) { + sender.sendMessage(plugin.getMessageConfig().getComponent("errors.player-not-found")); + return true; + } + + // Размучиваем оффлайн игрока + if (plugin.getMuteManager().unmute(offlinePlayer.getUniqueId())) { + sender.sendMessage("§aИгрок §e" + targetName + " §aразмучен"); + + if (!silent) { + Bukkit.broadcast(plugin.getMessageConfig().getComponent("mute.unmuted", + "{player}", targetName)); + } + } else { + sender.sendMessage("§cИгрок не замучен!"); + } + return true; + } + + // Размучиваем онлайн игрока + if (plugin.getMuteManager().unmute(target.getUniqueId())) { + sender.sendMessage("§aИгрок §e" + target.getName() + " §aразмучен"); + target.sendMessage("§aВы были размучены!"); + + if (!silent) { + Bukkit.broadcast(plugin.getMessageConfig().getComponent("mute.unmuted", + "{player}", target.getName())); + } + } else { + sender.sendMessage("§cИгрок не замучен!"); + } + + return true; + } + + @Override + public @Nullable List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, + @NotNull String alias, @NotNull String[] args) { + List completions = new ArrayList<>(); + + if (args.length == 1) { + String prefix = args[0].toLowerCase(); + for (Player player : Bukkit.getOnlinePlayers()) { + if (player.getName().toLowerCase().startsWith(prefix)) { + completions.add(player.getName()); + } + } + } else if (args.length == 2) { + completions.add("-s"); + } + + return completions; + } +} diff --git a/src/main/java/com/loki/lochat/integrations/LibertyBansHook.java b/src/main/java/com/loki/lochat/integrations/LibertyBansHook.java index a03553c..4230ec8 100644 --- a/src/main/java/com/loki/lochat/integrations/LibertyBansHook.java +++ b/src/main/java/com/loki/lochat/integrations/LibertyBansHook.java @@ -2,102 +2,36 @@ package com.loki.lochat.integrations; import com.loki.lochat.LoChat; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; -import org.bukkit.event.Listener; -import org.bukkit.event.player.AsyncPlayerChatEvent; -import org.bukkit.plugin.Plugin; - -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; /** - * Интеграция с LibertyBans для проверки мутов - * Использует событийный подход для отслеживания мутов + * Заглушка для совместимости с LibertyBans + * Теперь используется встроенная система мутов LoChat */ -public class LibertyBansHook implements Listener { +public class LibertyBansHook { private final LoChat plugin; - private boolean enabled = false; - private Plugin libertyBansPlugin; - private final Set mutedPlayers = ConcurrentHashMap.newKeySet(); public LibertyBansHook(LoChat plugin) { this.plugin = plugin; - initialize(); - } - - private void initialize() { - libertyBansPlugin = plugin.getServer().getPluginManager().getPlugin("LibertyBans"); - if (libertyBansPlugin == null || !libertyBansPlugin.isEnabled()) { - plugin.getLogger().info("LibertyBans не найден, интеграция отключена"); - return; + + // Проверяем наличие LibertyBans + if (plugin.getServer().getPluginManager().getPlugin("LibertyBans") != null) { + plugin.getLogger().info("LibertyBans обнаружен. Используйте встроенную систему мутов LoChat (/lmute, /lunmute)"); } - - this.enabled = true; - // Регистрируем слушатель для отслеживания попыток чата замученных игроков - plugin.getServer().getPluginManager().registerEvents(this, plugin); - plugin.getLogger().info("LibertyBans интеграция включена (событийный режим)"); } /** - * Слушаем события чата с высоким приоритетом, чтобы отследить, - * какие игроки не могут писать (значит они замучены) + * Проверяет, замучен ли игрок через встроенную систему LoChat */ - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onAsyncPlayerChat(AsyncPlayerChatEvent event) { - // Если событие не отменено, значит игрок не замучен - UUID playerId = event.getPlayer().getUniqueId(); - mutedPlayers.remove(playerId); - } - - /** - * Проверяет, замучен ли игрок - * В событийном режиме мы полагаемся на LibertyBans для блокировки чата - * @param player игрок для проверки - * @return CompletableFuture - результат проверки - */ - public CompletableFuture isMuted(Player player) { - if (!enabled) { - return CompletableFuture.completedFuture(false); - } - - // В событийном режиме мы не можем точно определить мут без API - // Поэтому возвращаем false и полагаемся на то, что LibertyBans сам заблокирует чат - return CompletableFuture.completedFuture(false); - } - - /** - * В событийном режиме недоступно без API - */ - public CompletableFuture getActiveMute(Player player) { - return CompletableFuture.completedFuture(null); + public boolean isMuted(Player player) { + return plugin.getMuteManager().isMuted(player.getUniqueId()); } public boolean isEnabled() { - return enabled; + return true; // Всегда используем встроенную систему } public boolean isPluginPresent() { - return libertyBansPlugin != null && libertyBansPlugin.isEnabled(); + return true; } - - /** - * Добавляет игрока в список потенциально замученных - * (используется когда LibertyBans блокирует чат) - */ - public void markAsPotentiallyMuted(UUID playerId) { - if (enabled) { - mutedPlayers.add(playerId); - } - } - - /** - * Проверяет, был ли игрок отмечен как потенциально замученный - */ - public boolean isPotentiallyMuted(UUID playerId) { - return enabled && mutedPlayers.contains(playerId); - } -} \ No newline at end of file +} diff --git a/src/main/java/com/loki/lochat/listeners/ChatListener.java b/src/main/java/com/loki/lochat/listeners/ChatListener.java index 7192735..d5afc45 100644 --- a/src/main/java/com/loki/lochat/listeners/ChatListener.java +++ b/src/main/java/com/loki/lochat/listeners/ChatListener.java @@ -37,12 +37,20 @@ public class ChatListener implements Listener { } private void processChat(Player player, Component message, String plainMessage) { - // ===== LIBERTYBANS COMPATIBILITY ===== - // LibertyBans будет сам блокировать чат для замученных игроков - // Наша интеграция просто обеспечивает совместимость - if (plugin.getLibertyBansHook().isPluginPresent()) { - // LibertyBans присутствует, он сам обработает муты - // Мы просто продолжаем нормальную обработку + // ===== MUTE CHECK ===== + if (plugin.getMuteManager().isMuted(player.getUniqueId())) { + var muteData = plugin.getMuteManager().getMuteData(player.getUniqueId()); + if (muteData != null) { + if (muteData.isPermanent()) { + player.sendMessage(plugin.getMessageConfig().getComponent("mute.permanent")); + } else { + long remaining = plugin.getMuteManager().getRemainingTime(player.getUniqueId()); + String timeStr = plugin.getMuteManager().formatTime(remaining); + player.sendMessage(plugin.getMessageConfig().getComponent("mute.you-muted", + "{time}", timeStr)); + } + } + return; } // Продолжаем обработку чата diff --git a/src/main/java/com/loki/lochat/managers/MuteManager.java b/src/main/java/com/loki/lochat/managers/MuteManager.java new file mode 100644 index 0000000..836cf39 --- /dev/null +++ b/src/main/java/com/loki/lochat/managers/MuteManager.java @@ -0,0 +1,201 @@ +package com.loki.lochat.managers; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.reflect.TypeToken; +import com.loki.lochat.LoChat; + +import java.io.*; +import java.lang.reflect.Type; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Менеджер мутов для LoChat + */ +public class MuteManager { + + private final LoChat plugin; + private final Map mutes = new ConcurrentHashMap<>(); + private final File dataFile; + private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); + + public MuteManager(LoChat plugin) { + this.plugin = plugin; + this.dataFile = new File(plugin.getDataFolder(), "mutes.json"); + load(); + } + + /** + * Замутить игрока + * @param uuid UUID игрока + * @param duration длительность в миллисекундах (0 = перманентный) + * @param reason причина + * @param mutedBy кто замутил + */ + public void mute(UUID uuid, long duration, String reason, String mutedBy) { + long endTime = duration > 0 ? System.currentTimeMillis() + duration : 0; + mutes.put(uuid, new MuteData(endTime, reason, mutedBy, System.currentTimeMillis())); + saveAsync(); + } + + /** + * Размутить игрока + * @param uuid UUID игрока + * @return true если игрок был замучен + */ + public boolean unmute(UUID uuid) { + MuteData removed = mutes.remove(uuid); + if (removed != null) { + saveAsync(); + return true; + } + return false; + } + + /** + * Проверить, замучен ли игрок + */ + public boolean isMuted(UUID uuid) { + MuteData data = mutes.get(uuid); + if (data == null) return false; + + // Проверяем истёк ли мут + if (data.endTime > 0 && System.currentTimeMillis() > data.endTime) { + mutes.remove(uuid); + saveAsync(); + return false; + } + return true; + } + + /** + * Получить данные мута + */ + public MuteData getMuteData(UUID uuid) { + if (!isMuted(uuid)) return null; + return mutes.get(uuid); + } + + /** + * Получить оставшееся время мута + */ + public long getRemainingTime(UUID uuid) { + MuteData data = mutes.get(uuid); + if (data == null) return 0; + if (data.endTime == 0) return -1; // Перманентный + return Math.max(0, data.endTime - System.currentTimeMillis()); + } + + /** + * Форматировать время + */ + public String formatTime(long millis) { + if (millis <= 0) return "0с"; + + long seconds = millis / 1000; + long minutes = seconds / 60; + long hours = minutes / 60; + long days = hours / 24; + + if (days > 0) { + return days + "д " + (hours % 24) + "ч"; + } else if (hours > 0) { + return hours + "ч " + (minutes % 60) + "м"; + } else if (minutes > 0) { + return minutes + "м " + (seconds % 60) + "с"; + } else { + return seconds + "с"; + } + } + + /** + * Парсинг времени из строки (1d, 2h, 30m, 60s) + */ + public long parseTime(String timeStr) { + if (timeStr == null || timeStr.isEmpty()) return 0; + + timeStr = timeStr.toLowerCase(); + long multiplier = 1000; // базово в миллисекундах + + try { + if (timeStr.endsWith("d")) { + return Long.parseLong(timeStr.replace("d", "")) * 24 * 60 * 60 * multiplier; + } else if (timeStr.endsWith("h")) { + return Long.parseLong(timeStr.replace("h", "")) * 60 * 60 * multiplier; + } else if (timeStr.endsWith("m")) { + return Long.parseLong(timeStr.replace("m", "")) * 60 * multiplier; + } else if (timeStr.endsWith("s")) { + return Long.parseLong(timeStr.replace("s", "")) * multiplier; + } else { + // Если без суффикса - считаем минутами + return Long.parseLong(timeStr) * 60 * multiplier; + } + } catch (NumberFormatException e) { + return -1; // Ошибка парсинга + } + } + + private void load() { + if (!dataFile.exists()) return; + + try (Reader reader = new FileReader(dataFile)) { + Type type = new TypeToken>(){}.getType(); + Map loaded = gson.fromJson(reader, type); + if (loaded != null) { + loaded.forEach((key, value) -> { + try { + mutes.put(UUID.fromString(key), value); + } catch (IllegalArgumentException ignored) {} + }); + } + } catch (IOException e) { + plugin.getLogger().warning("Ошибка загрузки мутов: " + e.getMessage()); + } + } + + public void save() { + try { + if (!dataFile.getParentFile().exists()) { + dataFile.getParentFile().mkdirs(); + } + + Map toSave = new ConcurrentHashMap<>(); + mutes.forEach((uuid, data) -> toSave.put(uuid.toString(), data)); + + try (Writer writer = new FileWriter(dataFile)) { + gson.toJson(toSave, writer); + } + } catch (IOException e) { + plugin.getLogger().warning("Ошибка сохранения мутов: " + e.getMessage()); + } + } + + private void saveAsync() { + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, this::save); + } + + /** + * Данные о муте + */ + public static class MuteData { + public long endTime; // 0 = перманентный + public String reason; + public String mutedBy; + public long mutedAt; + + public MuteData() {} + + public MuteData(long endTime, String reason, String mutedBy, long mutedAt) { + this.endTime = endTime; + this.reason = reason; + this.mutedBy = mutedBy; + this.mutedAt = mutedAt; + } + + public boolean isPermanent() { + return endTime == 0; + } + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 4c35bcd..014995a 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -97,6 +97,18 @@ commands: permission: gradient.admin aliases: [gradientadmin, азкуашч, пкфвшуетефвьшт] + lmute: + description: Mute a player + usage: /lmute