Skip to content

Commit d39dfe7

Browse files
committed
Merge remote-tracking branch 'javabot/main' into custom-automod-search
2 parents b8539e7 + 957ab6d commit d39dfe7

31 files changed

Lines changed: 357 additions & 149 deletions

src/main/java/net/discordjug/javabot/data/config/SystemsConfig.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ public class SystemsConfig {
2424
* The Key used for the bing-search-api.
2525
*/
2626
private String azureSubscriptionKey = "";
27+
28+
/**
29+
* The API key of the Answer Overflow API.
30+
*/
31+
private String answerOverflowApiKey = "";
2732

2833
/**
2934
* The DSN for the Sentry API.

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/MigrationUtils.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,32 @@
77
import java.nio.file.*;
88
import java.util.HashMap;
99
import java.util.Map;
10+
import java.util.Objects;
1011

12+
import lombok.extern.slf4j.Slf4j;
1113
import net.discordjug.javabot.data.h2db.commands.MigrationsListSubcommand;
1214

1315
/**
1416
* Utility class that handles SQL Migrations.
1517
*/
18+
@Slf4j
1619
public class MigrationUtils {
20+
21+
private static final Path migrationDirectory;
1722

1823
private MigrationUtils() {
1924
}
25+
26+
static {
27+
Path dir;
28+
try {
29+
dir = createMigrationsDirectory();
30+
} catch (IOException | URISyntaxException e) {
31+
log.error("Cannot create database migration directory", e);
32+
dir = null;
33+
}
34+
migrationDirectory = dir;
35+
}
2036

2137
/**
2238
* Tries to get the Migrations Directories' Path.
@@ -26,6 +42,10 @@ private MigrationUtils() {
2642
* @throws IOException If an error occurs.
2743
*/
2844
public static Path getMigrationsDirectory() throws URISyntaxException, IOException {
45+
return Objects.requireNonNull(migrationDirectory);
46+
}
47+
48+
private static Path createMigrationsDirectory() throws IOException, URISyntaxException {
2949
URL resource = MigrationsListSubcommand.class.getResource("/database/migrations/");
3050
if (resource == null) throw new IOException("Missing resource /migrations/");
3151
URI uri = resource.toURI();

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ public ExportTableSubcommand(ExecutorService asyncPool, SystemsConfig systemsCon
5252
.addChoice("Message Cache", "MESSAGE_CACHE")
5353
.addChoice("Question of the Week Accounts", "QOTW_POINTS")
5454
.addChoice("Question of the Week Questions", "QOTW_QUESTION")
55-
.addChoice("Reserved Help Channels", "RESERVED_HELP_CHANNELS")
5655
.addChoice("Starboard", "STARBOARD")
5756
.addChoice("Warns", "WARN")
5857
.addChoice("User Preferences", "USER_PREFERENCES"),

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ public void execute(@NotNull SlashCommandInteractionEvent event) {
9393
return;
9494
}
9595
String sql = Files.readString(migrationFile);
96-
migrationsDir.getFileSystem().close();
9796
String[] statements = sql.split("\\s*;\\s*");
9897
if (statements.length == 0) {
9998
Responses.error(event, "The migration `" + migrationName + "` does not contain any statements. Please remove or edit it before running again.").queue();

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import net.dv8tion.jda.api.components.buttons.Button;
1717
import net.dv8tion.jda.api.entities.*;
1818
import net.dv8tion.jda.api.entities.Message.Attachment;
19+
import net.dv8tion.jda.api.entities.channel.Channel;
1920
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
2021
import net.dv8tion.jda.api.requests.restaction.MessageCreateAction;
2122
import net.dv8tion.jda.api.utils.FileUpload;
@@ -149,7 +150,7 @@ public void sendUpdatedMessageToLog(Message updated, CachedMessage before) {
149150
* @param channel The message's {@link MessageChannel}.
150151
* @param message The {@link CachedMessage}.
151152
*/
152-
public void sendDeletedMessageToLog(Guild guild, MessageChannel channel, CachedMessage message) {
153+
public void sendDeletedMessageToLog(Guild guild, Channel channel, CachedMessage message) {
153154
MessageCacheConfig config = botConfig.get(guild).getMessageCacheConfig();
154155
if (config.getMessageCacheLogChannel() == null) return;
155156
guild.getJDA().retrieveUserById(message.getAuthorId()).queue(author -> {
@@ -199,7 +200,7 @@ private void requestMessageAttachments(CachedMessage message) {
199200
* @param contentFieldName the name of the field containing the message content in the embed.
200201
* @return an {@link EmbedBuilder} with information about the message.
201202
*/
202-
public EmbedBuilder buildMessageCacheEmbed(MessageChannel channel, User author, CachedMessage message, String contentFieldName) {
203+
public EmbedBuilder buildMessageCacheEmbed(Channel channel, User author, CachedMessage message, String contentFieldName) {
203204
long epoch = IdCalculatorCommand.getTimestampFromId(message.getMessageId()) / 1000;
204205
return new EmbedBuilder()
205206
.setAuthor(UserUtils.getUserTag(author), null, author.getEffectiveAvatarUrl())
@@ -236,7 +237,7 @@ private MessageEmbed buildMessageEditEmbed(User author, MessageChannel channel,
236237
.build();
237238
}
238239

239-
private MessageEmbed buildMessageDeleteEmbed(User author, MessageChannel channel, CachedMessage message) {
240+
private MessageEmbed buildMessageDeleteEmbed(User author, Channel channel, CachedMessage message) {
240241
EmbedBuilder eb = buildMessageCacheEmbed(channel, author, message, "Message Content")
241242
.setTitle("Message Deleted")
242243
.setColor(Responses.Type.ERROR.getColor());

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

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import net.discordjug.javabot.data.config.guild.MessageCacheConfig;
55
import net.discordjug.javabot.data.h2db.message_cache.model.CachedMessage;
66
import net.dv8tion.jda.api.entities.Message;
7+
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
8+
import net.dv8tion.jda.api.events.channel.ChannelDeleteEvent;
79
import net.dv8tion.jda.api.events.message.MessageDeleteEvent;
810
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
911
import net.dv8tion.jda.api.events.message.MessageUpdateEvent;
@@ -39,25 +41,35 @@ public void onMessageUpdate(@NotNull MessageUpdateEvent event) {
3941
CachedMessage before;
4042
if (optional.isPresent()) {
4143
CachedMessage inCache= optional.get();
42-
before = new CachedMessage(inCache.getMessageId(), inCache.getAuthorId(), inCache.getMessageContent(), inCache.getAttachments());
44+
before = new CachedMessage(inCache.getMessageId(), inCache.getAuthorId(), inCache.getChannelId(),inCache.getMessageContent(), inCache.getAttachments());
4345
inCache.init(event.getMessage());
4446
} else {
45-
before = new CachedMessage(event.getMessageIdLong(), event.getAuthor().getIdLong(), "[unknown content]", List.of());
47+
before = new CachedMessage(event.getMessageIdLong(), event.getAuthor().getIdLong(), event.getChannel().getIdLong(),"[unknown content]", List.of());
4648
messageCache.cache(event.getMessage());
4749
}
4850
messageCache.sendUpdatedMessageToLog(event.getMessage(), before);
4951
}
5052

5153
@Override
5254
public void onMessageDelete(@NotNull MessageDeleteEvent event) {
53-
Optional<CachedMessage> optional = messageCache.cache.stream().filter(m -> m.getMessageId() == event.getMessageIdLong()).findFirst();
55+
processMessageDeletion(event.getMessageIdLong(), event.getGuildChannel());
56+
}
57+
58+
@Override
59+
public void onChannelDelete(ChannelDeleteEvent event) {
60+
if (event.getChannelType().isThread()) {
61+
processMessageDeletion(event.getChannel().getIdLong(), event.getChannel().asThreadChannel().getParentChannel());
62+
}
63+
}
64+
65+
private void processMessageDeletion(long messageId, GuildChannel channel) {
66+
Optional<CachedMessage> optional = messageCache.cache.stream().filter(m -> m.getMessageId() == messageId).findFirst();
5467
optional.ifPresent(message -> {
55-
messageCache.sendDeletedMessageToLog(event.getGuild(), event.getChannel(), message);
68+
messageCache.sendDeletedMessageToLog(channel.getGuild(), channel, message);
5669
messageCache.cache.remove(message);
5770
});
5871
}
5972

60-
6173
/**
6274
* Checks whether the given message should be ignored by the cache.
6375
* <p>

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,15 +44,16 @@ private void clear() throws DataAccessException {
4444
jdbcTemplate.update("DELETE FROM message_cache WHERE 1 = 1");
4545
}
4646

47-
private void insertList(@NotNull List<CachedMessage> messages) throws DataAccessException {
48-
jdbcTemplate.batchUpdate("MERGE INTO message_cache (message_id, author_id, message_content) VALUES (?, ?, ?)",
47+
public void insertList(@NotNull List<CachedMessage> messages) throws DataAccessException {
48+
jdbcTemplate.batchUpdate("MERGE INTO message_cache (message_id, author_id, channel_id, message_content) VALUES (?, ?, ?, ?)",
4949
new BatchPreparedStatementSetter() {
5050
@Override
5151
public void setValues(PreparedStatement stmt, int i) throws SQLException {
5252
CachedMessage msg = messages.get(i);
5353
stmt.setLong(1, msg.getMessageId());
5454
stmt.setLong(2, msg.getAuthorId());
55-
stmt.setString(3, msg.getMessageContent());
55+
stmt.setLong(3,msg.getChannelId());
56+
stmt.setString(4, msg.getMessageContent());
5657
stmt.executeUpdate();
5758
}
5859

@@ -101,7 +102,7 @@ public List<CachedMessage> getAll() throws DataAccessException {
101102
messages.merge(msg.getMessageId(), msg, (oldValue, value) -> {
102103
ArrayList<String> attachments = new ArrayList<>(oldValue.getAttachments());
103104
attachments.addAll(value.getAttachments());
104-
return new CachedMessage(oldValue.getMessageId(), oldValue.getAuthorId(), oldValue.getMessageContent(), attachments);
105+
return new CachedMessage(oldValue.getMessageId(), oldValue.getAuthorId(), oldValue.getChannelId(),oldValue.getMessageContent(), attachments);
105106
});
106107
}
107108
return new ArrayList<>(messages.values());
@@ -116,6 +117,7 @@ private CachedMessage read(ResultSet rs) throws SQLException {
116117
return new CachedMessage(
117118
rs.getLong("message_cache.message_id"),
118119
rs.getLong("author_id"),
120+
rs.getLong("channel_id"),
119121
rs.getString("message_content"),
120122
attachments);
121123
}

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
@@ -19,25 +19,29 @@
1919
public class CachedMessage {
2020
private final long messageId;
2121
private final long authorId;
22+
private final long channelId;
2223
private String messageContent;
2324
private List<String> attachments=new ArrayList<>();
2425

25-
private CachedMessage(long messageId, long authorId) {
26+
private CachedMessage(long messageId, long authorId,long channelId) {
2627
this.messageId = messageId;
2728
this.authorId = authorId;
29+
this.channelId = channelId;
2830
}
2931

3032
/**
3133
* Creates a {@link CachedMessage} with the given information.
3234
* @param messageId The Discord ID of the message
3335
* @param authorId The Discord ID of the message author
36+
* @param channelId The Discord ID of the message channel
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
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package net.discordjug.javabot.systems.help;
2+
3+
import java.net.URI;
4+
import java.net.http.HttpClient;
5+
import java.net.http.HttpRequest;
6+
import java.net.http.HttpRequest.BodyPublishers;
7+
import java.net.http.HttpResponse.BodyHandlers;
8+
9+
import lombok.extern.slf4j.Slf4j;
10+
import net.discordjug.javabot.data.config.BotConfig;
11+
import org.springframework.stereotype.Service;
12+
13+
/**
14+
* A service class for interacting with the Answer Overflow bot.
15+
*/
16+
@Service
17+
@Slf4j
18+
public class AnswerOverflowService {
19+
private final String apiKey;
20+
21+
public AnswerOverflowService(BotConfig config) {
22+
apiKey = config.getSystems().getAnswerOverflowApiKey();
23+
}
24+
25+
/**
26+
* Marks a message as the answer of a forum post with Answer Overflow.
27+
* @param postId The Discord ID of the forum post
28+
* @param messageId The Discord ID of the message that should be marked as the answer.
29+
*/
30+
public void markAnswer(long postId, long messageId) {
31+
if (apiKey == null || apiKey.isBlank()) {
32+
return;
33+
}
34+
35+
try (HttpClient client = HttpClient.newHttpClient()) {
36+
client.sendAsync(
37+
HttpRequest.newBuilder(URI.create("https://www.answeroverflow.com/api/v1/messages/" + postId))
38+
.header("x-api-key", apiKey)
39+
.header("Content-Type", "application/json")
40+
.POST(BodyPublishers.ofString("""
41+
{
42+
"solutionId": "%d"
43+
}
44+
""".formatted(messageId)))
45+
.build(),
46+
BodyHandlers.ofString())
47+
.thenAccept(response -> {
48+
if (response.statusCode() != 200) {
49+
log.warn("Answer Overflow responded with unexpected status code {}, post ID: {}, message ID: {}, body: {}", response.statusCode(), postId, messageId, response.body());
50+
}
51+
})
52+
.exceptionally(e -> {
53+
log.error("An exception occured trying to mark a post as an answer.", e);
54+
return null;
55+
});
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)