Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public class SystemsConfig {
* The Key used for the bing-search-api.
*/
private String azureSubscriptionKey = "";

/**
* The API key of the Answer Overflow API.
*/
private String answerOverflowApiKey = "";

/**
* The DSN for the Sentry API.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package net.discordjug.javabot.systems.help;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse.BodyHandlers;

import lombok.extern.slf4j.Slf4j;
import net.discordjug.javabot.data.config.BotConfig;
import org.springframework.stereotype.Service;

/**
* A service class for interacting with the Answer Overflow bot.
*/
@Service
@Slf4j
public class AnswerOverflowService {
private final String apiKey;

public AnswerOverflowService(BotConfig config) {
apiKey = config.getSystems().getAnswerOverflowApiKey();
}

/**
* Marks a message as the answer of a forum post with Answer Overflow.
* @param postId The Discord ID of the forum post
* @param messageId The Discord ID of the message that should be marked as the answer.
*/
public void markAnswer(long postId, long messageId) {
if (apiKey == null || apiKey.isBlank()) {
return;
}

try (HttpClient client = HttpClient.newHttpClient()) {
client.sendAsync(
HttpRequest.newBuilder(URI.create("https://www.answeroverflow.com/api/v1/messages/" + postId))
.header("x-api-key", apiKey)
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofString("""
{
"solutionId": "%d"
}
""".formatted(messageId)))
.build(),
BodyHandlers.ofString())
.thenAccept(response -> {
if (response.statusCode() != 200) {
log.warn("Answer Overflow responded with unexpected status code {}, post ID: {}, message ID: {}, body: {}", response.statusCode(), postId, messageId, response.body());
}
})
.exceptionally(e -> {
log.error("An exception occured trying to mark a post as an answer.", e);
return null;
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import xyz.dynxsty.dih4jda.util.ComponentIdBuilder;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
Expand Down Expand Up @@ -79,6 +80,7 @@ protected boolean removeEldestEntry(Map.Entry<Long, Long> eldest) {
return System.currentTimeMillis() > eldest.getValue() || size() >= 32;
}
};
private final AnswerOverflowService answerOverflowService;

@Override
public void onMessageReceived(@NotNull MessageReceivedEvent event) {
Expand Down Expand Up @@ -121,12 +123,8 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) {
return;
}
// cache messages
List<Message> messages = new ArrayList<>();
messages.add(event.getMessage());
if (HELP_POST_MESSAGES.containsKey(post.getIdLong())) {
messages.addAll(HELP_POST_MESSAGES.get(post.getIdLong()));
}
HELP_POST_MESSAGES.put(post.getIdLong(), messages);
HELP_POST_MESSAGES.computeIfAbsent(post.getIdLong(), _ -> new ArrayList<>())
.add(event.getMessage());
// suggest to close post on "problem solved"-messages
replyCloseSuggestionIfPatternMatches(event.getMessage());
}
Expand Down Expand Up @@ -291,17 +289,34 @@ private void handleThanksCloseButton(@NotNull ButtonInteractionEvent event, Help
event.getMessage().delete().queue(s -> {
experienceService.addMessageBasedHelpXP(post, true);
// thank all helpers
getButtonStream(event.getMessage())
.filter(b -> b.getCustomId() != null)
.filter(b -> b.isDisabled() || (b.getCustomId().equals(additionalButtonId)))
.forEach(b -> manager.thankHelper(
event.getGuild(),
post,
Long.parseLong(ComponentIdBuilder.split(b.getCustomId())[2])
));

Set<Long> helpersToThank = getButtonStream(event.getMessage())
.filter(b -> b.getCustomId() != null)
.filter(b -> b.isDisabled() || (b.getCustomId().equals(additionalButtonId)))
.map(b -> Long.parseLong(ComponentIdBuilder.split(b.getCustomId())[2]))
.collect(Collectors.toSet());

for (Long helperId : helpersToThank) {
manager.thankHelper(event.getGuild(), post, helperId);
}
markAnswer(post, helpersToThank);
HELP_POST_MESSAGES.remove(post.getIdLong());
});
}

private void markAnswer(ThreadChannel post, Set<Long> helpers) {
List<Message> messages = HELP_POST_MESSAGES.get(post.getIdLong());
if (messages == null) {
return;
}
for (Message msg : messages.reversed()) {
if (helpers.contains(msg.getAuthor().getIdLong())) {
answerOverflowService.markAnswer(post.getIdLong(), msg.getIdLong());
return;
}
}
}

private void handleReplyGuidelines(@NotNull IReplyCallback callback, @NotNull ForumChannel channel) {
callback.replyEmbeds(new EmbedBuilder().setTitle("Help Guidelines")
.setDescription(channel.getTopic())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.discordjug.javabot.data.config.GuildConfig;
import net.discordjug.javabot.data.config.SystemsConfig;
import net.discordjug.javabot.data.config.guild.QOTWConfig;
import net.discordjug.javabot.systems.help.AnswerOverflowService;
import net.discordjug.javabot.systems.notification.NotificationService;
import net.discordjug.javabot.systems.qotw.QOTWPointsService;
import net.discordjug.javabot.systems.qotw.dao.QuestionQueueRepository;
Expand Down Expand Up @@ -63,6 +64,7 @@ public class QOTWCloseSubmissionsJob {
private final ExecutorService asyncPool;
private final QOTWPointsService pointsService;
private final NotificationService notificationService;
private final AnswerOverflowService answerOverflowService;
private final BotConfig botConfig;

/**
Expand Down Expand Up @@ -131,7 +133,7 @@ private void sendReviewInformation(Guild guild, QOTWConfig qotwConfig, TextChann
s.retrieveAuthor(author -> {
submission.removeThreadMember(author).queue();
if (author.getIdLong() == qotwConfig.getQotwSampleAnswerUserId()) {
SubmissionManager manager = new SubmissionManager(botConfig.get(guild).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool);
SubmissionManager manager = new SubmissionManager(botConfig.get(guild).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool, answerOverflowService);
manager.copySampleAnswerSubmission(submission, author);
} else {
thread
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import net.discordjug.javabot.data.config.BotConfig;
import net.discordjug.javabot.data.config.guild.QOTWConfig;
import net.discordjug.javabot.systems.help.AnswerOverflowService;
import net.discordjug.javabot.systems.notification.NotificationService;
import net.discordjug.javabot.systems.qotw.QOTWPointsService;
import net.discordjug.javabot.systems.qotw.dao.QuestionQueueRepository;
Expand Down Expand Up @@ -33,6 +34,7 @@ public class QOTWUserReminderJob {
private final QOTWPointsService pointsService;
private final NotificationService notificationService;
private final QuestionQueueRepository questionQueueRepository;
private final AnswerOverflowService answerOverflowService;
private final ExecutorService asyncPool;

/**
Expand All @@ -42,7 +44,7 @@ public class QOTWUserReminderJob {
public void execute() {
for (Guild guild : jda.getGuilds()) {
QOTWConfig config = botConfig.get(guild).getQotwConfig();
List<QOTWSubmission> submissions = new SubmissionManager(config, pointsService, questionQueueRepository, notificationService, asyncPool).getActiveSubmissions();
List<QOTWSubmission> submissions = new SubmissionManager(config, pointsService, questionQueueRepository, notificationService, asyncPool, answerOverflowService).getActiveSubmissions();
for (QOTWSubmission submission : submissions) {
submission.retrieveAuthor(author -> {
UserPreference preference = userPreferenceService.getOrCreate(author.getIdLong(), Preference.QOTW_REMINDER);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import lombok.RequiredArgsConstructor;
import net.discordjug.javabot.annotations.AutoDetectableComponentHandler;
import net.discordjug.javabot.data.config.BotConfig;
import net.discordjug.javabot.systems.help.AnswerOverflowService;
import net.discordjug.javabot.systems.notification.NotificationService;
import net.discordjug.javabot.systems.qotw.QOTWPointsService;
import net.discordjug.javabot.systems.qotw.dao.QuestionQueueRepository;
Expand All @@ -29,11 +30,12 @@ public class SubmissionInteractionManager implements ButtonHandler, StringSelect
private final NotificationService notificationService;
private final BotConfig botConfig;
private final QuestionQueueRepository questionQueueRepository;
private final AnswerOverflowService answerOverflowService;
private final ExecutorService asyncPool;

@Override
public void handleButton(@NotNull ButtonInteractionEvent event, Button button) {
SubmissionManager manager = new SubmissionManager(botConfig.get(event.getGuild()).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool);
SubmissionManager manager = new SubmissionManager(botConfig.get(event.getGuild()).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool, answerOverflowService);
String[] id = ComponentIdBuilder.split(event.getComponentId());
switch (id[1]) {
case "submit" -> manager.handleSubmission(event, Integer.parseInt(id[2])).queue();
Expand All @@ -43,7 +45,7 @@ public void handleButton(@NotNull ButtonInteractionEvent event, Button button) {

@Override
public void handleStringSelectMenu(@NotNull StringSelectInteractionEvent event, @NotNull List<String> values) {
SubmissionManager manager = new SubmissionManager(botConfig.get(event.getGuild()).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool);
SubmissionManager manager = new SubmissionManager(botConfig.get(event.getGuild()).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool, answerOverflowService);
String[] id = ComponentIdBuilder.split(event.getComponentId());
switch (id[1]) {
case "review" -> manager.handleSelectReview(event, id[2]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.discordjug.javabot.data.config.guild.QOTWConfig;
import net.discordjug.javabot.systems.help.AnswerOverflowService;
import net.discordjug.javabot.systems.notification.NotificationService;
import net.discordjug.javabot.systems.qotw.QOTWPointsService;
import net.discordjug.javabot.systems.qotw.dao.QuestionQueueRepository;
Expand Down Expand Up @@ -39,6 +40,8 @@
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;

import club.minnced.discord.webhook.receive.ReadonlyMessage;

/**
* Handles & manages QOTW Submissions by using Discords {@link ThreadChannel}s.
*/
Expand All @@ -57,6 +60,7 @@ public class SubmissionManager {
private final QuestionQueueRepository questionQueueRepository;
private final NotificationService notificationService;
private final ExecutorService asyncPool;
private final AnswerOverflowService answerOverflowService;

/**
* Handles the "Submit your Answer" Button interaction.
Expand Down Expand Up @@ -221,14 +225,19 @@ private void sendToQOTWAnswerArchive(ThreadChannel thread, User author, Accepted
ThreadChannel newestPost = newestPostOptional.get();
WebhookUtil.ensureWebhookExists(newestPost.getParentChannel().asForumChannel(), wh ->
getMessagesByUser(thread, author).thenAccept(messages -> {
for (Message message : messages) {
boolean lastMessage = messages.indexOf(message) + 1 == messages.size();
for (int i = 0; i < messages.size(); i++) {
Message message = messages.get(i);
boolean lastMessage = i + 1 == messages.size();
if (message.getAuthor().isBot() || message.getType() != MessageType.DEFAULT) continue;
ReadonlyMessage firstPostedMessage;
if (message.getContentRaw().length() > 2000) {
WebhookUtil.mirrorMessageToWebhook(wh, message, message.getContentRaw().substring(0, 2000), newestPost.getIdLong(), null, null).join();
firstPostedMessage = WebhookUtil.mirrorMessageToWebhook(wh, message, message.getContentRaw().substring(0, 2000), newestPost.getIdLong(), null, null).join();
WebhookUtil.mirrorMessageToWebhook(wh, message, message.getContentRaw().substring(2000), newestPost.getIdLong(), null, lastMessage ? List.of(buildAuthorEmbed(author, type)) : null).join();
} else {
WebhookUtil.mirrorMessageToWebhook(wh, message, message.getContentRaw(), newestPost.getIdLong(), null, lastMessage ? List.of(buildAuthorEmbed(author, type)) : null).join();
firstPostedMessage = WebhookUtil.mirrorMessageToWebhook(wh, message, message.getContentRaw(), newestPost.getIdLong(), null, lastMessage ? List.of(buildAuthorEmbed(author, type)) : null).join();
}
if (i == 0 && (type == AcceptedAnswerType.BEST_ANSWER || type == AcceptedAnswerType.SAMPLE_ANSWER)) {
answerOverflowService.markAnswer(firstPostedMessage.getChannelId(), firstPostedMessage.getId());
}
}
}).exceptionally(err->{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import net.discordjug.javabot.data.config.BotConfig;
import net.discordjug.javabot.data.config.guild.QOTWConfig;
import net.discordjug.javabot.systems.help.AnswerOverflowService;
import net.discordjug.javabot.systems.notification.NotificationService;
import net.discordjug.javabot.systems.qotw.QOTWPointsService;
import net.discordjug.javabot.systems.qotw.dao.QuestionQueueRepository;
Expand Down Expand Up @@ -34,7 +35,7 @@ public class QOTWReviewSubcommand extends SlashCommand.Subcommand {
private final QuestionQueueRepository questionQueueRepository;
private final BotConfig botConfig;
private final ExecutorService asyncPool;

private final AnswerOverflowService answerOverflowService;

/**
* The constructor of this class, which sets the corresponding {@link SubcommandData}.
Expand All @@ -43,13 +44,15 @@ public class QOTWReviewSubcommand extends SlashCommand.Subcommand {
* @param questionQueueRepository The {@link QuestionQueueRepository}.
* @param botConfig The main configuration of the bot
* @param asyncPool The main thread pool for asynchronous operations
* @param answerOverflowService The {@link AnswerOverflowService} for interacting with the Answer Overflow bot.
*/
public QOTWReviewSubcommand(QOTWPointsService pointsService, NotificationService notificationService, QuestionQueueRepository questionQueueRepository, BotConfig botConfig, ExecutorService asyncPool) {
public QOTWReviewSubcommand(QOTWPointsService pointsService, NotificationService notificationService, QuestionQueueRepository questionQueueRepository, BotConfig botConfig, ExecutorService asyncPool, AnswerOverflowService answerOverflowService) {
this.pointsService = pointsService;
this.notificationService = notificationService;
this.questionQueueRepository = questionQueueRepository;
this.botConfig = botConfig;
this.asyncPool = asyncPool;
this.answerOverflowService = answerOverflowService;
setCommandData(new SubcommandData("review", "Administrative command for reviewing QOTW-submissions")
.addOptions(
new OptionData(OptionType.CHANNEL, "submission", "A users' submission", true)
Expand Down Expand Up @@ -80,7 +83,7 @@ public void execute(@NotNull SlashCommandInteractionEvent event) {
event.deferReply().queue();
QOTWSubmission submission = new QOTWSubmission(submissionThread);
submission.retrieveAuthor(author -> {
SubmissionManager manager = new SubmissionManager(qotwConfig, pointsService, questionQueueRepository, notificationService, asyncPool);
SubmissionManager manager = new SubmissionManager(qotwConfig, pointsService, questionQueueRepository, notificationService, asyncPool, answerOverflowService);
if (state.contains("ACCEPT")) {
manager.acceptSubmission(submissionThread, author, event.getMember(), state.equals("ACCEPT_BEST"));
Responses.success(event.getHook(), "Submission Accepted", "Successfully accepted submission by " + author.getAsMention()).queue();
Expand Down