| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -24,6 +24,11 @@ public class SystemsConfig { | |||
| 24 | 24 | * The Key used for the bing-search-api. | |
| 25 | 25 | */ | |
| 26 | 26 | private String azureSubscriptionKey = ""; | |
| 27 | + | ||
| 28 | + /** | ||
| 29 | + * The API key of the Answer Overflow API. | ||
| 30 | + */ | ||
| 31 | + private String answerOverflowApiKey = ""; | ||
| 27 | 32 | ||
| 28 | 33 | /** | |
| 29 | 34 | * The DSN for the Sentry API. | |
| Original file line number | Diff line number | Diff 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 | + } | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -30,6 +30,7 @@ | |||
| 30 | 30 | import xyz.dynxsty.dih4jda.util.ComponentIdBuilder; | |
| 31 | 31 | ||
| 32 | 32 | import java.util.*; | |
| 33 | + import java.util.stream.Collectors; | ||
| 33 | 34 | import java.util.stream.Stream; | |
| 34 | 35 | ||
| 35 | 36 | /** | |
@@ -79,6 +80,7 @@ protected boolean removeEldestEntry(Map.Entry<Long, Long> eldest) { | |||
| 79 | 80 | return System.currentTimeMillis() > eldest.getValue() || size() >= 32; | |
| 80 | 81 | } | |
| 81 | 82 | }; | |
| 83 | + private final AnswerOverflowService answerOverflowService; | ||
| 82 | 84 | ||
| 83 | 85 | @Override | |
| 84 | 86 | public void onMessageReceived(@NotNull MessageReceivedEvent event) { | |
@@ -121,12 +123,8 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) { | |||
| 121 | 123 | return; | |
| 122 | 124 | } | |
| 123 | 125 | // cache messages | |
| 124 | - List<Message> messages = new ArrayList<>(); | ||
| 125 | - messages.add(event.getMessage()); | ||
| 126 | - if (HELP_POST_MESSAGES.containsKey(post.getIdLong())) { | ||
| 127 | - messages.addAll(HELP_POST_MESSAGES.get(post.getIdLong())); | ||
| 128 | - } | ||
| 129 | - HELP_POST_MESSAGES.put(post.getIdLong(), messages); | ||
| 126 | + HELP_POST_MESSAGES.computeIfAbsent(post.getIdLong(), _ -> new ArrayList<>()) | ||
| 127 | + .add(event.getMessage()); | ||
| 130 | 128 | // suggest to close post on "problem solved"-messages | |
| 131 | 129 | replyCloseSuggestionIfPatternMatches(event.getMessage()); | |
| 132 | 130 | } | |
@@ -291,17 +289,34 @@ private void handleThanksCloseButton(@NotNull ButtonInteractionEvent event, Help | |||
| 291 | 289 | event.getMessage().delete().queue(s -> { | |
| 292 | 290 | experienceService.addMessageBasedHelpXP(post, true); | |
| 293 | 291 | // thank all helpers | |
| 294 | - getButtonStream(event.getMessage()) | ||
| 295 | - .filter(b -> b.getCustomId() != null) | ||
| 296 | - .filter(b -> b.isDisabled() || (b.getCustomId().equals(additionalButtonId))) | ||
| 297 | - .forEach(b -> manager.thankHelper( | ||
| 298 | - event.getGuild(), | ||
| 299 | - post, | ||
| 300 | - Long.parseLong(ComponentIdBuilder.split(b.getCustomId())[2]) | ||
| 301 | - )); | ||
| 292 | + | ||
| 293 | + Set<Long> helpersToThank = getButtonStream(event.getMessage()) | ||
| 294 | + .filter(b -> b.getCustomId() != null) | ||
| 295 | + .filter(b -> b.isDisabled() || (b.getCustomId().equals(additionalButtonId))) | ||
| 296 | + .map(b -> Long.parseLong(ComponentIdBuilder.split(b.getCustomId())[2])) | ||
| 297 | + .collect(Collectors.toSet()); | ||
| 298 | + | ||
| 299 | + for (Long helperId : helpersToThank) { | ||
| 300 | + manager.thankHelper(event.getGuild(), post, helperId); | ||
| 301 | + } | ||
| 302 | + markAnswer(post, helpersToThank); | ||
| 303 | + HELP_POST_MESSAGES.remove(post.getIdLong()); | ||
| 302 | 304 | }); | |
| 303 | 305 | } | |
| 304 | 306 | ||
| 307 | + private void markAnswer(ThreadChannel post, Set<Long> helpers) { | ||
| 308 | + List<Message> messages = HELP_POST_MESSAGES.get(post.getIdLong()); | ||
| 309 | + if (messages == null) { | ||
| 310 | + return; | ||
| 311 | + } | ||
| 312 | + for (Message msg : messages.reversed()) { | ||
| 313 | + if (helpers.contains(msg.getAuthor().getIdLong())) { | ||
| 314 | + answerOverflowService.markAnswer(post.getIdLong(), msg.getIdLong()); | ||
| 315 | + return; | ||
| 316 | + } | ||
| 317 | + } | ||
| 318 | + } | ||
| 319 | + | ||
| 305 | 320 | private void handleReplyGuidelines(@NotNull IReplyCallback callback, @NotNull ForumChannel channel) { | |
| 306 | 321 | callback.replyEmbeds(new EmbedBuilder().setTitle("Help Guidelines") | |
| 307 | 322 | .setDescription(channel.getTopic()) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,6 +5,7 @@ | |||
| 5 | 5 | import net.discordjug.javabot.data.config.GuildConfig; | |
| 6 | 6 | import net.discordjug.javabot.data.config.SystemsConfig; | |
| 7 | 7 | import net.discordjug.javabot.data.config.guild.QOTWConfig; | |
| 8 | + import net.discordjug.javabot.systems.help.AnswerOverflowService; | ||
| 8 | 9 | import net.discordjug.javabot.systems.notification.NotificationService; | |
| 9 | 10 | import net.discordjug.javabot.systems.qotw.QOTWPointsService; | |
| 10 | 11 | import net.discordjug.javabot.systems.qotw.dao.QuestionQueueRepository; | |
@@ -63,6 +64,7 @@ public class QOTWCloseSubmissionsJob { | |||
| 63 | 64 | private final ExecutorService asyncPool; | |
| 64 | 65 | private final QOTWPointsService pointsService; | |
| 65 | 66 | private final NotificationService notificationService; | |
| 67 | + private final AnswerOverflowService answerOverflowService; | ||
| 66 | 68 | private final BotConfig botConfig; | |
| 67 | 69 | ||
| 68 | 70 | /** | |
@@ -131,7 +133,7 @@ private void sendReviewInformation(Guild guild, QOTWConfig qotwConfig, TextChann | |||
| 131 | 133 | s.retrieveAuthor(author -> { | |
| 132 | 134 | submission.removeThreadMember(author).queue(); | |
| 133 | 135 | if (author.getIdLong() == qotwConfig.getQotwSampleAnswerUserId()) { | |
| 134 | - SubmissionManager manager = new SubmissionManager(botConfig.get(guild).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool); | ||
| 136 | + SubmissionManager manager = new SubmissionManager(botConfig.get(guild).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool, answerOverflowService); | ||
| 135 | 137 | manager.copySampleAnswerSubmission(submission, author); | |
| 136 | 138 | } else { | |
| 137 | 139 | thread | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,6 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | 3 | import net.discordjug.javabot.data.config.BotConfig; | |
| 4 | 4 | import net.discordjug.javabot.data.config.guild.QOTWConfig; | |
| 5 | + import net.discordjug.javabot.systems.help.AnswerOverflowService; | ||
| 5 | 6 | import net.discordjug.javabot.systems.notification.NotificationService; | |
| 6 | 7 | import net.discordjug.javabot.systems.qotw.QOTWPointsService; | |
| 7 | 8 | import net.discordjug.javabot.systems.qotw.dao.QuestionQueueRepository; | |
@@ -33,6 +34,7 @@ public class QOTWUserReminderJob { | |||
| 33 | 34 | private final QOTWPointsService pointsService; | |
| 34 | 35 | private final NotificationService notificationService; | |
| 35 | 36 | private final QuestionQueueRepository questionQueueRepository; | |
| 37 | + private final AnswerOverflowService answerOverflowService; | ||
| 36 | 38 | private final ExecutorService asyncPool; | |
| 37 | 39 | ||
| 38 | 40 | /** | |
@@ -42,7 +44,7 @@ public class QOTWUserReminderJob { | |||
| 42 | 44 | public void execute() { | |
| 43 | 45 | for (Guild guild : jda.getGuilds()) { | |
| 44 | 46 | QOTWConfig config = botConfig.get(guild).getQotwConfig(); | |
| 45 | - List<QOTWSubmission> submissions = new SubmissionManager(config, pointsService, questionQueueRepository, notificationService, asyncPool).getActiveSubmissions(); | ||
| 47 | + List<QOTWSubmission> submissions = new SubmissionManager(config, pointsService, questionQueueRepository, notificationService, asyncPool, answerOverflowService).getActiveSubmissions(); | ||
| 46 | 48 | for (QOTWSubmission submission : submissions) { | |
| 47 | 49 | submission.retrieveAuthor(author -> { | |
| 48 | 50 | UserPreference preference = userPreferenceService.getOrCreate(author.getIdLong(), Preference.QOTW_REMINDER); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,7 @@ | |||
| 8 | 8 | import lombok.RequiredArgsConstructor; | |
| 9 | 9 | import net.discordjug.javabot.annotations.AutoDetectableComponentHandler; | |
| 10 | 10 | import net.discordjug.javabot.data.config.BotConfig; | |
| 11 | + import net.discordjug.javabot.systems.help.AnswerOverflowService; | ||
| 11 | 12 | import net.discordjug.javabot.systems.notification.NotificationService; | |
| 12 | 13 | import net.discordjug.javabot.systems.qotw.QOTWPointsService; | |
| 13 | 14 | import net.discordjug.javabot.systems.qotw.dao.QuestionQueueRepository; | |
@@ -29,11 +30,12 @@ public class SubmissionInteractionManager implements ButtonHandler, StringSelect | |||
| 29 | 30 | private final NotificationService notificationService; | |
| 30 | 31 | private final BotConfig botConfig; | |
| 31 | 32 | private final QuestionQueueRepository questionQueueRepository; | |
| 33 | + private final AnswerOverflowService answerOverflowService; | ||
| 32 | 34 | private final ExecutorService asyncPool; | |
| 33 | 35 | ||
| 34 | 36 | @Override | |
| 35 | 37 | public void handleButton(@NotNull ButtonInteractionEvent event, Button button) { | |
| 36 | - SubmissionManager manager = new SubmissionManager(botConfig.get(event.getGuild()).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool); | ||
| 38 | + SubmissionManager manager = new SubmissionManager(botConfig.get(event.getGuild()).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool, answerOverflowService); | ||
| 37 | 39 | String[] id = ComponentIdBuilder.split(event.getComponentId()); | |
| 38 | 40 | switch (id[1]) { | |
| 39 | 41 | case "submit" -> manager.handleSubmission(event, Integer.parseInt(id[2])).queue(); | |
@@ -43,7 +45,7 @@ public void handleButton(@NotNull ButtonInteractionEvent event, Button button) { | |||
| 43 | 45 | ||
| 44 | 46 | @Override | |
| 45 | 47 | public void handleStringSelectMenu(@NotNull StringSelectInteractionEvent event, @NotNull List<String> values) { | |
| 46 | - SubmissionManager manager = new SubmissionManager(botConfig.get(event.getGuild()).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool); | ||
| 48 | + SubmissionManager manager = new SubmissionManager(botConfig.get(event.getGuild()).getQotwConfig(), pointsService, questionQueueRepository, notificationService, asyncPool, answerOverflowService); | ||
| 47 | 49 | String[] id = ComponentIdBuilder.split(event.getComponentId()); | |
| 48 | 50 | switch (id[1]) { | |
| 49 | 51 | case "review" -> manager.handleSelectReview(event, id[2]); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -4,6 +4,7 @@ | |||
| 4 | 4 | import lombok.RequiredArgsConstructor; | |
| 5 | 5 | import lombok.extern.slf4j.Slf4j; | |
| 6 | 6 | import net.discordjug.javabot.data.config.guild.QOTWConfig; | |
| 7 | + import net.discordjug.javabot.systems.help.AnswerOverflowService; | ||
| 7 | 8 | import net.discordjug.javabot.systems.notification.NotificationService; | |
| 8 | 9 | import net.discordjug.javabot.systems.qotw.QOTWPointsService; | |
| 9 | 10 | import net.discordjug.javabot.systems.qotw.dao.QuestionQueueRepository; | |
@@ -39,6 +40,8 @@ | |||
| 39 | 40 | import java.util.concurrent.CompletableFuture; | |
| 40 | 41 | import java.util.concurrent.ExecutorService; | |
| 41 | 42 | ||
| 43 | + import club.minnced.discord.webhook.receive.ReadonlyMessage; | ||
| 44 | + | ||
| 42 | 45 | /** | |
| 43 | 46 | * Handles & manages QOTW Submissions by using Discords {@link ThreadChannel}s. | |
| 44 | 47 | */ | |
@@ -57,6 +60,7 @@ public class SubmissionManager { | |||
| 57 | 60 | private final QuestionQueueRepository questionQueueRepository; | |
| 58 | 61 | private final NotificationService notificationService; | |
| 59 | 62 | private final ExecutorService asyncPool; | |
| 63 | + private final AnswerOverflowService answerOverflowService; | ||
| 60 | 64 | ||
| 61 | 65 | /** | |
| 62 | 66 | * Handles the "Submit your Answer" Button interaction. | |
@@ -221,14 +225,19 @@ private void sendToQOTWAnswerArchive(ThreadChannel thread, User author, Accepted | |||
| 221 | 225 | ThreadChannel newestPost = newestPostOptional.get(); | |
| 222 | 226 | WebhookUtil.ensureWebhookExists(newestPost.getParentChannel().asForumChannel(), wh -> | |
| 223 | 227 | getMessagesByUser(thread, author).thenAccept(messages -> { | |
| 224 | - for (Message message : messages) { | ||
| 225 | - boolean lastMessage = messages.indexOf(message) + 1 == messages.size(); | ||
| 228 | + for (int i = 0; i < messages.size(); i++) { | ||
| 229 | + Message message = messages.get(i); | ||
| 230 | + boolean lastMessage = i + 1 == messages.size(); | ||
| 226 | 231 | if (message.getAuthor().isBot() || message.getType() != MessageType.DEFAULT) continue; | |
| 232 | + ReadonlyMessage firstPostedMessage; | ||
| 227 | 233 | if (message.getContentRaw().length() > 2000) { | |
| 228 | - WebhookUtil.mirrorMessageToWebhook(wh, message, message.getContentRaw().substring(0, 2000), newestPost.getIdLong(), null, null).join(); | ||
| 234 | + firstPostedMessage = WebhookUtil.mirrorMessageToWebhook(wh, message, message.getContentRaw().substring(0, 2000), newestPost.getIdLong(), null, null).join(); | ||
| 229 | 235 | WebhookUtil.mirrorMessageToWebhook(wh, message, message.getContentRaw().substring(2000), newestPost.getIdLong(), null, lastMessage ? List.of(buildAuthorEmbed(author, type)) : null).join(); | |
| 230 | 236 | } else { | |
| 231 | - WebhookUtil.mirrorMessageToWebhook(wh, message, message.getContentRaw(), newestPost.getIdLong(), null, lastMessage ? List.of(buildAuthorEmbed(author, type)) : null).join(); | ||
| 237 | + firstPostedMessage = WebhookUtil.mirrorMessageToWebhook(wh, message, message.getContentRaw(), newestPost.getIdLong(), null, lastMessage ? List.of(buildAuthorEmbed(author, type)) : null).join(); | ||
| 238 | + } | ||
| 239 | + if (i == 0 && (type == AcceptedAnswerType.BEST_ANSWER || type == AcceptedAnswerType.SAMPLE_ANSWER)) { | ||
| 240 | + answerOverflowService.markAnswer(firstPostedMessage.getChannelId(), firstPostedMessage.getId()); | ||
| 232 | 241 | } | |
| 233 | 242 | } | |
| 234 | 243 | }).exceptionally(err->{ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,6 +2,7 @@ | |||
| 2 | 2 | ||
| 3 | 3 | import net.discordjug.javabot.data.config.BotConfig; | |
| 4 | 4 | import net.discordjug.javabot.data.config.guild.QOTWConfig; | |
| 5 | + import net.discordjug.javabot.systems.help.AnswerOverflowService; | ||
| 5 | 6 | import net.discordjug.javabot.systems.notification.NotificationService; | |
| 6 | 7 | import net.discordjug.javabot.systems.qotw.QOTWPointsService; | |
| 7 | 8 | import net.discordjug.javabot.systems.qotw.dao.QuestionQueueRepository; | |
@@ -34,7 +35,7 @@ public class QOTWReviewSubcommand extends SlashCommand.Subcommand { | |||
| 34 | 35 | private final QuestionQueueRepository questionQueueRepository; | |
| 35 | 36 | private final BotConfig botConfig; | |
| 36 | 37 | private final ExecutorService asyncPool; | |
| 37 | - | ||
| 38 | + private final AnswerOverflowService answerOverflowService; | ||
| 38 | 39 | ||
| 39 | 40 | /** | |
| 40 | 41 | * The constructor of this class, which sets the corresponding {@link SubcommandData}. | |
@@ -43,13 +44,15 @@ public class QOTWReviewSubcommand extends SlashCommand.Subcommand { | |||
| 43 | 44 | * @param questionQueueRepository The {@link QuestionQueueRepository}. | |
| 44 | 45 | * @param botConfig The main configuration of the bot | |
| 45 | 46 | * @param asyncPool The main thread pool for asynchronous operations | |
| 47 | + * @param answerOverflowService The {@link AnswerOverflowService} for interacting with the Answer Overflow bot. | ||
| 46 | 48 | */ | |
| 47 | - public QOTWReviewSubcommand(QOTWPointsService pointsService, NotificationService notificationService, QuestionQueueRepository questionQueueRepository, BotConfig botConfig, ExecutorService asyncPool) { | ||
| 49 | + public QOTWReviewSubcommand(QOTWPointsService pointsService, NotificationService notificationService, QuestionQueueRepository questionQueueRepository, BotConfig botConfig, ExecutorService asyncPool, AnswerOverflowService answerOverflowService) { | ||
| 48 | 50 | this.pointsService = pointsService; | |
| 49 | 51 | this.notificationService = notificationService; | |
| 50 | 52 | this.questionQueueRepository = questionQueueRepository; | |
| 51 | 53 | this.botConfig = botConfig; | |
| 52 | 54 | this.asyncPool = asyncPool; | |
| 55 | + this.answerOverflowService = answerOverflowService; | ||
| 53 | 56 | setCommandData(new SubcommandData("review", "Administrative command for reviewing QOTW-submissions") | |
| 54 | 57 | .addOptions( | |
| 55 | 58 | new OptionData(OptionType.CHANNEL, "submission", "A users' submission", true) | |
@@ -80,7 +83,7 @@ public void execute(@NotNull SlashCommandInteractionEvent event) { | |||
| 80 | 83 | event.deferReply().queue(); | |
| 81 | 84 | QOTWSubmission submission = new QOTWSubmission(submissionThread); | |
| 82 | 85 | submission.retrieveAuthor(author -> { | |
| 83 | - SubmissionManager manager = new SubmissionManager(qotwConfig, pointsService, questionQueueRepository, notificationService, asyncPool); | ||
| 86 | + SubmissionManager manager = new SubmissionManager(qotwConfig, pointsService, questionQueueRepository, notificationService, asyncPool, answerOverflowService); | ||
| 84 | 87 | if (state.contains("ACCEPT")) { | |
| 85 | 88 | manager.acceptSubmission(submissionThread, author, event.getMember(), state.equals("ACCEPT_BEST")); | |
| 86 | 89 | Responses.success(event.getHook(), "Submission Accepted", "Successfully accepted submission by " + author.getAsMention()).queue(); | |
| Back | FazBrowse Home | New Git URL |
0 commit comments