From b96963ae2f762127835cf60d8eb99841ac76ae65 Mon Sep 17 00:00:00 2001 From: hypherionmc Date: Sun, 12 Jan 2025 14:15:06 +0200 Subject: [PATCH] [FEAT] Implement basic message spam detector --- .../api/messaging/discord/DiscordMessage.java | 9 +++ .../sdlink/core/discord/BotController.java | 14 ++-- .../sdlink/core/managers/SpamManager.java | 68 +++++++++++++++++++ 3 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 Common/src/main/java/com/hypherionmc/sdlink/core/managers/SpamManager.java diff --git a/Common/src/main/java/com/hypherionmc/sdlink/api/messaging/discord/DiscordMessage.java b/Common/src/main/java/com/hypherionmc/sdlink/api/messaging/discord/DiscordMessage.java index 6c98132..dfb7e88 100644 --- a/Common/src/main/java/com/hypherionmc/sdlink/api/messaging/discord/DiscordMessage.java +++ b/Common/src/main/java/com/hypherionmc/sdlink/api/messaging/discord/DiscordMessage.java @@ -63,6 +63,15 @@ public void sendMessage() { if (message.isEmpty()) return; + BotController.INSTANCE.getSpamManager().receiveMessage(message); + + if (BotController.INSTANCE.getSpamManager().isBlocked(message)) { + if (SDLinkConfig.INSTANCE.generalConfig.debugging) + BotController.INSTANCE.getLogger().warn("Blocked message {} due to spam", message); + + return; + } + try { if (messageType == MessageType.CONSOLE) { sendConsoleMessage(); diff --git a/Common/src/main/java/com/hypherionmc/sdlink/core/discord/BotController.java b/Common/src/main/java/com/hypherionmc/sdlink/core/discord/BotController.java index 50a5473..4f28887 100644 --- a/Common/src/main/java/com/hypherionmc/sdlink/core/discord/BotController.java +++ b/Common/src/main/java/com/hypherionmc/sdlink/core/discord/BotController.java @@ -9,10 +9,7 @@ import com.hypherionmc.sdlink.core.discord.commands.CommandManager; import com.hypherionmc.sdlink.core.discord.events.DiscordEventHandler; import com.hypherionmc.sdlink.core.editor.ConfigEditorClient; -import com.hypherionmc.sdlink.core.managers.DatabaseManager; -import com.hypherionmc.sdlink.core.managers.EmbedManager; -import com.hypherionmc.sdlink.core.managers.HiddenPlayersManager; -import com.hypherionmc.sdlink.core.managers.WebhookManager; +import com.hypherionmc.sdlink.core.managers.*; import com.hypherionmc.sdlink.util.EncryptionUtil; import com.hypherionmc.sdlink.util.ThreadedEventManager; import com.jagrosh.jdautilities.command.CommandClient; @@ -31,6 +28,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; /** * @author HypherionSA @@ -40,7 +38,7 @@ public final class BotController { // Thread Execution Manager public final ExecutorService taskManager = Executors.newCachedThreadPool(); - public final ScheduledExecutorService updatesManager = Executors.newScheduledThreadPool(2); + public final ScheduledExecutorService updatesManager = Executors.newScheduledThreadPool(4); // Public instance of this class that can be called anywhere public static BotController INSTANCE; @@ -50,6 +48,9 @@ public final class BotController { @Getter private final Logger logger; + @Getter + private final SpamManager spamManager; + // Required Variables private JDA _jda; private boolean shutdownCalled = false; @@ -81,6 +82,9 @@ private BotController(Logger logger, boolean wasReload) { // Initialize Hidden players HiddenPlayersManager.INSTANCE.loadHiddenPlayers(); + + // Initialize spam detector with the following limits: 5 Messages in 2 seconds, with an expiration of 2 minutes + spamManager = new SpamManager(5, 2000, 120000, updatesManager); } /** diff --git a/Common/src/main/java/com/hypherionmc/sdlink/core/managers/SpamManager.java b/Common/src/main/java/com/hypherionmc/sdlink/core/managers/SpamManager.java new file mode 100644 index 0000000..50aa7a5 --- /dev/null +++ b/Common/src/main/java/com/hypherionmc/sdlink/core/managers/SpamManager.java @@ -0,0 +1,68 @@ +package com.hypherionmc.sdlink.core.managers; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * @author HypherionSA + * Basic Message Spam Detector + */ +public class SpamManager { + + private final ConcurrentHashMap> messageTimestamps = new ConcurrentHashMap<>(); + private final Set blockedMessages = new HashSet<>(); + + private final int threshold; + private final int timeWindowMillis; + private final int blockMillis; + private final ScheduledExecutorService executor; + + public SpamManager(int threshold, int timeWindowMillis, int blockMillis, ScheduledExecutorService executor) { + this.threshold = threshold; + this.timeWindowMillis = timeWindowMillis; + this.blockMillis = blockMillis; + this.executor = executor; + startSpamChecker(); + } + + public void receiveMessage(String message) { + long currentTime = System.currentTimeMillis(); + + messageTimestamps.compute(message, (msg, timestamps) -> { + if (timestamps == null) + timestamps = new ArrayList<>(); + + timestamps.add(currentTime); + return timestamps.stream() + .filter(timestamp -> currentTime - timestamp <= timeWindowMillis) + .toList(); + }); + + if (messageTimestamps.get(message).size() >= threshold) + blockedMessages.add(message); + } + + public boolean isBlocked(String message) { + return blockedMessages.contains(message); + } + + private void startSpamChecker() { + executor.scheduleAtFixedRate(() -> { + long currentTime = System.currentTimeMillis(); + blockedMessages.removeIf(message -> { + List timestamps = messageTimestamps.getOrDefault(message, new ArrayList<>()); + timestamps = timestamps.stream() + .filter(timestamp -> currentTime - timestamp <= timeWindowMillis) + .toList(); + messageTimestamps.put(message, timestamps); + return timestamps.size() < threshold; + }); + }, blockMillis, blockMillis, TimeUnit.MILLISECONDS); + } + +}