Skip to content
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
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>4.0.3</version>
<version>4.1.0</version>
</parent>

<properties>
Expand Down Expand Up @@ -43,7 +43,7 @@
<dependency>
<groupId>net.dv8tion</groupId>
<artifactId>JDA</artifactId>
<version>6.2.0</version>
<version>6.5.0</version>
<exclusions>
<exclusion>
<artifactId>opus-java</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package net.discordjug.javabot;

import java.util.concurrent.ThreadPoolExecutor;

import club.minnced.discord.webhook.send.WebhookEmbed;
import com.zaxxer.hikari.HikariConfig;
import net.discordjug.javabot.data.config.BotConfig;
Expand All @@ -15,6 +17,7 @@
import net.discordjug.javabot.data.config.guild.QOTWConfig;
import net.discordjug.javabot.data.config.guild.ServerLockConfig;
import net.discordjug.javabot.data.config.guild.StarboardConfig;
import net.dv8tion.jda.api.entities.SoundboardSound;
import net.dv8tion.jda.api.hooks.ListenerAdapter;
import net.dv8tion.jda.internal.entities.GuildVoiceStateImpl;
import net.dv8tion.jda.internal.requests.restaction.PermOverrideData;
Expand Down Expand Up @@ -60,6 +63,8 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
// JDA needs to be able to access listener methods
hints.reflection().registerType(ListenerAdapter.class, MemberCategory.INVOKE_PUBLIC_METHODS);

hints.reflection().registerType(ThreadPoolExecutor.class, MemberCategory.INVOKE_PUBLIC_METHODS);

// caffeine
hints.reflection().registerTypeIfPresent(getClass().getClassLoader(), "com.github.benmanes.caffeine.cache.SSW", MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS);

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

hints.reflection().registerType(GuildVoiceStateImpl[].class, MemberCategory.UNSAFE_ALLOCATED);
hints.reflection().registerType(SoundboardSound[].class, MemberCategory.UNSAFE_ALLOCATED);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public class MessageRule {
*/
private Set<String> attachmentSHAs = new HashSet<>();

/**
* There must be no messages older than that number of seconds.
* If this value is {@code 0} or negative, this condition is ignored.
*/
private long noMessagesFromAuthorBeforeSeconds = -1;

/**
* The action to execute on the message.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import xyz.dynxsty.dih4jda.interactions.commands.application.SlashCommand;
import net.discordjug.javabot.data.config.BotConfig;
import net.discordjug.javabot.data.config.GuildConfig;
import net.discordjug.javabot.data.config.guild.MessageCacheConfig;
import net.discordjug.javabot.data.h2db.DbActions;
import net.discordjug.javabot.data.h2db.message_cache.MessageCache;
import net.discordjug.javabot.util.Responses;
Expand Down Expand Up @@ -44,13 +45,14 @@ public void execute(SlashCommandInteractionEvent event) {

private MessageEmbed buildInfoEmbed(GuildConfig config, User author) {
long messages = dbActions.count("SELECT count(*) FROM message_cache");
int maxMessages = config.getMessageCacheConfig().getMaxCachedMessages();
MessageCacheConfig messageCacheConfig = config.getMessageCacheConfig();
int maxMessages = messageCacheConfig.getMaxCachedMessages();
return new EmbedBuilder()
.setAuthor(UserUtils.getUserTag(author), null, author.getEffectiveAvatarUrl())
.setTitle("Message Cache Info")
.setColor(Responses.Type.DEFAULT.getColor())
.addField("Table Size", dbActions.getLogicalSize("message_cache") + " bytes", false)
.addField("Message Count", String.valueOf(messageCache.getMessageCount()), true)
.addField("Messages since synchronization", messageCache.getMessageCount() + "/" + messageCacheConfig.getMessageSynchronizationInterval(), true)
.addField("Cached (Memory)", String.format("%s/%s (%.2f%%)", messageCache.cache.size(), maxMessages, ((float) messageCache.cache.size() / maxMessages) * 100), true)
.addField("Cached (Database)", String.format("%s/%s (%.2f%%)", messages, maxMessages, ((float) messages / maxMessages) * 100), true)
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutorService;
import java.util.stream.Collectors;

Expand All @@ -50,7 +51,7 @@ public class MessageCache {
/**
* A memory-cache (list) of sent Messages, wrapped to a {@link CachedMessage} object.
*/
public Deque<CachedMessage> cache = new ArrayDeque<>();
public Deque<CachedMessage> cache = new ConcurrentLinkedDeque<>();
/**
* Amount of messages since the last synchronization.
* <p>
Expand Down Expand Up @@ -95,8 +96,10 @@ public void synchronize() {
* Synchronizes Messages saved in the Database with what is currently stored in memory and wait until the synchronization finishes.
*/
public void synchronizeNow() {
cacheRepository.delete(cache.size());
cacheRepository.insertList(new ArrayList<>(cache));
if (messageCount == 0) {
return;
}
cacheRepository.replaceWithList(new ArrayList<>(cache));
messageCount = 0;
log.info("Synchronized Database with local Cache.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.support.TransactionTemplate;

import java.sql.*;
import java.util.ArrayList;
Expand All @@ -23,14 +24,27 @@
@Repository
public class MessageCacheRepository {
private final JdbcTemplate jdbcTemplate;

private final TransactionTemplate template;

/**
* Inserts a {@link List} of {@link CachedMessage} objects.
* Replaces the entire message cache with a {@link List} of {@link CachedMessage} objects.
*
* @param messages The List to insert.
* @throws SQLException If an error occurs.
* @param messages The new message cache.
*/
public void insertList(@NotNull List<CachedMessage> messages) throws DataAccessException {
public void replaceWithList(@NotNull List<CachedMessage> messages) {
template.execute(_ -> {
clear();
insertList(messages);
return null;
});
}

private void clear() throws DataAccessException {
jdbcTemplate.update("DELETE FROM message_cache_attachments WHERE 1 = 1");
jdbcTemplate.update("DELETE FROM message_cache WHERE 1 = 1");
}

private void insertList(@NotNull List<CachedMessage> messages) throws DataAccessException {
jdbcTemplate.batchUpdate("MERGE INTO message_cache (message_id, author_id, message_content) VALUES (?, ?, ?)",
new BatchPreparedStatementSetter() {
@Override
Expand Down Expand Up @@ -77,11 +91,10 @@ public int getBatchSize() {
* Gets all Messages from the Database.
*
* @return A {@link List} of {@link CachedMessage}s.
* @throws SQLException If anything goes wrong.
*/
public List<CachedMessage> getAll() throws DataAccessException {
List<CachedMessage> messagesWithLink = jdbcTemplate.query(
"SELECT * FROM message_cache LEFT JOIN message_cache_attachments ON message_cache.message_id = message_cache_attachments.message_id",
"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",
(rs, _) -> this.read(rs));
Map<Long, CachedMessage> messages=new LinkedHashMap<>();
for (CachedMessage msg : messagesWithLink) {
Expand All @@ -94,22 +107,6 @@ public List<CachedMessage> getAll() throws DataAccessException {
return new ArrayList<>(messages.values());
}

/**
* Deletes the given amount of Messages.
*
* @param amount The amount to delete.
* @return If any rows we're affected.
* @throws SQLException If anything goes wrong.
*/
public boolean delete(int amount) throws DataAccessException {
int rows = jdbcTemplate.update("DELETE FROM message_cache LIMIT ?", amount);
if(rows > 0){
jdbcTemplate.update("DELETE FROM message_cache_attachments WHERE message_id NOT IN (SELECT message_id FROM message_cache)");
return true;
}
return false;
}

private CachedMessage read(ResultSet rs) throws SQLException {
List<String> attachments = new ArrayList<>();
String attachment = rs.getString("link");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;

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

private final List<MessageFilter> filters;
private final AutoMod autoMod;
private final Executor asyncPool;

@Override
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
Expand All @@ -42,36 +44,39 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) {
new ArrayList<>(event.getMessage().getEmbeds())
);

boolean handled = false;

for (MessageFilter filter : filters) {
MessageModificationStatus status = filter.processMessage(content);
if (status == MessageModificationStatus.MODIFIED) {
handled = true;
} else if (status == MessageModificationStatus.STOP_PROCESSING) {
return;
}
}
asyncPool.execute(() -> {
boolean handled = false;

if (handled) {
IWebhookContainer webhookContainer = null;
long threadId = 0;
if (event.isFromType(ChannelType.TEXT)) {
webhookContainer = event.getChannel().asTextChannel();
for (MessageFilter filter : filters) {
MessageModificationStatus status = filter.processMessage(content);
if (status == MessageModificationStatus.MODIFIED) {
handled = true;
} else if (status == MessageModificationStatus.STOP_PROCESSING) {
return;
}
}
if (event.isFromThread()) {
StandardGuildChannel parentChannel = event.getChannel()
.asThreadChannel()
.getParentChannel()
.asStandardGuildChannel();
threadId = event.getChannel().getIdLong();
webhookContainer = (IWebhookContainer) parentChannel;

if (handled) {
IWebhookContainer webhookContainer = null;
long threadId = 0;
if (event.isFromType(ChannelType.TEXT)) {
webhookContainer = event.getChannel().asTextChannel();
}
if (event.isFromThread()) {
StandardGuildChannel parentChannel = event.getChannel()
.asThreadChannel()
.getParentChannel()
.asStandardGuildChannel();
threadId = event.getChannel().getIdLong();
webhookContainer = (IWebhookContainer) parentChannel;
}
if (webhookContainer == null) {
return;
}
replaceMessage(webhookContainer, threadId, content);
}
if (webhookContainer == null) {
return;
}
replaceMessage(webhookContainer, threadId, content);
}
});
}

private boolean shouldRunFilters(@NotNull MessageReceivedEvent event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.Base64;
import java.util.List;
import java.util.stream.Collectors;
Expand All @@ -25,6 +26,8 @@
import net.discordjug.javabot.util.GsonUtils;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.entities.Message.Attachment;
import net.dv8tion.jda.api.utils.TimeUtil;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import org.springframework.stereotype.Component;

Expand Down Expand Up @@ -103,7 +106,28 @@ private boolean matches(MessageContent content, MessageRule rule) {
}
}
}
return matchesSHA;
if (!matchesSHA) {
return false;
}
if (rule.getNoMessagesFromAuthorBeforeSeconds() > 0) {
OffsetDateTime maxTime = content.event().getMessage().getTimeCreated()
.minusSeconds(rule.getNoMessagesFromAuthorBeforeSeconds());
long authorId = content.event().getAuthor().getIdLong();
return !hasMessagesOlderThan(maxTime, content.event().getGuild(), authorId);
}
return true;
}

private boolean hasMessagesOlderThan(OffsetDateTime maxTime, Guild guild, long authorId) {
if (messageCache.cache.stream()
.filter(msg -> TimeUtil.getTimeCreated(msg.getMessageId()).isBefore(maxTime))
.anyMatch(msg -> msg.getAuthorId() == authorId)) {
return true;
}
return guild.searchMessages()
.authors(authorId)
.maxId(TimeUtil.getDiscordTimestamp(maxTime.toInstant().toEpochMilli()))
.complete().asResults().getTotalResults() > 0;
}

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