Skip to content

Commit 8e0f29f

Browse files
committed
Add cross-channel spam automod
1 parent cf9421d commit 8e0f29f

5 files changed

Lines changed: 64 additions & 12 deletions

File tree

src/main/java/net/discordjug/javabot/data/config/guild/ModerationConfig.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,18 @@ public class ModerationConfig extends GuildConfigItem {
2828
private long adminRoleId = 0;
2929
private long expertRoleId = 0;
3030

31+
/**
32+
* The time window, in seconds, that the cross-channel spam automod looks back over when
33+
* counting a user's recent messages. If this is {@code 0}, the cross-channel spam automod
34+
* is disabled.
35+
*/
36+
private int crossChannelSpamWindowSeconds = 0;
37+
38+
/**
39+
* The number of distinct channels a user must post in within
40+
* {@link #crossChannelSpamWindowSeconds} seconds before the cross-channel spam automod acts.
41+
*/
42+
private int crossChannelSpamMinChannels = 3;
3143
/**
3244
* ID of the share-knowledge channel.
3345
*/

src/main/java/net/discordjug/javabot/data/h2db/message_cache/MessageCacheListener.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ public void onMessageUpdate(@NotNull MessageUpdateEvent event) {
3939
CachedMessage before;
4040
if (optional.isPresent()) {
4141
CachedMessage inCache= optional.get();
42-
before = new CachedMessage(inCache.getMessageId(), inCache.getAuthorId(), inCache.getMessageContent(), inCache.getAttachments());
42+
before = new CachedMessage(inCache.getMessageId(), inCache.getAuthorId(), inCache.getChannelId(),inCache.getMessageContent(), inCache.getAttachments());
4343
inCache.init(event.getMessage());
4444
} else {
45-
before = new CachedMessage(event.getMessageIdLong(), event.getAuthor().getIdLong(), "[unknown content]", List.of());
45+
before = new CachedMessage(event.getMessageIdLong(), event.getAuthor().getIdLong(), event.getChannel().getIdLong(),"[unknown content]", List.of());
4646
messageCache.cache(event.getMessage());
4747
}
4848
messageCache.sendUpdatedMessageToLog(event.getMessage(), before);

src/main/java/net/discordjug/javabot/data/h2db/message_cache/dao/MessageCacheRepository.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public List<CachedMessage> getAll() throws DataAccessException {
8888
messages.merge(msg.getMessageId(), msg, (oldValue, value) -> {
8989
ArrayList<String> attachments = new ArrayList<>(oldValue.getAttachments());
9090
attachments.addAll(value.getAttachments());
91-
return new CachedMessage(oldValue.getMessageId(), oldValue.getAuthorId(), oldValue.getMessageContent(), attachments);
91+
return new CachedMessage(oldValue.getMessageId(), oldValue.getAuthorId(), oldValue.getChannelId(),oldValue.getMessageContent(), attachments);
9292
});
9393
}
9494
return new ArrayList<>(messages.values());
@@ -119,6 +119,7 @@ private CachedMessage read(ResultSet rs) throws SQLException {
119119
return new CachedMessage(
120120
rs.getLong("message_cache.message_id"),
121121
rs.getLong("author_id"),
122+
rs.getLong("channel_id"),
122123
rs.getString("message_content"),
123124
attachments);
124125
}

src/main/java/net/discordjug/javabot/data/h2db/message_cache/model/CachedMessage.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import net.discordjug.javabot.util.MessageUtils;
1010
import net.dv8tion.jda.api.entities.Message;
1111
import net.dv8tion.jda.api.entities.Message.Attachment;
12+
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion;
1213

1314
/**
1415
* Represents a cached Message.
@@ -19,12 +20,14 @@
1920
public class CachedMessage {
2021
private final long messageId;
2122
private final long authorId;
23+
private final long channelId;
2224
private String messageContent;
2325
private List<String> attachments=new ArrayList<>();
2426

25-
private CachedMessage(long messageId, long authorId) {
27+
private CachedMessage(long messageId, long authorId,long channelId) {
2628
this.messageId = messageId;
2729
this.authorId = authorId;
30+
this.channelId = channelId;
2831
}
2932

3033
/**
@@ -34,10 +37,11 @@ private CachedMessage(long messageId, long authorId) {
3437
* @param messageContent the textual content of the message
3538
* @param attachments The attachment URLs
3639
*/
37-
public CachedMessage(long messageId, long authorId, String messageContent, List<String> attachments) {
40+
public CachedMessage(long messageId, long authorId, long channelId, String messageContent, List<String> attachments) {
3841
super();
3942
this.messageId = messageId;
4043
this.authorId = authorId;
44+
this.channelId = channelId;
4145
this.messageContent = messageContent;
4246
this.attachments = List.copyOf(attachments);
4347
}
@@ -49,7 +53,7 @@ public CachedMessage(long messageId, long authorId, String messageContent, List<
4953
* @return The built {@link CachedMessage}.
5054
*/
5155
public static CachedMessage of(Message message) {
52-
CachedMessage cachedMessage = new CachedMessage(message.getIdLong(), message.getAuthor().getIdLong());
56+
CachedMessage cachedMessage = new CachedMessage(message.getIdLong(), message.getAuthor().getIdLong(),message.getChannelIdLong());
5357
cachedMessage.init(message);
5458
return cachedMessage;
5559
}

src/main/java/net/discordjug/javabot/systems/moderation/AutoMod.java

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import lombok.extern.slf4j.Slf4j;
44
import net.discordjug.javabot.data.config.BotConfig;
5+
import net.discordjug.javabot.data.config.guild.ModerationConfig;
56
import net.discordjug.javabot.data.h2db.message_cache.MessageCache;
7+
import net.discordjug.javabot.data.h2db.message_cache.model.CachedMessage;
68
import net.discordjug.javabot.systems.moderation.warn.model.WarnSeverity;
79
import net.discordjug.javabot.systems.notification.NotificationService;
810
import net.discordjug.javabot.util.ExceptionLogger;
911
import net.discordjug.javabot.util.MessageUtils;
1012
import net.dv8tion.jda.api.Permission;
13+
import net.dv8tion.jda.api.entities.Guild;
1114
import net.dv8tion.jda.api.entities.Member;
1215
import net.dv8tion.jda.api.entities.Message;
1316
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion;
@@ -24,6 +27,7 @@
2427
import java.net.URL;
2528
import java.time.Duration;
2629
import java.time.temporal.ChronoUnit;
30+
import java.util.ArrayList;
2731
import java.util.Collections;
2832
import java.util.List;
2933
import java.util.Scanner;
@@ -50,7 +54,7 @@ public class AutoMod extends ListenerAdapter {
5054
private final MessageCache messageCache;
5155

5256
/**
53-
* Constructor of the class, that creates a list of strings with potential spam/scam urls.
57+
* Constructor of the class, that creates a list of strings with potential spam/scam URLs.
5458
* @param notificationService The {@link QOTWPointsService}
5559
* @param botConfig The main configuration of the bot
5660
* @param moderationService Service object for moderating members
@@ -108,18 +112,33 @@ private void checkNewMessageAutomod(@Nonnull Message message) {
108112
.stream()
109113
.filter(cached -> cached.getMessageId() != message.getIdLong()) // exclude new/current message
110114
.filter(cached -> cached.getAuthorId() == message.getAuthor().getIdLong())
111-
.filter(cached ->
115+
.filter(cached ->
112116
// only java files -> not spam
113-
cached.getAttachments().isEmpty() ||
117+
cached.getAttachments().isEmpty() ||
114118
cached.getAttachments().stream()
115119
.anyMatch(attachment -> !attachment.contains(".java?")))
116120
.count() + 1; // include new message
117-
121+
118122
if (spamCount >= 5) {
119123
handleSpam(message, message.getMember());
120124
}
121125

122126
checkContentAutomod(message);
127+
checkCrossChannelSpam(message);
128+
}
129+
130+
private void checkCrossChannelSpam(@Nonnull Message message) {
131+
List<CachedMessage> spamMessages = new ArrayList<>();
132+
messageCache.getMessagesAfter(message.getTimeCreated().minusSeconds((botConfig.get(message.getGuild()).getModerationConfig()).getCrossChannelSpamWindowSeconds())).stream()
133+
.filter(cachedMessage -> cachedMessage.getAuthorId() == message.getAuthor().getIdLong())
134+
.filter(cachedMessage -> spamMessages.stream().noneMatch(
135+
spamMessage -> spamMessage.getChannelId() == cachedMessage.getChannelId()))
136+
.forEach(spamMessages::add);
137+
138+
if(spamMessages.size() > (botConfig.get(message.getGuild()).getModerationConfig()).getCrossChannelSpamMinChannels()){
139+
handleSpam(spamMessages,message);
140+
}
141+
123142
}
124143

125144
/**
@@ -174,6 +193,22 @@ private void handleSpam(@Nonnull Message msg, Member member) {
174193
msg.delete().queue();
175194
}
176195

196+
private void handleSpam(@Nonnull List<CachedMessage> cachedMessages, Message message) {
197+
Guild guild = message.getGuild();
198+
moderationService
199+
.timeout(
200+
message.getAuthor(),
201+
"Automod: Spam",
202+
message.getGuild().getSelfMember(),
203+
Duration.of(6, ChronoUnit.HOURS),
204+
message.getChannel(),
205+
false
206+
);
207+
208+
cachedMessages.forEach(cachedMessage -> guild.getTextChannelById(cachedMessage.getChannelId())
209+
.deleteMessageById(cachedMessage.getMessageId()).queue());
210+
}
211+
177212
/**
178213
* returns the original String cleaned up of unused code points and spaces.
179214
*
@@ -218,7 +253,7 @@ public boolean hasSuspiciousLink(@NotNull Message message) {
218253
* Checks whether the given message contains a discord invite link.
219254
*
220255
* @param message The Message to check.
221-
* @return True if an invite is found and False if not.
256+
* @return True if an invitation is found and False if not.
222257
*/
223258
public boolean hasAdvertisingLink(@NotNull Message message) {
224259
// Advertising
@@ -237,5 +272,5 @@ private boolean isSuggestionsChannel(@NotNull MessageChannelUnion channel) {
237272
return channel.getType().isGuild() &&
238273
channel.getIdLong() == botConfig.get(channel.asGuildMessageChannel().getGuild()).getModerationConfig().getSuggestionChannel().getIdLong();
239274
}
240-
275+
241276
}

0 commit comments

Comments
 (0)