Add built-in mute system with /lmute and /lunmute commands

- Added MuteManager for handling mutes with persistence (mutes.json)
- Added /lmute <nick> <time> [-s] [reason] command
- Added /lunmute <nick> [-s] command
- Time formats: 1d, 2h, 30m, 60s, 0 for permanent
- -s flag for silent mute/unmute (no broadcast)
- Mute check integrated into ChatListener
- Removed LibertyBans API dependency (using built-in system)
This commit is contained in:
loki5512344 2026-01-03 12:20:35 +01:00
parent d44c5f811f
commit c6175d9d61
7 changed files with 524 additions and 85 deletions

View file

@ -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;
}
}

View file

@ -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<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());
}
}
} 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;
}
}

View file

@ -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<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());
}
}
} else if (args.length == 2) {
completions.add("-s");
}
return completions;
}
}

View file

@ -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<UUID> 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<Boolean> - результат проверки
*/
public CompletableFuture<Boolean> isMuted(Player player) {
if (!enabled) {
return CompletableFuture.completedFuture(false);
}
// В событийном режиме мы не можем точно определить мут без API
// Поэтому возвращаем false и полагаемся на то, что LibertyBans сам заблокирует чат
return CompletableFuture.completedFuture(false);
}
/**
* В событийном режиме недоступно без API
*/
public CompletableFuture<Object> 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);
}
}
}

View file

@ -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;
}
// Продолжаем обработку чата

View file

@ -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<UUID, MuteData> 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<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("Ошибка загрузки мутов: " + e.getMessage());
}
}
public void save() {
try {
if (!dataFile.getParentFile().exists()) {
dataFile.getParentFile().mkdirs();
}
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("Ошибка сохранения мутов: " + 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;
}
}
}

View file

@ -97,6 +97,18 @@ commands:
permission: gradient.admin
aliases: [gradientadmin, азкуашч, пкфвшуетефвьшт]
lmute:
description: Mute a player
usage: /lmute <player> <time> [-s] [reason]
permission: lochat.mute
aliases: [дьгеу]
lunmute:
description: Unmute a player
usage: /lunmute <player> [-s]
permission: lochat.unmute
aliases: [дгтьгеу]
permissions:
chat.global.use:
default: true
@ -140,3 +152,7 @@ permissions:
default: op
lochat.admin:
default: op
lochat.mute:
default: op
lochat.unmute:
default: op