Add /lmutelist and /lmutehistory commands

- /lmutelist - shows all active mutes with remaining time
- /lmutehistory <player> - shows mute history for a player
- Added mute history tracking (mute-history.json)
- History includes: who muted, when, duration, reason, unmute info
- Permissions: lochat.mutelist, lochat.mutehistory
This commit is contained in:
loki5512344 2026-01-03 12:29:24 +01:00
parent c6175d9d61
commit d92799613e
7 changed files with 428 additions and 10 deletions

View file

@ -120,6 +120,12 @@ public final class LoChat extends JavaPlugin {
UnmuteCommand unmuteCommand = new UnmuteCommand(this); UnmuteCommand unmuteCommand = new UnmuteCommand(this);
getCommand("lunmute").setExecutor(unmuteCommand); getCommand("lunmute").setExecutor(unmuteCommand);
getCommand("lunmute").setTabCompleter(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() { public void reload() {

View file

@ -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); String timeDisplay = duration == 0 ? "навсегда" : plugin.getMuteManager().formatTime(duration);
sender.sendMessage("§aИгрок §e" + targetName + " §aзамучен на §e" + timeDisplay); 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); String timeDisplay = duration == 0 ? "навсегда" : plugin.getMuteManager().formatTime(duration);

View file

@ -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<MuteManager.MuteHistoryEntry> 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<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command,
@NotNull String alias, @NotNull String[] args) {
List<String> 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;
}
}

View file

@ -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<UUID, MuteManager.MuteData> activeMutes = plugin.getMuteManager().getActiveMutes();
if (activeMutes.isEmpty()) {
sender.sendMessage("§aНет активных мутов");
return true;
}
sender.sendMessage("§6§l=== Активные муты (" + activeMutes.size() + ") ===");
int index = 1;
for (Map.Entry<UUID, MuteManager.MuteData> 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;
}
}

View file

@ -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размучен"); sender.sendMessage("§aИгрок §e" + targetName + " §aразмучен");
if (!silent) { 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размучен"); sender.sendMessage("§aИгрок §e" + target.getName() + " §aразмучен");
target.sendMessage("§aВы были размучены!"); target.sendMessage("§aВы были размучены!");

View file

@ -7,8 +7,7 @@ import com.loki.lochat.LoChat;
import java.io.*; import java.io.*;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Map; import java.util.*;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
/** /**
@ -18,42 +17,69 @@ public class MuteManager {
private final LoChat plugin; private final LoChat plugin;
private final Map<UUID, MuteData> mutes = new ConcurrentHashMap<>(); private final Map<UUID, MuteData> mutes = new ConcurrentHashMap<>();
private final Map<UUID, List<MuteHistoryEntry>> history = new ConcurrentHashMap<>();
private final File dataFile; private final File dataFile;
private final File historyFile;
private final Gson gson = new GsonBuilder().setPrettyPrinting().create(); private final Gson gson = new GsonBuilder().setPrettyPrinting().create();
public MuteManager(LoChat plugin) { public MuteManager(LoChat plugin) {
this.plugin = plugin; this.plugin = plugin;
this.dataFile = new File(plugin.getDataFolder(), "mutes.json"); this.dataFile = new File(plugin.getDataFolder(), "mutes.json");
this.historyFile = new File(plugin.getDataFolder(), "mute-history.json");
load(); load();
loadHistory();
} }
/** /**
* Замутить игрока * Замутить игрока
* @param uuid UUID игрока * @param uuid UUID игрока
* @param playerName имя игрока
* @param duration длительность в миллисекундах (0 = перманентный) * @param duration длительность в миллисекундах (0 = перманентный)
* @param reason причина * @param reason причина
* @param mutedBy кто замутил * @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; 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(); saveAsync();
} }
/**
* Замутить игрока (без имени - для совместимости)
*/
public void mute(UUID uuid, long duration, String reason, String mutedBy) {
mute(uuid, null, duration, reason, mutedBy);
}
/** /**
* Размутить игрока * Размутить игрока
* @param uuid UUID игрока * @param uuid UUID игрока
* @param unmutedBy кто размутил (null если автоматически)
* @return true если игрок был замучен * @return true если игрок был замучен
*/ */
public boolean unmute(UUID uuid) { public boolean unmute(UUID uuid, String unmutedBy) {
MuteData removed = mutes.remove(uuid); MuteData removed = mutes.remove(uuid);
if (removed != null) { if (removed != null) {
// Обновляем историю - помечаем как размученный
updateHistoryUnmute(uuid, unmutedBy);
saveAsync(); saveAsync();
return true; return true;
} }
return false; 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<Map<String, List<MuteHistoryEntry>>>(){}.getType();
Map<String, List<MuteHistoryEntry>> loaded = gson.fromJson(reader, type);
if (loaded != null) {
loaded.forEach((key, value) -> {
try {
history.put(UUID.fromString(key), new ArrayList<>(value));
} catch (IllegalArgumentException ignored) {}
});
}
} catch (IOException e) {
plugin.getLogger().warning("Ошибка загрузки истории мутов: " + e.getMessage());
}
}
public void saveHistory() {
try {
if (!historyFile.getParentFile().exists()) {
historyFile.getParentFile().mkdirs();
}
Map<String, List<MuteHistoryEntry>> toSave = new ConcurrentHashMap<>();
history.forEach((uuid, data) -> toSave.put(uuid.toString(), data));
try (Writer writer = new FileWriter(historyFile)) {
gson.toJson(toSave, writer);
}
} catch (IOException e) {
plugin.getLogger().warning("Ошибка сохранения истории мутов: " + e.getMessage());
}
}
private void saveHistoryAsync() {
plugin.getServer().getScheduler().runTaskAsynchronously(plugin, this::saveHistory);
}
private void saveAsync() { 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<MuteHistoryEntry> 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<MuteHistoryEntry> 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<UUID, MuteData> getActiveMutes() {
// Очищаем истёкшие муты
mutes.entrySet().removeIf(entry -> {
MuteData data = entry.getValue();
return data.endTime > 0 && System.currentTimeMillis() > data.endTime;
});
return new HashMap<>(mutes);
}
/**
* Получить историю мутов игрока
*/
public List<MuteHistoryEntry> getPlayerHistory(UUID uuid) {
return history.getOrDefault(uuid, new ArrayList<>());
}
/**
* Получить UUID по имени из истории
*/
public UUID getUUIDByName(String name) {
for (Map.Entry<UUID, List<MuteHistoryEntry>> 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 reason;
public String mutedBy; public String mutedBy;
public long mutedAt; public long mutedAt;
public String playerName;
public MuteData() {} public MuteData() {}
public MuteData(long endTime, String reason, String mutedBy, long mutedAt) { 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.endTime = endTime;
this.reason = reason; this.reason = reason;
this.mutedBy = mutedBy; this.mutedBy = mutedBy;
this.mutedAt = mutedAt; this.mutedAt = mutedAt;
this.playerName = playerName;
} }
public boolean isPermanent() { public boolean isPermanent() {
return endTime == 0; 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;
}
}
} }

View file

@ -109,6 +109,18 @@ commands:
permission: lochat.unmute permission: lochat.unmute
aliases: [дгтьгеу] aliases: [дгтьгеу]
lmutelist:
description: List all active mutes
usage: /lmutelist
permission: lochat.mutelist
aliases: [дьгеудшые]
lmutehistory:
description: View mute history of a player
usage: /lmutehistory <player>
permission: lochat.mutehistory
aliases: [дьгеершыещку]
permissions: permissions:
chat.global.use: chat.global.use:
default: true default: true
@ -156,3 +168,7 @@ permissions:
default: op default: op
lochat.unmute: lochat.unmute:
default: op default: op
lochat.mutelist:
default: op
lochat.mutehistory:
default: op