made anti chat spam work

thank you maniaplay for making me find out about magic algorithm
This commit is contained in:
blackilykat 2023-06-30 20:29:04 +02:00
parent bf7fd7df93
commit 402ddef938
4 changed files with 51 additions and 291 deletions

View file

@ -21,6 +21,8 @@ repositories {
}
dependencies {
implementation 'org.apache.commons:commons-text:1.10.0' // Use the version that best suits your project
// To change the versions see the gradle.properties file
minecraft "com.mojang:minecraft:${project.minecraft_version}"
mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2"

View file

@ -43,8 +43,9 @@ public class Configuration {
}
public static class AntiSpam {
public int matchingMessagesToBeSpam = 50;
public int matchingMessagesToBeSpam = 20;
public int messageTimeInTicks = 100;
public int blockedPatternDurationInTicks = 72000;
public int blockedPatternDurationInTicks = 0; //TODO: remove
public int minimumLevenshteinDistanceToBeSpam = 20;
}
}

View file

@ -51,10 +51,11 @@ public class ChatHudMixin {
listener.chatMessageReceived(message);
}
for (AntiChatSpamModule.BlockedPattern pattern : AntiChatSpamModule.instance.patterns) {
if(pattern.pattern().matcher(message.getString()).matches()) ci.cancel();
}
new AntiChatSpamModule.ChatMessage(message.getString());
// for (AntiChatSpamModule.BlockedPattern pattern : AntiChatSpamModule.instance.patterns) {
// if(pattern.pattern().matcher(message.getString()).matches()) ci.cancel();
// }
AntiChatSpamModule.ChatMessage cmessage = new AntiChatSpamModule.ChatMessage(message.getString());
if(cmessage.hidden) ci.cancel();
}
}

View file

@ -1,320 +1,76 @@
package land.chipmunk.chipmunkmod.testclient.modules.utility;
import land.chipmunk.chipmunkmod.ChipmunkMod;
import land.chipmunk.chipmunkmod.Configuration;
import land.chipmunk.chipmunkmod.testclient.gui.components.Module;
import land.chipmunk.chipmunkmod.util.Chat;
import land.chipmunk.chipmunkmod.util.Debug;
import land.chipmunk.chipmunkmod.util.Executor;
import lombok.Getter;
import lombok.Setter;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.minecraft.client.MinecraftClient;
import org.apache.commons.text.similarity.LevenshteinDistance;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
public class AntiChatSpamModule extends Module {
public static AntiChatSpamModule instance = new AntiChatSpamModule();
public ArrayList<ChatMessage> messages = new ArrayList<>();
public ArrayList<BlockedPattern> patterns = new ArrayList<>();
private int threadCheckStateTimer = 0;
private static final String debugCallerPrefix = "AntiChatSpam.";
private static final String debugTickedCaller = debugCallerPrefix + "tick";
private static final String debugThreadCaller = debugCallerPrefix + "thread";
private static final String debugRegexProcessCaller = debugCallerPrefix + "regex_process";
private static final String debugRegexOutputCaller = debugCallerPrefix + "regex_output";
private static final String debugComparableStringCaller = debugCallerPrefix + "comparable_string";
private static final String debugLevenshteinDistanceCaller = debugCallerPrefix + "levenshtein_distance";
private static final String debugLevenshteinDistanceCallerSpamless = debugCallerPrefix + "levenshtein_distance_spamless";
public AntiChatSpamModule() {
super("Anti chat spam");
ClientTickEvents.END_CLIENT_TICK.register(client -> {
for (int i = 0; i < messages.size(); i++) {
Debug.debug("Ticked message: "+messages.get(i).content, debugTickedCaller);
Debug.debug("Ticked message: " + messages.get(i).content, debugTickedCaller);
messages.get(i).tick();
}
for (int i = 0; i < patterns.size(); i++) {
Debug.debug("Ticked pattern: "+patterns.get(i).pattern().toString(), debugTickedCaller);
patterns.get(i).tick();
}
if(threadCheckStateTimer >= 10) {
Debug.debug("Thread done: "+thread.isDone(), debugThreadCaller);
threadCheckStateTimer = -1;
}
threadCheckStateTimer++;
});
// ig make theredaadd thingy here because else it says it's null and stuff or idk
thread = Executor.submit(() -> {
boolean interrupted = false;
String caller = debugThreadCaller;
while(!interrupted) {
Debug.debug("woke up", caller);
while(threadQueue.size()>0) {
threadQueue.get(0).run();
threadQueue.remove(0);
Debug.debug("ran a thing in the queue now "+threadQueue.size()+" left", caller);
}
Debug.debug("going zzz", caller);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
interrupted = true;
Debug.debug("Thread interrupted by exception!", caller);
} catch (Exception e) {
Debug.debug("Got an exception or idk printing trace", caller);
e.printStackTrace();
interrupted = true;
}
}
Debug.debug("out of while loop or idk bye ig!!!", caller);
});
} // 72000 ticks for expiry
// and ig remember messages for 100 ticks
public static class ComparableString{
@Getter @Setter private String value;
public int index = 0;
public ComparableString(String value) {
Debug.debug("Created comparable string with value: "+value, debugComparableStringCaller);
this.value = value;
}
public String getChar() {
if (index < 0 || value.length() < index) throw new RuntimeException("Index is out of bounds! (INDEX:"+index+", LENGTH:"+value.length()+")");
String toReturn = switch (value.charAt(index)) {
case '.' -> "\\.";
case '[' -> "\\[";
case ']' -> "\\]";
case '(' -> "\\(";
case ')' -> "\\)";
case '*' -> "\\*";
case '?' -> "\\?";
default -> String.valueOf(value.charAt(index));
};
Debug.debug("Got character: "+toReturn, debugComparableStringCaller);
return toReturn;
}
public void repeat(RepeatableRunnable runnable) {
while(index<value.length()) {
Debug.debug("Repeated some with index "+index, debugComparableStringCaller);
if(runnable.run(index)) {
Debug.debug("Brokne the repeat stuffthingy index "+index, debugComparableStringCaller);
break;
}
index++;
}
Debug.debug("while loop ended (index: "+index+" length: "+value.length()+")", debugComparableStringCaller);
}
}
public static Pattern getPattern(ComparableString firstString, ComparableString secondString) {
Debug.debug("Started getting pattern", debugRegexProcessCaller);
Debug.debug("First string: "+firstString.value(), debugRegexProcessCaller);
Debug.debug("Second string: "+secondString.value(), debugRegexProcessCaller);
ArrayList<CharacterInPattern> characters = new ArrayList<>();
firstString.index = 0;
firstString.repeat(i -> {
Debug.debug("[A] Char index "+i+": "+firstString.getChar(), debugRegexProcessCaller);
secondString.index = 0;
secondString.repeat(j -> {
Debug.debug(" [B] Char index "+j+": "+secondString.getChar(), debugRegexProcessCaller);
if(firstString.getChar().equals(secondString.getChar())) {
Debug.debug("OMGOMG THEY MATCH adding to characters array", debugRegexProcessCaller);
characters.add(new CharacterInPattern(firstString.getChar(), i, j));
return true;
}
return false;
});
return false;
});
Debug.debug("--- SORTING CHARS ---", debugRegexProcessCaller);
// sort based on index sum
Comparator<CharacterInPattern> comparator = new Comparator<CharacterInPattern>() {
@Override
public int compare(CharacterInPattern character1, CharacterInPattern character2) {
return Integer.compare(character1.indexesSum(), character2.indexesSum());
}
};
characters.sort(comparator);
for (CharacterInPattern character : characters) {
Debug.debug(character.indexesSum() + " - " + character.character(), debugRegexProcessCaller);
}
Debug.debug("--- CONFIRMING & PLACING CHARACTERS ---", debugRegexProcessCaller);
ArrayList<CharacterInPattern> confirmedCharacters = new ArrayList<>();
for (int k = 0, charactersSize = characters.size(); k < charactersSize; k++) {
CharacterInPattern character = characters.get(k);
Debug.debug("Finding if B has been placed", debugRegexProcessCaller);
//find if index B has been placed somewhere
AtomicBoolean isIndexBTaken = new AtomicBoolean(false);
confirmedCharacters.forEach(character1 -> {
if (character.indexB() == character1.indexB()) isIndexBTaken.set(true);
});
// if B has already been placed somewhere, resume analysis
if (isIndexBTaken.get()) {
Debug.debug("It has! resuping analysis", debugRegexProcessCaller);
firstString.index = character.indexA();
secondString.index = character.indexB()+1;
Debug.debug("[A] Char index " + firstString.index + ": " + firstString.getChar(), debugRegexProcessCaller);
secondString.repeat(j -> {
Debug.debug(" [B] Char index " + j + ": " + secondString.getChar(), debugRegexProcessCaller);
if (firstString.getChar().equals(secondString.getChar())) {
Debug.debug("OMGOMG THEY MATCH adding to characters array", debugRegexProcessCaller);
// characters.add(new CharacterInPattern(firstString.getChar(), firstString.index, j));
for (int i = 0, sortedCharactersSize = characters.size(); i < sortedCharactersSize; i++) {
CharacterInPattern sortedCharacter = characters.get(i);
if (sortedCharacter.indexesSum() < j) {
characters.add(i, new CharacterInPattern(firstString.getChar(), firstString.index, j));
Debug.debug("Added to sorted chars at index " + i, debugRegexProcessCaller);
break;
}
}
return true;
}
return false;
});
}
// check if you can place it somewhere in the output
// this means that one index must never be greater than another character's if the other is not.
// if it's the first character, add it no matter what. No need to check lol
if (character.indexesSum() == 0) {
Debug.debug("First char so putting it at start of output", debugRegexProcessCaller);
confirmedCharacters.add(character);
continue;
}
// if output is empty and it's not the first character, add a .*? and then the character
if (confirmedCharacters.isEmpty()) {
Debug.debug("Not first char but output empty so putting .*? then char at start", debugRegexProcessCaller);
confirmedCharacters.add(new CharacterInPattern(".*?", 0, 0));
confirmedCharacters.add(character);
continue;
}
Debug.debug("Not at the start so checking where tf it is", debugRegexProcessCaller);
// end of special cases now just check if u can place it somewhere
AtomicInteger index = new AtomicInteger();
boolean rightAfter = false;
for (int i = 0; i < confirmedCharacters.size(); i++) {
CharacterInPattern character1 = confirmedCharacters.get(i);
Debug.debug("Checking character " + character1.character(), debugRegexProcessCaller);
// right after the character
if (character.indexA() == character1.indexA() + 1 && character.indexB() == character1.indexB() + 1) {
Debug.debug("It's right after the char so placing it there", debugRegexProcessCaller);
i++;
if (i >= confirmedCharacters.size()) confirmedCharacters.add(character);
else confirmedCharacters.add(i, character);
rightAfter = true;
break;
}
}
for (int i = 0; i < confirmedCharacters.size() && !rightAfter; i++) {
CharacterInPattern character1 = confirmedCharacters.get(i);
Debug.debug("Checking character " + character1.character(), debugRegexProcessCaller);
//somewhere else after che character
if (character1.indexA() < character.indexA() && character1.indexB() < character.indexB()) {
Debug.debug("It's somewhere after the char so putting it there with .*? in front", debugRegexProcessCaller);
if (index.get() >= confirmedCharacters.size()) {
i++;
confirmedCharacters.add(new CharacterInPattern(".*?", character1.indexA() + 1, character.indexB() + 1));
i++;
confirmedCharacters.add(character);
} else {
i++;
confirmedCharacters.add(i, new CharacterInPattern(".*?", character1.indexA() + 1, character.indexB() + 1));
i++;
confirmedCharacters.add(i, character);
}
}
index.getAndIncrement();
}
}
StringBuilder regex = new StringBuilder();
for (int i = 0, confirmedCharactersSize = confirmedCharacters.size(); i < confirmedCharactersSize; i++) {
CharacterInPattern c = confirmedCharacters.get(i);
regex.append(c.character);
}
Debug.debug("Returning regex "+regex, debugRegexProcessCaller);
Debug.debug("Returning regex '"+regex+"' for strings:", debugRegexOutputCaller);
Debug.debug(" - "+firstString, debugRegexOutputCaller);
Debug.debug(" - "+secondString, debugRegexOutputCaller);
return Pattern.compile(regex.toString());
}
public static class CharacterInPattern {
@Getter private final String character;
@Getter private final int indexA;
@Getter private final int indexB; // setter so I can change it while resuming analysis later on
@Getter private final int indexesSum;
public CharacterInPattern(String character, int indexA, int indexB) {
this.character = character;
this.indexA = indexA;
this.indexB = indexB;
indexesSum = indexA + indexB;
}
}
public interface RepeatableRunnable{
/**
* @param i the current index
* @return if the loop should break
*/
boolean run(int i); // to repeat for the length of the string ig
}
public static class ChatMessage {
@Getter private final String content;
@Getter
private final String content;
public boolean hidden = false;
public int timer = ChipmunkMod.CONFIG.antiSpam.messageTimeInTicks;
public ChatMessage(String content) {
this.content = content;
threadQueue.add(() -> {
ArrayList<ChatMessage> chatMessages = instance.messages;
for (int i = 0; i < chatMessages.size(); i++) {
ChatMessage message = chatMessages.get(i);
Pattern pattern = getPattern(new ComparableString(this.content()), new ComparableString(message.content()));
int matching = 0;
for (ChatMessage message1 : instance.messages) {
if (pattern.matcher(message1.content()).matches()) matching++;
}
if (matching >= ChipmunkMod.CONFIG.antiSpam.matchingMessagesToBeSpam) {
instance.patterns.add(new BlockedPattern(pattern));
}
}
});
ArrayList<ChatMessage> chatMessages = instance.messages;
int similarMessages = 0;
LevenshteinDistance ld = new LevenshteinDistance(); // thanks maniaplay fo r teaching me about levenshtein distance
for (int i = 0; i < chatMessages.size(); i++) {
ChatMessage message = chatMessages.get(i);
int distance = ld.apply(this.content(), message.content());
Debug.debug("Distance: " + distance, debugLevenshteinDistanceCaller);
if (distance <= ChipmunkMod.CONFIG.antiSpam.minimumLevenshteinDistanceToBeSpam) similarMessages++;
// Pattern pattern = getPattern(new ComparableString(this.content()), new ComparableString(message.content()));
// int matching = 0;
// ArrayList<ChatMessage> chatMessageArrayList = instance.messages;
// for (int j = 0; j < chatMessageArrayList.size(); j++) {
// ChatMessage message1 = chatMessageArrayList.get(j);
// if (pattern.matcher(message1.content()).matches()) matching++;
// }
// if (matching >= ChipmunkMod.CONFIG.antiSpam.matchingMessagesToBeSpam) {
// instance.patterns.add(new BlockedPattern(pattern));
// }
}
Debug.debug("Similar messages: " + similarMessages, debugLevenshteinDistanceCaller);
Debug.debug("Similar messages: " + similarMessages, debugLevenshteinDistanceCallerSpamless);
if (similarMessages >= ChipmunkMod.CONFIG.antiSpam.matchingMessagesToBeSpam) hidden = true;
Debug.debug("Hidden: " + hidden, debugLevenshteinDistanceCaller);
Debug.debug("Hidden: " + hidden, debugLevenshteinDistanceCallerSpamless);
instance.messages.add(this);
}
public void tick() {timer--; if(timer <= 0) instance.messages.remove(this);}
}
// threadQueue.add(() -> {
// // code above used to be here but i cant decide if i should show it or not depending on the thread cuz i cant make it wait
// });
public static class BlockedPattern {
@Getter private final Pattern pattern;
public int timer = ChipmunkMod.CONFIG.antiSpam.blockedPatternDurationInTicks;
public BlockedPattern(Pattern pattern) {
this.pattern = pattern;
instance.patterns.add(this);
Chat.send("[AntiSpam] Blocked pattern "+pattern.toString());
}
public void tick() {timer--; if(timer <= 0) instance.patterns.remove(this);}
}
public static ArrayList<Runnable> threadQueue = new ArrayList<>();
public static Future<?> thread;
public static void debug(String message) {
Debug.debug(message, "AntiChatSpam");
public void tick() {
timer--;
if (timer <= 0) instance.messages.remove(this);
}
}
} // holy shit 200 lines and i havent ran this once this is not going to go well
// it did not go well
}