From de1e46e92eedcb2ee256baf9eb338956f600f96d Mon Sep 17 00:00:00 2001 From: danthe1st Date: Sat, 11 Jul 2026 17:42:28 +0200 Subject: [PATCH 1/4] mark solution with Answer Overflow when users are thanked by OP --- .../javabot/data/config/SystemsConfig.java | 5 ++ .../systems/help/AnswerOverflowService.java | 50 +++++++++++++++++++ .../javabot/systems/help/HelpListener.java | 43 ++++++++++------ 3 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java diff --git a/src/main/java/net/discordjug/javabot/data/config/SystemsConfig.java b/src/main/java/net/discordjug/javabot/data/config/SystemsConfig.java index e9501c181..3e62a6a7e 100644 --- a/src/main/java/net/discordjug/javabot/data/config/SystemsConfig.java +++ b/src/main/java/net/discordjug/javabot/data/config/SystemsConfig.java @@ -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. diff --git a/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java b/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java new file mode 100644 index 000000000..9767c8b8c --- /dev/null +++ b/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java @@ -0,0 +1,50 @@ +package net.discordjug.javabot.systems.help; + +import java.io.IOException; +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; +import java.net.http.HttpResponse.BodyHandlers; + +import lombok.extern.slf4j.Slf4j; +import net.discordjug.javabot.data.config.BotConfig; +import org.springframework.stereotype.Service; + +@Service +@Slf4j +public class AnswerOverflowService { + private final String apiKey; + + public AnswerOverflowService(BotConfig config) { + apiKey = config.getSystems().getAnswerOverflowApiKey(); + } + + public void markAnswer(long postId, long messageId) { + if (apiKey == null || apiKey.isBlank()) { + return; + } + + try (HttpClient client = HttpClient.newHttpClient()) { + HttpResponse response = client.send( + 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()); + if (response.statusCode() != 200) { + log.warn("Answer Overflow responded with unexpected status code {}, post ID: {}, message ID: {}, body: {}", response.statusCode(), postId, messageId, response.body()); + } + } catch (IOException e) { + log.error("An exception occured trying to mark a post as an answer.", e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + }; + } +} diff --git a/src/main/java/net/discordjug/javabot/systems/help/HelpListener.java b/src/main/java/net/discordjug/javabot/systems/help/HelpListener.java index 889d820a4..efcd8b87e 100644 --- a/src/main/java/net/discordjug/javabot/systems/help/HelpListener.java +++ b/src/main/java/net/discordjug/javabot/systems/help/HelpListener.java @@ -30,6 +30,7 @@ import xyz.dynxsty.dih4jda.util.ComponentIdBuilder; import java.util.*; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -79,6 +80,7 @@ protected boolean removeEldestEntry(Map.Entry eldest) { return System.currentTimeMillis() > eldest.getValue() || size() >= 32; } }; + private final AnswerOverflowService answerOverflowService; @Override public void onMessageReceived(@NotNull MessageReceivedEvent event) { @@ -121,12 +123,8 @@ public void onMessageReceived(@NotNull MessageReceivedEvent event) { return; } // cache messages - List 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()); } @@ -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 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 helpers) { + List 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()) From c8a864540881a74503f76c8d109d0a4e3c1c5f67 Mon Sep 17 00:00:00 2001 From: danthe1st Date: Sat, 11 Jul 2026 18:18:21 +0200 Subject: [PATCH 2/4] use async http calls for Answer Overflow --- .../systems/help/AnswerOverflowService.java | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java b/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java index 9767c8b8c..710a1004b 100644 --- a/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java +++ b/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java @@ -1,11 +1,9 @@ package net.discordjug.javabot.systems.help; -import java.io.IOException; 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; import java.net.http.HttpResponse.BodyHandlers; import lombok.extern.slf4j.Slf4j; @@ -21,13 +19,18 @@ 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()) { - HttpResponse response = client.send( + client.sendAsync( HttpRequest.newBuilder(URI.create("https://www.answeroverflow.com/api/v1/messages/" + postId)) .header("x-api-key", apiKey) .header("Content-Type", "application/json") @@ -37,14 +40,16 @@ public void markAnswer(long postId, long messageId) { } """.formatted(messageId))) .build(), - BodyHandlers.ofString()); - if (response.statusCode() != 200) { - log.warn("Answer Overflow responded with unexpected status code {}, post ID: {}, message ID: {}, body: {}", response.statusCode(), postId, messageId, response.body()); - } - } catch (IOException e) { - log.error("An exception occured trying to mark a post as an answer.", e); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - }; + 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; + }); + } } } From 646b192d9e4cd25483a28c8d8cafdf7a401ee508 Mon Sep 17 00:00:00 2001 From: danthe1st Date: Sat, 11 Jul 2026 18:48:34 +0200 Subject: [PATCH 3/4] Answer Overflow integration in QOTW --- .../systems/help/AnswerOverflowService.java | 3 +++ .../qotw/jobs/QOTWCloseSubmissionsJob.java | 4 +++- .../systems/qotw/jobs/QOTWUserReminderJob.java | 4 +++- .../SubmissionInteractionManager.java | 6 ++++-- .../qotw/submissions/SubmissionManager.java | 17 +++++++++++++---- .../subcommands/QOTWReviewSubcommand.java | 9 ++++++--- 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java b/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java index 710a1004b..935b4ebde 100644 --- a/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java +++ b/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java @@ -10,6 +10,9 @@ 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 { diff --git a/src/main/java/net/discordjug/javabot/systems/qotw/jobs/QOTWCloseSubmissionsJob.java b/src/main/java/net/discordjug/javabot/systems/qotw/jobs/QOTWCloseSubmissionsJob.java index c9597562a..e00b76e88 100644 --- a/src/main/java/net/discordjug/javabot/systems/qotw/jobs/QOTWCloseSubmissionsJob.java +++ b/src/main/java/net/discordjug/javabot/systems/qotw/jobs/QOTWCloseSubmissionsJob.java @@ -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; @@ -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; /** @@ -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 diff --git a/src/main/java/net/discordjug/javabot/systems/qotw/jobs/QOTWUserReminderJob.java b/src/main/java/net/discordjug/javabot/systems/qotw/jobs/QOTWUserReminderJob.java index 61bf0227a..d5158f5d1 100644 --- a/src/main/java/net/discordjug/javabot/systems/qotw/jobs/QOTWUserReminderJob.java +++ b/src/main/java/net/discordjug/javabot/systems/qotw/jobs/QOTWUserReminderJob.java @@ -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; @@ -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; /** @@ -42,7 +44,7 @@ public class QOTWUserReminderJob { public void execute() { for (Guild guild : jda.getGuilds()) { QOTWConfig config = botConfig.get(guild).getQotwConfig(); - List submissions = new SubmissionManager(config, pointsService, questionQueueRepository, notificationService, asyncPool).getActiveSubmissions(); + List 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); diff --git a/src/main/java/net/discordjug/javabot/systems/qotw/submissions/SubmissionInteractionManager.java b/src/main/java/net/discordjug/javabot/systems/qotw/submissions/SubmissionInteractionManager.java index 661102bda..cb633b162 100644 --- a/src/main/java/net/discordjug/javabot/systems/qotw/submissions/SubmissionInteractionManager.java +++ b/src/main/java/net/discordjug/javabot/systems/qotw/submissions/SubmissionInteractionManager.java @@ -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; @@ -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(); @@ -43,7 +45,7 @@ public void handleButton(@NotNull ButtonInteractionEvent event, Button button) { @Override public void handleStringSelectMenu(@NotNull StringSelectInteractionEvent event, @NotNull List 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]); diff --git a/src/main/java/net/discordjug/javabot/systems/qotw/submissions/SubmissionManager.java b/src/main/java/net/discordjug/javabot/systems/qotw/submissions/SubmissionManager.java index 7c48fb749..1d29dd97f 100644 --- a/src/main/java/net/discordjug/javabot/systems/qotw/submissions/SubmissionManager.java +++ b/src/main/java/net/discordjug/javabot/systems/qotw/submissions/SubmissionManager.java @@ -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; @@ -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. */ @@ -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. @@ -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->{ diff --git a/src/main/java/net/discordjug/javabot/systems/qotw/submissions/subcommands/QOTWReviewSubcommand.java b/src/main/java/net/discordjug/javabot/systems/qotw/submissions/subcommands/QOTWReviewSubcommand.java index 6d356c76a..37a286024 100644 --- a/src/main/java/net/discordjug/javabot/systems/qotw/submissions/subcommands/QOTWReviewSubcommand.java +++ b/src/main/java/net/discordjug/javabot/systems/qotw/submissions/subcommands/QOTWReviewSubcommand.java @@ -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; @@ -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}. @@ -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) @@ -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(); From 6363d2cbd25f84d48956edd639664c97074700ed Mon Sep 17 00:00:00 2001 From: danthe1st Date: Sat, 11 Jul 2026 18:52:05 +0200 Subject: [PATCH 4/4] use spaces in text block for solution id to make checkstyle happy --- .../discordjug/javabot/systems/help/AnswerOverflowService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java b/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java index 935b4ebde..78f5b5f48 100644 --- a/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java +++ b/src/main/java/net/discordjug/javabot/systems/help/AnswerOverflowService.java @@ -39,7 +39,7 @@ public void markAnswer(long postId, long messageId) { .header("Content-Type", "application/json") .POST(BodyPublishers.ofString(""" { - "solutionId": "%d" + "solutionId": "%d" } """.formatted(messageId))) .build(),