Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ java {
dependencies {
compileOnly("org.jetbrains:annotations:26.1.0")
compileOnly("com.google.code.gson:gson:2.12.1")
compileOnly("com.google.guava:guava:33.4.0-jre")
compileOnly("com.google.guava:guava:${property("guavaVersion")}")
}

buildConfig {
Expand Down
2 changes: 2 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ pluginVersion=2.1.0
versionChannel=release

# dependencies
paperVersion=1.21.11-R0.1-SNAPSHOT
cloudVersion=2.0.0-beta.15
langchain4jVersion=1.16.3
jmhVersion=1.37
guavaVersion=33.4.0-jre
6 changes: 3 additions & 3 deletions paper/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ dependencies {
implementation("org.incendo:cloud-paper:${property("cloudVersion")}")
implementation("org.incendo:cloud-minecraft-extras:${property("cloudVersion")}")

compileOnly("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
compileOnly("io.papermc.paper:paper-api:${property("paperVersion")}")
compileOnly("me.clip:placeholderapi:2.12.2")
compileOnly("org.geysermc.floodgate:api:2.2.5-SNAPSHOT")
compileOnly("fr.xephi:authme:5.7.0-SNAPSHOT")
Expand All @@ -55,15 +55,15 @@ dependencies {
runtimeOnly("org.snakeyaml:snakeyaml-engine:2.7")

testImplementation("org.junit.jupiter:junit-jupiter:6.1.1")
testImplementation("io.papermc.paper:paper-api:1.21.11-R0.1-SNAPSHOT")
testImplementation("io.papermc.paper:paper-api:${property("paperVersion")}")
testImplementation("dev.langchain4j:langchain4j-open-ai:${property("langchain4jVersion")}")
testImplementation("dev.langchain4j:langchain4j-anthropic:${property("langchain4jVersion")}")
testImplementation("dev.langchain4j:langchain4j-http-client-jdk:${property("langchain4jVersion")}")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:6.1.0")

add(jmh.implementationConfigurationName, "org.openjdk.jmh:jmh-core:${property("jmhVersion")}")
add(jmh.annotationProcessorConfigurationName, "org.openjdk.jmh:jmh-generator-annprocess:${property("jmhVersion")}")
add(jmh.runtimeOnlyConfigurationName, "com.google.guava:guava:33.4.0-jre")
add(jmh.runtimeOnlyConfigurationName, "com.google.guava:guava:${property("guavaVersion")}")
}

configurations.named(jmh.implementationConfigurationName) {
Expand Down
86 changes: 80 additions & 6 deletions paper/src/main/java/io/wdsj/asw/bukkit/AdvancedSensitiveWords.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
import com.github.Anon8281.universalScheduler.scheduling.tasks.MyScheduledTask;
import com.github.houbb.sensitive.word.api.IWordAllow;
import com.github.houbb.sensitive.word.api.IWordDeny;
import com.github.houbb.sensitive.word.api.IWordResult;
import com.github.houbb.sensitive.word.api.IWordResultCondition;
import com.github.houbb.sensitive.word.bs.SensitiveWordBs;
import com.github.houbb.sensitive.word.support.allow.WordAllows;
import com.github.houbb.sensitive.word.support.check.WordChecks;
import com.github.houbb.sensitive.word.support.deny.WordDenys;
import com.github.houbb.sensitive.word.support.result.WordResultHandlers;
import com.github.houbb.sensitive.word.support.resultcondition.WordResultConditions;
import com.github.houbb.sensitive.word.support.tag.WordTags;
import io.wdsj.asw.bukkit.command.AswCommandRegistrar;
Expand Down Expand Up @@ -44,6 +46,10 @@
import org.bukkit.plugin.java.JavaPlugin;
import org.slf4j.Logger;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;

import static io.wdsj.asw.bukkit.util.LoggingUtils.purgeLog;
import static io.wdsj.asw.bukkit.util.TimingUtils.resetStatistics;
import static io.wdsj.asw.bukkit.util.Utils.*;
Expand All @@ -52,6 +58,7 @@
public final class AdvancedSensitiveWords extends JavaPlugin {
public static volatile boolean isInitialized = false;
public static SensitiveWordBs sensitiveWordBs;
public static SensitiveWordBs networkSensitiveWordBs;
public static boolean isAuthMeAvailable;
public static final String PLUGIN_VERSION = PluginBuildInfo.VERSION;
private static AdvancedSensitiveWords instance;
Expand Down Expand Up @@ -134,6 +141,7 @@ public void doInitTasks() {
IWordAllow wA = WordAllows.chains(WordAllows.defaults(), new WordAllow(), new ExternalWordAllow(this));
isInitialized = false;
sensitiveWordBs = null;
networkSensitiveWordBs = null;
IWordResultCondition condition = createWordResultCondition();
getScheduler().runTaskAsynchronously(() -> {
IWordDeny wordDeny = createWordDeny();
Expand All @@ -145,20 +153,20 @@ public void doInitTasks() {
.ignoreEnglishStyle(configurationService.get(PluginSettings.IGNORE_ENGLISH_STYLE))
.ignoreRepeat(configurationService.get(PluginSettings.IGNORE_REPEAT))
.enableNumCheck(configurationService.get(PluginSettings.ENABLE_NUM_CHECK))
.enableEmailCheck(configurationService.get(PluginSettings.ENABLE_EMAIL_CHECK))
.enableUrlCheck(configurationService.get(PluginSettings.ENABLE_URL_CHECK))
.enableEmailCheck(false)
.enableUrlCheck(false)
.enableWordCheck(configurationService.get(PluginSettings.ENABLE_WORD_CHECK))
.wordResultCondition(condition)
.wordCheckUrl(configurationService.get(PluginSettings.URL_CHECK_NO_PREFIX) ? WordChecks.urlNoPrefix() : WordChecks.url())
.wordDeny(wordDeny)
.wordAllow(wA)
.numCheckLen(configurationService.get(PluginSettings.NUM_CHECK_LEN))
.wordReplace(new WordReplace())
.wordTag(WordTags.none())
.charIgnore(new CharIgnore())
.enableIpv4Check(configurationService.get(PluginSettings.ENABLE_IP_CHECK))
.charIgnore(new CharIgnore(configurationService.get(PluginSettings.IGNORE_CHAR)))
.enableIpv4Check(false)
.wordFailFast(configurationService.get(PluginSettings.FAIL_FAST))
.init();
networkSensitiveWordBs = createNetworkSensitiveWordBs();
isInitialized = true;
});
}
Expand All @@ -178,7 +186,12 @@ public void onDisable() {
ViolationCounter.INSTANCE.resetAllViolations();
SchedulingUtils.cancelTaskSafely(violationResetTask);
if (permCache != null) permCache.disable();
if (isInitialized) sensitiveWordBs.destroy();
if (isInitialized) {
sensitiveWordBs.destroy();
if (networkSensitiveWordBs != null) {
networkSensitiveWordBs.destroy();
}
}
commandRegistrar = null;
LOGGER.info("AdvancedSensitiveWords is disabled.");
}
Expand Down Expand Up @@ -282,6 +295,34 @@ private IWordResultCondition createWordResultCondition() {
};
}

private SensitiveWordBs createNetworkSensitiveWordBs() {
boolean enableEmail = configurationService.get(PluginSettings.ENABLE_EMAIL_CHECK);
boolean enableUrl = configurationService.get(PluginSettings.ENABLE_URL_CHECK);
boolean enableIp = configurationService.get(PluginSettings.ENABLE_IP_CHECK);
if (!enableEmail && !enableUrl && !enableIp) {
return null;
}
return SensitiveWordBs.newInstance()
.ignoreCase(configurationService.get(PluginSettings.IGNORE_CASE))
.ignoreWidth(configurationService.get(PluginSettings.IGNORE_WIDTH))
.ignoreNumStyle(configurationService.get(PluginSettings.IGNORE_NUM_STYLE))
.ignoreChineseStyle(configurationService.get(PluginSettings.IGNORE_CHINESE_STYLE))
.ignoreEnglishStyle(configurationService.get(PluginSettings.IGNORE_ENGLISH_STYLE))
.ignoreRepeat(configurationService.get(PluginSettings.IGNORE_REPEAT))
.enableNumCheck(false)
.enableEmailCheck(enableEmail)
.enableUrlCheck(enableUrl)
.enableWordCheck(false)
.wordResultCondition(WordResultConditions.alwaysTrue())
.wordCheckUrl(configurationService.get(PluginSettings.URL_CHECK_NO_PREFIX) ? WordChecks.urlNoPrefix() : WordChecks.url())
.wordReplace(new WordReplace())
.wordTag(WordTags.none())
.charIgnore(new CharIgnore(configurationService.get(PluginSettings.IGNORE_CHAR), CharIgnore.NETWORK_SYNTAX_CHARS))
.enableIpv4Check(enableIp)
.wordFailFast(configurationService.get(PluginSettings.FAIL_FAST))
.init();
}

private IWordDeny createWordDeny() {
boolean enableDefaultWords = configurationService.get(PluginSettings.ENABLE_DEFAULT_WORDS);
boolean enableOnlineWords = configurationService.get(PluginSettings.ENABLE_ONLINE_WORDS);
Expand All @@ -297,4 +338,37 @@ private IWordDeny createWordDeny() {
return WordDenys.chains(new WordDeny(), new ExternalWordDeny(this));
}

public static List<String> findAllSensitive(String text) {
LinkedHashSet<String> results = new LinkedHashSet<>();
SensitiveWordBs wordBs = sensitiveWordBs;
if (wordBs != null) {
results.addAll(wordBs.findAll(text));
}
SensitiveWordBs networkWordBs = networkSensitiveWordBs;
if (networkWordBs != null) {
results.addAll(networkWordBs.findAll(text));
}
return List.copyOf(results);
}

public static List<IWordResult> findAllSensitiveRaw(String text) {
List<IWordResult> results = new ArrayList<>();
SensitiveWordBs wordBs = sensitiveWordBs;
if (wordBs != null) {
results.addAll(wordBs.findAll(text, WordResultHandlers.raw()));
}
SensitiveWordBs networkWordBs = networkSensitiveWordBs;
if (networkWordBs != null) {
results.addAll(networkWordBs.findAll(text, WordResultHandlers.raw()));
}
return results;
}

public static String replaceSensitive(String text) {
SensitiveWordBs wordBs = sensitiveWordBs;
String result = wordBs == null ? text : wordBs.replace(text);
SensitiveWordBs networkWordBs = networkSensitiveWordBs;
return networkWordBs == null ? result : networkWordBs.replace(result);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
import io.wdsj.asw.bukkit.api.event.AsyncModerationResponseEvent;
import io.wdsj.asw.bukkit.api.moderation.LlmChatModerationResult;
import io.wdsj.asw.bukkit.api.moderation.LlmModerationCategory;
import io.wdsj.asw.bukkit.permission.option.PlayerOptionView;
import io.wdsj.asw.bukkit.permission.option.PlayerOptions;
import io.wdsj.asw.bukkit.setting.PaperConfigurationService;
import io.wdsj.asw.bukkit.setting.PluginSettings;
import io.wdsj.asw.bukkit.setting.SettingKey;
import io.wdsj.asw.bukkit.type.ModuleType;
import io.wdsj.asw.bukkit.util.SchedulingUtils;
import io.wdsj.asw.bukkit.util.ViolationReporter;
import io.wdsj.asw.common.utils.MessageEntropy;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerKickEvent;
import org.bukkit.event.player.PlayerQuitEvent;
Expand All @@ -29,6 +33,7 @@
import java.util.function.Function;
import java.nio.charset.StandardCharsets;
import io.wdsj.asw.bukkit.setting.SettingsConfiguration;
import org.jetbrains.annotations.VisibleForTesting;

public final class LlmChatDetectionService implements Listener, AutoCloseable {
private final PaperConfigurationService configuration;
Expand Down Expand Up @@ -80,28 +85,46 @@ public synchronized void reload() {
}
}

@VisibleForTesting
public void submit(UUID playerId, String playerName, String message) {
submit(playerId, playerName, message, null);
}

public void submit(Player player, String message, PlayerOptionView options) {
submit(player.getUniqueId(), player.getName(), message, options);
}

private void submit(UUID playerId, String playerName, String message, PlayerOptionView options) {
RuntimeState state = runtime;
if (state == null || closed || !AdvancedSensitiveWords.isInitialized || AdvancedSensitiveWords.sensitiveWordBs == null) {
return;
}

LlmSettings settings = state.settings();
if (!optionBoolean(options, PlayerOptions.AI_ENABLED, PluginSettings.AI_ENABLED)) {
return;
}
int rawCodePoints = message.codePointCount(0, message.length());
if (rawCodePoints > settings.maximumMessageCodePoints()) {
if (rawCodePoints > optionInt(options, PlayerOptions.AI_MAXIMUM_MESSAGE_CODE_POINTS, PluginSettings.AI_MAXIMUM_MESSAGE_CODE_POINTS)) {
return;
}
int visibleCodePoints = MessageEntropy.visibleCodePointCount(message);
if (visibleCodePoints < settings.minimumMessageCodePoints()) {
if (visibleCodePoints < optionInt(options, PlayerOptions.AI_MINIMUM_MESSAGE_CODE_POINTS, PluginSettings.AI_MINIMUM_MESSAGE_CODE_POINTS)) {
return;
}

double entropy = MessageEntropy.shannonEntropyBits(message);
if (entropy < settings.minimumEntropyBits()) {
if (entropy < optionDouble(options, PlayerOptions.AI_MINIMUM_ENTROPY_BITS, PluginSettings.AI_MINIMUM_ENTROPY_BITS)) {
return;
}

Candidate candidate = reserveCandidate(state, playerId, playerName, message, entropy);
Candidate candidate = reserveCandidate(
state,
playerId,
playerName,
message,
entropy,
optionInt(options, PlayerOptions.AI_PER_PLAYER_COOLDOWN_SECONDS, PluginSettings.AI_PLAYER_COOLDOWN_SECONDS)
);
if (candidate == null) {
droppedRequests.increment();
return;
Expand Down Expand Up @@ -207,14 +230,15 @@ private Candidate reserveCandidate(
UUID playerId,
String playerName,
String message,
double entropy
double entropy,
int cooldownSeconds
) {
if (!isCurrent(state) || inFlightGenerations.putIfAbsent(playerId, state.generation()) != null) {
return null;
}

long now = System.currentTimeMillis();
long deadline = now + TimeUnit.SECONDS.toMillis(state.settings().perPlayerCooldownSeconds());
long deadline = now + TimeUnit.SECONDS.toMillis(Math.max(0, cooldownSeconds));
AtomicBoolean granted = new AtomicBoolean();
cooldowns.compute(playerId, (ignored, existing) -> {
if (existing != null && existing.deadlineMillis() > now) {
Expand Down Expand Up @@ -399,6 +423,18 @@ private static Map<LlmModerationCategory, LlmCategoryPolicy> toCategoryPolicies(
return policies;
}

private boolean optionBoolean(PlayerOptionView options, String optionPath, SettingKey<Boolean> key) {
return options == null ? configuration.get(key) : options.bool(optionPath, key);
}

private int optionInt(PlayerOptionView options, String optionPath, SettingKey<Integer> key) {
return options == null ? configuration.get(key) : options.integer(optionPath, key);
}

private double optionDouble(PlayerOptionView options, String optionPath, SettingKey<Double> key) {
return options == null ? configuration.get(key) : options.decimal(optionPath, key);
}

record LlmSettings(
String baseUrl,
LlmApiMode apiMode,
Expand Down
4 changes: 2 additions & 2 deletions paper/src/main/java/io/wdsj/asw/bukkit/api/CoreApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public boolean isInitialized() {
* @see #isInitialized()
*/
public List<String> findSensitiveWords(String text) {
return AdvancedSensitiveWords.sensitiveWordBs.findAll(text);
return AdvancedSensitiveWords.findAllSensitive(text);
}

/**
Expand All @@ -41,6 +41,6 @@ public List<String> findSensitiveWords(String text) {
* @see #isInitialized()
*/
public String replaceSensitiveWords(String text) {
return AdvancedSensitiveWords.sensitiveWordBs.replace(text);
return AdvancedSensitiveWords.replaceSensitive(text);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public final class AswCommandRegistrar {
.toList()
);
private static final ParserDescriptor<Source, ModuleType> VIOLATION_MODULE_PARSER =
StringParser.<Source>stringParser().flatMapSuccess(ModuleType.class, (context, value) -> {
StringParser.<Source>stringParser().flatMapSuccess(ModuleType.class, (ignored, value) -> {
ModuleType moduleType = ModuleType.parseViolationModule(value);
if (moduleType == null) {
return ArgumentParseResult.failureFuture(new ViolationModuleParseException(value));
Expand Down Expand Up @@ -235,4 +235,5 @@ public Throwable fillInStackTrace() {
return this;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ public void reloadAll(CommandSender sender) {

plugin.reloadPluginConfiguration();
AdvancedSensitiveWords.sensitiveWordBs.destroy();
if (AdvancedSensitiveWords.networkSensitiveWordBs != null) {
AdvancedSensitiveWords.networkSensitiveWordBs.destroy();
}
plugin.doInitTasks();
if (configuration.get(PluginSettings.BOOK_CACHE_CLEAR_ON_RELOAD)
&& configuration.get(PluginSettings.BOOK_CACHE)) {
Expand Down Expand Up @@ -103,15 +106,15 @@ public void test(CommandSender sender, String text) {
return;
}

List<String> censoredWords = AdvancedSensitiveWords.sensitiveWordBs.findAll(text);
List<String> censoredWords = AdvancedSensitiveWords.findAllSensitive(text);
if (censoredWords.isEmpty()) {
MessageUtils.sendMessage(sender, PluginMessages.MESSAGE_ON_COMMAND_TEST_PASS);
return;
}

String message = MessageUtils.retrieveMessage(PluginMessages.MESSAGE_ON_COMMAND_TEST)
.replace("%original_msg%", text)
.replace("%processed_msg%", AdvancedSensitiveWords.sensitiveWordBs.replace(text))
.replace("%processed_msg%", AdvancedSensitiveWords.replaceSensitive(text))
.replace("%censored_list%", censoredWords.toString());
MessageUtils.sendMessage(sender, message);
}
Expand Down Expand Up @@ -229,4 +232,5 @@ private static String formatCategoryPolicies(Map<LlmModerationCategory, LlmCateg
.collect(Collectors.joining(", "));
return result.isEmpty() ? "none" : result;
}

}
Loading