diff --git a/src/main/java/com/loki/lochat/LoChat.java b/src/main/java/com/loki/lochat/LoChat.java index 07bcb31..0b493a7 100644 --- a/src/main/java/com/loki/lochat/LoChat.java +++ b/src/main/java/com/loki/lochat/LoChat.java @@ -120,6 +120,12 @@ public final class LoChat extends JavaPlugin { UnmuteCommand unmuteCommand = new UnmuteCommand(this); getCommand("lunmute").setExecutor(unmuteCommand); getCommand("lunmute").setTabCompleter(unmuteCommand); + + getCommand("lmutelist").setExecutor(new MuteListCommand(this)); + + MuteHistoryCommand muteHistoryCommand = new MuteHistoryCommand(this); + getCommand("lmutehistory").setExecutor(muteHistoryCommand); + getCommand("lmutehistory").setTabCompleter(muteHistoryCommand); } public void reload() { diff --git a/src/main/java/com/loki/lochat/commands/MuteCommand.java b/src/main/java/com/loki/lochat/commands/MuteCommand.java index d61b80d..c8566a2 100644 --- a/src/main/java/com/loki/lochat/commands/MuteCommand.java +++ b/src/main/java/com/loki/lochat/commands/MuteCommand.java @@ -76,7 +76,8 @@ public class MuteCommand implements CommandExecutor, TabCompleter { } // Мутим оффлайн игрока - plugin.getMuteManager().mute(offlinePlayer.getUniqueId(), duration, reason, sender.getName()); + String offlineName = offlinePlayer.getName() != null ? offlinePlayer.getName() : targetName; + plugin.getMuteManager().mute(offlinePlayer.getUniqueId(), offlineName, duration, reason, sender.getName()); String timeDisplay = duration == 0 ? "навсегда" : plugin.getMuteManager().formatTime(duration); sender.sendMessage("§aИгрок §e" + targetName + " §aзамучен на §e" + timeDisplay); @@ -102,7 +103,7 @@ public class MuteCommand implements CommandExecutor, TabCompleter { } // Мутим игрока - plugin.getMuteManager().mute(target.getUniqueId(), duration, reason, sender.getName()); + plugin.getMuteManager().mute(target.getUniqueId(), target.getName(), duration, reason, sender.getName()); String timeDisplay = duration == 0 ? "навсегда" : plugin.getMuteManager().formatTime(duration); diff --git a/src/main/java/com/loki/lochat/commands/MuteHistoryCommand.java b/src/main/java/com/loki/lochat/commands/MuteHistoryCommand.java new file mode 100644 index 0000000..78d2ceb --- /dev/null +++ b/src/main/java/com/loki/lochat/commands/MuteHistoryCommand.java @@ -0,0 +1,142 @@ +package com.loki.lochat.commands; + +import com.loki.lochat.LoChat; +import com.loki.lochat.managers.MuteManager; +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.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +/** + * Команда /lmutehistory [ник] - история мутов игрока + */ +public class MuteHistoryCommand implements CommandExecutor, TabCompleter { + + private final LoChat plugin; + private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm"); + + public MuteHistoryCommand(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.mutehistory")) { + sender.sendMessage(plugin.getMessageConfig().getComponent("errors.no-permission")); + return true; + } + + if (args.length < 1) { + sender.sendMessage("§cИспользование: /lmutehistory <ник>"); + return true; + } + + String targetName = args[0]; + UUID targetUUID = null; + + // Ищем игрока + Player target = Bukkit.getPlayer(targetName); + if (target != null) { + targetUUID = target.getUniqueId(); + } else { + // Пробуем найти оффлайн игрока + @SuppressWarnings("deprecation") + var offlinePlayer = Bukkit.getOfflinePlayer(targetName); + if (offlinePlayer.hasPlayedBefore() || offlinePlayer.isOnline()) { + targetUUID = offlinePlayer.getUniqueId(); + } else { + // Пробуем найти по имени в истории + targetUUID = plugin.getMuteManager().getUUIDByName(targetName); + } + } + + if (targetUUID == null) { + sender.sendMessage(plugin.getMessageConfig().getComponent("errors.player-not-found")); + return true; + } + + List history = plugin.getMuteManager().getPlayerHistory(targetUUID); + + if (history.isEmpty()) { + sender.sendMessage("§aУ игрока §e" + targetName + " §aнет истории мутов"); + return true; + } + + sender.sendMessage("§6§l=== История мутов " + targetName + " (" + history.size() + ") ==="); + + int index = 1; + // Выводим от новых к старым + for (int i = history.size() - 1; i >= 0; i--) { + MuteManager.MuteHistoryEntry entry = history.get(i); + + // Статус + String status; + if (entry.unmuted) { + status = "§aРазмучен"; + } else { + // Проверяем, активен ли ещё мут + if (entry.isPermanent()) { + status = "§cАктивен (навсегда)"; + } else { + long endTime = entry.mutedAt + entry.duration; + if (System.currentTimeMillis() > endTime) { + status = "§7Истёк"; + } else { + long remaining = endTime - System.currentTimeMillis(); + status = "§cАктивен (" + plugin.getMuteManager().formatTime(remaining) + ")"; + } + } + } + + // Длительность + String durationStr = entry.isPermanent() ? "Навсегда" : plugin.getMuteManager().formatTime(entry.duration); + + sender.sendMessage("§7" + index + ". §f" + dateFormat.format(new Date(entry.mutedAt)) + " §7- " + status); + sender.sendMessage(" §7Длительность: §f" + durationStr); + sender.sendMessage(" §7Причина: §f" + (entry.reason != null ? entry.reason : "Не указана")); + sender.sendMessage(" §7Выдал: §f" + entry.mutedBy); + + if (entry.unmuted && entry.unmutedBy != null) { + sender.sendMessage(" §7Размутил: §f" + entry.unmutedBy + " §7(" + dateFormat.format(new Date(entry.unmutedAt)) + ")"); + } + + index++; + + // Ограничиваем вывод + if (index > 10) { + sender.sendMessage("§7... и ещё " + (history.size() - 10) + " записей"); + break; + } + } + + 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()); + } + } + } + + return completions; + } +} diff --git a/src/main/java/com/loki/lochat/commands/MuteListCommand.java b/src/main/java/com/loki/lochat/commands/MuteListCommand.java new file mode 100644 index 0000000..07f5b0d --- /dev/null +++ b/src/main/java/com/loki/lochat/commands/MuteListCommand.java @@ -0,0 +1,76 @@ +package com.loki.lochat.commands; + +import com.loki.lochat.LoChat; +import com.loki.lochat.managers.MuteManager; +import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; +import java.util.UUID; + +/** + * Команда /lmutelist - список активных мутов + */ +public class MuteListCommand implements CommandExecutor { + + private final LoChat plugin; + private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm"); + + public MuteListCommand(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.mutelist")) { + sender.sendMessage(plugin.getMessageConfig().getComponent("errors.no-permission")); + return true; + } + + Map activeMutes = plugin.getMuteManager().getActiveMutes(); + + if (activeMutes.isEmpty()) { + sender.sendMessage("§aНет активных мутов"); + return true; + } + + sender.sendMessage("§6§l=== Активные муты (" + activeMutes.size() + ") ==="); + + int index = 1; + for (Map.Entry entry : activeMutes.entrySet()) { + UUID uuid = entry.getKey(); + MuteManager.MuteData data = entry.getValue(); + + // Получаем имя игрока + String playerName = data.playerName; + if (playerName == null) { + OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(uuid); + playerName = offlinePlayer.getName() != null ? offlinePlayer.getName() : uuid.toString().substring(0, 8); + } + + // Форматируем время + String timeStr; + if (data.isPermanent()) { + timeStr = "§4Навсегда"; + } else { + long remaining = data.endTime - System.currentTimeMillis(); + timeStr = "§e" + plugin.getMuteManager().formatTime(remaining); + } + + sender.sendMessage("§7" + index + ". §f" + playerName + " §7- " + timeStr); + sender.sendMessage(" §7Причина: §f" + (data.reason != null ? data.reason : "Не указана")); + sender.sendMessage(" §7Выдал: §f" + data.mutedBy + " §7(" + dateFormat.format(new Date(data.mutedAt)) + ")"); + + index++; + } + + return true; + } +} diff --git a/src/main/java/com/loki/lochat/commands/UnmuteCommand.java b/src/main/java/com/loki/lochat/commands/UnmuteCommand.java index d13e885..c5d8b33 100644 --- a/src/main/java/com/loki/lochat/commands/UnmuteCommand.java +++ b/src/main/java/com/loki/lochat/commands/UnmuteCommand.java @@ -52,7 +52,7 @@ public class UnmuteCommand implements CommandExecutor, TabCompleter { } // Размучиваем оффлайн игрока - if (plugin.getMuteManager().unmute(offlinePlayer.getUniqueId())) { + if (plugin.getMuteManager().unmute(offlinePlayer.getUniqueId(), sender.getName())) { sender.sendMessage("§aИгрок §e" + targetName + " §aразмучен"); if (!silent) { @@ -66,7 +66,7 @@ public class UnmuteCommand implements CommandExecutor, TabCompleter { } // Размучиваем онлайн игрока - if (plugin.getMuteManager().unmute(target.getUniqueId())) { + if (plugin.getMuteManager().unmute(target.getUniqueId(), sender.getName())) { sender.sendMessage("§aИгрок §e" + target.getName() + " §aразмучен"); target.sendMessage("§aВы были размучены!"); diff --git a/src/main/java/com/loki/lochat/managers/MuteManager.java b/src/main/java/com/loki/lochat/managers/MuteManager.java index 836cf39..ab484c8 100644 --- a/src/main/java/com/loki/lochat/managers/MuteManager.java +++ b/src/main/java/com/loki/lochat/managers/MuteManager.java @@ -7,8 +7,7 @@ import com.loki.lochat.LoChat; import java.io.*; import java.lang.reflect.Type; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** @@ -18,42 +17,69 @@ public class MuteManager { private final LoChat plugin; private final Map mutes = new ConcurrentHashMap<>(); + private final Map> history = new ConcurrentHashMap<>(); private final File dataFile; + private final File historyFile; private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); public MuteManager(LoChat plugin) { this.plugin = plugin; this.dataFile = new File(plugin.getDataFolder(), "mutes.json"); + this.historyFile = new File(plugin.getDataFolder(), "mute-history.json"); load(); + loadHistory(); } /** * Замутить игрока * @param uuid UUID игрока + * @param playerName имя игрока * @param duration длительность в миллисекундах (0 = перманентный) * @param reason причина * @param mutedBy кто замутил */ - public void mute(UUID uuid, long duration, String reason, String mutedBy) { + public void mute(UUID uuid, String playerName, long duration, String reason, String mutedBy) { long endTime = duration > 0 ? System.currentTimeMillis() + duration : 0; - mutes.put(uuid, new MuteData(endTime, reason, mutedBy, System.currentTimeMillis())); + MuteData data = new MuteData(endTime, reason, mutedBy, System.currentTimeMillis(), playerName); + mutes.put(uuid, data); + + // Добавляем в историю + addToHistory(uuid, playerName, duration, reason, mutedBy); + saveAsync(); } + /** + * Замутить игрока (без имени - для совместимости) + */ + public void mute(UUID uuid, long duration, String reason, String mutedBy) { + mute(uuid, null, duration, reason, mutedBy); + } + /** * Размутить игрока * @param uuid UUID игрока + * @param unmutedBy кто размутил (null если автоматически) * @return true если игрок был замучен */ - public boolean unmute(UUID uuid) { + public boolean unmute(UUID uuid, String unmutedBy) { MuteData removed = mutes.remove(uuid); if (removed != null) { + // Обновляем историю - помечаем как размученный + updateHistoryUnmute(uuid, unmutedBy); saveAsync(); return true; } return false; } + /** + * Размутить игрока (для совместимости) + */ + public boolean unmute(UUID uuid) { + return unmute(uuid, null); + } + /** * Проверить, замучен ли игрок */ @@ -172,8 +198,121 @@ public class MuteManager { } } + private void loadHistory() { + if (!historyFile.exists()) return; + + try (Reader reader = new FileReader(historyFile)) { + Type type = new TypeToken>>(){}.getType(); + Map> loaded = gson.fromJson(reader, type); + if (loaded != null) { + loaded.forEach((key, value) -> { + try { + history.put(UUID.fromString(key), new ArrayList<>(value)); + } catch (IllegalArgumentException ignored) {} + }); + } + } catch (IOException e) { + plugin.getLogger().warning("Ошибка загрузки истории мутов: " + e.getMessage()); + } + } + + public void saveHistory() { + try { + if (!historyFile.getParentFile().exists()) { + historyFile.getParentFile().mkdirs(); + } + + Map> toSave = new ConcurrentHashMap<>(); + history.forEach((uuid, data) -> toSave.put(uuid.toString(), data)); + + try (Writer writer = new FileWriter(historyFile)) { + gson.toJson(toSave, writer); + } + } catch (IOException e) { + plugin.getLogger().warning("Ошибка сохранения истории мутов: " + e.getMessage()); + } + } + + private void saveHistoryAsync() { + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, this::saveHistory); + } + private void saveAsync() { - plugin.getServer().getScheduler().runTaskAsynchronously(plugin, this::save); + plugin.getServer().getScheduler().runTaskAsynchronously(plugin, () -> { + save(); + saveHistory(); + }); + } + + /** + * Добавить запись в историю + */ + private void addToHistory(UUID uuid, String playerName, long duration, String reason, String mutedBy) { + List playerHistory = history.computeIfAbsent(uuid, k -> new ArrayList<>()); + playerHistory.add(new MuteHistoryEntry( + playerName, + duration, + reason, + mutedBy, + System.currentTimeMillis(), + false, + null, + 0 + )); + saveHistoryAsync(); + } + + /** + * Обновить историю при размуте + */ + private void updateHistoryUnmute(UUID uuid, String unmutedBy) { + List playerHistory = history.get(uuid); + if (playerHistory != null && !playerHistory.isEmpty()) { + // Находим последний активный мут + for (int i = playerHistory.size() - 1; i >= 0; i--) { + MuteHistoryEntry entry = playerHistory.get(i); + if (!entry.unmuted) { + entry.unmuted = true; + entry.unmutedBy = unmutedBy; + entry.unmutedAt = System.currentTimeMillis(); + break; + } + } + } + saveHistoryAsync(); + } + + /** + * Получить список всех активных мутов + */ + public Map getActiveMutes() { + // Очищаем истёкшие муты + mutes.entrySet().removeIf(entry -> { + MuteData data = entry.getValue(); + return data.endTime > 0 && System.currentTimeMillis() > data.endTime; + }); + return new HashMap<>(mutes); + } + + /** + * Получить историю мутов игрока + */ + public List getPlayerHistory(UUID uuid) { + return history.getOrDefault(uuid, new ArrayList<>()); + } + + /** + * Получить UUID по имени из истории + */ + public UUID getUUIDByName(String name) { + for (Map.Entry> entry : history.entrySet()) { + for (MuteHistoryEntry historyEntry : entry.getValue()) { + if (historyEntry.playerName != null && historyEntry.playerName.equalsIgnoreCase(name)) { + return entry.getKey(); + } + } + } + return null; } /** @@ -184,18 +323,56 @@ public class MuteManager { public String reason; public String mutedBy; public long mutedAt; + public String playerName; public MuteData() {} public MuteData(long endTime, String reason, String mutedBy, long mutedAt) { + this(endTime, reason, mutedBy, mutedAt, null); + } + + public MuteData(long endTime, String reason, String mutedBy, long mutedAt, String playerName) { this.endTime = endTime; this.reason = reason; this.mutedBy = mutedBy; this.mutedAt = mutedAt; + this.playerName = playerName; } public boolean isPermanent() { return endTime == 0; } } + + /** + * Запись истории мута + */ + public static class MuteHistoryEntry { + public String playerName; + public long duration; // 0 = перманентный + public String reason; + public String mutedBy; + public long mutedAt; + public boolean unmuted; + public String unmutedBy; + public long unmutedAt; + + public MuteHistoryEntry() {} + + public MuteHistoryEntry(String playerName, long duration, String reason, String mutedBy, + long mutedAt, boolean unmuted, String unmutedBy, long unmutedAt) { + this.playerName = playerName; + this.duration = duration; + this.reason = reason; + this.mutedBy = mutedBy; + this.mutedAt = mutedAt; + this.unmuted = unmuted; + this.unmutedBy = unmutedBy; + this.unmutedAt = unmutedAt; + } + + public boolean isPermanent() { + return duration == 0; + } + } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 014995a..92b2b10 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -109,6 +109,18 @@ commands: permission: lochat.unmute aliases: [дгтьгеу] + lmutelist: + description: List all active mutes + usage: /lmutelist + permission: lochat.mutelist + aliases: [дьгеудшые] + + lmutehistory: + description: View mute history of a player + usage: /lmutehistory + permission: lochat.mutehistory + aliases: [дьгеершыещку] + permissions: chat.global.use: default: true @@ -156,3 +168,7 @@ permissions: default: op lochat.unmute: default: op + lochat.mutelist: + default: op + lochat.mutehistory: + default: op