Compare commits

..

3 Commits

Author SHA1 Message Date
itqop f14c4e3fc0 chore(release): v0.1.9
Added hot-swap profanity filtering modes and custom private messages to replace /w, /tell, /msg commands.
2024-10-31 17:26:18 +03:00
itqop a5d3df7de0 chore(release): v0.1.8
Change message color from white to yellow in ChatEventHandler.java
2024-10-30 20:48:50 +03:00
itqop 7ae321a413 chore(release): v0.1.7
Set HttpClient version to HTTP/1.1 in ProfanityChecker.java
2024-10-23 02:33:05 +03:00
13 changed files with 345 additions and 22 deletions

View File

@ -4,6 +4,37 @@ 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/). 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.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
- **ProfanityChecker.java**:
- Set HTTP version to HTTP/1.1 for HttpClient.
## [0.1.6] - 2024-10-21 ## [0.1.6] - 2024-10-21
### Fixed ### Fixed

View File

@ -4,6 +4,38 @@
Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/), и этот проект придерживается [Семантического Версионирования](https://semver.org/lang/ru/). Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.0.0/), и этот проект придерживается [Семантического Версионирования](https://semver.org/lang/ru/).
## [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
### Изменено
- **ProfanityChecker.java**:
- Установлена версия HTTP на HTTP/1.1 для HttpClient.
## [0.1.6] - 2024-10-21 ## [0.1.6] - 2024-10-21
### Исправлено ### Исправлено

View File

@ -0,0 +1 @@
Readme for usage - https://github.com/itqop/ChatIT

View File

@ -1,10 +1,12 @@
buildscript { buildscript {
repositories { repositories {
// Эти репозитории предназначены только для Gradle-плагинов, другие репозитории размещайте в блоке ниже // Эти репозитории предназначены только для Gradle-плагинов, другие репозитории размещайте в блоке ниже
maven { url = 'https://repo.spongepowered.org/repository/maven-public/' }
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
// После удаления Mixin здесь больше не требуется зависимостей // После удаления Mixin здесь больше не требуется зависимостей
classpath 'org.spongepowered:mixingradle:0.7-SNAPSHOT'
} }
} }
@ -14,6 +16,8 @@ plugins {
id 'net.minecraftforge.gradle' version '[6.0.16,6.2)' id 'net.minecraftforge.gradle' version '[6.0.16,6.2)'
} }
apply plugin: 'org.spongepowered.mixin'
group = mod_group_id group = mod_group_id
version = mod_version 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' } 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/artifact_dependencies_tutorial.html
// http://www.gradle.org/docs/current/userguide/dependency_management.html // http://www.gradle.org/docs/current/userguide/dependency_management.html
annotationProcessor 'org.spongepowered:mixin:0.8.5:processor'
} }
tasks.named('processResources', ProcessResources).configure { tasks.named('processResources', ProcessResources).configure {
@ -192,3 +203,27 @@ tasks.named('jar', Jar).configure {
tasks.withType(JavaCompile).configureEach { tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8' // Используйте кодировку UTF-8 для компиляции Java 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)

View File

@ -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. # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=All Rights Reserved mod_license=All Rights Reserved
# The mod version. See https://semver.org/ # The mod version. See https://semver.org/
mod_version=0.1.6-BETA mod_version=0.1.9-BETA
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # 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. # This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html # See https://maven.apache.org/guides/mini/guide-naming-conventions.html

View File

@ -1,14 +1,21 @@
package org.itqop.chatit.commands; package org.itqop.chatit.commands;
import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.StringArgumentType;
import net.minecraft.ChatFormatting;
import net.minecraft.commands.Commands; import net.minecraft.commands.Commands;
import net.minecraft.commands.CommandSourceStack; 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.server.level.ServerPlayer;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraftforge.event.RegisterCommandsEvent; import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import org.itqop.chatit.utils.PlayerConfigManager; import org.itqop.chatit.utils.PlayerConfigManager;
import org.itqop.chatit.utils.Config;
import org.itqop.chatit.utils.ProfanityMode;
@Mod.EventBusSubscriber @Mod.EventBusSubscriber
public class ChatITCommand { public class ChatITCommand {
@ -29,6 +36,94 @@ public class ChatITCommand {
return 1; return 1;
}) })
) )
// Добавление подкоманды mode
.then(Commands.literal("mode")
.requires(source -> source.hasPermission(2)) // Требует уровень разрешения 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;
})
)
)
);
}
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("Только игроки могут отправлять личные сообщения."));
return 0;
}
// Проверка, что отправитель не отправляет сообщение самому себе
if (sender.getUUID().equals(target.getUUID())) {
sender.sendSystemMessage(Component.literal("Вы не можете отправлять сообщения самому себе."));
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);
return 1;
}
} }

View File

@ -136,8 +136,9 @@ public class ChatEventHandler {
MutableComponent prefixComponent = openBracket.append(letterComponent).append(closeBracket); MutableComponent prefixComponent = openBracket.append(letterComponent).append(closeBracket);
Component playerName = Component.literal(player.getName().getString()); Component playerName = Component.literal(player.getName().getString());
Component messageComponent = Component.literal(": " + message); Component separatorComponent = Component.literal(": ");
Component messageComponent = Component.literal(message).withStyle(ChatFormatting.YELLOW);
return prefixComponent.append(playerName).append(messageComponent); return prefixComponent.append(playerName).append(separatorComponent).append(messageComponent);
} }
} }

View File

@ -0,0 +1,23 @@
package org.itqop.chatit.mixins;
import com.mojang.brigadier.CommandDispatcher;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.server.commands.MsgCommand;
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(MsgCommand.class)
public abstract class MixinMessageCommand {
@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();
}
}

View File

@ -5,11 +5,14 @@ import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.fml.event.config.ModConfigEvent; import net.minecraftforge.fml.event.config.ModConfigEvent;
import org.itqop.chatit.ChatIT; import org.itqop.chatit.ChatIT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Mod.EventBusSubscriber(modid = ChatIT.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) @Mod.EventBusSubscriber(modid = ChatIT.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)
public class Config { public class Config {
private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
private static final Logger LOGGER = LoggerFactory.getLogger(Config.class);
public static ForgeConfigSpec COMMON_CONFIG; 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_ADULT;
public static ForgeConfigSpec.ConfigValue<String> MESSAGE_LOCAL; public static ForgeConfigSpec.ConfigValue<String> MESSAGE_LOCAL;
public static ForgeConfigSpec.ConfigValue<String> PROFANITY_MODE;
static { static {
BUILDER.comment("Настройки ChatIT"); BUILDER.comment("Настройки ChatIT");
@ -38,7 +43,7 @@ public class Config {
PROFANITY_THRESHOLD = BUILDER PROFANITY_THRESHOLD = BUILDER
.comment("Порог для определения мата (0.0 - 1.0)") .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 PROFANITY_REGEX = BUILDER
.comment("Регулярное выражение для проверки мата") .comment("Регулярное выражение для проверки мата")
@ -52,9 +57,15 @@ public class Config {
.comment("Сообщение предупреждения о том, что никого рядом нет") .comment("Сообщение предупреждения о том, что никого рядом нет")
.define("message_local", "Вас никто не услышал, поставьте ! перед сообщением."); .define("message_local", "Вас никто не услышал, поставьте ! перед сообщением.");
// Определение нового параметра profanity_mode
PROFANITY_MODE = BUILDER
.comment("Режим проверки мата: off, regex, api")
.define("profanity_mode", "regex"); // Дефолтное значение: api
COMMON_CONFIG = BUILDER.build(); COMMON_CONFIG = BUILDER.build();
} }
// Статические переменные для доступа к настройкам
public static String hostApi; public static String hostApi;
public static boolean defaultAdult; public static boolean defaultAdult;
public static boolean useRegex; public static boolean useRegex;
@ -62,6 +73,7 @@ public class Config {
public static String profanityRegex; public static String profanityRegex;
public static String messageAdult; public static String messageAdult;
public static String messageLocal; public static String messageLocal;
public static ProfanityMode profanityMode;
@SubscribeEvent @SubscribeEvent
public static void onLoad(final ModConfigEvent event) { public static void onLoad(final ModConfigEvent event) {
@ -72,5 +84,37 @@ public class Config {
profanityRegex = PROFANITY_REGEX.get(); profanityRegex = PROFANITY_REGEX.get();
messageAdult = MESSAGE_ADULT.get(); messageAdult = MESSAGE_ADULT.get();
messageLocal = MESSAGE_LOCAL.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;
} }
} }

View File

@ -12,6 +12,7 @@ import org.slf4j.Logger;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject;
public class ProfanityChecker { public class ProfanityChecker {
@ -22,27 +23,40 @@ public class ProfanityChecker {
private static Pattern profanityPattern = null; private static Pattern profanityPattern = null;
public static CompletableFuture<Double> checkMessageAsync(String message) { 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(() -> { return CompletableFuture.supplyAsync(() -> {
try { try {
String apiUrl = Config.hostApi; String apiUrl = Config.hostApi;
boolean useRegex = Config.useRegex;
if (apiUrl == null || apiUrl.isEmpty()) { if (apiUrl == null || apiUrl.isEmpty()) {
if (useRegex) { LOGGER.warn("URL API не задан. Возвращается 0.0");
if (containsProfanity(message)) { return 0.0;
return 1.0;
} else {
return 0.0;
}
} else {
return 0.0;
}
} }
String jsonRequest = GSON.toJson(new MessageRequest(message)); String jsonRequest = GSON.toJson(new MessageRequest(message));
HttpRequest request = HttpRequest.newBuilder() HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(apiUrl)) .uri(URI.create(apiUrl))
.version(HttpClient.Version.HTTP_1_1)
.header("Content-Type", "application/json") .header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonRequest, StandardCharsets.UTF_8)) .POST(HttpRequest.BodyPublishers.ofString(jsonRequest, StandardCharsets.UTF_8))
.build(); .build();
@ -51,20 +65,26 @@ public class ProfanityChecker {
String responseBody = response.body(); String responseBody = response.body();
return Double.parseDouble(responseBody); if (response.statusCode() != 200) {
} catch (Exception e) { throw new RuntimeException("Ошибка API: " + responseBody);
LOGGER.error(e.toString());
if (Config.useRegex) {
if (containsProfanity(message)) {
return 1.0;
}
} }
JsonObject jsonResponse = GSON.fromJson(responseBody, JsonObject.class);
return jsonResponse.get("toxicity_score").getAsDouble();
} catch (Exception e) {
LOGGER.error("Ошибка при вызове API: ", e);
return 0.0; return 0.0;
} }
}); });
} }
private static boolean containsProfanity(String message) { private static boolean containsProfanity(String message) {
ProfanityMode mode = Config.getProfanityMode();
if (mode != ProfanityMode.REGEX) {
return false;
}
if (profanityPattern == null || !profanityPattern.pattern().equals(Config.profanityRegex)) { if (profanityPattern == null || !profanityPattern.pattern().equals(Config.profanityRegex)) {
profanityPattern = Pattern.compile(Config.profanityRegex); profanityPattern = Pattern.compile(Config.profanityRegex);
} }
@ -72,8 +92,18 @@ public class ProfanityChecker {
} }
private static class MessageRequest { private static class MessageRequest {
private String text;
public MessageRequest(String text) { public MessageRequest(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
} }
} }
} }

View File

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

View File

@ -61,3 +61,7 @@ mandatory = true
versionRange = "${minecraft_version_range}" versionRange = "${minecraft_version_range}"
ordering = "NONE" ordering = "NONE"
side = "BOTH" side = "BOTH"
[[mixins]]
mod = "chatit"
config = "chatit.mixins.json"

View File

@ -0,0 +1,11 @@
{
"required": true,
"minVersion": "0.8",
"package": "org.itqop.chatit.mixins",
"refmap": "chatit.refmap.json",
"mixins": [
"MixinMessageCommand"
],
"client": [],
"server": []
}