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; + } }