Make trivia loop through all questions

This commit is contained in:
Bea 2022-12-21 04:09:27 +01:00
parent 0be4389448
commit c9082e84cc
4 changed files with 212 additions and 131 deletions

View File

@ -1,25 +1,22 @@
package wtf.beatrice.hidekobot.commands.message; package wtf.beatrice.hidekobot.commands.message;
import net.dv8tion.jda.api.EmbedBuilder;
import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; 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.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.interactions.components.buttons.Button;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.json.JSONObject;
import wtf.beatrice.hidekobot.Cache; import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.objects.TriviaQuestion;
import wtf.beatrice.hidekobot.objects.commands.CommandCategory; import wtf.beatrice.hidekobot.objects.commands.CommandCategory;
import wtf.beatrice.hidekobot.objects.commands.MessageCommand; import wtf.beatrice.hidekobot.objects.commands.MessageCommand;
import wtf.beatrice.hidekobot.runnables.TriviaTask;
import wtf.beatrice.hidekobot.util.TriviaUtil; import wtf.beatrice.hidekobot.util.TriviaUtil;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class TriviaCommand implements MessageCommand public class TriviaCommand implements MessageCommand
@ -62,11 +59,11 @@ public class TriviaCommand implements MessageCommand
@Override @Override
public void runCommand(MessageReceivedEvent event, String label, String[] args) public void runCommand(MessageReceivedEvent event, String label, String[] args)
{ {
Channel channel = event.getChannel(); MessageChannel channel = event.getChannel();
if(!(channel instanceof TextChannel)) if(!(channel instanceof TextChannel))
{ {
event.getMessage().reply("\uD83D\uDE22 Sorry! Trivia doesn't work in DMs.").queue(); channel.sendMessage("\uD83D\uDE22 Sorry! Trivia doesn't work in DMs.").queue();
return; return;
} }
@ -84,45 +81,15 @@ public class TriviaCommand implements MessageCommand
} }
JSONObject triviaJson = TriviaUtil.fetchTrivia(); TriviaTask triviaTask = new TriviaTask(event.getAuthor(), channel);
List<TriviaQuestion> questions = TriviaUtil.getQuestions(triviaJson); //todo null check, rate limiting... ScheduledFuture<?> future =
TriviaQuestion first = questions.get(0); Cache.getTaskScheduler().scheduleAtFixedRate(triviaTask,
0,
10,
TimeUnit.SECONDS);
triviaTask.setScheduledFuture(future);
List<Button> answerButtons = new ArrayList<>(); TriviaUtil.channelsRunningTrivia.add(channel.getId());
Button correctAnswerButton = Button.primary("trivia_correct", first.correctAnswer());
answerButtons.add(correctAnswerButton);
int i = 0; // we need to add a number because buttons can't have the same id
for(String wrongAnswer : first.wrongAnswers())
{
i++;
Button wrongAnswerButton = Button.primary("trivia_wrong_" + i, wrongAnswer);
answerButtons.add(wrongAnswerButton);
}
Collections.shuffle(answerButtons);
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setTitle("Trivia");
embedBuilder.addField("Question", first.question(), false);
Message botMessage = event.getChannel()
.sendMessageEmbeds(embedBuilder.build())
.setActionRow(answerButtons)
.complete();
Cache.getDatabaseSource().trackRanCommandReply(botMessage, event.getAuthor());
// todo: ^ we should get rid of this tracking, since we don't need to know who started the trivia.
// todo: however, for now, that's the only way to avoid a thread-locking scenario as some data is
// todo: only stored in that table. this should be solved when we merge / fix the two main tables.
// todo: then, we can remove this instruction.
Cache.getDatabaseSource().queueDisabling(botMessage);
TriviaUtil.channelsRunningTrivia.add(event.getChannel().getId());
} }
} }

View File

@ -10,6 +10,7 @@ import net.dv8tion.jda.api.requests.RestAction;
import wtf.beatrice.hidekobot.Cache; import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.HidekoBot; import wtf.beatrice.hidekobot.HidekoBot;
import wtf.beatrice.hidekobot.datasources.DatabaseSource; import wtf.beatrice.hidekobot.datasources.DatabaseSource;
import wtf.beatrice.hidekobot.util.CommandUtil;
import wtf.beatrice.hidekobot.util.Logger; import wtf.beatrice.hidekobot.util.Logger;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -63,93 +64,9 @@ public class ExpiredMessageTask implements Runnable {
if(now.isAfter(expiryDate)) if(now.isAfter(expiryDate))
{ {
if(Cache.isVerbose()) logger.log("expired: " + messageId); if(Cache.isVerbose()) logger.log("expired: " + messageId);
disableExpired(messageId); CommandUtil.disableExpired(messageId);
} }
} }
} }
private void disableExpired(String messageId)
{
String channelId = databaseSource.getQueuedExpiringMessageChannel(messageId);
// todo: warning, the following method + related if check are thread-locking.
// todo: we should probably merge the two tables somehow, since they have redundant information.
ChannelType msgChannelType = databaseSource.getTrackedMessageChannelType(messageId);
MessageChannel textChannel = null;
// this should never happen, but only message channels are supported.
if(!msgChannelType.isMessage())
{
databaseSource.untrackExpiredMessage(messageId);
return;
}
// if this is a DM
if(!(msgChannelType.isGuild()))
{
String userId = databaseSource.getTrackedReplyUserId(messageId);
User user = userId == null ? null : HidekoBot.getAPI().retrieveUserById(userId).complete();
if(user == null)
{
// if user is not found, consider it expired
// (deleted profile, or blocked the bot)
databaseSource.untrackExpiredMessage(messageId);
return;
}
textChannel = user.openPrivateChannel().complete();
}
else
{
String guildId = databaseSource.getQueuedExpiringMessageGuild(messageId);
Guild guild = guildId == null ? null : HidekoBot.getAPI().getGuildById(guildId);
if(guild == null)
{
// if guild is not found, consider it expired
// (server was deleted or bot was kicked)
databaseSource.untrackExpiredMessage(messageId);
return;
}
textChannel = guild.getTextChannelById(channelId);
}
if(textChannel == null)
{
// if channel is not found, count it as expired
// (channel was deleted or bot permissions restricted)
databaseSource.untrackExpiredMessage(messageId);
return;
}
RestAction<Message> retrieveAction = textChannel.retrieveMessageById(messageId);
if(Cache.isVerbose()) logger.log("cleaning up: " + messageId);
retrieveAction.queue(
message -> {
if(message == null)
{
databaseSource.untrackExpiredMessage(messageId);
return;
}
List<LayoutComponent> components = message.getComponents();
List<LayoutComponent> newComponents = new ArrayList<>();
for (LayoutComponent component : components)
{
component = component.asDisabled();
newComponents.add(component);
}
message.editMessageComponents(newComponents).queue();
databaseSource.untrackExpiredMessage(messageId);
},
(error) -> {
databaseSource.untrackExpiredMessage(messageId);
});
}
} }

View File

@ -1,10 +1,107 @@
package wtf.beatrice.hidekobot.runnables; package wtf.beatrice.hidekobot.runnables;
import net.dv8tion.jda.api.EmbedBuilder;
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.interactions.components.buttons.Button;
import org.json.JSONObject;
import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.objects.TriviaQuestion;
import wtf.beatrice.hidekobot.util.CommandUtil;
import wtf.beatrice.hidekobot.util.TriviaUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
public class TriviaTask implements Runnable public class TriviaTask implements Runnable
{ {
private final User author;
private final MessageChannel channel;
private Message previousMessage = null;
private final JSONObject triviaJson;
private final List<TriviaQuestion> questions;
ScheduledFuture<?> future = null;
private int iteration = 0;
public TriviaTask(User author, MessageChannel channel)
{
this.author = author;
this.channel = channel;
triviaJson = TriviaUtil.fetchTrivia();
questions = TriviaUtil.getQuestions(triviaJson); //todo: null check, rate limiting...
}
public void setScheduledFuture(ScheduledFuture<?> future)
{
this.future = future;
}
@Override @Override
public void run() { public void run() {
if(previousMessage != null)
{
// todo: we shouldn't use this method, since it messes with the database...
CommandUtil.disableExpired(previousMessage.getId());
}
if(iteration >= questions.size())
{
// todo: nicer-looking embed with stats
channel.sendMessage("Trivia session is over!").queue();
TriviaUtil.channelsRunningTrivia.remove(channel.getId());
future.cancel(false);
// we didn't implement null checks on the future on purpose, because we need to know if we were unable
// to cancel it (and console errors should make it clear enough).
return;
}
TriviaQuestion currentTriviaQuestion = questions.get(iteration);
List<Button> answerButtons = new ArrayList<>();
Button correctAnswerButton = Button.primary("trivia_correct", currentTriviaQuestion.correctAnswer());
answerButtons.add(correctAnswerButton);
int i = 0; // we need to add a number because buttons can't have the same id
for(String wrongAnswer : currentTriviaQuestion.wrongAnswers())
{
i++;
Button wrongAnswerButton = Button.primary("trivia_wrong_" + i, wrongAnswer);
answerButtons.add(wrongAnswerButton);
}
Collections.shuffle(answerButtons);
EmbedBuilder embedBuilder = new EmbedBuilder();
embedBuilder.setColor(Cache.getBotColor());
embedBuilder.setTitle("Trivia (" + (iteration+1) + "/" + questions.size() + ")");
embedBuilder.addField("Question", currentTriviaQuestion.question(), false);
previousMessage = channel
.sendMessageEmbeds(embedBuilder.build())
.setActionRow(answerButtons)
.complete();
Cache.getDatabaseSource().trackRanCommandReply(previousMessage, author);
// todo: ^ we should get rid of this tracking, since we don't need to know who started the trivia.
// todo: however, for now, that's the only way to avoid a thread-locking scenario as some data is
// todo: only stored in that table. this should be solved when we merge / fix the two main tables.
// todo: then, we can remove this instruction.
Cache.getDatabaseSource().queueDisabling(previousMessage);
iteration++;
} }
} }

View File

@ -1,11 +1,19 @@
package wtf.beatrice.hidekobot.util; package wtf.beatrice.hidekobot.util;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Message;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.channel.ChannelType;
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.ButtonInteractionEvent;
import net.dv8tion.jda.api.interactions.commands.Command; import net.dv8tion.jda.api.interactions.commands.Command;
import net.dv8tion.jda.api.interactions.commands.build.CommandData; import net.dv8tion.jda.api.interactions.commands.build.CommandData;
import net.dv8tion.jda.api.interactions.components.LayoutComponent;
import net.dv8tion.jda.api.requests.RestAction;
import wtf.beatrice.hidekobot.Cache; import wtf.beatrice.hidekobot.Cache;
import wtf.beatrice.hidekobot.HidekoBot; import wtf.beatrice.hidekobot.HidekoBot;
import wtf.beatrice.hidekobot.datasources.DatabaseSource;
import wtf.beatrice.hidekobot.objects.commands.SlashCommand; import wtf.beatrice.hidekobot.objects.commands.SlashCommand;
import java.util.ArrayList; import java.util.ArrayList;
@ -134,4 +142,96 @@ public class CommandUtil
logger.log("Commands updated. New total: " + allCommands.size() + "."); logger.log("Commands updated. New total: " + allCommands.size() + ".");
} }
} }
/**
* Method to disable all buttons from an expired message.
*
* @param messageId the message id to disable.
*/
public static void disableExpired(String messageId)
{
DatabaseSource databaseSource = Cache.getDatabaseSource();
String channelId = databaseSource.getQueuedExpiringMessageChannel(messageId);
// todo: warning, the following method + related if check are thread-locking.
// todo: we should probably merge the two tables somehow, since they have redundant information.
ChannelType msgChannelType = databaseSource.getTrackedMessageChannelType(messageId);
MessageChannel textChannel = null;
// this should never happen, but only message channels are supported.
if(!msgChannelType.isMessage())
{
databaseSource.untrackExpiredMessage(messageId);
return;
}
// if this is a DM
if(!(msgChannelType.isGuild()))
{
String userId = databaseSource.getTrackedReplyUserId(messageId);
User user = userId == null ? null : HidekoBot.getAPI().retrieveUserById(userId).complete();
if(user == null)
{
// if user is not found, consider it expired
// (deleted profile, or blocked the bot)
databaseSource.untrackExpiredMessage(messageId);
return;
}
textChannel = user.openPrivateChannel().complete();
}
else
{
String guildId = databaseSource.getQueuedExpiringMessageGuild(messageId);
Guild guild = guildId == null ? null : HidekoBot.getAPI().getGuildById(guildId);
if(guild == null)
{
// if guild is not found, consider it expired
// (server was deleted or bot was kicked)
databaseSource.untrackExpiredMessage(messageId);
return;
}
textChannel = guild.getTextChannelById(channelId);
}
if(textChannel == null)
{
// if channel is not found, count it as expired
// (channel was deleted or bot permissions restricted)
databaseSource.untrackExpiredMessage(messageId);
return;
}
RestAction<Message> retrieveAction = textChannel.retrieveMessageById(messageId);
if(Cache.isVerbose()) logger.log("cleaning up: " + messageId);
retrieveAction.queue(
message -> {
if(message == null)
{
databaseSource.untrackExpiredMessage(messageId);
return;
}
List<LayoutComponent> components = message.getComponents();
List<LayoutComponent> newComponents = new ArrayList<>();
for (LayoutComponent component : components)
{
component = component.asDisabled();
newComponents.add(component);
}
message.editMessageComponents(newComponents).queue();
databaseSource.untrackExpiredMessage(messageId);
},
(error) -> {
databaseSource.untrackExpiredMessage(messageId);
});
}
} }