Chat Click Callback and false positive reporting

This commit is contained in:
TheTrouper
2023-09-14 17:08:44 -05:00
parent e33568d015
commit 84fa24aeb8
7 changed files with 226 additions and 19 deletions

View File

@@ -110,6 +110,7 @@ public final class Sentinel extends JavaPlugin {
new ReplyCommand().register(); new ReplyCommand().register();
new ReopCommand().register(); new ReopCommand().register();
new SocialSpyCommand().register(); new SocialSpyCommand().register();
new ChatClickCallback().register();
// Events // Events
manager.registerEvents(new CommandEvent(),this); manager.registerEvents(new CommandEvent(),this);

View File

@@ -0,0 +1,56 @@
/**
* This file is for tutorial purposes made by ImproperIssues. Distribute if you want :)
*/
package io.github.thetrouper.sentinel.commands;
import io.github.thetrouper.sentinel.server.functions.ProfanityFilter;
import io.github.thetrouper.sentinel.server.functions.ReportFalsePositives;
import io.github.thetrouper.sentinel.server.util.Cooldown;
import io.github.thetrouper.sentinel.server.util.TextUtils;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import java.util.HashSet;
import java.util.Map;
import java.util.UUID;
/**
* Example command
*/
public class ChatClickCallback extends CustomCommand {
public static Cooldown<UUID> fpReportCooldown;
public ChatClickCallback() {
super("sentinelcallback");
this.setPrintStacktrace(true);
}
@Override
public void dispatchCommand(CommandSender sender, Command command, String label, String[] args) {
Player p = (Player) sender;
switch (args[0]) {
case "fpreport" -> {
if (fpReportCooldown.isOnCooldown(p.getUniqueId()) && !p.isOp()) {
p.sendMessage(TextUtils.prefix("This action is on cooldown! " + fpReportCooldown.getCooldown(p.getUniqueId())));
} else {
ReportFalsePositives.sendFalsePositiveReport(args[1]);
p.sendMessage(TextUtils.prefix("Successfully reported a false positive!"));
}
}
}
}
@Override
public void registerCompletions(CompletionBuilder builder) {
builder.addCompletion(1,"you");
builder.addCompletion(1,"must");
builder.addCompletion(1,"be");
builder.addCompletion(1,"called");
builder.addCompletion(1,"before");
builder.addCompletion(1,"running");
builder.addCompletion(1,"a");
builder.addCompletion(1,"callback");
}
}

View File

@@ -5,6 +5,7 @@
package io.github.thetrouper.sentinel.commands; package io.github.thetrouper.sentinel.commands;
import io.github.thetrouper.sentinel.server.functions.ProfanityFilter; import io.github.thetrouper.sentinel.server.functions.ProfanityFilter;
import io.github.thetrouper.sentinel.server.functions.ReportFalsePositives;
import io.github.thetrouper.sentinel.server.util.TextUtils; import io.github.thetrouper.sentinel.server.util.TextUtils;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -12,6 +13,8 @@ import org.bukkit.entity.Player;
import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.AsyncPlayerChatEvent;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.UUID;
/** /**
* Example command * Example command

View File

@@ -41,31 +41,31 @@ public class ProfanityFilter {
ServerUtils.sendDebugMessage("AntiSwear Flag, Message: " + message + " Concentrated: " + fullSimplify(message) + " Severity: " + severity + " Previous Score: " + scoreMap.get(p) +" Adding Score: " + Config.lowScore); ServerUtils.sendDebugMessage("AntiSwear Flag, Message: " + message + " Concentrated: " + fullSimplify(message) + " Severity: " + severity + " Previous Score: " + scoreMap.get(p) +" Adding Score: " + Config.lowScore);
scoreMap.put(p, scoreMap.get(p) + Config.lowScore); scoreMap.put(p, scoreMap.get(p) + Config.lowScore);
e.setCancelled(true); e.setCancelled(true);
blockSwear(p,highlightProfanity(message),message,severity); blockSwear(p,highlightProfanity(message),message,severity,e);
} }
case "medium-low" -> { case "medium-low" -> {
ServerUtils.sendDebugMessage("AntiSwear Flag, Message: " + message + " Concentrated: " + fullSimplify(message) + " Severity: " + severity + " Previous Score: " + scoreMap.get(p) +" Adding Score: " + Config.mediumLowScore); ServerUtils.sendDebugMessage("AntiSwear Flag, Message: " + message + " Concentrated: " + fullSimplify(message) + " Severity: " + severity + " Previous Score: " + scoreMap.get(p) +" Adding Score: " + Config.mediumLowScore);
scoreMap.put(p, scoreMap.get(p) + Config.mediumLowScore); scoreMap.put(p, scoreMap.get(p) + Config.mediumLowScore);
e.setCancelled(true); e.setCancelled(true);
blockSwear(p,highlightProfanity(message),message,severity); blockSwear(p,highlightProfanity(message),message,severity,e);
} }
case "medium" -> { case "medium" -> {
ServerUtils.sendDebugMessage("AntiSwear Flag, Message: " + message + " Concentrated: " + fullSimplify(message) + " Severity: " + severity + " Previous Score: " + scoreMap.get(p) +" Adding Score: " + Config.mediumScore); ServerUtils.sendDebugMessage("AntiSwear Flag, Message: " + message + " Concentrated: " + fullSimplify(message) + " Severity: " + severity + " Previous Score: " + scoreMap.get(p) +" Adding Score: " + Config.mediumScore);
scoreMap.put(p, scoreMap.get(p) + Config.mediumScore); scoreMap.put(p, scoreMap.get(p) + Config.mediumScore);
e.setCancelled(true); e.setCancelled(true);
blockSwear(p,highlightProfanity(message),message,severity); blockSwear(p,highlightProfanity(message),message,severity,e);
} }
case "medium-high" -> { case "medium-high" -> {
ServerUtils.sendDebugMessage("AntiSwear Flag, Message: " + message + " Concentrated: " + fullSimplify(message) + " Severity: " + severity + " Previous Score: " + scoreMap.get(p) +" Adding Score: " + Config.mediumHighScore); ServerUtils.sendDebugMessage("AntiSwear Flag, Message: " + message + " Concentrated: " + fullSimplify(message) + " Severity: " + severity + " Previous Score: " + scoreMap.get(p) +" Adding Score: " + Config.mediumHighScore);
scoreMap.put(p, scoreMap.get(p) + Config.mediumHighScore); scoreMap.put(p, scoreMap.get(p) + Config.mediumHighScore);
e.setCancelled(true); e.setCancelled(true);
blockSwear(p,highlightProfanity(message),message,severity); blockSwear(p,highlightProfanity(message),message,severity,e);
} }
case "high" -> { case "high" -> {
ServerUtils.sendDebugMessage("AntiSwear Flag, Message: " + message + " Concentrated: " + fullSimplify(message) + " Severity: " + severity + " Previous Score: " + scoreMap.get(p) +" Adding Score: " + Config.highScore); ServerUtils.sendDebugMessage("AntiSwear Flag, Message: " + message + " Concentrated: " + fullSimplify(message) + " Severity: " + severity + " Previous Score: " + scoreMap.get(p) +" Adding Score: " + Config.highScore);
scoreMap.put(p, scoreMap.get(p) + Config.highScore); scoreMap.put(p, scoreMap.get(p) + Config.highScore);
e.setCancelled(true); e.setCancelled(true);
blockSwear(p,highlightProfanity(message),message,severity); blockSwear(p,highlightProfanity(message),message,severity,e);
} }
case "slur" -> { case "slur" -> {
// Insta-Punish // Insta-Punish
@@ -107,14 +107,14 @@ public class ProfanityFilter {
}); });
if (Config.logSwear) WebhookSender.sendSlurLog(player,origMessage,scoreMap.get(player)); if (Config.logSwear) WebhookSender.sendSlurLog(player,origMessage,scoreMap.get(player));
} }
public static void blockSwear(Player player, String highlightedMSG, String origMessage, String severity) { public static void blockSwear(Player player, String highlightedMSG, String origMessage, String severity, AsyncPlayerChatEvent e) {
player.sendMessage(TextUtils.prefix(("§cPlease do not swear in chat! Attempting to bypass this filter will result in a mute!"))); player.sendMessage(TextUtils.prefix(("§cPlease do not swear in chat! Attempting to bypass this filter will result in a mute!")));
String hover = ("§bOriginal: §f" + origMessage + "\n§bSanitized: §f" + highlightedMSG + "\n§bSeverity: §c" + severity + "\n§7§o(click to copy)"); String hover = ("§bOriginal: §f" + origMessage + "\n§bSanitized: §f" + highlightedMSG + "\n§bSeverity: §c" + severity + "\n§7§o(click to report false positive)");
TextComponent text = new TextComponent(); TextComponent text = new TextComponent();
text.setText(TextUtils.prefix( text.setText(TextUtils.prefix(
("§b§n" + player.getName() + "§7 has triggered the anti-swear! §8(§c" + scoreMap.get(player) + "§7/§4" + Config.punishScore + "§8)"))); ("§b§n" + player.getName() + "§7 has triggered the anti-swear! §8(§c" + scoreMap.get(player) + "§7/§4" + Config.punishScore + "§8)")));
text.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.fromLegacyText(hover))); text.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.fromLegacyText(hover)));
text.setClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, origMessage)); text.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "sentinelcallback fpreport " + ReportFalsePositives.generateReport(e)));
ServerUtils.forEachStaff(staff -> { ServerUtils.forEachStaff(staff -> {
staff.spigot().sendMessage(text); staff.spigot().sendMessage(text);
@@ -221,13 +221,10 @@ public class ProfanityFilter {
return "safe"; return "safe";
} }
private static String removeFalsePositives(String text) {
for (String falsePositive : swearWhitelist) {
text = text.replace(falsePositive, "");
}
return text;
}
public static boolean ContainsProfanity(String text) {
return containsSwears(text) || containsSlurs(text);
}
private static boolean containsSwears(String text) { private static boolean containsSwears(String text) {
ServerUtils.sendDebugMessage("Debug: [AntiSwear] Checking for swears: " + swearBlacklist.toString()); ServerUtils.sendDebugMessage("Debug: [AntiSwear] Checking for swears: " + swearBlacklist.toString());
for (String swear : swearBlacklist) { for (String swear : swearBlacklist) {
@@ -242,23 +239,28 @@ public class ProfanityFilter {
} }
return false; return false;
} }
public static String removeFalsePositives(String text) {
private static String convertLeetSpeakCharacters(String text) { for (String falsePositive : swearWhitelist) {
text = text.replace(falsePositive, "");
}
return text;
}
public static String convertLeetSpeakCharacters(String text) {
text = TextUtils.fromLeetString(text); text = TextUtils.fromLeetString(text);
return text; return text;
} }
private static String stripSpecialCharacters(String text) { public static String stripSpecialCharacters(String text) {
text = text.replaceAll("[^A-Za-z0-9.,!?;:'\"()\\[\\]{}]", "").trim(); text = text.replaceAll("[^A-Za-z0-9.,!?;:'\"()\\[\\]{}]", "").trim();
return text; return text;
} }
private static String simplifyRepeatingLetters(String text) { public static String simplifyRepeatingLetters(String text) {
text = TextUtils.replaceRepeatingLetters(text); text = TextUtils.replaceRepeatingLetters(text);
return text; return text;
} }
private static String removePeriodsAndSpaces(String text) { public static String removePeriodsAndSpaces(String text) {
return text.replaceAll("[^A-Za-z0-9]", "").replace(" ", ""); return text.replaceAll("[^A-Za-z0-9]", "").replace(" ", "");
} }
public static void decayScore() { public static void decayScore() {

View File

@@ -0,0 +1,84 @@
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.Emojis;
import io.github.thetrouper.sentinel.discord.DiscordWebhook;
import io.github.thetrouper.sentinel.server.util.Randomizer;
import io.github.thetrouper.sentinel.server.util.ServerUtils;
import io.github.thetrouper.sentinel.server.util.TextUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import java.awt.*;
import java.io.IOException;
import java.util.Map;
public class ReportFalsePositives {
public static Map<String,AsyncPlayerChatEvent> reportMap;
public static String generateReport(AsyncPlayerChatEvent e) {
final long reportLong = Randomizer.generateID();
final String reportID = Long.toString(reportLong);
ServerUtils.sendDebugMessage(TextUtils.prefix("DEBUG: Generating chat filter report"));
reportMap.put(reportID,e);
ServerUtils.sendDebugMessage(TextUtils.prefix("DEBUG: Generated report. ID:" + reportID + " Message: \"" + reportMap.get(reportID).getMessage() + "\" Expires in 60 seconds"));
Bukkit.getScheduler().runTaskLater(Sentinel.getInstance(),()->{
reportMap.remove(reportID);
ServerUtils.sendDebugMessage(TextUtils.prefix("DEBUG: A report expired. ID: " + reportID));
},60000);
return reportID;
}
public static void sendFalsePositiveReport(String reportID) {
AsyncPlayerChatEvent e = reportMap.get(reportID);
String orig = e.getMessage();
String lowercasedText = orig.toLowerCase();
String remFP = ProfanityFilter.removeFalsePositives(lowercasedText);
String convertedLeet = ProfanityFilter.convertLeetSpeakCharacters(remFP);
String remSpecials = ProfanityFilter.stripSpecialCharacters(convertedLeet);
String simplifyRep = ProfanityFilter.simplifyRepeatingLetters(remSpecials);
String sanitized = ProfanityFilter. removePeriodsAndSpaces(simplifyRep);
sendEmbed(e.getPlayer(),orig,lowercasedText,remFP,convertedLeet,remSpecials,simplifyRep,sanitized);
}
public static void sendEmbed(Player player,
String message,
String lowercased,
String remFP,
String convertedLeet,
String remSpecials,
String simplifyRep,
String sanitized) {
ServerUtils.sendDebugMessage("Creating FalsePositive Webhook...");
DiscordWebhook webhook = new DiscordWebhook(Config.webhook);
webhook.setAvatarUrl("https://r2.e-z.host/d440b58a-ba90-4839-8df6-8bba298cf817/3lwit5nt.png");
webhook.setUsername("Sentinel Anti-Nuke | Logs");
DiscordWebhook.EmbedObject embed = new DiscordWebhook.EmbedObject()
.setAuthor("Anti-Swear False Positive","","")
.setTitle("Flag Report:")
.setDescription(
Emojis.rightSort + "Player: " + player.getName() + " " + Emojis.target + "\\n" +
Emojis.space + Emojis.arrowRight + "UUID: `" + player.getUniqueId() + "`\\n"
)
.addField("Original Message", "`" + message + "` " + (ProfanityFilter.ContainsProfanity(message) ? Emojis.alarm : ""), false)
.addField("Lowercase", "`" + lowercased + "` " + (ProfanityFilter.ContainsProfanity(lowercased) ? Emojis.alarm : ""), false)
.addField("Removed FPs", "`" + remFP + "` " + (ProfanityFilter.ContainsProfanity(remFP) ? Emojis.alarm : ""), false)
.addField("Converted Leet", "`" + convertedLeet + "` " + (ProfanityFilter.ContainsProfanity(convertedLeet) ? Emojis.alarm : ""), false)
.addField("Removed Specials", "`" + remSpecials + "` " + (ProfanityFilter.ContainsProfanity(remSpecials) ? Emojis.alarm : ""), false)
.addField("Simplify Repeats", "`" + simplifyRep + "` " + (ProfanityFilter.ContainsProfanity(simplifyRep) ? Emojis.alarm : ""), false)
.addField("Fully Sanitized Message", ProfanityFilter.highlightProfanity(sanitized,"`", "`") + " " + Emojis.noDM, false)
.setColor(Color.green)
.setThumbnail("https://crafatar.com/avatars/" + player.getUniqueId() + "?size=64&&overlay");
webhook.addEmbed(embed);
try {
ServerUtils.sendDebugMessage("Executing webhook...");
webhook.execute();
} catch (IOException e) {
ServerUtils.sendDebugMessage(TextUtils.prefix("Epic webhook failure!!!"));
Sentinel.log.info(e.toString());
}
}
}

View File

@@ -0,0 +1,38 @@
package io.github.thetrouper.sentinel.server.util;
import java.util.HashMap;
import java.util.Map;
public class Cooldown<T> {
private final Map<T,Long> timer;
public Cooldown() {
this.timer = new HashMap<>();
}
private <O> O getOrDefault(O value, O def) {
return value != null ? value : def;
}
public long getCooldown(T obj) {
return Math.max(getOrDefault(timer.get(obj), 0L) - System.currentTimeMillis(), 0L);
}
public double getCooldownSec(T obj) {
final long cooldown = this.getCooldown(obj);
return MathUtils.round(cooldown / 1000.0, 100);
}
public boolean isOnCooldown(T obj) {
return getCooldown(obj) > 0L;
}
public void setCooldown(T obj, long millis) {
timer.put(obj, System.currentTimeMillis() + millis);
}
public void addCooldown(T obj, long millis) {
setCooldown(obj, getCooldown(obj) + millis);
}
}

View File

@@ -0,0 +1,23 @@
package io.github.thetrouper.sentinel.server.util;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
public final class MathUtils {
public static double avg(Integer... ints) {
final List<Integer> list = Arrays.stream(ints).filter(Objects::nonNull).toList();
return avg(list);
}
public static double avg(List<Integer> ints) {
double sum = 0.0;
for (Integer i : ints) sum += i;
return sum / ints.size();
}
public static double round(double value, int nthPlace) {
return Math.floor(value * nthPlace) / nthPlace;
}
}