diff --git a/app/src/main/java/com/togetherjava/tjplays/Bot.java b/app/src/main/java/com/togetherjava/tjplays/Bot.java index 292172d..826486e 100644 --- a/app/src/main/java/com/togetherjava/tjplays/Bot.java +++ b/app/src/main/java/com/togetherjava/tjplays/Bot.java @@ -4,6 +4,7 @@ import java.nio.file.Path; import java.util.List; import com.togetherjava.tjplays.listeners.commands.*; +import com.togetherjava.tjplays.trivia.TriviaManager; import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDABuilder; @@ -32,9 +33,12 @@ private static JDA createBot(Config config) { } private static List getCommands(Config config) { - return List.of( + // Construct JavaQuizCommand with TriviaManager and ChatGptService + TriviaManager triviaManager = new TriviaManager(new com.togetherjava.tjplays.services.chatgpt.ChatGptService(config.openAIApiKey())); + return java.util.Arrays.asList( new PingCommand(), - new Game2048Command() + new Game2048Command(), + new JavaQuizCommand(triviaManager) ); } } diff --git a/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java b/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java new file mode 100644 index 0000000..ba8dd5a --- /dev/null +++ b/app/src/main/java/com/togetherjava/tjplays/listeners/commands/JavaQuizCommand.java @@ -0,0 +1,117 @@ +package com.togetherjava.tjplays.listeners.commands; + +import com.togetherjava.tjplays.trivia.QuizQuestion; +import com.togetherjava.tjplays.trivia.TriviaManager; + +import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.interactions.commands.build.Commands; +import net.dv8tion.jda.api.interactions.components.buttons.Button; + +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Handles the /javaquiz command, presenting Java trivia questions to users. + * Uses TriviaManager to fetch questions and manages user quiz state. + * Future contributors can extend this for more quiz features. + */ +public final class JavaQuizCommand extends SlashCommand { + private static final String COMMAND_NAME = "javaquiz"; + + private final TriviaManager triviaManager; + // messageId -> QuizQuestion + private final ConcurrentHashMap activeQuestions = new ConcurrentHashMap<>(); + // userId -> messageId + private final ConcurrentHashMap userActiveQuiz = new ConcurrentHashMap<>(); + + /** + * Constructs a JavaQuizCommand with a TriviaManager instance. + * @param triviaManager the TriviaManager to use for fetching questions + */ + public JavaQuizCommand(TriviaManager triviaManager) { + super(Commands.slash(COMMAND_NAME, "Get a random Java trivia question")); + this.triviaManager = triviaManager; + } + + @Override + public void onSlashCommand(SlashCommandInteractionEvent event) { + String userId = event.getUser().getId(); + // Prevent new quiz if user has an active one + if (userActiveQuiz.containsKey(userId)) { + event.reply("You already have an active quiz. Please answer it before starting a new one.").setEphemeral(true).queue(); + return; + } + + event.deferReply().queue(); + + Optional question = triviaManager.fetchRandomQuestion(); + if (question.isEmpty()) { + event.getHook().editOriginal("Could not fetch a quiz question. Try again later.").queue(); + return; + } + + QuizQuestion quizQuestion = question.get(); + List choices = quizQuestion.getChoices(); + + String quizContent = String.format("**Java Quiz**\n\n%s\n\n%s", + quizQuestion.getQuestion(), + java.util.stream.IntStream.range(0, choices.size()) + .mapToObj(i -> String.format("`%d`) %s", i + 1, choices.get(i))) + .collect(java.util.stream.Collectors.joining("\n")) + ); + + String messageId = event.getHook().editOriginal(quizContent) + .setActionRow( + Button.primary(COMMAND_NAME + "-1-" + userId, "1"), + Button.primary(COMMAND_NAME + "-2-" + userId, "2"), + Button.primary(COMMAND_NAME + "-3-" + userId, "3"), + Button.primary(COMMAND_NAME + "-4-" + userId, "4") + ) + .complete() + .getId(); + + activeQuestions.put(messageId, quizQuestion); + userActiveQuiz.put(userId, messageId); + } + + @Override + public void onButtonInteraction(ButtonInteractionEvent event) { + String buttonId = event.getButton().getId(); + if (buttonId == null || !buttonId.startsWith(COMMAND_NAME)) { + return; + } + + if (!buttonId.contains(event.getUser().getId())) { + event.reply("This isn't your quiz!").setEphemeral(true).queue(); + return; + } + + String userId = event.getUser().getId(); + QuizQuestion quizQuestion = activeQuestions.remove(event.getMessageId()); + // Remove user's active quiz + userActiveQuiz.remove(userId); + if (quizQuestion == null) { + event.reply("This quiz has already been answered.").setEphemeral(true).queue(); + return; + } + + int chosen = Character.getNumericValue(buttonId.charAt(COMMAND_NAME.length() + 1)) - 1; + boolean correct = chosen == quizQuestion.getCorrectAnswerIndex(); + + String correctAnswer = quizQuestion.getChoices().get(quizQuestion.getCorrectAnswerIndex()); + String result = correct + ? "Correct! The answer is: **" + correctAnswer + "**" + : "Wrong! The correct answer was: **" + correctAnswer + "**"; + + event.editMessage(event.getMessage().getContentRaw() + "\n\n" + result) + .setActionRow( + Button.primary(COMMAND_NAME + "-1-" + userId, "1").asDisabled(), + Button.primary(COMMAND_NAME + "-2-" + userId, "2").asDisabled(), + Button.primary(COMMAND_NAME + "-3-" + userId, "3").asDisabled(), + Button.primary(COMMAND_NAME + "-4-" + userId, "4").asDisabled() + ) + .queue(); + } +} diff --git a/app/src/main/java/com/togetherjava/tjplays/services/chatgpt/ChatGptService.java b/app/src/main/java/com/togetherjava/tjplays/services/chatgpt/ChatGptService.java index e697dd7..e3025e4 100644 --- a/app/src/main/java/com/togetherjava/tjplays/services/chatgpt/ChatGptService.java +++ b/app/src/main/java/com/togetherjava/tjplays/services/chatgpt/ChatGptService.java @@ -11,7 +11,6 @@ import java.time.Duration; import java.util.List; -import java.util.Objects; import java.util.Optional; /** diff --git a/app/src/main/java/com/togetherjava/tjplays/trivia/QuizQuestion.java b/app/src/main/java/com/togetherjava/tjplays/trivia/QuizQuestion.java new file mode 100644 index 0000000..96cf1ed --- /dev/null +++ b/app/src/main/java/com/togetherjava/tjplays/trivia/QuizQuestion.java @@ -0,0 +1,34 @@ +package com.togetherjava.tjplays.trivia; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * Represents a single quiz question for Java trivia. + * Used by TriviaManager and JavaQuizCommand to display questions and validate answers. + * Future contributors can use this for any quiz/trivia feature needing a question, choices, and answer index. + */ +public record QuizQuestion( + @JsonProperty("question") String question, + @JsonProperty("choices") List choices, + @JsonProperty("correct") int correctAnswerIndex +) { + @JsonCreator + public QuizQuestion {} + + public String getQuestion() { + return question; + } + + public List getChoices() { + return choices; + } + + /** + * Returns the index of the correct answer in the choices list. + */ + public int getCorrectAnswerIndex() { + return correctAnswerIndex; + } +} diff --git a/app/src/main/java/com/togetherjava/tjplays/trivia/TriviaManager.java b/app/src/main/java/com/togetherjava/tjplays/trivia/TriviaManager.java new file mode 100644 index 0000000..0291159 --- /dev/null +++ b/app/src/main/java/com/togetherjava/tjplays/trivia/TriviaManager.java @@ -0,0 +1,34 @@ +package com.togetherjava.tjplays.trivia; + +import com.togetherjava.tjplays.services.chatgpt.ChatGptService; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Optional; + +/** + * Manages Java trivia questions using ChatGptService. + * Used by quiz commands to fetch questions for users. + * Future contributors can use this for any trivia/quiz feature. + */ +public class TriviaManager { + private final ChatGptService chatGptService; + private final ObjectMapper mapper = new ObjectMapper(); + + public TriviaManager(ChatGptService chatGptService) { + this.chatGptService = chatGptService; + } + + public Optional fetchRandomQuestion() { + String prompt = "Provide one Java trivia question as a JSON object like" + + " {\"question\":\"...\",\"choices\":[\"A\",\"B\",\"C\",\"D\"],\"correct\":}"; + Optional raw = chatGptService.ask(prompt, "java quiz"); + if (raw.isEmpty()) { + return Optional.empty(); + } + try { + QuizQuestion question = mapper.readValue(raw.get(), QuizQuestion.class); + return Optional.of(question); + } catch (Exception e) { + return Optional.empty(); + } + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..e69de29