Compare commits
5 Commits
Author | SHA1 | Date |
---|---|---|
|
5446a54289 | |
|
b666930fe7 | |
|
8fc7b9a5f9 | |
|
f14c4e3fc0 | |
|
a5d3df7de0 |
43
CHANGELOG.md
43
CHANGELOG.md
|
@ -4,6 +4,49 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [0.2] - 2024-11-10
|
||||
|
||||
### Added
|
||||
|
||||
- **ChatITCommand.java**:
|
||||
- **`/r` Command**:
|
||||
- Introduced the `/r` command for quick replies to the last received private message.
|
||||
- Allows players to send responses without specifying the recipient's name.
|
||||
|
||||
- **Utils.java**:
|
||||
- **Message Logging**:
|
||||
- Implemented logging for private and global messages into separate files: `logs/private_messages.log` and `logs/global_messages.log`.
|
||||
- Each message is logged with timestamp, sender's IP address, player names, and message content.
|
||||
|
||||
- **Clickable Nicknames and Links in Chat**:
|
||||
- Added clickable player nicknames in chat. Clicking on a player's nickname suggests sending a private message to them.
|
||||
- Enabled clickable URLs in chat messages. Valid URLs are automatically converted into clickable links that open in the player's browser upon clicking.
|
||||
- Implemented URL validation before converting them into clickable components.
|
||||
|
||||
## [0.1.9] - 2024-10-31
|
||||
|
||||
### Added
|
||||
|
||||
- **Config.java**:
|
||||
- Added hot-swap profanity filtering modes (`off`, `regex`, `api`).
|
||||
- Implemented the ability to change profanity filtering mode during server runtime using the command `/chatit mode (off|regex|api)`.
|
||||
|
||||
- **ChatITCommand.java**:
|
||||
- Added custom private messages to replace `/w`, `/tell`, `/msg` commands.
|
||||
- Updated the logic for sending and formatting private messages.
|
||||
|
||||
### Changed
|
||||
|
||||
- **ProfanityChecker.java**:
|
||||
- Updated profanity checking logic to accommodate new filtering modes.
|
||||
|
||||
## [0.1.8] - 2024-10-30
|
||||
|
||||
### Changed
|
||||
|
||||
- **ChatEventHandler.java**:
|
||||
- Change message color from white to yellow.
|
||||
|
||||
## [0.1.7] - 2024-10-23
|
||||
|
||||
### Changed
|
||||
|
|
|
@ -4,6 +4,51 @@
|
|||
|
||||
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/), и этот проект придерживается [Семантического Версионирования](https://semver.org/lang/ru/).
|
||||
|
||||
## [0.2] - 2024-11-10
|
||||
|
||||
### Добавлено
|
||||
|
||||
- **ChatITCommand.java**:
|
||||
- **Команда `/r`**:
|
||||
- Добавлена команда `/r` для быстрого ответа на последнее полученное приватное сообщение.
|
||||
- Позволяет игрокам быстро отправлять ответы без необходимости указывать имя получателя.
|
||||
|
||||
- **Utils.java**:
|
||||
- **Логирование сообщений**:
|
||||
- Реализовано логирование приватных и глобальных сообщений в отдельные файлы `logs/private_messages.log` и `logs/global_messages.log`.
|
||||
- Каждое сообщение логируется с указанием времени, IP-адреса отправителя, имен игроков и содержания сообщения.
|
||||
|
||||
- **Кликабельность ников и ссылок в чате**:
|
||||
- Добавлена возможность кликабельности ников игроков в чате. При клике на ник игрока предлагается отправить приватное сообщение.
|
||||
- Внедрена кликабельность ссылок в сообщениях чата. Валидные URL автоматически преобразуются в кликабельные ссылки, которые открываются в браузере игрока при клике.
|
||||
- Реализована проверка валидности ссылок перед их преобразованием в кликабельные компоненты.
|
||||
|
||||
|
||||
## [0.1.9] - 2024-10-31
|
||||
|
||||
### Добавлено
|
||||
|
||||
- **Config.java**:
|
||||
- Добавлен hot-swap режимов фильтрации мата (`off`, `regex`, `api`).
|
||||
- Реализована возможность изменения режима фильтрации мата во время работы сервера через команду `/chatit mode (off|regex|api)`.
|
||||
|
||||
- **ChatITCommand.java**:
|
||||
- Добавлены кастомные личные сообщения на замену команд `/w`, `/tell`, `/msg`.
|
||||
- Обновлена логика отправки и форматирования приватных сообщений.
|
||||
|
||||
### Изменено
|
||||
|
||||
- **ProfanityChecker.java**:
|
||||
- Обновлена логика проверки мата с учётом новых режимов фильтрации.
|
||||
|
||||
|
||||
## [0.1.8] - 2024-10-30
|
||||
|
||||
### Изменено
|
||||
|
||||
- **ChatEventHandler.java**:
|
||||
- Изменен цвет сообщений с белого на жёлтый.
|
||||
|
||||
## [0.1.7] - 2024-10-23
|
||||
|
||||
### Изменено
|
||||
|
|
35
build.gradle
35
build.gradle
|
@ -1,10 +1,12 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
// Эти репозитории предназначены только для Gradle-плагинов, другие репозитории размещайте в блоке ниже
|
||||
maven { url = 'https://repo.spongepowered.org/repository/maven-public/' }
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
// После удаления Mixin здесь больше не требуется зависимостей
|
||||
classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +16,8 @@ plugins {
|
|||
id 'net.minecraftforge.gradle' version '[6.0.16,6.2)'
|
||||
}
|
||||
|
||||
apply plugin: 'org.spongepowered.mixin'
|
||||
|
||||
group = mod_group_id
|
||||
version = mod_version
|
||||
|
||||
|
@ -117,6 +121,11 @@ minecraft {
|
|||
}
|
||||
}
|
||||
|
||||
mixin {
|
||||
add sourceSets.main, "${mod_id}.refmap.json"
|
||||
|
||||
config "${mod_id}.mixins.json"
|
||||
}
|
||||
// Включите ресурсы, сгенерированные генераторами данных.
|
||||
sourceSets.main.resources { srcDir 'src/generated/resources' }
|
||||
|
||||
|
@ -153,6 +162,8 @@ dependencies {
|
|||
// Для дополнительной информации:
|
||||
// http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html
|
||||
// http://www.gradle.org/docs/current/userguide/dependency_management.html
|
||||
|
||||
annotationProcessor 'org.spongepowered:mixin:0.8.5:processor'
|
||||
}
|
||||
|
||||
tasks.named('processResources', ProcessResources).configure {
|
||||
|
@ -192,3 +203,27 @@ tasks.named('jar', Jar).configure {
|
|||
tasks.withType(JavaCompile).configureEach {
|
||||
options.encoding = 'UTF-8' // Используйте кодировку UTF-8 для компиляции Java
|
||||
}
|
||||
|
||||
// Определите путь к вашей целевой папке
|
||||
def targetModsDir = "D:/server_sandbox/ForgeCreate/mods"
|
||||
|
||||
// Задача для копирования JAR-файла
|
||||
tasks.register('copyJarToMods', Copy) {
|
||||
// Указывает, откуда копировать файл (из build/libs)
|
||||
from jar.archiveFile
|
||||
|
||||
// Указывает, куда копировать файл
|
||||
into targetModsDir
|
||||
|
||||
// Фильтр для выбора нужных файлов, если необходимо
|
||||
//include "${project.name}-${project.version}.jar"
|
||||
|
||||
// Можно добавить переименование файла, если требуется
|
||||
// rename "${project.name}-${project.version}.jar", "${project.name}.jar"
|
||||
}
|
||||
|
||||
// Убедитесь, что задача `copyJarToMods` выполняется после сборки JAR
|
||||
jar.finalizedBy(copyJarToMods)
|
||||
|
||||
// (Опционально) Добавьте зависимость на задачу `copyJarToMods` к задаче `build`
|
||||
build.dependsOn(copyJarToMods)
|
||||
|
|
|
@ -38,7 +38,7 @@ mod_name=ChatIT
|
|||
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||
mod_license=All Rights Reserved
|
||||
# The mod version. See https://semver.org/
|
||||
mod_version=0.1.7-BETA
|
||||
mod_version=0.2.1-BETA
|
||||
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
|
||||
# This should match the base package used for the mod sources.
|
||||
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
||||
|
|
|
@ -1,18 +1,36 @@
|
|||
package org.itqop.chatit.commands;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.commands.arguments.MessageArgument;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent;
|
||||
|
||||
import org.itqop.chatit.utils.PlayerConfigManager;
|
||||
import org.itqop.chatit.utils.Config;
|
||||
import org.itqop.chatit.utils.ProfanityMode;
|
||||
import static org.itqop.chatit.utils.Utils.logPrivateMessage;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
@Mod.EventBusSubscriber
|
||||
public class ChatITCommand {
|
||||
|
||||
private static final Map<UUID, UUID> lastSenders = new ConcurrentHashMap<>();
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRegisterCommands(RegisterCommandsEvent event) {
|
||||
CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();
|
||||
|
@ -29,6 +47,135 @@ public class ChatITCommand {
|
|||
return 1;
|
||||
})
|
||||
)
|
||||
.then(Commands.literal("mode")
|
||||
.requires(source -> source.hasPermission(2))
|
||||
.then(Commands.argument("mode", StringArgumentType.word())
|
||||
.executes(context -> {
|
||||
String modeInput = StringArgumentType.getString(context, "mode").toLowerCase();
|
||||
ProfanityMode newMode;
|
||||
try {
|
||||
newMode = ProfanityMode.fromString(modeInput);
|
||||
} catch (IllegalArgumentException e) {
|
||||
context.getSource().sendFailure(Component.literal("Неверный режим. Используйте: off, regex, api.")
|
||||
.withStyle(ChatFormatting.RED));
|
||||
return 0;
|
||||
}
|
||||
|
||||
Config.setProfanityMode(newMode);
|
||||
|
||||
context.getSource().sendSuccess(
|
||||
() -> Component.literal("Режим проверки мата установлен на: " + newMode.name().toLowerCase() + ".")
|
||||
.withStyle(ChatFormatting.GREEN),
|
||||
true
|
||||
);
|
||||
|
||||
return 1;
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
dispatcher.register(Commands.literal("r")
|
||||
.then(Commands.argument("message", MessageArgument.message())
|
||||
.executes(context -> {
|
||||
ServerPlayer sender = context.getSource().getPlayerOrException();
|
||||
UUID lastSenderUUID = lastSenders.get(sender.getUUID());
|
||||
if (lastSenderUUID == null) {
|
||||
context.getSource().sendFailure(Component.literal("Нет игрока для ответа.")
|
||||
.withStyle(ChatFormatting.RED));
|
||||
return 0;
|
||||
}
|
||||
|
||||
ServerPlayer target = context.getSource().getServer().getPlayerList().getPlayer(lastSenderUUID);
|
||||
if (target == null) {
|
||||
context.getSource().sendFailure(Component.literal("Последний отправитель недоступен.")
|
||||
.withStyle(ChatFormatting.RED));
|
||||
return 0;
|
||||
}
|
||||
|
||||
Component message = MessageArgument.getMessage(context, "message");
|
||||
|
||||
return sendPrivateMessage(context.getSource(), target, message);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static void registerPrivateMessageCommand(CommandDispatcher<CommandSourceStack> dispatcher, String commandName) {
|
||||
dispatcher.register(Commands.literal(commandName)
|
||||
.then(Commands.argument("target", EntityArgument.player())
|
||||
.then(Commands.argument("message", MessageArgument.message())
|
||||
.executes(context -> sendPrivateMessage(context.getSource(),
|
||||
EntityArgument.getPlayer(context, "target"),
|
||||
MessageArgument.getMessage(context, "message")))
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для отправки приватного сообщения от одного игрока к другому.
|
||||
*
|
||||
* @param source Источник команды (должен быть игроком).
|
||||
* @param target Целевой игрок.
|
||||
* @param message Сообщение.
|
||||
* @return Статус выполнения команды.
|
||||
*/
|
||||
private static int sendPrivateMessage(CommandSourceStack source, ServerPlayer target, Component message) {
|
||||
if (!(source.getEntity() instanceof ServerPlayer sender)) {
|
||||
source.sendFailure(Component.literal("Только игроки могут отправлять личные сообщения.")
|
||||
.withStyle(ChatFormatting.RED));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sender.getUUID().equals(target.getUUID())) {
|
||||
sender.sendSystemMessage(Component.literal("Вы не можете отправлять сообщения самому себе.")
|
||||
.withStyle(ChatFormatting.RED));
|
||||
return 0;
|
||||
}
|
||||
|
||||
MutableComponent pmText = Component.literal("ЛС");
|
||||
MutableComponent fromText = Component.literal(" от ");
|
||||
MutableComponent toText = Component.literal(" для ");
|
||||
MutableComponent closeBracket = Component.literal(": ");
|
||||
|
||||
MutableComponent messageFormat = message.copy().withStyle(ChatFormatting.YELLOW);
|
||||
|
||||
MutableComponent messageTo = pmText.copy()
|
||||
.append(toText)
|
||||
.append(target.getDisplayName().copy().withStyle(ChatFormatting.GREEN))
|
||||
.append(closeBracket)
|
||||
.append(messageFormat);
|
||||
|
||||
MutableComponent messageFrom = pmText.copy()
|
||||
.append(fromText)
|
||||
.append(sender.getDisplayName().copy().withStyle(ChatFormatting.GREEN))
|
||||
.append(closeBracket)
|
||||
.append(messageFormat);
|
||||
|
||||
target.sendSystemMessage(messageFrom);
|
||||
sender.sendSystemMessage(messageTo);
|
||||
|
||||
lastSenders.put(target.getUUID(), sender.getUUID());
|
||||
|
||||
logPrivateMessage(sender, target, message);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Слушатель события отключения игрока для очистки хэшмапы.
|
||||
*
|
||||
* @param event Событие отключения игрока.
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLogout(PlayerLoggedOutEvent event) {
|
||||
Player player = event.getEntity();
|
||||
UUID playerUUID = player.getUUID();
|
||||
|
||||
lastSenders.values().removeIf(senderUUID -> senderUUID.equals(playerUUID));
|
||||
|
||||
lastSenders.remove(playerUUID);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package org.itqop.chatit.handlers;
|
||||
|
||||
import net.minecraft.network.chat.ClickEvent;
|
||||
import net.minecraft.network.chat.HoverEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.event.ServerChatEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
@ -15,16 +17,27 @@ import org.slf4j.Logger;
|
|||
import com.mojang.logging.LogUtils;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.itqop.chatit.utils.Utils.isValidUrl;
|
||||
import static org.itqop.chatit.utils.Utils.logGlobalMessage;
|
||||
|
||||
@Mod.EventBusSubscriber
|
||||
public class ChatEventHandler {
|
||||
|
||||
private static final Logger LOGGER = LogUtils.getLogger();
|
||||
|
||||
private static final Pattern URL_PATTERN = Pattern.compile(
|
||||
"(https?://\\S+)", Pattern.CASE_INSENSITIVE
|
||||
);
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onServerChat(ServerChatEvent event) {
|
||||
ServerPlayer sender = event.getPlayer();
|
||||
MutableComponent errorComponent = createErrorMessage(Config.messageLocal);
|
||||
MutableComponent adultComponent = createAdultMessage();
|
||||
|
||||
ServerPlayer sender = event.getPlayer();
|
||||
MinecraftServer server = sender.getServer();
|
||||
String originalMessage = event.getMessage().getString();
|
||||
|
||||
|
@ -37,6 +50,8 @@ public class ChatEventHandler {
|
|||
|
||||
MutableComponent chatComponent = createChatMessage(sender, message, isGlobal);
|
||||
|
||||
logGlobalMessage(chatComponent);
|
||||
|
||||
CompletableFuture<Double> future = ProfanityChecker.checkMessageAsync(message);
|
||||
|
||||
future.thenAccept(profanityScore -> {
|
||||
|
@ -45,7 +60,6 @@ public class ChatEventHandler {
|
|||
double threshold = Config.profanityThreshold;
|
||||
|
||||
if (profanityScore > threshold && !PlayerConfigManager.hasAdultContentEnabled(sender)) {
|
||||
MutableComponent adultComponent = createAdultMessage();
|
||||
sender.sendSystemMessage(adultComponent);
|
||||
} else {
|
||||
boolean localMessageSent = false;
|
||||
|
@ -73,7 +87,6 @@ public class ChatEventHandler {
|
|||
|
||||
if (!isGlobal) {
|
||||
if (!localMessageSent) {
|
||||
MutableComponent errorComponent = createErrorMessage(Config.messageLocal);
|
||||
sender.sendSystemMessage(errorComponent);
|
||||
} else {
|
||||
sender.sendSystemMessage(chatComponent);
|
||||
|
@ -86,8 +99,8 @@ public class ChatEventHandler {
|
|||
LOGGER.error(ex.toString());
|
||||
if (server != null) {
|
||||
server.execute(() -> {
|
||||
MutableComponent errorComponent = createErrorMessage(message);
|
||||
sender.sendSystemMessage(errorComponent);
|
||||
MutableComponent errorCustomComponent = createErrorMessage(message);
|
||||
sender.sendSystemMessage(errorCustomComponent);
|
||||
});
|
||||
}
|
||||
return null;
|
||||
|
@ -120,6 +133,14 @@ public class ChatEventHandler {
|
|||
return prefixComponent.append(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для создания сообщения чата с кликабельными ссылками.
|
||||
*
|
||||
* @param player Игрок, отправляющий сообщение.
|
||||
* @param message Текст сообщения.
|
||||
* @param isGlobal Флаг, указывающий на глобальное или локальное сообщение.
|
||||
* @return Кликабельное сообщение чата.
|
||||
*/
|
||||
private static MutableComponent createChatMessage(ServerPlayer player, String message, boolean isGlobal) {
|
||||
MutableComponent openBracket = Component.literal("[");
|
||||
MutableComponent letterComponent;
|
||||
|
@ -135,9 +156,47 @@ public class ChatEventHandler {
|
|||
|
||||
MutableComponent prefixComponent = openBracket.append(letterComponent).append(closeBracket);
|
||||
|
||||
Component playerName = Component.literal(player.getName().getString());
|
||||
Component messageComponent = Component.literal(": " + message);
|
||||
MutableComponent playerName = Component.literal(player.getName().getString())
|
||||
.withStyle(style -> style
|
||||
.withClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/msg " + player.getName().getString() + " "))
|
||||
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("Нажмите, чтобы ответить")))
|
||||
.withColor(ChatFormatting.WHITE)
|
||||
);
|
||||
|
||||
return prefixComponent.append(playerName).append(messageComponent);
|
||||
Component separatorComponent = Component.literal(": ");
|
||||
|
||||
MutableComponent messageComponent = Component.empty();
|
||||
Matcher matcher = URL_PATTERN.matcher(message);
|
||||
int lastEnd = 0;
|
||||
|
||||
while (matcher.find()) {
|
||||
if (matcher.start() > lastEnd) {
|
||||
String textBeforeUrl = message.substring(lastEnd, matcher.start());
|
||||
messageComponent.append(Component.literal(textBeforeUrl));
|
||||
}
|
||||
|
||||
String url = matcher.group(1);
|
||||
if (isValidUrl(url)) {
|
||||
MutableComponent clickableLink = Component.literal(url)
|
||||
.withStyle(style -> style
|
||||
.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url))
|
||||
.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, Component.literal("Нажмите, чтобы открыть ссылку")))
|
||||
.withColor(ChatFormatting.BLUE)
|
||||
.withUnderlined(true)
|
||||
);
|
||||
messageComponent.append(clickableLink);
|
||||
} else {
|
||||
messageComponent.append(Component.literal(url));
|
||||
}
|
||||
|
||||
lastEnd = matcher.end();
|
||||
}
|
||||
|
||||
if (lastEnd < message.length()) {
|
||||
String remainingText = message.substring(lastEnd);
|
||||
messageComponent.append(Component.literal(remainingText));
|
||||
}
|
||||
|
||||
return prefixComponent.append(playerName).append(separatorComponent).append(messageComponent.withStyle(ChatFormatting.YELLOW));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package org.itqop.chatit.mixins;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import org.itqop.chatit.commands.ChatITCommand;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
@Mixin(net.minecraft.server.commands.MsgCommand.class)
|
||||
public abstract class MsgCommand {
|
||||
|
||||
@Inject(method = "register", at = @At("HEAD"), cancellable = true)
|
||||
private static void onRegister(CommandDispatcher<CommandSourceStack> dispatcher, CallbackInfo ci) {
|
||||
ChatITCommand.registerPrivateMessageCommand(dispatcher, "tell");
|
||||
ChatITCommand.registerPrivateMessageCommand(dispatcher, "w");
|
||||
ChatITCommand.registerPrivateMessageCommand(dispatcher, "msg");
|
||||
|
||||
ci.cancel();
|
||||
}
|
||||
}
|
|
@ -5,11 +5,14 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
|
|||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.config.ModConfigEvent;
|
||||
import org.itqop.chatit.ChatIT;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@Mod.EventBusSubscriber(modid = ChatIT.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||
public class Config {
|
||||
|
||||
private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Config.class);
|
||||
|
||||
public static ForgeConfigSpec COMMON_CONFIG;
|
||||
|
||||
|
@ -21,6 +24,8 @@ public class Config {
|
|||
public static ForgeConfigSpec.ConfigValue<String> MESSAGE_ADULT;
|
||||
public static ForgeConfigSpec.ConfigValue<String> MESSAGE_LOCAL;
|
||||
|
||||
public static ForgeConfigSpec.ConfigValue<String> PROFANITY_MODE;
|
||||
|
||||
static {
|
||||
BUILDER.comment("Настройки ChatIT");
|
||||
|
||||
|
@ -38,7 +43,7 @@ public class Config {
|
|||
|
||||
PROFANITY_THRESHOLD = BUILDER
|
||||
.comment("Порог для определения мата (0.0 - 1.0)")
|
||||
.defineInRange("profanity_threshold", 0.5, 0.0, 1.0); // Дефолтное значение 0.5
|
||||
.defineInRange("profanity_threshold", 0.75, 0.0, 1.0); // Дефолтное значение 0.5
|
||||
|
||||
PROFANITY_REGEX = BUILDER
|
||||
.comment("Регулярное выражение для проверки мата")
|
||||
|
@ -52,9 +57,15 @@ public class Config {
|
|||
.comment("Сообщение предупреждения о том, что никого рядом нет")
|
||||
.define("message_local", "Вас никто не услышал, поставьте ! перед сообщением.");
|
||||
|
||||
// Определение нового параметра profanity_mode
|
||||
PROFANITY_MODE = BUILDER
|
||||
.comment("Режим проверки мата: off, regex, api")
|
||||
.define("profanity_mode", "regex"); // Дефолтное значение: api
|
||||
|
||||
COMMON_CONFIG = BUILDER.build();
|
||||
}
|
||||
|
||||
// Статические переменные для доступа к настройкам
|
||||
public static String hostApi;
|
||||
public static boolean defaultAdult;
|
||||
public static boolean useRegex;
|
||||
|
@ -62,6 +73,7 @@ public class Config {
|
|||
public static String profanityRegex;
|
||||
public static String messageAdult;
|
||||
public static String messageLocal;
|
||||
public static ProfanityMode profanityMode;
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLoad(final ModConfigEvent event) {
|
||||
|
@ -72,5 +84,37 @@ public class Config {
|
|||
profanityRegex = PROFANITY_REGEX.get();
|
||||
messageAdult = MESSAGE_ADULT.get();
|
||||
messageLocal = MESSAGE_LOCAL.get();
|
||||
|
||||
// Чтение и установка режима проверки мата
|
||||
String modeString = PROFANITY_MODE.get().toLowerCase();
|
||||
try {
|
||||
profanityMode = ProfanityMode.fromString(modeString);
|
||||
} catch (IllegalArgumentException e) {
|
||||
profanityMode = ProfanityMode.REGEX; // Значение по умолчанию
|
||||
LOGGER.warn("Неверный режим проверки мата в конфигурации: {}. Используется режим API.", modeString);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для установки нового режима проверки мата.
|
||||
*
|
||||
* @param mode Новый режим.
|
||||
*/
|
||||
public static void setProfanityMode(ProfanityMode mode) {
|
||||
profanityMode = mode;
|
||||
PROFANITY_MODE.set(mode.name().toLowerCase());
|
||||
|
||||
COMMON_CONFIG.save();
|
||||
LOGGER.info("Режим проверки мата установлен на: {} и конфигурация сохранена.", mode.name().toLowerCase());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Метод для получения текущего режима проверки мата.
|
||||
*
|
||||
* @return Текущий режим.
|
||||
*/
|
||||
public static ProfanityMode getProfanityMode() {
|
||||
return profanityMode;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,17 +23,34 @@ public class ProfanityChecker {
|
|||
private static Pattern profanityPattern = null;
|
||||
|
||||
public static CompletableFuture<Double> checkMessageAsync(String message) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
ProfanityMode mode = Config.getProfanityMode();
|
||||
|
||||
return switch (mode) {
|
||||
case OFF -> 0.0; // Проверка отключена
|
||||
case REGEX -> containsProfanity(message) ? 1.0 : 0.0;
|
||||
case API -> checkViaApi(message).get();
|
||||
default -> {
|
||||
LOGGER.warn("Неизвестный режим проверки мата: " + mode);
|
||||
yield 0.0;
|
||||
}
|
||||
};
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("Ошибка при проверке мата: ", e);
|
||||
return 0.0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static CompletableFuture<Double> checkViaApi(String message) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
String apiUrl = Config.hostApi;
|
||||
boolean useRegex = Config.useRegex;
|
||||
|
||||
if (apiUrl == null || apiUrl.isEmpty()) {
|
||||
if (useRegex) {
|
||||
return containsProfanity(message) ? 1.0 : 0.0;
|
||||
} else {
|
||||
return 0.0;
|
||||
}
|
||||
LOGGER.warn("URL API не задан. Возвращается 0.0");
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
String jsonRequest = GSON.toJson(new MessageRequest(message));
|
||||
|
@ -56,16 +73,18 @@ public class ProfanityChecker {
|
|||
|
||||
return jsonResponse.get("toxicity_score").getAsDouble();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(e.toString());
|
||||
if (Config.useRegex) {
|
||||
return containsProfanity(message) ? 1.0 : 0.0;
|
||||
}
|
||||
LOGGER.error("Ошибка при вызове API: ", e);
|
||||
return 0.0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean containsProfanity(String message) {
|
||||
ProfanityMode mode = Config.getProfanityMode();
|
||||
if (mode != ProfanityMode.REGEX) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profanityPattern == null || !profanityPattern.pattern().equals(Config.profanityRegex)) {
|
||||
profanityPattern = Pattern.compile(Config.profanityRegex);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package org.itqop.chatit.utils;
|
||||
|
||||
public enum ProfanityMode {
|
||||
OFF,
|
||||
REGEX,
|
||||
API;
|
||||
|
||||
public static ProfanityMode fromString(String mode) {
|
||||
return switch (mode.toLowerCase()) {
|
||||
case "off" -> OFF;
|
||||
case "regex" -> REGEX;
|
||||
case "api" -> API;
|
||||
default -> throw new IllegalArgumentException("Неверный режим проверки мата: " + mode);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package org.itqop.chatit.utils;
|
||||
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.server.network.ServerGamePacketListenerImpl;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.net.URL;
|
||||
|
||||
|
||||
public class Utils {
|
||||
|
||||
private static final DateTimeFormatter LOG_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private static final String PRIVATE_MESSAGES_LOG = "logs/private_messages.log";
|
||||
private static final String GLOBAL_MESSAGES_LOG = "logs/global_messages.log";
|
||||
|
||||
/**
|
||||
* Метод для получения IP-адреса игрока.
|
||||
*
|
||||
* @param player Игрок, чей IP требуется получить.
|
||||
* @return IP-адрес игрока в строковом формате.
|
||||
*/
|
||||
private static String getPlayerIp(ServerPlayer player) {
|
||||
ServerGamePacketListenerImpl connection = player.connection;
|
||||
|
||||
InetSocketAddress address = (InetSocketAddress) connection.connection.getRemoteAddress();
|
||||
|
||||
InetAddress inetAddress = address.getAddress();
|
||||
if (inetAddress == null) {
|
||||
return "Unknown IP";
|
||||
}
|
||||
|
||||
if (inetAddress instanceof Inet4Address) {
|
||||
return inetAddress.getHostAddress();
|
||||
} else if (inetAddress instanceof Inet6Address) {
|
||||
String ip = inetAddress.getHostAddress();
|
||||
int percentIndex = ip.indexOf('%');
|
||||
if (percentIndex != -1) {
|
||||
ip = ip.substring(0, percentIndex);
|
||||
}
|
||||
return ip;
|
||||
} else {
|
||||
return inetAddress.getHostAddress();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для логирования приватных сообщений в файл.
|
||||
*
|
||||
* @param sender Игрок, отправляющий сообщение.
|
||||
* @param target Целевой игрок.
|
||||
* @param message Текст сообщения.
|
||||
*/
|
||||
public static void logPrivateMessage(ServerPlayer sender, ServerPlayer target, Component message) {
|
||||
String time = LocalDateTime.now().format(LOG_TIME_FORMAT);
|
||||
String senderIp = getPlayerIp(sender);
|
||||
String senderName = sender.getName().getString();
|
||||
String targetName = target.getName().getString();
|
||||
String messageText = message.getString();
|
||||
String logEntry = String.format("[%s] [%s] - %s -> %s: %s%n", time, senderIp, senderName, targetName, messageText);
|
||||
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(PRIVATE_MESSAGES_LOG, true))) {
|
||||
writer.write(logEntry);
|
||||
} catch (IOException e) {
|
||||
//LOGGER.error("Не удалось записать приватное сообщение в лог.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для логирования глобальных сообщений в файл.
|
||||
*
|
||||
* @param message Глобальное сообщение в формате MutableComponent.
|
||||
*/
|
||||
public static void logGlobalMessage(MutableComponent message) {
|
||||
String time = LocalDateTime.now().format(LOG_TIME_FORMAT);
|
||||
String messageText = message.getString();
|
||||
String logEntry = String.format("[%s] %s%n", time, messageText);
|
||||
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(GLOBAL_MESSAGES_LOG, true))) {
|
||||
writer.write(logEntry);
|
||||
} catch (IOException e) {
|
||||
//LOGGER.error("Не удалось записать глобальное сообщение в лог.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Метод для проверки валидности URL.
|
||||
* В будущем сюда можно добавить дополнительные проверки, например, DGA.
|
||||
*
|
||||
* @param url Строка с URL для проверки.
|
||||
* @return true, если URL валиден, иначе false.
|
||||
*/
|
||||
public static boolean isValidUrl(String url) {
|
||||
try {
|
||||
new URL(url).toURI(); // Проверяем синтаксис URL
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -61,3 +61,7 @@ mandatory = true
|
|||
versionRange = "${minecraft_version_range}"
|
||||
ordering = "NONE"
|
||||
side = "BOTH"
|
||||
|
||||
[[mixins]]
|
||||
mod = "chatit"
|
||||
config = "chatit.mixins.json"
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"required": true,
|
||||
"minVersion": "0.8",
|
||||
"package": "org.itqop.chatit.mixins",
|
||||
"refmap": "chatit.refmap.json",
|
||||
"mixins": [
|
||||
"MsgCommand"
|
||||
],
|
||||
"client": [],
|
||||
"server": []
|
||||
}
|
Loading…
Reference in New Issue