From 5a9e013b8464001a84374c03fc632b27eeb83ec0 Mon Sep 17 00:00:00 2001
From: Jadefalke2 <79963380+Jadefalke2@users.noreply.github.com>
Date: Sat, 6 Nov 2021 17:57:29 +0100
Subject: [PATCH 1/3] Spam detection
Added spam detection.
There still is a lot of TODO's and placeholders as some things are not implemented yet. Those need to be replaced.
Note that maybe the Config items I put in ModerationConfig could be put into another config eventually.
---
.../java/net/javadiscord/javabot2/Bot.java | 21 +++++
.../config/guild/ModerationConfig.java | 25 ++++++
.../systems/moderation/MessageCache.java | 76 +++++++++++++++++++
.../systems/moderation/SpamListener.java | 36 +++++++++
4 files changed, 158 insertions(+)
create mode 100644 src/main/java/net/javadiscord/javabot2/systems/moderation/MessageCache.java
create mode 100644 src/main/java/net/javadiscord/javabot2/systems/moderation/SpamListener.java
diff --git a/src/main/java/net/javadiscord/javabot2/Bot.java b/src/main/java/net/javadiscord/javabot2/Bot.java
index fe03a32..84cca05 100644
--- a/src/main/java/net/javadiscord/javabot2/Bot.java
+++ b/src/main/java/net/javadiscord/javabot2/Bot.java
@@ -9,6 +9,8 @@
import com.zaxxer.hikari.HikariDataSource;
import net.javadiscord.javabot2.command.SlashCommandListener;
import net.javadiscord.javabot2.config.BotConfig;
+import net.javadiscord.javabot2.systems.moderation.SpamListener;
+import net.javadiscord.javabot2.systems.moderation.MessageCache;
import org.javacord.api.DiscordApi;
import org.javacord.api.DiscordApiBuilder;
import org.javacord.api.entity.intent.Intent;
@@ -46,6 +48,11 @@ public class Bot {
*/
public static ScheduledExecutorService asyncPool;
+ /**
+ * The message cache.
+ */
+ public static MessageCache messageCache;
+
// Hide constructor.
private Bot() {}
@@ -60,8 +67,13 @@ public static void main(String[] args) {
.setToken(config.getSystems().getDiscordBotToken())
.setAllIntentsExcept(Intent.GUILD_MESSAGE_TYPING, Intent.GUILD_PRESENCES, Intent.GUILD_VOICE_STATES)
.login().join();
+
+ messageCache = new MessageCache();
+ initListeners(api);
+
config.loadGuilds(api.getServers()); // Once we've logged in, load all guild config files.
config.flush(); // Flush to save any new config files that are generated for new guilds.
+
SlashCommandListener commandListener = new SlashCommandListener(
api,
args.length > 0 && args[0].equalsIgnoreCase("--register-commands"),
@@ -70,6 +82,15 @@ public static void main(String[] args) {
api.addSlashCommandCreateListener(commandListener);
}
+ /**
+ * Initializes and adds all listeners to the API.
+ * @param api the API
+ */
+ private static void initListeners(DiscordApi api) {
+ api.addMessageCreateListener(new SpamListener());
+ api.addMessageCreateListener(messageCache);
+ }
+
/**
* Initializes all the basic data sources that are needed by the bot's other
* capabilities. This should be called before logging in
diff --git a/src/main/java/net/javadiscord/javabot2/config/guild/ModerationConfig.java b/src/main/java/net/javadiscord/javabot2/config/guild/ModerationConfig.java
index 4466891..bfca37f 100644
--- a/src/main/java/net/javadiscord/javabot2/config/guild/ModerationConfig.java
+++ b/src/main/java/net/javadiscord/javabot2/config/guild/ModerationConfig.java
@@ -16,6 +16,31 @@ public class ModerationConfig extends GuildConfigItem {
*/
private long staffRoleId;
+ /**
+ * The amount of seconds to be looked into the past to determine if a user is spamming.
+ */
+ private int pastMessageCountBeforeDurationInSeconds;
+
+ /**
+ * The amount of messages to be sent within {@link #pastMessageCountBeforeDurationInSeconds}.
+ */
+ private int messageSpamAmount;
+
+ /**
+ * The amount of messages to be cached for each user.
+ */
+ private int cachedMessagesPerUser;
+
+ /**
+ * The frequency of cleaning up the cached messages. Amount in minutes.
+ */
+ private int cachedMessageCleanupFrequency;
+
+ /**
+ * The amount of minutes for removing this cached messages.
+ */
+ private int amountOfMinutesForRemoval;
+
public Role getStaffRole() {
return this.getGuild().getRoleById(staffRoleId).orElseThrow();
}
diff --git a/src/main/java/net/javadiscord/javabot2/systems/moderation/MessageCache.java b/src/main/java/net/javadiscord/javabot2/systems/moderation/MessageCache.java
new file mode 100644
index 0000000..8e8176e
--- /dev/null
+++ b/src/main/java/net/javadiscord/javabot2/systems/moderation/MessageCache.java
@@ -0,0 +1,76 @@
+package net.javadiscord.javabot2.systems.moderation;
+
+import net.javadiscord.javabot2.Bot;
+import org.javacord.api.entity.message.Message;
+import org.javacord.api.entity.message.MessageAuthor;
+import org.javacord.api.event.message.MessageCreateEvent;
+import org.javacord.api.listener.message.MessageCreateListener;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.LinkedList;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Caches incoming messages and cleans it out when needed.
+ */
+public class MessageCache extends ConcurrentHashMap> implements MessageCreateListener {
+
+ private final ConcurrentHashMap lastModifications;
+
+ /**
+ * Creates the cache.
+ */
+ public MessageCache() {
+ lastModifications = new ConcurrentHashMap<>();
+ // TODO config
+ Bot.asyncPool.scheduleAtFixedRate(this::clean, -1, -1, TimeUnit.MINUTES);
+ }
+
+ @Override
+ public void onMessageCreate(MessageCreateEvent event) {
+ if (!event.getMessageAuthor().isYourself()) {
+ add(event.getMessage());
+ }
+ }
+
+ /**
+ * Removes all Map entries which have not been modified within the last 10 minutes.
+ */
+ private void clean() {
+ lastModifications.forEach((messageAuthor, instant) -> {
+ // if last modification is older than n minutes
+ // TODO config
+ if (instant.isAfter(Instant.now().minus(Duration.ofMinutes(-1)))) {
+ this.remove(messageAuthor);
+ lastModifications.remove(messageAuthor);
+ }
+ });
+ }
+
+ /**
+ * Add the message either as a new Map entry or to the existing list per user.
+ * Also removes if the amount of messages per user is over a certain treshold.
+ * @param msg the message to be added
+ */
+ private void add(Message msg) {
+ if (this.containsKey(msg.getAuthor())) {
+ LinkedList msgList = this.get(msg.getAuthor());
+ msgList.offer(msg);
+ lastModifications.replace(msg.getAuthor(), Instant.now());
+
+ // TODO config
+ if (msgList.size() >= -1) {
+ msgList.poll();
+ }
+
+ } else {
+ LinkedList messages = new LinkedList<>();
+ messages.add(msg);
+ this.put(msg.getAuthor(), messages);
+ lastModifications.put(msg.getAuthor(), Instant.now());
+ }
+ }
+}
+
diff --git a/src/main/java/net/javadiscord/javabot2/systems/moderation/SpamListener.java b/src/main/java/net/javadiscord/javabot2/systems/moderation/SpamListener.java
new file mode 100644
index 0000000..3176923
--- /dev/null
+++ b/src/main/java/net/javadiscord/javabot2/systems/moderation/SpamListener.java
@@ -0,0 +1,36 @@
+package net.javadiscord.javabot2.systems.moderation;
+
+import net.javadiscord.javabot2.Bot;
+import org.javacord.api.event.message.MessageCreateEvent;
+import org.javacord.api.listener.message.MessageCreateListener;
+
+import java.time.Duration;
+import java.time.Instant;
+
+/**
+ * Listens for spam using the {@link MessageCache}.
+ */
+public class SpamListener implements MessageCreateListener {
+
+ @Override
+ public void onMessageCreate(MessageCreateEvent event) {
+ if (!event.getMessageAuthor().isYourself() && Bot.messageCache.containsKey(event.getMessageAuthor())) {
+ int amountOfMessages = (int) Bot.messageCache.get(event.getMessageAuthor())
+ .stream()
+ //TODO get time from config
+ .filter(m -> m.getCreationTimestamp().isAfter(Instant.now().minus(Duration.ofSeconds(-1))))
+ .count();
+
+ System.out.println(amountOfMessages);
+
+ //TODO get amount from config
+ if (amountOfMessages >= -1) {
+ //TODO spam detected
+ // placeholder for checkstyle not to complain.
+ // should be replaced with a warn + purge which is not implemented yet
+ event.deleteMessage("spam");
+ }
+ }
+ }
+
+}
From 92935aa9d0fdf35af37e9f96af56e29fb451c2a2 Mon Sep 17 00:00:00 2001
From: Jadefalke2 <79963380+Jadefalke2@users.noreply.github.com>
Date: Sat, 6 Nov 2021 19:36:47 +0100
Subject: [PATCH 2/3] remove log + refactor
---
.../javadiscord/javabot2/systems/moderation/SpamListener.java | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/main/java/net/javadiscord/javabot2/systems/moderation/SpamListener.java b/src/main/java/net/javadiscord/javabot2/systems/moderation/SpamListener.java
index 3176923..0015c48 100644
--- a/src/main/java/net/javadiscord/javabot2/systems/moderation/SpamListener.java
+++ b/src/main/java/net/javadiscord/javabot2/systems/moderation/SpamListener.java
@@ -18,11 +18,9 @@ public void onMessageCreate(MessageCreateEvent event) {
int amountOfMessages = (int) Bot.messageCache.get(event.getMessageAuthor())
.stream()
//TODO get time from config
- .filter(m -> m.getCreationTimestamp().isAfter(Instant.now().minus(Duration.ofSeconds(-1))))
+ .filter(msg -> msg.getCreationTimestamp().isAfter(Instant.now().minus(Duration.ofSeconds(-1))))
.count();
- System.out.println(amountOfMessages);
-
//TODO get amount from config
if (amountOfMessages >= -1) {
//TODO spam detected
From b63e9709e63ac37d3eff42d70cfdb13107174339 Mon Sep 17 00:00:00 2001
From: Jadefalke2 <79963380+Jadefalke2@users.noreply.github.com>
Date: Sat, 6 Nov 2021 19:45:45 +0100
Subject: [PATCH 3/3] Refactor inhertitance to normal reference
---
src/main/java/net/javadiscord/javabot2/Bot.java | 12 +++++++++---
.../systems/moderation/MessageCache.java | 16 +++++++++++-----
2 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/src/main/java/net/javadiscord/javabot2/Bot.java b/src/main/java/net/javadiscord/javabot2/Bot.java
index 84cca05..8545421 100644
--- a/src/main/java/net/javadiscord/javabot2/Bot.java
+++ b/src/main/java/net/javadiscord/javabot2/Bot.java
@@ -14,8 +14,12 @@
import org.javacord.api.DiscordApi;
import org.javacord.api.DiscordApiBuilder;
import org.javacord.api.entity.intent.Intent;
+import org.javacord.api.entity.message.Message;
+import org.javacord.api.entity.message.MessageAuthor;
import java.nio.file.Path;
+import java.util.LinkedList;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@@ -51,7 +55,7 @@ public class Bot {
/**
* The message cache.
*/
- public static MessageCache messageCache;
+ public static ConcurrentHashMap> messageCache;
// Hide constructor.
private Bot() {}
@@ -68,7 +72,6 @@ public static void main(String[] args) {
.setAllIntentsExcept(Intent.GUILD_MESSAGE_TYPING, Intent.GUILD_PRESENCES, Intent.GUILD_VOICE_STATES)
.login().join();
- messageCache = new MessageCache();
initListeners(api);
config.loadGuilds(api.getServers()); // Once we've logged in, load all guild config files.
@@ -87,8 +90,11 @@ public static void main(String[] args) {
* @param api the API
*/
private static void initListeners(DiscordApi api) {
+ MessageCache cache = new MessageCache();
+ messageCache = cache.getCache();
+
api.addMessageCreateListener(new SpamListener());
- api.addMessageCreateListener(messageCache);
+ api.addMessageCreateListener(cache);
}
/**
diff --git a/src/main/java/net/javadiscord/javabot2/systems/moderation/MessageCache.java b/src/main/java/net/javadiscord/javabot2/systems/moderation/MessageCache.java
index 8e8176e..0210454 100644
--- a/src/main/java/net/javadiscord/javabot2/systems/moderation/MessageCache.java
+++ b/src/main/java/net/javadiscord/javabot2/systems/moderation/MessageCache.java
@@ -15,15 +15,17 @@
/**
* Caches incoming messages and cleans it out when needed.
*/
-public class MessageCache extends ConcurrentHashMap> implements MessageCreateListener {
+public class MessageCache implements MessageCreateListener {
private final ConcurrentHashMap lastModifications;
+ private final ConcurrentHashMap> cache;
/**
* Creates the cache.
*/
public MessageCache() {
lastModifications = new ConcurrentHashMap<>();
+ cache = new ConcurrentHashMap<>();
// TODO config
Bot.asyncPool.scheduleAtFixedRate(this::clean, -1, -1, TimeUnit.MINUTES);
}
@@ -43,7 +45,7 @@ private void clean() {
// if last modification is older than n minutes
// TODO config
if (instant.isAfter(Instant.now().minus(Duration.ofMinutes(-1)))) {
- this.remove(messageAuthor);
+ cache.remove(messageAuthor);
lastModifications.remove(messageAuthor);
}
});
@@ -55,8 +57,8 @@ private void clean() {
* @param msg the message to be added
*/
private void add(Message msg) {
- if (this.containsKey(msg.getAuthor())) {
- LinkedList msgList = this.get(msg.getAuthor());
+ if (cache.containsKey(msg.getAuthor())) {
+ LinkedList msgList = cache.get(msg.getAuthor());
msgList.offer(msg);
lastModifications.replace(msg.getAuthor(), Instant.now());
@@ -68,9 +70,13 @@ private void add(Message msg) {
} else {
LinkedList messages = new LinkedList<>();
messages.add(msg);
- this.put(msg.getAuthor(), messages);
+ cache.put(msg.getAuthor(), messages);
lastModifications.put(msg.getAuthor(), Instant.now());
}
}
+
+ public ConcurrentHashMap> getCache() {
+ return cache;
+ }
}