New similarity calculator (Levenshtein distance) + fixes to the dictionary and chat notifications. Debug commands need work, and then testing for release can begin

This commit is contained in:
TheTrouper
2023-10-17 17:01:12 -05:00
committed by obvWolf
parent 5ccaf5a99d
commit dd2a4e772f
6 changed files with 111 additions and 49 deletions

View File

@@ -4,8 +4,12 @@
package io.github.thetrouper.sentinel.commands; package io.github.thetrouper.sentinel.commands;
import io.github.thetrouper.sentinel.Sentinel;
import io.github.thetrouper.sentinel.data.Config;
import io.github.thetrouper.sentinel.server.functions.AntiSpam;
import io.github.thetrouper.sentinel.server.functions.ProfanityFilter; import io.github.thetrouper.sentinel.server.functions.ProfanityFilter;
import io.github.thetrouper.sentinel.server.util.Text; import io.github.thetrouper.sentinel.server.util.Text;
import org.bukkit.Bukkit;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -28,28 +32,53 @@ public class SentinelCommand extends CustomCommand {
public void dispatchCommand(CommandSender sender, Command command, String label, String[] args) { public void dispatchCommand(CommandSender sender, Command command, String label, String[] args) {
Player p = (Player) sender; Player p = (Player) sender;
switch (args[0]) { switch (args[0]) {
case "debugmode" -> { case "debug" -> {
debugmode = !debugmode; switch (args[1]) {
p.sendMessage(Text.prefix((debugmode ? "enabled" : "disabled") + " debug mode.")); case "testantiswear" -> {
} HashSet<Player> players = new HashSet<>();
case "testantiswear" -> { players.add((Player) sender);
HashSet<Player> players = new HashSet<>(); String msg = "";
players.add((Player) sender); for (int i = 1; i < args.length; i++) {
String msg = ""; msg = msg.concat(" " + args[i]);
for (int i = 1; i < args.length; i++) { }
msg = msg.concat(" " + args[i]); msg = msg.trim();
AsyncPlayerChatEvent e = new AsyncPlayerChatEvent(true, (Player) sender, msg, players);
ProfanityFilter.handleProfanityFilter(e);
}
case "testantispam" -> {
HashSet<Player> players = new HashSet<>();
players.add((Player) sender);
String msg = "";
for (int i = 1; i < args.length; i++) {
msg = msg.concat(" " + args[i]);
}
msg = msg.trim();
AsyncPlayerChatEvent e = new AsyncPlayerChatEvent(true, (Player) sender, msg, players);
AntiSpam.handleAntiSpam(e);
}
case "testlang" -> {
p.sendMessage(Sentinel.dict.get("exmaple-message"));
}
case "toggle" -> {
debugmode = !debugmode;
p.sendMessage(Text.prefix((debugmode ? "enabled" : "disabled") + " debug mode."));
}
} }
msg = msg.trim(); }
AsyncPlayerChatEvent e = new AsyncPlayerChatEvent(true, (Player) sender, msg, players); case "getHeat" -> {
ProfanityFilter.handleProfanityFilter(e); Player target = Bukkit.getPlayer(args[1]);
if (target == null) {
p.sendMessage(Text.prefix("Invalid Player!"));
return;
}
p.sendMessage(Text.prefix("Heat of " + target.getName() + ": &8(&c" + AntiSpam.heatMap.get(target) + "&7/&4" + Config.punishHeat + "&8)"));
} }
} }
} }
@Override @Override
public void registerCompletions(CompletionBuilder builder) { public void registerCompletions(CompletionBuilder builder) {
builder.addCompletion(1,"debugmode"); builder.addCompletion(1,"debug");
builder.addCompletion(1,"testantiswear"); builder.addCompletion(1,"getHeat");
} }
} }

View File

@@ -6,7 +6,7 @@ public enum FAT {
// I couldn't miss the opportunity to call the "Filter Action Type" FAT // I couldn't miss the opportunity to call the "Filter Action Type" FAT
// Its rly just to make the tab completion of FilterAction easier // Its rly just to make the tab completion of FilterAction easier
BLOCK_SWEAR("Sentinel Profanity Filter",null,"swear-block-warn", "swear-block-notification", null,null), BLOCK_SWEAR("Sentinel Profanity Filter",null,"swear-block-warn", "swear-block-notification", null,null),
BLOCK_SPAM("Sentinel Anti-Spam", null, "spam-warning", "spam-notification",null,null), BLOCK_SPAM("Sentinel Anti-Spam", null, "spam-block-warn", "spam-notification",null,null),
SWEAR("Sentinel Anti-Swear Log","Anti-Swear", "profanity-mute-warn", "profanity-mute-notification", Config.swearPunishCommand, Color.orange), SWEAR("Sentinel Anti-Swear Log","Anti-Swear", "profanity-mute-warn", "profanity-mute-notification", Config.swearPunishCommand, Color.orange),
SLUR("Sentinel Anti-Slur Log", "Anti-Slur", "slur-mute-warn", "slur-mute-notification", Config.strictPunishCommand, Color.red), SLUR("Sentinel Anti-Slur Log", "Anti-Slur", "slur-mute-warn", "slur-mute-notification", Config.strictPunishCommand, Color.red),
SPAM("Sentinel Anti-Spam Log", "Anti-Spam", "spam-mute-warn", "spam-mute-notification", Config.spamPunishCommand, Color.pink); SPAM("Sentinel Anti-Spam Log", "Anti-Spam", "spam-mute-warn", "spam-mute-notification", Config.spamPunishCommand, Color.pink);

View File

@@ -35,13 +35,15 @@ public class FilterAction {
fs.setRoundingMode(RoundingMode.DOWN); fs.setRoundingMode(RoundingMode.DOWN);
TextComponent notif = new TextComponent(); TextComponent notif = new TextComponent();
notif.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.fromLegacyText((type != FAT.SPAM ? Sentinel.dict.get("severity-notification-hover").formatted(e.getMessage(), highlighted, severity) : Sentinel.dict.get("spam-notification-hover").formatted(e.getMessage(),lastMessageMap.get(offender),fs.format(similarity)))))); notif.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.fromLegacyText((type != FAT.SPAM && type != FAT.BLOCK_SPAM ? Sentinel.dict.get("severity-notification-hover").formatted(e.getMessage(), highlighted, severity) : Sentinel.dict.get("spam-notification-hover").formatted(e.getMessage(),lastMessageMap.get(offender),fs.format(similarity))))));
notif.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/sentinelcallback fpreport " + report)); notif.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/sentinelcallback fpreport " + report));
warn.setText(Text.prefix(Sentinel.dict.get(type.getWarnTranslationKey()))); warn.setText(Text.prefix(Sentinel.dict.get(type.getWarnTranslationKey())));
offender.spigot().sendMessage(warn); offender.spigot().sendMessage(warn);
notif.setText(Text.prefix(Sentinel.dict.get(type.getNotifTranslationKey()).formatted(offender.getName(), scoreMap.get(offender), Config.punishScore))); String notiftext = Sentinel.dict.get(type.getNotifTranslationKey());
notif.setText(Text.prefix((type != FAT.SPAM && type != FAT.BLOCK_SPAM ? notiftext.formatted(offender.getName(), scoreMap.get(offender), Config.punishScore) : notiftext.formatted(offender.getName(),heatMap.get(offender),Config.punishHeat))));
ServerUtils.forEachStaff(staffmember -> { ServerUtils.forEachStaff(staffmember -> {
staffmember.spigot().sendMessage(notif); staffmember.spigot().sendMessage(notif);
}); });
@@ -67,10 +69,10 @@ public class FilterAction {
public static void sendConsoleLog(Player offender, AsyncPlayerChatEvent e, FAT type) { public static void sendConsoleLog(Player offender, AsyncPlayerChatEvent e, FAT type) {
String log = "]=-" + type.getTitle() + "-=[\n" + String log = "]=-" + type.getTitle() + "-=[\n" +
"Player: " + offender.getName() + "Player: " + offender.getName() +
(type != FAT.BLOCK_SPAM ? "> Score: `" + scoreMap.get(offender) + "/" + Config.punishScore : "> Heat: `" + heatMap.get(offender) + "/" + Config.punishHeat) + "\n" + (type != FAT.BLOCK_SPAM && type != FAT.SPAM ? "> Score: `" + scoreMap.get(offender) + "/" + Config.punishScore : "> Heat: `" + heatMap.get(offender) + "/" + Config.punishHeat) + "\n" +
"> UUID: " + offender.getUniqueId() + "\n" + "> UUID: " + offender.getUniqueId() + "\n" +
(type != FAT.BLOCK_SPAM ? "Message: " + e.getMessage() : "Previous: " + lastMessageMap.get(offender)) + "\n" + (type != FAT.BLOCK_SPAM && type != FAT.SPAM ? "Message: " + e.getMessage() : "Previous: " + lastMessageMap.get(offender)) + "\n" +
(type != FAT.BLOCK_SPAM ? "Reduced: " + fullSimplify(e.getMessage()) : "Current: " + e.getMessage()) + "\n" + (type != FAT.BLOCK_SPAM && type != FAT.SPAM ? "Reduced: " + fullSimplify(e.getMessage()) : "Current: " + e.getMessage()) + "\n" +
(type.getExecutedCommand() != null ? "Executed: " + type.getExecutedCommand() : "Executed: Nothing, its a standard flag. You shouldn't be seeing this, please report it."); (type.getExecutedCommand() != null ? "Executed: " + type.getExecutedCommand() : "Executed: Nothing, its a standard flag. You shouldn't be seeing this, please report it.");
Sentinel.log.info(log); Sentinel.log.info(log);
} }

View File

@@ -1,25 +1,16 @@
package io.github.thetrouper.sentinel.server.functions; package io.github.thetrouper.sentinel.server.functions;
import io.github.thetrouper.sentinel.Sentinel;
import io.github.thetrouper.sentinel.data.Config; import io.github.thetrouper.sentinel.data.Config;
import io.github.thetrouper.sentinel.data.FAT; import io.github.thetrouper.sentinel.data.FAT;
import io.github.thetrouper.sentinel.data.FilterAction; import io.github.thetrouper.sentinel.data.FilterAction;
import io.github.thetrouper.sentinel.discord.WebhookSender;
import io.github.thetrouper.sentinel.server.util.GPTUtils; import io.github.thetrouper.sentinel.server.util.GPTUtils;
import io.github.thetrouper.sentinel.server.util.ServerUtils;
import io.github.thetrouper.sentinel.server.util.Text; import io.github.thetrouper.sentinel.server.util.Text;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.AsyncPlayerChatEvent;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import static io.github.thetrouper.sentinel.server.util.GPTUtils.calculateSimilarity;
public class AntiSpam { public class AntiSpam {
public static Map<Player, Integer> heatMap; public static Map<Player, Integer> heatMap;
public static Map<Player, String> lastMessageMap; public static Map<Player, String> lastMessageMap;
@@ -31,27 +22,34 @@ public class AntiSpam {
public static void handleAntiSpam(AsyncPlayerChatEvent e) { public static void handleAntiSpam(AsyncPlayerChatEvent e) {
Player p = e.getPlayer(); Player p = e.getPlayer();
String message = Text.removeFirstColor(e.getMessage()); String message = Text.removeFirstColor(e.getMessage());
Double sim = calculateSimilarity(e.getMessage(),lastMessageMap.get(p));
if (!heatMap.containsKey(p)) heatMap.put(p, 0); if (!lastMessageMap.containsKey(p)) {
lastMessageMap.put(p,"/* Placeholder Message from Sentinel */");
}
if (!heatMap.containsKey(p)) {
heatMap.put(p,0);
}
if (lastMessageMap.containsKey(p)) {
String lastMessage = lastMessageMap.get(p);
double similarity = GPTUtils.calcSim(message, lastMessage);
if (similarity > 0.25) heatMap.put(p, heatMap.get(p) + Config.lowGain);
if (similarity > 0.5) heatMap.put(p, heatMap.get(p) + Config.mediumGain);
if (similarity > 0.9) heatMap.put(p, heatMap.get(p) + Config.highGain);
}
if (heatMap.get(p) > Config.punishHeat) { if (heatMap.get(p) > Config.punishHeat) {
e.setCancelled(true); e.setCancelled(true);
FilterAction.filterAction(p,e,null,null, sim, FAT.SPAM); FilterAction.filterAction(p,e,null,null, GPTUtils.calcSim(e.getMessage(),lastMessageMap.get(p)), FAT.SPAM);
return; return;
} }
if (heatMap.get(p) > Config.blockHeat) { if (heatMap.get(p) > Config.blockHeat) {
e.setCancelled(true); e.setCancelled(true);
FilterAction.filterAction(p,e,null,null, sim, FAT.BLOCK_SPAM); FilterAction.filterAction(p,e,null,null, GPTUtils.calcSim(e.getMessage(),lastMessageMap.get(p)), FAT.BLOCK_SPAM);
heatMap.put(p, heatMap.get(p) + Config.highGain); heatMap.put(p, heatMap.get(p) + Config.highGain);
return; return;
} }
if (lastMessageMap.containsKey(p)) {
String lastMessage = lastMessageMap.get(p);
double similarity = calculateSimilarity(message, lastMessage);
if (similarity > 0.25) heatMap.put(p, heatMap.get(p) + Config.lowGain);
if (similarity > 0.5) heatMap.put(p, heatMap.get(p) + Config.mediumGain);
if (similarity > 0.9) heatMap.put(p, heatMap.get(p) + Config.highGain);
}
lastMessageMap.put(p, message); lastMessageMap.put(p, message);
} }
public static void decayHeat() { public static void decayHeat() {

View File

@@ -32,4 +32,37 @@ public class GPTUtils {
double similarity = ((double) (maxLen - distance) / maxLen) * 100; double similarity = ((double) (maxLen - distance) / maxLen) * 100;
return similarity; return similarity;
} }
public static double calcSim(String s1, String s2) {
int maxLength = Math.max(s1.length(), s2.length());
if (maxLength == 0) {
return 100.0;
}
int distance = calculateLevenshteinDistance(s1, s2);
double similarity = ((double) (maxLength - distance) / maxLength) * 100.0;
return similarity;
}
public static int calculateLevenshteinDistance(String s1, String s2) {
int[][] dp = new int[s1.length() + 1][s2.length() + 1];
for (int i = 0; i <= s1.length(); i++) {
dp[i][0] = i;
}
for (int j = 0; j <= s2.length(); j++) {
dp[0][j] = j;
}
for (int i = 1; i <= s1.length(); i++) {
for (int j = 1; j <= s2.length(); j++) {
int cost = (s1.charAt(i - 1) == s2.charAt(j - 1)) ? 0 : 1;
dp[i][j] = Math.min(Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + cost);
}
}
return dp[s1.length()][s2.length()];
}
} }

View File

@@ -14,25 +14,25 @@
"no-user-reply" : "§cYou have nobody to reply to!", "no-user-reply" : "§cYou have nobody to reply to!",
"spy-enabled" : "SocialSpy is now enabled.", "spy-enabled" : "SocialSpy is now enabled.",
"spy-disabled" : "SocialSpy is now disabled.", "spy-disabled" : "SocialSpy is now disabled.",
"spam-warning" : "Do not spam in chat! Please wait before sending another message.",
"action-automatic" : "§7This action was preformed automatically\n§7by the §bSentinel Anti-Spam§7 algorithm.", "action-automatic" : "§7This action was preformed automatically\n§7by the §bSentinel Anti-Spam§7 algorithm.",
"spam-notification" : "§b§n%1$s§7 might be spamming! §8(§c%2$s§7/§4%3$s§8)", "action-automatic-reportable" : "§7This action was preformed automatically \n§7by the §bSentinel Profanity Filter§7 algorithm!\n§8§o(Click to report false positive)",
"spam-notification-hover" : "§8]==-- §d§lSentinel §8--==[\n§bPrevious: §f%1$s\n§bCurrent: §f%2$s\n§bSimilarity §f%3$s",
"spam-punished" : "§cYou have been auto-punished for violating the anti-spam repetitively!",
"spam-punish-notification" : "§b§n%1$s§7 has been auto-muted by the anti spam! §8(§c%2$s§7/§4%3$s§8)",
"unicode-warn" : "§cDo not send non standard unicode in chat!", "unicode-warn" : "§cDo not send non standard unicode in chat!",
"message-sent" : "§d§lMessage §8» §b[§fYou §e>§f %1$s§b] §7%2$s", "message-sent" : "§d§lMessage §8» §b[§fYou §e>§f %1$s§b] §7%2$s",
"message-received" : "§d§lMessage §8» §b[§f%1$s §e>§f You§b] §7%2$s", "message-received" : "§d§lMessage §8» §b[§f%1$s §e>§f You§b] §7%2$s",
"spy-message" : "§d§lSpy §8» §b§n%1$s§7 has messaged §b§n%2$s§7.", "spy-message" : "§d§lSpy §8» §b§n%1$s§7 has messaged §b§n%2$s§7.",
"spy-message-hover" : "§8]==-- §d§lSocialSpy §8--==[\n§bSender: §f%1$S\n§bReceiver: §f%2$S\n§bMessage: §f%3$S", "spy-message-hover" : "§8]==-- §d§lSocialSpy §8--==[\n§bSender: §f%1$S\n§bReceiver: §f%2$S\n§bMessage: §f%3$S",
"action-automatic-reportable" : "§7This action was preformed automatically \n§7by the §bSentinel Profanity Filter§7 algorithm!\n§8§o(Click to report false positive)",
"profanity-mute-warn" : "You have been auto muted for repeated violation of the profanity filter! §7§o(Hover for more info)", "profanity-mute-warn" : "You have been auto muted for repeated violation of the profanity filter! §7§o(Hover for more info)",
"profanity-mute-notification" : "§b§n%1$s§7 has been auto-muted by the anti-swear! §8(§c%2$s§7/§4%3$s§8)", "profanity-mute-notification" : "§b§n%1$s§7 has been auto-muted by the anti-swear! §8(§c%2$s§7/§4%3$s§8)",
"slur-mute-warn" : "§cYou have been insta-punished by the anti-slur! §7§o(Hover for more info)", "slur-mute-warn" : "§cYou have been insta-punished by the anti-slur! §7§o(Hover for more info)",
"slur-mute-notification" : "§b§n%1$s§7 has been insta-muted by the anti-swear! §8(§c%2$s§7/§4%3$s§8)", "slur-mute-notification" : "§b§n%1$s§7 has been insta-muted by the anti-swear! §8(§c%2$s§7/§4%3$s§8)",
"swear-block-warn" : "§cPlease do not swear in chat! Attempting to bypass this filter will result in a mute! §7§o(Hover for more info)", "swear-block-warn" : "§cPlease do not swear in chat! Attempting to bypass this filter will result in a mute! §7§o(Hover for more info)",
"swear-block-notification" : "§b§n%1$s§7 has triggered the anti-swear! §8(§c%2$s§7/§4%3$s§8)", "swear-block-notification" : "§b§n%1$s§7 has triggered the anti-swear! §8(§c%2$s§7/§4%3$s§8)",
"spam-notification" : "§b§n%1$s§7 might be spamming! §8(§c%2$s§7/§4%3$s§8)",
"spam-notification-hover" : "§8]==-- §d§lSentinel §8--==[\n§bPrevious: §f%1$s\n§bCurrent: §f%2$s\n§bSimilarity §f%3$s",
"spam-block-warn" : "Do not spam in chat! Please wait before sending another message.",
"spam-mute-warn" : "§cYou have been auto-punished for violating the anti-spam repetitively!",
"spam-mute-notification" : "§b§n%1$s§7 has been auto-muted by the anti spam! §8(§c%2$s§7/§4%3$s§8)",
"filter-notification-hover" : "§bOriginal: §f%1$s\n§bSanitized: §f%2$s\n§8§o(Click to report false positive)", "filter-notification-hover" : "§bOriginal: §f%1$s\n§bSanitized: §f%2$s\n§8§o(Click to report false positive)",
"severity-notification-hover" : "§bOriginal: §f%1$s\n§bSanitized: §f%2$s\n§bSeverity: §c%3$s\n§7§o(click to report false positive)", "severity-notification-hover" : "§bOriginal: §f%1$s\n§bSanitized: §f%2$s\n§bSeverity: §c%3$s\n§7§o(click to report false positive)"
} }
} }