Remove spamdetector

This commit is contained in:
destro174 2022-02-19 20:10:31 +01:00
parent 537803028b
commit f4c6dcbf19
3 changed files with 0 additions and 476 deletions

View File

@ -126,9 +126,6 @@ class PlayerEventHandler implements Listener
//matcher for banned words
private final WordFinder bannedWordFinder;
//spam tracker
SpamDetector spamDetector = new SpamDetector();
//typical constructor, yawn
PlayerEventHandler(DataStore dataStore, GriefPrevention plugin)
{

View File

@ -1,270 +0,0 @@
package me.ryanhamshire.GriefPrevention;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
class SpamDetector
{
//last chat message shown and its timestamp, regardless of who sent it
private String lastChatMessage = "";
private long lastChatMessageTimestamp = 0;
//number of identical chat messages in a row
private int duplicateMessageCount = 0;
//data for individual chatters
ConcurrentHashMap<UUID, ChatterData> dataStore = new ConcurrentHashMap<>();
private ChatterData getChatterData(UUID chatterID)
{
ChatterData data = this.dataStore.get(chatterID);
if (data == null)
{
data = new ChatterData();
this.dataStore.put(chatterID, data);
}
return data;
}
SpamAnalysisResult AnalyzeMessage(UUID chatterID, String message, long timestamp)
{
SpamAnalysisResult result = new SpamAnalysisResult();
result.finalMessage = message;
//remedy any CAPS SPAM, exception for very short messages which could be emoticons like =D or XD
if (message.length() > 4 && this.stringsAreSimilar(message.toUpperCase(), message))
{
message = message.toLowerCase();
result.finalMessage = message;
}
boolean spam = false;
ChatterData chatterData = this.getChatterData(chatterID);
//mute if total volume of text from this player is too high
if (message.length() > 50 && chatterData.getTotalRecentLength(timestamp) > 200)
{
spam = true;
result.muteReason = "too much chat sent in 10 seconds";
chatterData.spamLevel++;
}
//always mute an exact match to the last chat message
if (result.finalMessage.equals(this.lastChatMessage) && timestamp - this.lastChatMessageTimestamp < 2000)
{
chatterData.spamLevel += ++this.duplicateMessageCount;
spam = true;
result.muteReason = "repeat message";
}
else
{
this.lastChatMessage = message;
this.lastChatMessageTimestamp = timestamp;
this.duplicateMessageCount = 0;
}
//check message content and timing
long millisecondsSinceLastMessage = timestamp - chatterData.lastMessageTimestamp;
//if the message came too close to the last one
if (millisecondsSinceLastMessage < 1500)
{
//increment the spam counter
chatterData.spamLevel++;
spam = true;
}
//if it's exactly the same as the last message from the same player and within 30 seconds
if (result.muteReason == null && millisecondsSinceLastMessage < 30000 && result.finalMessage.equalsIgnoreCase(chatterData.lastMessage))
{
chatterData.spamLevel++;
spam = true;
result.muteReason = "repeat message";
}
//if it's very similar to the last message from the same player and within 10 seconds of that message
if (result.muteReason == null && millisecondsSinceLastMessage < 10000 && this.stringsAreSimilar(message.toLowerCase(), chatterData.lastMessage.toLowerCase()))
{
chatterData.spamLevel++;
spam = true;
if (chatterData.spamLevel > 2)
{
result.muteReason = "similar message";
}
}
//if the message was mostly non-alpha-numerics or doesn't include much whitespace, consider it a spam (probably ansi art or random text gibberish)
if (result.muteReason == null && message.length() > 5)
{
int symbolsCount = 0;
int whitespaceCount = 0;
for (int i = 0; i < message.length(); i++)
{
char character = message.charAt(i);
if (!(Character.isLetterOrDigit(character)))
{
symbolsCount++;
}
if (Character.isWhitespace(character))
{
whitespaceCount++;
}
}
if (symbolsCount > message.length() / 2 || (message.length() > 15 && whitespaceCount < message.length() / 10))
{
spam = true;
if (chatterData.spamLevel > 0) result.muteReason = "gibberish";
chatterData.spamLevel++;
}
}
//very short messages close together are spam
if (result.muteReason == null && message.length() < 5 && millisecondsSinceLastMessage < 3000)
{
spam = true;
chatterData.spamLevel++;
}
//if the message was determined to be a spam, consider taking action
if (spam)
{
//anything above level 8 for a player which has received a warning... kick or if enabled, ban
if (chatterData.spamLevel > 8 && chatterData.spamWarned)
{
result.shouldBanChatter = true;
}
else if (chatterData.spamLevel >= 4)
{
if (!chatterData.spamWarned)
{
chatterData.spamWarned = true;
result.shouldWarnChatter = true;
}
if (result.muteReason == null)
{
result.muteReason = "too-frequent text";
}
}
}
//otherwise if not a spam, reduce the spam level for this player
else
{
chatterData.spamLevel = 0;
chatterData.spamWarned = false;
}
chatterData.AddMessage(message, timestamp);
return result;
}
//if two strings are 75% identical, they're too close to follow each other in the chat
private boolean stringsAreSimilar(String message, String lastMessage)
{
//ignore differences in only punctuation and whitespace
message = message.replaceAll("[^\\p{Alpha}]", "");
lastMessage = lastMessage.replaceAll("[^\\p{Alpha}]", "");
//determine which is shorter
String shorterString, longerString;
if (lastMessage.length() < message.length())
{
shorterString = lastMessage;
longerString = message;
}
else
{
shorterString = message;
longerString = lastMessage;
}
if (shorterString.length() <= 5) return shorterString.equals(longerString);
//set similarity tolerance
int maxIdenticalCharacters = longerString.length() - longerString.length() / 4;
//trivial check on length
if (shorterString.length() < maxIdenticalCharacters) return false;
//compare forward
int identicalCount = 0;
int i;
for (i = 0; i < shorterString.length(); i++)
{
if (shorterString.charAt(i) == longerString.charAt(i)) identicalCount++;
if (identicalCount > maxIdenticalCharacters) return true;
}
//compare backward
int j;
for (j = 0; j < shorterString.length() - i; j++)
{
if (shorterString.charAt(shorterString.length() - j - 1) == longerString.charAt(longerString.length() - j - 1))
identicalCount++;
if (identicalCount > maxIdenticalCharacters) return true;
}
return false;
}
}
class SpamAnalysisResult
{
String finalMessage;
boolean shouldWarnChatter = false;
boolean shouldBanChatter = false;
String muteReason;
}
class ChatterData
{
public String lastMessage = ""; //the player's last chat message, or slash command complete with parameters
public long lastMessageTimestamp; //last time the player sent a chat message or used a monitored slash command
public int spamLevel = 0; //number of consecutive "spams"
public boolean spamWarned = false; //whether the player has received a warning recently
//all recent message lengths and their total
private final ConcurrentLinkedQueue<LengthTimestampPair> recentMessageLengths = new ConcurrentLinkedQueue<>();
private int recentTotalLength = 0;
public void AddMessage(String message, long timestamp)
{
int length = message.length();
this.recentMessageLengths.add(new LengthTimestampPair(length, timestamp));
this.recentTotalLength += length;
this.lastMessage = message;
this.lastMessageTimestamp = timestamp;
}
public int getTotalRecentLength(long timestamp)
{
LengthTimestampPair oldestPair = this.recentMessageLengths.peek();
while (oldestPair != null && timestamp - oldestPair.timestamp > 10000)
{
this.recentMessageLengths.poll();
this.recentTotalLength -= oldestPair.length;
oldestPair = this.recentMessageLengths.peek();
}
return this.recentTotalLength;
}
}
class LengthTimestampPair
{
public long timestamp;
public int length;
public LengthTimestampPair(int length, long timestamp)
{
this.length = length;
this.timestamp = timestamp;
}
}

View File

@ -89,207 +89,4 @@ public class Tests
assertFalse(finder.hasMatch("?asdfa sdfas df"));
}
private final UUID player1 = UUID.fromString("f13c5a98-3777-4659-a111-5617adb7d7fb");
private final UUID player2 = UUID.fromString("8667ba71-b85a-4004-af54-457a9734eed7");
@Test
public void testSpamDetectorBasicChatOK()
{
SpamDetector detector = new SpamDetector();
String message = "Hi, everybody! :)";
SpamAnalysisResult result = detector.AnalyzeMessage(player1, message, 1000);
assertNull(result.muteReason);
assertFalse(result.shouldWarnChatter);
assertFalse(result.shouldBanChatter);
assertEquals(result.finalMessage, message);
}
@Test
public void testSpamDetectorCoordinatesOK()
{
SpamDetector detector = new SpamDetector();
assertNull(detector.AnalyzeMessage(player1, "1029,2945", 0).muteReason);
assertNull(detector.AnalyzeMessage(player1, "x1029 z2945", 100000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "x=1029; y=60; z=2945", 200000).muteReason);
}
@Test
public void testSpamDetectorNumbersOK()
{
SpamDetector detector = new SpamDetector();
assertNull(detector.AnalyzeMessage(player1, "25", 0).muteReason);
assertNull(detector.AnalyzeMessage(player1, "12,234.89", 100000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "20078", 200000).muteReason);
}
@Test
public void testSpamDetectorRepetitionExact()
{
SpamDetector detector = new SpamDetector();
String message = "Hi, everybody! :)";
assertNull(detector.AnalyzeMessage(player1, message, 1000).muteReason);
assertNotNull(detector.AnalyzeMessage(player1, message, 28000).muteReason);
}
@Test
public void testSpamDetectorRepetitionExactOK()
{
SpamDetector detector = new SpamDetector();
String message = "Hi, everybody! :)";
assertNull(detector.AnalyzeMessage(player1, message, 1000).muteReason);
assertNull(detector.AnalyzeMessage(player1, message, 35000).muteReason);
}
@Test
public void testSpamDetectorPadding()
{
SpamDetector detector = new SpamDetector();
assertNull(detector.AnalyzeMessage(player1, "Hacking is really fun guys!! :) 123123123456.12398127498762935", 1000).muteReason);
assertNotNull(detector.AnalyzeMessage(player1, "Hacking is really fun guys!! :) 112321523456.1239345498762935", 1000).muteReason);
}
@Test
public void testSpamDetectorRepetition()
{
SpamDetector detector = new SpamDetector();
assertNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 1000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 5000).muteReason);
assertNotNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 9000).muteReason);
}
@Test
public void testSpamDetectorTeamRepetition()
{
SpamDetector detector = new SpamDetector();
assertNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 1000).muteReason);
assertNotNull(detector.AnalyzeMessage(player2, "Hi, everybody! :)", 2500).muteReason);
}
@Test
public void testSpamDetectorTeamRepetitionOK()
{
SpamDetector detector = new SpamDetector();
assertNull(detector.AnalyzeMessage(player1, "hi", 1000).muteReason);
assertNull(detector.AnalyzeMessage(player2, "hi", 3000).muteReason);
}
@Test
public void testSpamDetectorGibberish()
{
SpamDetector detector = new SpamDetector();
assertNotNull(detector.AnalyzeMessage(player1, "poiufpoiuasdfpoiuasdfuaufpoiasfopiuasdfpoiuasdufsdf", 1000).muteReason);
assertNotNull(detector.AnalyzeMessage(player2, "&^%(& (&^%(% (*%#@^ #$&(_||", 3000).muteReason);
}
@Test
public void testSpamDetectorRepetitionOK()
{
SpamDetector detector = new SpamDetector();
assertNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 1000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 12000).muteReason);
}
@Test
public void testSpamDetectorTooFast()
{
SpamDetector detector = new SpamDetector();
assertNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 1000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "How's it going? :)", 2000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "Oh how I've missed you all! :)", 3000).muteReason);
assertNotNull(detector.AnalyzeMessage(player1, "Why is nobody responding to me??!", 4000).muteReason);
}
@Test
public void testSpamDetectorTooMuchVolume()
{
SpamDetector detector = new SpamDetector();
assertNull(detector.AnalyzeMessage(player1, "Once upon a time there was this guy who wanted to be a hacker. So he started logging into Minecraft servers and threatening to DDOS them.", 1000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "Everybody knew that he couldn't be a real hacker, because no real hacker would consider hacking Minecraft to be worth their time, but he didn't understand that even after it was explained to him.", 3000).muteReason);
//start of mute
assertNotNull(detector.AnalyzeMessage(player1, "After I put him in jail and he wasted half an hour of his time trying to solve the (unsolvable) jail 'puzzle', he offered his services to me in exchange for being let out of jail.", 10000).muteReason);
//forgiven after taking a break
assertNull(detector.AnalyzeMessage(player1, "He promised to DDOS any of my 'rival servers'. So I offered him an opportunity to prove he could do what he said, and I gave him his own IP address from our server logs. Then he disappeared for a while.", 16000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "When he finally came back, I /SoftMuted him and left him in the jail.", 28000).muteReason);
}
@Test
public void testSpamDetectorCaps()
{
SpamDetector detector = new SpamDetector();
String message = "OMG I LUFF U KRISTINAAAAAA!";
SpamAnalysisResult result = detector.AnalyzeMessage(player1, message, 1000);
assertEquals(result.finalMessage, message.toLowerCase());
assertNull(result.muteReason);
}
@Test
public void testSpamDetectorCapsOK()
{
SpamDetector detector = new SpamDetector();
String message = "=D";
SpamAnalysisResult result = detector.AnalyzeMessage(player1, message, 1000);
assertEquals(result.finalMessage, message);
assertNull(result.muteReason);
}
@Test
public void testSpamDetectorWarnAndBan()
{
SpamDetector detector = new SpamDetector();
//allowable noise
assertNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 1000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "How's it going? :)", 2000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "Oh how I've missed you all! :)", 3000).muteReason);
//begin mute and warning
SpamAnalysisResult result = detector.AnalyzeMessage(player1, "Why is nobody responding to me??!", 4000);
assertNotNull(result.muteReason);
assertTrue(result.shouldWarnChatter);
assertFalse(result.shouldBanChatter);
assertNotNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 5000).muteReason);
assertNotNull(detector.AnalyzeMessage(player1, "Oh how I've missed you all! :)", 6000).muteReason);
assertNotNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 7000).muteReason);
assertNotNull(detector.AnalyzeMessage(player1, "How's it going? :)", 8000).muteReason);
//ban
result = detector.AnalyzeMessage(player1, "Why is nobody responding to me??!", 9000);
assertTrue(result.shouldBanChatter);
}
@Test
public void testSpamDetectorForgiveness()
{
SpamDetector detector = new SpamDetector();
//allowable noise
assertNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 1000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "How's it going? :)", 2000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "Oh how I've missed you all! :)", 3000).muteReason);
//start of mutes, and a warning
SpamAnalysisResult result = detector.AnalyzeMessage(player1, "Why is nobody responding to me??!", 4000);
assertNotNull(result.muteReason);
assertTrue(result.shouldWarnChatter);
assertFalse(result.shouldBanChatter);
assertNotNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 5000).muteReason);
assertNotNull(detector.AnalyzeMessage(player1, "Oh how I've missed you all! :)", 6000).muteReason);
assertNotNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 7000).muteReason);
assertNotNull(detector.AnalyzeMessage(player1, "How's it going? :)", 8000).muteReason);
//long delay before next message, not muted anymore
result = detector.AnalyzeMessage(player1, "Why is nobody responding to me??!", 20000);
assertFalse(result.shouldBanChatter);
assertNull(detector.AnalyzeMessage(player1, "Hi, everybody! :)", 21000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "How's it going? :)", 22000).muteReason);
assertNull(detector.AnalyzeMessage(player1, "Oh how I've missed you all! :)", 23000).muteReason);
//mutes start again, and warning appears again
result = detector.AnalyzeMessage(player1, "Why is nobody responding to me??!", 24000);
assertNotNull(result.muteReason);
assertTrue(result.shouldWarnChatter);
assertFalse(result.shouldBanChatter);
}
}