Skip to content

Commit b8539e7

Browse files
committed
add search-based condition to rule-based automod
1 parent cf9421d commit b8539e7

8 files changed

Lines changed: 100 additions & 57 deletions

File tree

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
<parent>
88
<groupId>org.springframework.boot</groupId>
99
<artifactId>spring-boot-starter-parent</artifactId>
10-
<version>4.0.3</version>
10+
<version>4.1.0</version>
1111
</parent>
1212

1313
<properties>
@@ -43,7 +43,7 @@
4343
<dependency>
4444
<groupId>net.dv8tion</groupId>
4545
<artifactId>JDA</artifactId>
46-
<version>6.2.0</version>
46+
<version>6.5.0</version>
4747
<exclusions>
4848
<exclusion>
4949
<artifactId>opus-java</artifactId>

src/main/java/net/discordjug/javabot/RuntimeHintsConfiguration.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package net.discordjug.javabot;
22

3+
import java.util.concurrent.ThreadPoolExecutor;
4+
35
import club.minnced.discord.webhook.send.WebhookEmbed;
46
import com.zaxxer.hikari.HikariConfig;
57
import net.discordjug.javabot.data.config.BotConfig;
@@ -15,6 +17,7 @@
1517
import net.discordjug.javabot.data.config.guild.QOTWConfig;
1618
import net.discordjug.javabot.data.config.guild.ServerLockConfig;
1719
import net.discordjug.javabot.data.config.guild.StarboardConfig;
20+
import net.dv8tion.jda.api.entities.SoundboardSound;
1821
import net.dv8tion.jda.api.hooks.ListenerAdapter;
1922
import net.dv8tion.jda.internal.entities.GuildVoiceStateImpl;
2023
import net.dv8tion.jda.internal.requests.restaction.PermOverrideData;
@@ -60,6 +63,8 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
6063
// JDA needs to be able to access listener methods
6164
hints.reflection().registerType(ListenerAdapter.class, MemberCategory.INVOKE_PUBLIC_METHODS);
6265

66+
hints.reflection().registerType(ThreadPoolExecutor.class, MemberCategory.INVOKE_PUBLIC_METHODS);
67+
6368
// caffeine
6469
hints.reflection().registerTypeIfPresent(getClass().getClassLoader(), "com.github.benmanes.caffeine.cache.SSW", MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);
6570

@@ -77,5 +82,6 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
7782
}
7883

7984
hints.reflection().registerType(GuildVoiceStateImpl[].class, MemberCategory.UNSAFE_ALLOCATED);
85+
hints.reflection().registerType(SoundboardSound[].class, MemberCategory.UNSAFE_ALLOCATED);
8086
}
8187
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ public class MessageRule {
3333
*/
3434
private Set<String> attachmentSHAs = new HashSet<>();
3535

36+
/**
37+
* There must be no messages older than that number of seconds.
38+
* If this value is {@code 0} or negative, this condition is ignored.
39+
*/
40+
private long noMessagesFromAuthorBeforeSeconds = -1;
41+
3642
/**
3743
* The action to execute on the message.
3844
*/

src/main/java/net/discordjug/javabot/data/h2db/commands/MessageCacheInfoSubcommand.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand;
44
import net.discordjug.javabot.data.config.BotConfig;
55
import net.discordjug.javabot.data.config.GuildConfig;
6+
import net.discordjug.javabot.data.config.guild.MessageCacheConfig;
67
import net.discordjug.javabot.data.h2db.DbActions;
78
import net.discordjug.javabot.data.h2db.message_cache.MessageCache;
89
import net.discordjug.javabot.util.Responses;
@@ -44,13 +45,14 @@ public void execute(SlashCommandInteractionEvent event) {
4445

4546
private MessageEmbed buildInfoEmbed(GuildConfig config, User author) {
4647
long messages = dbActions.count("SELECT count(*) FROM message_cache");
47-
int maxMessages = config.getMessageCacheConfig().getMaxCachedMessages();
48+
MessageCacheConfig messageCacheConfig = config.getMessageCacheConfig();
49+
int maxMessages = messageCacheConfig.getMaxCachedMessages();
4850
return new EmbedBuilder()
4951
.setAuthor(UserUtils.getUserTag(author), null, author.getEffectiveAvatarUrl())
5052
.setTitle("Message Cache Info")
5153
.setColor(Responses.Type.DEFAULT.getColor())
5254
.addField("Table Size", dbActions.getLogicalSize("message_cache") + " bytes", false)
53-
.addField("Message Count", String.valueOf(messageCache.getMessageCount()), true)
55+
.addField("Messages since synchronization", messageCache.getMessageCount() + "/" + messageCacheConfig.getMessageSynchronizationInterval(), true)
5456
.addField("Cached (Memory)", String.format("%s/%s (%.2f%%)", messageCache.cache.size(), maxMessages, ((float) messageCache.cache.size() / maxMessages) * 100), true)
5557
.addField("Cached (Database)", String.format("%s/%s (%.2f%%)", messages, maxMessages, ((float) messages / maxMessages) * 100), true)
5658
.build();

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import java.util.ArrayList;
3636
import java.util.Deque;
3737
import java.util.List;
38+
import java.util.concurrent.ConcurrentLinkedDeque;
3839
import java.util.concurrent.ExecutorService;
3940
import java.util.stream.Collectors;
4041

@@ -50,7 +51,7 @@ public class MessageCache {
5051
/**
5152
* A memory-cache (list) of sent Messages, wrapped to a {@link CachedMessage} object.
5253
*/
53-
public Deque<CachedMessage> cache = new ArrayDeque<>();
54+
public Deque<CachedMessage> cache = new ConcurrentLinkedDeque<>();
5455
/**
5556
* Amount of messages since the last synchronization.
5657
* <p>
@@ -95,8 +96,10 @@ public void synchronize() {
9596
* Synchronizes Messages saved in the Database with what is currently stored in memory and wait until the synchronization finishes.
9697
*/
9798
public void synchronizeNow() {
98-
cacheRepository.delete(cache.size());
99-
cacheRepository.insertList(new ArrayList<>(cache));
99+
if (messageCount == 0) {
100+
return;
101+
}
102+
cacheRepository.replaceWithList(new ArrayList<>(cache));
100103
messageCount = 0;
101104
log.info("Synchronized Database with local Cache.");
102105
}

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

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
99
import org.springframework.jdbc.core.JdbcTemplate;
1010
import org.springframework.stereotype.Repository;
11+
import org.springframework.transaction.support.TransactionTemplate;
1112

1213
import java.sql.*;
1314
import java.util.ArrayList;
@@ -23,14 +24,27 @@
2324
@Repository
2425
public class MessageCacheRepository {
2526
private final JdbcTemplate jdbcTemplate;
26-
27+
private final TransactionTemplate template;
28+
2729
/**
28-
* Inserts a {@link List} of {@link CachedMessage} objects.
30+
* Replaces the entire message cache with a {@link List} of {@link CachedMessage} objects.
2931
*
30-
* @param messages The List to insert.
31-
* @throws SQLException If an error occurs.
32+
* @param messages The new message cache.
3233
*/
33-
public void insertList(@NotNull List<CachedMessage> messages) throws DataAccessException {
34+
public void replaceWithList(@NotNull List<CachedMessage> messages) {
35+
template.execute(_ -> {
36+
clear();
37+
insertList(messages);
38+
return null;
39+
});
40+
}
41+
42+
private void clear() throws DataAccessException {
43+
jdbcTemplate.update("DELETE FROM message_cache_attachments WHERE 1 = 1");
44+
jdbcTemplate.update("DELETE FROM message_cache WHERE 1 = 1");
45+
}
46+
47+
private void insertList(@NotNull List<CachedMessage> messages) throws DataAccessException {
3448
jdbcTemplate.batchUpdate("MERGE INTO message_cache (message_id, author_id, message_content) VALUES (?, ?, ?)",
3549
new BatchPreparedStatementSetter() {
3650
@Override
@@ -77,11 +91,10 @@ public int getBatchSize() {
7791
* Gets all Messages from the Database.
7892
*
7993
* @return A {@link List} of {@link CachedMessage}s.
80-
* @throws SQLException If anything goes wrong.
8194
*/
8295
public List<CachedMessage> getAll() throws DataAccessException {
8396
List<CachedMessage> messagesWithLink = jdbcTemplate.query(
84-
"SELECT * FROM message_cache LEFT JOIN message_cache_attachments ON message_cache.message_id = message_cache_attachments.message_id",
97+
"SELECT * FROM message_cache LEFT JOIN message_cache_attachments ON message_cache.message_id = message_cache_attachments.message_id ORDER BY message_cache.message_id ASC",
8598
(rs, _) -> this.read(rs));
8699
Map<Long, CachedMessage> messages=new LinkedHashMap<>();
87100
for (CachedMessage msg : messagesWithLink) {
@@ -94,22 +107,6 @@ public List<CachedMessage> getAll() throws DataAccessException {
94107
return new ArrayList<>(messages.values());
95108
}
96109

97-
/**
98-
* Deletes the given amount of Messages.
99-
*
100-
* @param amount The amount to delete.
101-
* @return If any rows we're affected.
102-
* @throws SQLException If anything goes wrong.
103-
*/
104-
public boolean delete(int amount) throws DataAccessException {
105-
int rows = jdbcTemplate.update("DELETE FROM message_cache LIMIT ?", amount);
106-
if(rows > 0){
107-
jdbcTemplate.update("DELETE FROM message_cache_attachments WHERE message_id NOT IN (SELECT message_id FROM message_cache)");
108-
return true;
109-
}
110-
return false;
111-
}
112-
113110
private CachedMessage read(ResultSet rs) throws SQLException {
114111
List<String> attachments = new ArrayList<>();
115112
String attachment = rs.getString("link");

src/main/java/net/discordjug/javabot/listener/filter/MessageFilterHandler.java

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import java.util.ArrayList;
1515
import java.util.List;
16+
import java.util.concurrent.Executor;
1617

1718
/**
1819
* This class is responsible for calling {@link MessageFilter}s on incoming messages and optionally replacing the message.
@@ -28,6 +29,7 @@ public class MessageFilterHandler extends ListenerAdapter {
2829

2930
private final List<MessageFilter> filters;
3031
private final AutoMod autoMod;
32+
private final Executor asyncPool;
3133

3234
@Override
3335
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
@@ -42,36 +44,39 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) {
4244
new ArrayList<>(event.getMessage().getEmbeds())
4345
);
4446

45-
boolean handled = false;
4647

47-
for (MessageFilter filter : filters) {
48-
MessageModificationStatus status = filter.processMessage(content);
49-
if (status == MessageModificationStatus.MODIFIED) {
50-
handled = true;
51-
} else if (status == MessageModificationStatus.STOP_PROCESSING) {
52-
return;
53-
}
54-
}
48+
asyncPool.execute(() -> {
49+
boolean handled = false;
5550

56-
if (handled) {
57-
IWebhookContainer webhookContainer = null;
58-
long threadId = 0;
59-
if (event.isFromType(ChannelType.TEXT)) {
60-
webhookContainer = event.getChannel().asTextChannel();
51+
for (MessageFilter filter : filters) {
52+
MessageModificationStatus status = filter.processMessage(content);
53+
if (status == MessageModificationStatus.MODIFIED) {
54+
handled = true;
55+
} else if (status == MessageModificationStatus.STOP_PROCESSING) {
56+
return;
57+
}
6158
}
62-
if (event.isFromThread()) {
63-
StandardGuildChannel parentChannel = event.getChannel()
64-
.asThreadChannel()
65-
.getParentChannel()
66-
.asStandardGuildChannel();
67-
threadId = event.getChannel().getIdLong();
68-
webhookContainer = (IWebhookContainer) parentChannel;
59+
60+
if (handled) {
61+
IWebhookContainer webhookContainer = null;
62+
long threadId = 0;
63+
if (event.isFromType(ChannelType.TEXT)) {
64+
webhookContainer = event.getChannel().asTextChannel();
65+
}
66+
if (event.isFromThread()) {
67+
StandardGuildChannel parentChannel = event.getChannel()
68+
.asThreadChannel()
69+
.getParentChannel()
70+
.asStandardGuildChannel();
71+
threadId = event.getChannel().getIdLong();
72+
webhookContainer = (IWebhookContainer) parentChannel;
73+
}
74+
if (webhookContainer == null) {
75+
return;
76+
}
77+
replaceMessage(webhookContainer, threadId, content);
6978
}
70-
if (webhookContainer == null) {
71-
return;
72-
}
73-
replaceMessage(webhookContainer, threadId, content);
74-
}
79+
});
7580
}
7681

7782
private boolean shouldRunFilters(@NotNull MessageReceivedEvent event) {

src/main/java/net/discordjug/javabot/listener/filter/MessageRuleFilter.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.security.MessageDigest;
1010
import java.security.NoSuchAlgorithmException;
1111
import java.time.Duration;
12+
import java.time.OffsetDateTime;
1213
import java.util.Base64;
1314
import java.util.List;
1415
import java.util.stream.Collectors;
@@ -25,6 +26,8 @@
2526
import net.discordjug.javabot.util.GsonUtils;
2627
import net.dv8tion.jda.api.EmbedBuilder;
2728
import net.dv8tion.jda.api.entities.Message.Attachment;
29+
import net.dv8tion.jda.api.utils.TimeUtil;
30+
import net.dv8tion.jda.api.entities.Guild;
2831
import net.dv8tion.jda.api.entities.Message;
2932
import org.springframework.stereotype.Component;
3033

@@ -103,7 +106,28 @@ private boolean matches(MessageContent content, MessageRule rule) {
103106
}
104107
}
105108
}
106-
return matchesSHA;
109+
if (!matchesSHA) {
110+
return false;
111+
}
112+
if (rule.getNoMessagesFromAuthorBeforeSeconds() > 0) {
113+
OffsetDateTime maxTime = content.event().getMessage().getTimeCreated()
114+
.minusSeconds(rule.getNoMessagesFromAuthorBeforeSeconds());
115+
long authorId = content.event().getAuthor().getIdLong();
116+
return !hasMessagesOlderThan(maxTime, content.event().getGuild(), authorId);
117+
}
118+
return true;
119+
}
120+
121+
private boolean hasMessagesOlderThan(OffsetDateTime maxTime, Guild guild, long authorId) {
122+
if (messageCache.cache.stream()
123+
.filter(msg -> TimeUtil.getTimeCreated(msg.getMessageId()).isBefore(maxTime))
124+
.anyMatch(msg -> msg.getAuthorId() == authorId)) {
125+
return true;
126+
}
127+
return guild.searchMessages()
128+
.authors(authorId)
129+
.maxId(TimeUtil.getDiscordTimestamp(maxTime.toInstant().toEpochMilli()))
130+
.complete().asResults().getTotalResults() > 0;
107131
}
108132

109133
private String computeAttachmentDescription(List<Message.Attachment> attachments) {

0 commit comments

Comments
 (0)