From 1c19f3c07f689171116688bf0d32652ef7af2dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beatrice=20Dellac=C3=A0?= Date: Wed, 21 Dec 2022 17:59:25 +0100 Subject: [PATCH] Implement trivia welcome screen with category picker --- .../wtf/beatrice/hidekobot/HidekoBot.java | 6 +- .../hidekobot/commands/base/DiceRoll.java | 2 +- .../commands/message/DiceRollCommand.java | 3 - .../commands/message/TriviaCommand.java | 52 ++++++++--- .../SelectMenuInteractionListener.java | 21 +++++ .../comparators/TriviaCategoryComparator.java | 17 ++++ .../comparators/TriviaScoreComparator.java | 4 +- .../hidekobot/objects/{ => fun}/Dice.java | 2 +- .../hidekobot/objects/fun/TriviaCategory.java | 5 ++ .../objects/{ => fun}/TriviaQuestion.java | 2 +- .../objects/{ => fun}/TriviaScore.java | 2 +- .../hidekobot/runnables/TriviaTask.java | 18 ++-- .../beatrice/hidekobot/util/TriviaUtil.java | 90 +++++++++++++++++-- 13 files changed, 184 insertions(+), 40 deletions(-) create mode 100644 src/main/java/wtf/beatrice/hidekobot/listeners/SelectMenuInteractionListener.java create mode 100644 src/main/java/wtf/beatrice/hidekobot/objects/comparators/TriviaCategoryComparator.java rename src/main/java/wtf/beatrice/hidekobot/objects/{ => fun}/Dice.java (94%) create mode 100644 src/main/java/wtf/beatrice/hidekobot/objects/fun/TriviaCategory.java rename src/main/java/wtf/beatrice/hidekobot/objects/{ => fun}/TriviaQuestion.java (77%) rename src/main/java/wtf/beatrice/hidekobot/objects/{ => fun}/TriviaScore.java (91%) diff --git a/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java b/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java index de02137..cb8797b 100644 --- a/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java +++ b/src/main/java/wtf/beatrice/hidekobot/HidekoBot.java @@ -11,10 +11,7 @@ import wtf.beatrice.hidekobot.commands.slash.*; import wtf.beatrice.hidekobot.datasources.ConfigurationSource; import wtf.beatrice.hidekobot.datasources.DatabaseSource; import wtf.beatrice.hidekobot.datasources.PropertiesSource; -import wtf.beatrice.hidekobot.listeners.ButtonInteractionListener; -import wtf.beatrice.hidekobot.listeners.MessageCommandListener; -import wtf.beatrice.hidekobot.listeners.SlashCommandCompletionListener; -import wtf.beatrice.hidekobot.listeners.SlashCommandListener; +import wtf.beatrice.hidekobot.listeners.*; import wtf.beatrice.hidekobot.runnables.ExpiredMessageTask; import wtf.beatrice.hidekobot.runnables.HeartBeatTask; import wtf.beatrice.hidekobot.runnables.RandomSeedTask; @@ -154,6 +151,7 @@ public class HidekoBot jda.addEventListener(slashCommandListener); jda.addEventListener(slashCommandCompletionListener); jda.addEventListener(new ButtonInteractionListener()); + jda.addEventListener(new SelectMenuInteractionListener()); // update slash commands (delayed) final boolean finalForceUpdateCommands = forceUpdateCommands; diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/base/DiceRoll.java b/src/main/java/wtf/beatrice/hidekobot/commands/base/DiceRoll.java index 7a99f63..ff257b9 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/base/DiceRoll.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/base/DiceRoll.java @@ -3,7 +3,7 @@ package wtf.beatrice.hidekobot.commands.base; import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.entities.User; import wtf.beatrice.hidekobot.Cache; -import wtf.beatrice.hidekobot.objects.Dice; +import wtf.beatrice.hidekobot.objects.fun.Dice; import wtf.beatrice.hidekobot.objects.MessageResponse; import java.util.LinkedHashMap; diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/message/DiceRollCommand.java b/src/main/java/wtf/beatrice/hidekobot/commands/message/DiceRollCommand.java index 27ba529..d4ef0a2 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/message/DiceRollCommand.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/message/DiceRollCommand.java @@ -1,13 +1,10 @@ package wtf.beatrice.hidekobot.commands.message; -import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import wtf.beatrice.hidekobot.Cache; import wtf.beatrice.hidekobot.commands.base.DiceRoll; -import wtf.beatrice.hidekobot.objects.Dice; import wtf.beatrice.hidekobot.objects.MessageResponse; import wtf.beatrice.hidekobot.objects.commands.CommandCategory; import wtf.beatrice.hidekobot.objects.commands.MessageCommand; diff --git a/src/main/java/wtf/beatrice/hidekobot/commands/message/TriviaCommand.java b/src/main/java/wtf/beatrice/hidekobot/commands/message/TriviaCommand.java index dd429b6..6884d4d 100644 --- a/src/main/java/wtf/beatrice/hidekobot/commands/message/TriviaCommand.java +++ b/src/main/java/wtf/beatrice/hidekobot/commands/message/TriviaCommand.java @@ -1,21 +1,25 @@ package wtf.beatrice.hidekobot.commands.message; +import net.dv8tion.jda.api.EmbedBuilder; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.events.message.MessageReceivedEvent; +import net.dv8tion.jda.api.interactions.components.selections.SelectMenu; +import net.dv8tion.jda.api.interactions.components.selections.StringSelectMenu; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.json.JSONObject; import wtf.beatrice.hidekobot.Cache; import wtf.beatrice.hidekobot.objects.commands.CommandCategory; import wtf.beatrice.hidekobot.objects.commands.MessageCommand; +import wtf.beatrice.hidekobot.objects.comparators.TriviaCategoryComparator; +import wtf.beatrice.hidekobot.objects.fun.TriviaCategory; import wtf.beatrice.hidekobot.runnables.TriviaTask; import wtf.beatrice.hidekobot.util.TriviaUtil; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -75,21 +79,41 @@ public class TriviaCommand implements MessageCommand Message err = event.getMessage().reply("Trivia is already running here!").complete(); Cache.getTaskScheduler().schedule(() -> err.delete().queue(), 10, TimeUnit.SECONDS); return; - } else { - // todo nicer looking - event.getMessage().reply("Starting new Trivia session!").queue(); } + // todo null checks + JSONObject categoriesJson = TriviaUtil.fetchJson(TriviaUtil.getCategoriesLink()); + List categories = TriviaUtil.parseCategories(categoriesJson); + categories.sort(new TriviaCategoryComparator()); - TriviaTask triviaTask = new TriviaTask(event.getAuthor(), channel); - ScheduledFuture future = - Cache.getTaskScheduler().scheduleAtFixedRate(triviaTask, - 0, - 15, - TimeUnit.SECONDS); - triviaTask.setScheduledFuture(future); + EmbedBuilder embedBuilder = new EmbedBuilder(); + embedBuilder.setColor(Cache.getBotColor()); + embedBuilder.setTitle("\uD83C\uDFB2 Trivia"); + embedBuilder.addField("\uD83D\uDCD6 Begin here", + "Select a category from the dropdown menu to start a match!", + false); + embedBuilder.addField("❓ How to play", + "A new question gets posted every few seconds." + + "\nIf you get it right, you earn points!" + + "\nIf you choose a wrong answer, you lose points." + + "\nIf you are unsure, you can wait without answering and your score won't change!", + false); + + StringSelectMenu.Builder menuBuilder = StringSelectMenu.create("trivia_categories"); + + for(TriviaCategory category : categories) + { + String name = category.categoryName(); + int id = category.categoryId(); + menuBuilder.addOption(name, String.valueOf(id)); + } + + event.getMessage().replyEmbeds(embedBuilder.build()).addActionRow(menuBuilder.build()).queue(message -> + { + Cache.getDatabaseSource().trackRanCommandReply(message, event.getAuthor()); + Cache.getDatabaseSource().queueDisabling(message); + }); - TriviaUtil.channelsRunningTrivia.add(channel.getId()); } } diff --git a/src/main/java/wtf/beatrice/hidekobot/listeners/SelectMenuInteractionListener.java b/src/main/java/wtf/beatrice/hidekobot/listeners/SelectMenuInteractionListener.java new file mode 100644 index 0000000..3acb9b1 --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/listeners/SelectMenuInteractionListener.java @@ -0,0 +1,21 @@ +package wtf.beatrice.hidekobot.listeners; + +import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent; +import net.dv8tion.jda.api.hooks.ListenerAdapter; +import net.dv8tion.jda.api.interactions.components.selections.SelectOption; +import wtf.beatrice.hidekobot.commands.base.CoinFlip; +import wtf.beatrice.hidekobot.util.TriviaUtil; + +public class SelectMenuInteractionListener extends ListenerAdapter +{ + + @Override + public void onStringSelectInteraction(StringSelectInteractionEvent event) + { + switch (event.getComponentId().toLowerCase()) { + + // trivia + case "trivia_categories" -> TriviaUtil.handleMenuSelection(event); + } + } +} diff --git a/src/main/java/wtf/beatrice/hidekobot/objects/comparators/TriviaCategoryComparator.java b/src/main/java/wtf/beatrice/hidekobot/objects/comparators/TriviaCategoryComparator.java new file mode 100644 index 0000000..33a8d0d --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/objects/comparators/TriviaCategoryComparator.java @@ -0,0 +1,17 @@ +package wtf.beatrice.hidekobot.objects.comparators; + +import wtf.beatrice.hidekobot.objects.fun.TriviaCategory; +import wtf.beatrice.hidekobot.objects.fun.TriviaScore; + +import java.util.Comparator; + +/** + * This class gets two trivia categories, and compares them by their name. + */ +public class TriviaCategoryComparator implements Comparator { + + @Override + public int compare(TriviaCategory o1, TriviaCategory o2) { + return CharSequence.compare(o1.categoryName(), o2.categoryName()); + } +} diff --git a/src/main/java/wtf/beatrice/hidekobot/objects/comparators/TriviaScoreComparator.java b/src/main/java/wtf/beatrice/hidekobot/objects/comparators/TriviaScoreComparator.java index 4af97e1..f2f85ef 100644 --- a/src/main/java/wtf/beatrice/hidekobot/objects/comparators/TriviaScoreComparator.java +++ b/src/main/java/wtf/beatrice/hidekobot/objects/comparators/TriviaScoreComparator.java @@ -1,10 +1,8 @@ package wtf.beatrice.hidekobot.objects.comparators; -import wtf.beatrice.hidekobot.objects.TriviaScore; +import wtf.beatrice.hidekobot.objects.fun.TriviaScore; import java.util.Comparator; -import java.util.LinkedList; -import java.util.List; /** * This class gets two trivia scores, and compares their score. diff --git a/src/main/java/wtf/beatrice/hidekobot/objects/Dice.java b/src/main/java/wtf/beatrice/hidekobot/objects/fun/Dice.java similarity index 94% rename from src/main/java/wtf/beatrice/hidekobot/objects/Dice.java rename to src/main/java/wtf/beatrice/hidekobot/objects/fun/Dice.java index d3012b6..8406a44 100644 --- a/src/main/java/wtf/beatrice/hidekobot/objects/Dice.java +++ b/src/main/java/wtf/beatrice/hidekobot/objects/fun/Dice.java @@ -1,4 +1,4 @@ -package wtf.beatrice.hidekobot.objects; +package wtf.beatrice.hidekobot.objects.fun; import wtf.beatrice.hidekobot.util.RandomUtil; diff --git a/src/main/java/wtf/beatrice/hidekobot/objects/fun/TriviaCategory.java b/src/main/java/wtf/beatrice/hidekobot/objects/fun/TriviaCategory.java new file mode 100644 index 0000000..e44ac72 --- /dev/null +++ b/src/main/java/wtf/beatrice/hidekobot/objects/fun/TriviaCategory.java @@ -0,0 +1,5 @@ +package wtf.beatrice.hidekobot.objects.fun; + +public record TriviaCategory(String categoryName, int categoryId) { + +} diff --git a/src/main/java/wtf/beatrice/hidekobot/objects/TriviaQuestion.java b/src/main/java/wtf/beatrice/hidekobot/objects/fun/TriviaQuestion.java similarity index 77% rename from src/main/java/wtf/beatrice/hidekobot/objects/TriviaQuestion.java rename to src/main/java/wtf/beatrice/hidekobot/objects/fun/TriviaQuestion.java index 3fba222..0d7952a 100644 --- a/src/main/java/wtf/beatrice/hidekobot/objects/TriviaQuestion.java +++ b/src/main/java/wtf/beatrice/hidekobot/objects/fun/TriviaQuestion.java @@ -1,4 +1,4 @@ -package wtf.beatrice.hidekobot.objects; +package wtf.beatrice.hidekobot.objects.fun; import java.util.List; diff --git a/src/main/java/wtf/beatrice/hidekobot/objects/TriviaScore.java b/src/main/java/wtf/beatrice/hidekobot/objects/fun/TriviaScore.java similarity index 91% rename from src/main/java/wtf/beatrice/hidekobot/objects/TriviaScore.java rename to src/main/java/wtf/beatrice/hidekobot/objects/fun/TriviaScore.java index bdba83a..16f3578 100644 --- a/src/main/java/wtf/beatrice/hidekobot/objects/TriviaScore.java +++ b/src/main/java/wtf/beatrice/hidekobot/objects/fun/TriviaScore.java @@ -1,4 +1,4 @@ -package wtf.beatrice.hidekobot.objects; +package wtf.beatrice.hidekobot.objects.fun; import net.dv8tion.jda.api.entities.User; diff --git a/src/main/java/wtf/beatrice/hidekobot/runnables/TriviaTask.java b/src/main/java/wtf/beatrice/hidekobot/runnables/TriviaTask.java index c8a99e7..156201f 100644 --- a/src/main/java/wtf/beatrice/hidekobot/runnables/TriviaTask.java +++ b/src/main/java/wtf/beatrice/hidekobot/runnables/TriviaTask.java @@ -8,8 +8,9 @@ import net.dv8tion.jda.api.entities.emoji.Emoji; import net.dv8tion.jda.api.interactions.components.buttons.Button; import org.json.JSONObject; import wtf.beatrice.hidekobot.Cache; -import wtf.beatrice.hidekobot.objects.TriviaQuestion; -import wtf.beatrice.hidekobot.objects.TriviaScore; +import wtf.beatrice.hidekobot.objects.fun.TriviaCategory; +import wtf.beatrice.hidekobot.objects.fun.TriviaQuestion; +import wtf.beatrice.hidekobot.objects.fun.TriviaScore; import wtf.beatrice.hidekobot.objects.comparators.TriviaScoreComparator; import wtf.beatrice.hidekobot.util.CommandUtil; import wtf.beatrice.hidekobot.util.TriviaUtil; @@ -26,18 +27,20 @@ public class TriviaTask implements Runnable private final JSONObject triviaJson; private final List questions; + private final TriviaCategory category; ScheduledFuture future = null; private int iteration = 0; - public TriviaTask(User author, MessageChannel channel) + public TriviaTask(User author, MessageChannel channel, TriviaCategory category) { this.author = author; this.channel = channel; + this.category = category; - triviaJson = TriviaUtil.fetchTrivia(); - questions = TriviaUtil.getQuestions(triviaJson); //todo: null check, rate limiting... + triviaJson = TriviaUtil.fetchJson(TriviaUtil.getTriviaLink(category.categoryId())); + questions = TriviaUtil.parseQuestions(triviaJson); //todo: null check, rate limiting... } public void setScheduledFuture(ScheduledFuture future) @@ -50,7 +53,7 @@ public class TriviaTask implements Runnable { if(previousMessage != null) { - // todo: we shouldn't use this method, since it messes with the database... + // todo: we shouldn't use this method, since it messes with the database... look at coin reflip CommandUtil.disableExpired(previousMessage.getId()); String previousCorrectAnswer = questions.get(iteration-1).correctAnswer(); @@ -172,7 +175,8 @@ public class TriviaTask implements Runnable EmbedBuilder embedBuilder = new EmbedBuilder(); embedBuilder.setColor(Cache.getBotColor()); - embedBuilder.setTitle("\uD83C\uDFB2 Trivia (" + (iteration+1) + "/" + questions.size() + ")"); + embedBuilder.setTitle("\uD83C\uDFB2 Trivia - " + category.categoryName() + + " (" + (iteration+1) + "/" + questions.size() + ")"); embedBuilder.addField("❓ Question", currentTriviaQuestion.question(), false); diff --git a/src/main/java/wtf/beatrice/hidekobot/util/TriviaUtil.java b/src/main/java/wtf/beatrice/hidekobot/util/TriviaUtil.java index f5bd950..bec479e 100644 --- a/src/main/java/wtf/beatrice/hidekobot/util/TriviaUtil.java +++ b/src/main/java/wtf/beatrice/hidekobot/util/TriviaUtil.java @@ -1,14 +1,19 @@ package wtf.beatrice.hidekobot.util; +import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.User; import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel; import net.dv8tion.jda.api.events.interaction.component.ButtonInteractionEvent; +import net.dv8tion.jda.api.events.interaction.component.StringSelectInteractionEvent; +import net.dv8tion.jda.api.interactions.components.selections.SelectOption; import org.apache.commons.text.StringEscapeUtils; import org.json.JSONArray; import org.json.JSONObject; import wtf.beatrice.hidekobot.Cache; -import wtf.beatrice.hidekobot.objects.TriviaQuestion; -import wtf.beatrice.hidekobot.objects.TriviaScore; +import wtf.beatrice.hidekobot.objects.fun.TriviaCategory; +import wtf.beatrice.hidekobot.objects.fun.TriviaQuestion; +import wtf.beatrice.hidekobot.objects.fun.TriviaScore; +import wtf.beatrice.hidekobot.runnables.TriviaTask; import java.io.BufferedReader; import java.io.IOException; @@ -18,11 +23,13 @@ import java.net.URLConnection; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; public class TriviaUtil { - private final static String link = "https://opentdb.com/api.php?amount=10&type=multiple"; + private final static String triviaLink = "https://opentdb.com/api.php?amount=10&type=multiple&category="; + private final static String categoriesLink = "https://opentdb.com/api_category.php"; public static List channelsRunningTrivia = new ArrayList<>(); @@ -32,7 +39,10 @@ public class TriviaUtil // first string is the channelId, the list contain all score records for that channel public static HashMap> channelAndScores = new HashMap<>(); - public static JSONObject fetchTrivia() + public static String getTriviaLink(int categoryId) {return triviaLink + categoryId; } + public static String getCategoriesLink() {return categoriesLink; } + + public static JSONObject fetchJson(String link) { try { URL url = new URL(link); @@ -54,7 +64,7 @@ public class TriviaUtil return null; } - public static List getQuestions(JSONObject jsonObject) + public static List parseQuestions(JSONObject jsonObject) { List questions = new ArrayList<>(); @@ -82,6 +92,23 @@ public class TriviaUtil return questions; } + public static List parseCategories(JSONObject jsonObject) + { + List categories = new ArrayList<>(); + JSONArray categoriesArray = jsonObject.getJSONArray("trivia_categories"); + for(Object categoryObject : categoriesArray) + { + JSONObject categoryJson = (JSONObject) categoryObject; + + String name = categoryJson.getString("name"); + int id = categoryJson.getInt("id"); + + categories.add(new TriviaCategory(name, id)); + } + + return categories; + } + public static void handleAnswer(ButtonInteractionEvent event, AnswerType answerType) { User user = event.getUser(); @@ -150,6 +177,59 @@ public class TriviaUtil } } + public static void handleMenuSelection(StringSelectInteractionEvent event) + { + // check if the user interacting is the same one who ran the command + if(!(Cache.getDatabaseSource().isUserTrackedFor(event.getUser().getId(), event.getMessageId()))) + { + event.reply("❌ You did not run this command!").setEphemeral(true).queue(); + return; + } + + // todo: we shouldn't use this method, since it messes with the database... look at coin reflip + CommandUtil.disableExpired(event.getMessageId()); + + SelectOption pickedOption = event.getInteraction().getSelectedOptions().get(0); + String categoryName = pickedOption.getLabel(); + String categoryIdString = pickedOption.getValue(); + Integer categoryId = Integer.parseInt(categoryIdString); + + TriviaCategory category = new TriviaCategory(categoryName, categoryId); + + startTrivia(event, category); + } + + public static void startTrivia(StringSelectInteractionEvent event, TriviaCategory category) + { + User author = event.getUser(); + Message message = event.getMessage(); + MessageChannel channel = message.getChannel(); + + if(TriviaUtil.channelsRunningTrivia.contains(channel.getId())) + { + // todo nicer looking + // todo: also what if the bot stops (database...?) + // todo: also what if the message is already deleted + Message err = event.reply("Trivia is already running here!").complete().retrieveOriginal().complete(); + Cache.getTaskScheduler().schedule(() -> err.delete().queue(), 10, TimeUnit.SECONDS); + return; + } else { + // todo nicer looking + event.reply("Starting new Trivia session!").queue(); + } + + + TriviaTask triviaTask = new TriviaTask(author, channel, category); + ScheduledFuture future = + Cache.getTaskScheduler().scheduleAtFixedRate(triviaTask, + 0, + 15, + TimeUnit.SECONDS); + triviaTask.setScheduledFuture(future); + + TriviaUtil.channelsRunningTrivia.add(channel.getId()); + } + public enum AnswerType { CORRECT, WRONG }