chore: clean up documentation and rebuild with badwords filter

- Remove outdated CODE_REVIEW_RU.md and REFACTORING_SERVICES.md documentation
- Add badwords.txt resource file for swear filter functionality
- Rebuild compiled classes with updated filter and configuration logic
- Update all configuration files (appearance, discord, filters, messages)
- Rebuild core components: ConfigManager, PluginInitializer, SwearFilter, GradientModule, PrefixService, PlayerEventListener, EnhancedChatRenderer
- Consolidate build artifacts with source changes for consistency
This commit is contained in:
loki5512344 2026-04-06 00:38:34 +02:00
parent bba480002f
commit 4f6eaf35e9
28 changed files with 1152 additions and 1364 deletions

View file

@ -275,4 +275,38 @@ public class ConfigManager {
public boolean useCustomPrefix() {
return config.getBoolean("lopreff.use-custom-prefix", true);
}
// ========== Кастомные сообщения ==========
public boolean isJoinMessageEnabled() {
return config.getBoolean("messages.join.enabled", true);
}
public String getJoinMessageFormat() {
return config.getString("messages.join.format", "&#9878C9✦ &#B798A8{player} &#7858E9зашел на сервер");
}
public boolean isQuitMessageEnabled() {
return config.getBoolean("messages.quit.enabled", true);
}
public String getQuitMessageFormat() {
return config.getString("messages.quit.format", "&#9878C9✦ &#B798A8{player} &#7858E9вышел с сервера");
}
public boolean isDeathMessageEnabled() {
return config.getBoolean("messages.death.enabled", true);
}
public String getDeathPlayerKillFormat() {
return config.getString("messages.death.player_kill", "&#CF6679☠ &#B798A8{player} &#7858E9был убит игроком &#B798A8{killer} &#9878C9({weapon})");
}
public String getDeathMobKillFormat() {
return config.getString("messages.death.mob_kill", "&#CF6679☠ &#B798A8{player} &#7858E9был убит мобом &#B798A8{killer}");
}
public String getDeathDefaultFormat() {
return config.getString("messages.death.default", "&#CF6679☠ &#B798A8{player} &#7858E9{death_message}");
}
}

View file

@ -103,7 +103,7 @@ public class PluginInitializer {
new ChatEventListener(plugin, serviceRegistry, filter), plugin
);
plugin.getServer().getPluginManager().registerEvents(
new PlayerEventListener(serviceRegistry, filter), plugin
new PlayerEventListener(plugin, serviceRegistry, filter), plugin
);
plugin.getServer().getPluginManager().registerEvents(
new ModerationListener(serviceRegistry), plugin

View file

@ -9,9 +9,7 @@ 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.*;
import java.util.regex.Pattern;
public class SwearFilter {
@ -19,6 +17,22 @@ public class SwearFilter {
private final boolean replaceMode;
private final String replacement;
private final Set<Pattern> swearPatterns = new HashSet<>();
// Карта замены латиницы на кириллицу для обхода фильтра
private static final Map<Character, Character> LATIN_TO_CYRILLIC = Map.ofEntries(
Map.entry('a', 'а'), Map.entry('A', 'А'),
Map.entry('e', 'е'), Map.entry('E', 'Е'),
Map.entry('o', 'о'), Map.entry('O', 'О'),
Map.entry('p', 'р'), Map.entry('P', 'Р'),
Map.entry('c', 'с'), Map.entry('C', 'С'),
Map.entry('y', 'у'), Map.entry('Y', 'У'),
Map.entry('x', 'х'), Map.entry('X', 'Х'),
Map.entry('k', 'к'), Map.entry('K', 'К'),
Map.entry('m', 'м'), Map.entry('M', 'М'),
Map.entry('h', 'н'), Map.entry('H', 'Н'),
Map.entry('t', 'т'), Map.entry('T', 'Т'),
Map.entry('b', 'в'), Map.entry('B', 'В')
);
public SwearFilter(FileConfiguration config, JavaPlugin plugin) {
this.enabled = config.getBoolean("filters.swear.enabled", true);
@ -37,13 +51,46 @@ public class SwearFilter {
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)
));
.forEach(word -> {
// Создаем паттерн с учетом возможной замены букв
String regex = buildFlexiblePattern(word);
swearPatterns.add(Pattern.compile(regex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE));
});
} catch (Exception e) {
plugin.getLogger().warning("Failed to load badwords.txt: " + e.getMessage());
}
}
/**
* Создает гибкий паттерн, который ловит замены букв (х->x, о->o и т.д.)
*/
private String buildFlexiblePattern(String word) {
StringBuilder pattern = new StringBuilder();
for (char c : word.toLowerCase().toCharArray()) {
// Для каждой буквы создаем группу с возможными заменами
switch (c) {
case 'а' -> pattern.append("[аaА@]");
case 'е' -> pattern.append("[еeЕ3]");
case 'о' -> pattern.append("[оoО0]");
case 'р' -> pattern.append("[рpР]");
case 'с' -> pattern.append("[сcС]");
case 'у' -> pattern.append("[уyУ]");
case 'х' -> pattern.append("[хxХ]");
case 'к' -> pattern.append("[кkК]");
case 'м' -> pattern.append("[мmМ]");
case 'н' -> pattern.append("[нhНN]");
case 'т' -> pattern.append("[тtТ]");
case 'в' -> pattern.append("[вbВ]");
case 'и' -> pattern.append("[иiИ1]");
case 'з' -> pattern.append("[з3З]");
case 'б' -> pattern.append("[б6Б]");
default -> pattern.append(Pattern.quote(String.valueOf(c)));
}
}
return pattern.toString();
}
public FilterResult filter(Player player, String message) {
if (!enabled || player.hasPermission("lochat.bypass.swear")) {

View file

@ -149,6 +149,16 @@ public class GradientModule {
return prefixService.getPrefix(player, data);
}
/**
* Получает суффикс игрока из LuckPerms с градиентом
*/
public String getSuffix(Player player) {
if (!enabled) return "";
GradientPlayerData data = dataManager.getPlayerData(player.getUniqueId());
return prefixService.getLuckPermsSuffix(player, data);
}
/**
* Получает только LuckPerms префикс
*/

View file

@ -79,10 +79,47 @@ public class PrefixService {
}
/**
* Получает полное имя игрока: префикс + ник с градиентом
* Получает суффикс из LuckPerms
*/
public String getLuckPermsSuffix(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 suffix = metaData.getSuffix();
if (suffix == null || suffix.isEmpty()) {
return "";
}
// Применяем градиент если нужно
if (data != null && data.hasColors() && data.isColorEnabled() && config.isGradientOnLuckPermsPrefix()) {
String cleanSuffix = GradientService.stripColors(suffix);
return GradientService.applyGradient(cleanSuffix, data.getColors());
}
return suffix;
} catch (Exception e) {
return "";
}
}
/**
* Получает полное имя игрока: префикс + ник с градиентом + суффикс
*/
public String getFullName(Player player, GradientPlayerData data) {
String prefix = getPrefix(player, data);
String suffix = getLuckPermsSuffix(player, data);
String nick = player.getName();
// Применяем градиент к нику если есть цвета
@ -90,7 +127,12 @@ public class PrefixService {
nick = GradientService.applyGradient(nick, data.getColors());
}
return prefix.isEmpty() ? nick : prefix + " " + nick;
StringBuilder result = new StringBuilder();
if (!prefix.isEmpty()) result.append(prefix).append(" ");
result.append(nick);
if (!suffix.isEmpty()) result.append(" ").append(suffix);
return result.toString();
}
/**
@ -98,6 +140,7 @@ public class PrefixService {
*/
public String getFullNameForTab(Player player, GradientPlayerData data) {
String prefix = getPrefix(player, data);
String suffix = getLuckPermsSuffix(player, data);
String nick = player.getName();
// Применяем градиент в формате TAB
@ -105,7 +148,12 @@ public class PrefixService {
nick = GradientService.applyGradientForTab(nick, data.getColors());
}
return prefix.isEmpty() ? nick : prefix + " " + nick;
StringBuilder result = new StringBuilder();
if (!prefix.isEmpty()) result.append(prefix).append(" ");
result.append(nick);
if (!suffix.isEmpty()) result.append(" ").append(suffix);
return result.toString();
}
public boolean isLuckPermsAvailable() {

View file

@ -1,30 +1,42 @@
package com.loki.lochat.listener;
import com.loki.lochat.LoChat;
import com.loki.lochat.api.service.MessagingService;
import com.loki.lochat.api.service.NickService;
import com.loki.lochat.api.service.PlayerService;
import com.loki.lochat.config.ConfigManager;
import com.loki.lochat.core.filter.AdvancedMessageFilter;
import com.loki.lochat.core.registry.ServiceRegistry;
import com.loki.lochat.utils.ChatFormatter;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Entity;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.PlayerDeathEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.ItemStack;
/**
* Слушатель событий игрока
*/
public class PlayerEventListener implements Listener {
private final LoChat plugin;
private final PlayerService playerService;
private final MessagingService messagingService;
private final NickService nickService;
private final AdvancedMessageFilter advancedFilter;
private final ConfigManager configManager;
public PlayerEventListener(ServiceRegistry registry, AdvancedMessageFilter advancedFilter) {
public PlayerEventListener(LoChat plugin, ServiceRegistry registry, AdvancedMessageFilter advancedFilter) {
this.plugin = plugin;
this.playerService = registry.get(PlayerService.class);
this.messagingService = registry.get(MessagingService.class);
this.nickService = registry.get(NickService.class);
this.advancedFilter = advancedFilter;
this.advancedFilter = advancedFilter;
this.configManager = plugin.getConfigManager();
}
@EventHandler
@ -33,6 +45,13 @@ public class PlayerEventListener implements Listener {
if (nickService != null) {
nickService.updatePlayerDisplay(player);
}
// Кастомное сообщение входа
if (configManager.isJoinMessageEnabled()) {
String format = configManager.getJoinMessageFormat();
String message = format.replace("{player}", player.getName());
event.joinMessage(ChatFormatter.parse(message));
}
}
@EventHandler
@ -44,7 +63,110 @@ public class PlayerEventListener implements Listener {
messagingService.removeConversation(uuid);
messagingService.removeSpy(uuid);
// Очищаем flood / spam трекеры — иначе данные висят в памяти вечно
// Очищаем flood / spam трекеры
advancedFilter.clearPlayer(uuid);
// Кастомное сообщение выхода
if (configManager.isQuitMessageEnabled()) {
String format = configManager.getQuitMessageFormat();
String message = format.replace("{player}", player.getName());
event.quitMessage(ChatFormatter.parse(message));
}
}
@EventHandler
public void onDeath(PlayerDeathEvent event) {
if (!configManager.isDeathMessageEnabled()) {
return;
}
Player player = event.getPlayer();
Player killer = player.getKiller();
String message;
if (killer != null) {
// Убийство игроком
ItemStack weapon = killer.getInventory().getItemInMainHand();
String weaponName = getWeaponName(weapon);
message = configManager.getDeathPlayerKillFormat()
.replace("{player}", player.getName())
.replace("{killer}", killer.getName())
.replace("{weapon}", weaponName);
} else {
// Проверяем убийцу-моба
Entity lastDamager = player.getLastDamageCause() != null ?
player.getLastDamageCause().getEntity() : null;
if (lastDamager instanceof LivingEntity mob && !(lastDamager instanceof Player)) {
String mobName = getMobName(mob);
message = configManager.getDeathMobKillFormat()
.replace("{player}", player.getName())
.replace("{killer}", mobName);
} else {
// Обычная смерть
Component deathMsg = event.deathMessage();
String deathText = deathMsg != null ? ChatFormatter.toPlain(deathMsg) : "умер";
message = configManager.getDeathDefaultFormat()
.replace("{player}", player.getName())
.replace("{death_message}", deathText);
}
}
event.deathMessage(ChatFormatter.parse(message));
}
private String getWeaponName(ItemStack item) {
if (item == null || item.getType().isAir()) {
return "Руки";
}
if (item.hasItemMeta() && item.getItemMeta().hasDisplayName()) {
return ChatFormatter.toPlain(item.getItemMeta().displayName());
}
return translateMaterial(item.getType().name());
}
private String getMobName(LivingEntity mob) {
if (mob.customName() != null) {
return ChatFormatter.toPlain(mob.customName());
}
return translateEntityType(mob.getType().name());
}
private String translateMaterial(String material) {
return switch (material) {
case "DIAMOND_SWORD" -> "Алмазный меч";
case "IRON_SWORD" -> "Железный меч";
case "GOLDEN_SWORD" -> "Золотой меч";
case "STONE_SWORD" -> "Каменный меч";
case "WOODEN_SWORD" -> "Деревянный меч";
case "NETHERITE_SWORD" -> "Незеритовый меч";
case "BOW" -> "Лук";
case "CROSSBOW" -> "Арбалет";
case "TRIDENT" -> "Трезубец";
case "DIAMOND_AXE" -> "Алмазный топор";
case "IRON_AXE" -> "Железный топор";
case "NETHERITE_AXE" -> "Незеритовый топор";
default -> material.replace("_", " ").toLowerCase();
};
}
private String translateEntityType(String entityType) {
return switch (entityType) {
case "ZOMBIE" -> "Зомби";
case "SKELETON" -> "Скелет";
case "CREEPER" -> "Крипер";
case "SPIDER" -> "Паук";
case "ENDERMAN" -> "Эндермен";
case "BLAZE" -> "Ифрит";
case "WITCH" -> "Ведьма";
case "PIGLIN" -> "Пиглин";
case "HOGLIN" -> "Хоглин";
case "WITHER_SKELETON" -> "Скелет-иссушитель";
default -> entityType.replace("_", " ").toLowerCase();
};
}
}

View file

@ -57,13 +57,14 @@ public class EnhancedChatRenderer implements ChatRenderer {
Component prefix = buildPrefix();
Component separator = buildSeparator();
Component playerPrefix = buildPlayerPrefix(source);
Component playerSuffix = buildPlayerSuffix(source);
Component player = buildPlayerComponent(source);
return buildFormattedMessage(format, emoji, prefix, separator, playerPrefix, player, processed);
return buildFormattedMessage(format, emoji, prefix, separator, playerPrefix, playerSuffix, player, processed);
}
private Component buildFormattedMessage(String format, Component emoji, Component prefix,
Component separator, Component playerPrefix,
Component separator, Component playerPrefix, Component playerSuffix,
Component player, Component message) {
Component result = Component.empty();
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("\\{([^}]+)\\}");
@ -83,6 +84,7 @@ public class EnhancedChatRenderer implements ChatRenderer {
case "separator" -> result = result.append(separator);
case "player_prefix" -> result = result.append(playerPrefix);
case "player" -> result = result.append(player);
case "player_suffix" -> result = result.append(playerSuffix);
case "message" -> result = result.append(message);
default -> result = result.append(Component.text("{" + placeholder + "}"));
}
@ -117,6 +119,20 @@ public class EnhancedChatRenderer implements ChatRenderer {
return Component.empty();
}
private Component buildPlayerSuffix(Player player) {
com.loki.lochat.LoChat loChat = (com.loki.lochat.LoChat) plugin;
com.loki.lochat.gradient.GradientModule gradientModule = loChat.getGradientModule();
if (gradientModule != null && gradientModule.isEnabled()) {
String suffix = gradientModule.getSuffix(player);
if (!suffix.isEmpty()) {
return com.loki.lochat.utils.ChatFormatter.parse(suffix);
}
}
return Component.empty();
}
private Component buildPrefix() {
com.loki.lochat.config.AppearanceConfig appearanceCfg = cfg.getAppearanceConfig();