Skip to content
This repository was archived by the owner on Dec 1, 2021. It is now read-only.

Spam detection #14

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions src/main/java/net/javadiscord/javabot2/Bot.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@
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;
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;

Expand Down Expand Up @@ -46,6 +52,11 @@ public class Bot {
*/
public static ScheduledExecutorService asyncPool;

/**
* The message cache.
*/
public static ConcurrentHashMap<MessageAuthor, LinkedList<Message>> messageCache;

// Hide constructor.
private Bot() {}

Expand All @@ -60,8 +71,12 @@ public static void main(String[] args) {
.setToken(config.getSystems().getDiscordBotToken())
.setAllIntentsExcept(Intent.GUILD_MESSAGE_TYPING, Intent.GUILD_PRESENCES, Intent.GUILD_VOICE_STATES)
.login().join();

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"),
Expand All @@ -70,6 +85,18 @@ 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) {
MessageCache cache = new MessageCache();
messageCache = cache.getCache();

api.addMessageCreateListener(new SpamListener());
api.addMessageCreateListener(cache);
}

/**
* Initializes all the basic data sources that are needed by the bot's other
* capabilities. This should be called <strong>before</strong> logging in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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 implements MessageCreateListener {

private final ConcurrentHashMap<MessageAuthor, Instant> lastModifications;
private final ConcurrentHashMap<MessageAuthor, LinkedList<Message>> cache;

/**
* Creates the cache.
*/
public MessageCache() {
lastModifications = new ConcurrentHashMap<>();
cache = new ConcurrentHashMap<>();
// TODO config
Bot.asyncPool.scheduleAtFixedRate(this::clean, -1, -1, TimeUnit.MINUTES);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this here; I would never expect constructing an object to begin an irreversible scheduled task.

}

@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)))) {
cache.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 (cache.containsKey(msg.getAuthor())) {
LinkedList<Message> msgList = cache.get(msg.getAuthor());
msgList.offer(msg);
lastModifications.replace(msg.getAuthor(), Instant.now());

// TODO config
if (msgList.size() >= -1) {
msgList.poll();
}

} else {
LinkedList<Message> messages = new LinkedList<>();
messages.add(msg);
cache.put(msg.getAuthor(), messages);
lastModifications.put(msg.getAuthor(), Instant.now());
}
}

public ConcurrentHashMap<MessageAuthor, LinkedList<Message>> getCache() {
return cache;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
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(msg -> msg.getCreationTimestamp().isAfter(Instant.now().minus(Duration.ofSeconds(-1))))
.count();

//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");
}
Comment on lines +18 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Address these TODOs

}
}

}