Skip to content

Commit d111e40

Browse files
committed
Add /qotw-view command
1 parent 2a9630c commit d111e40

File tree

9 files changed

+357
-29
lines changed

9 files changed

+357
-29
lines changed

src/main/java/net/javadiscord/javabot/systems/qotw/commands/QOTWCommand.java renamed to src/main/java/net/javadiscord/javabot/systems/qotw/commands/QOTWAdminCommand.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
import java.util.Set;
1616

1717
/**
18-
* Represents the `/qotw` command. This holds administrative commands for managing the Question of the Week.
18+
* Represents the `/qotw-admin` command. This holds administrative commands for managing the Question of the Week.
1919
*/
20-
public class QOTWCommand extends SlashCommand {
20+
public class QOTWAdminCommand extends SlashCommand {
2121
/**
2222
* This classes constructor which sets the {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData} and
2323
* adds the corresponding {@link net.dv8tion.jda.api.interactions.commands.Command.SubcommandGroup}s.
2424
*/
25-
public QOTWCommand() {
26-
setSlashCommandData(Commands.slash("qotw", "Administrative tools for managing the Question of the Week.")
25+
public QOTWAdminCommand() {
26+
setSlashCommandData(Commands.slash("qotw-admin", "Administrative tools for managing the Question of the Week.")
2727
.setDefaultPermissions(DefaultMemberPermissions.DISABLED)
2828
.setGuildOnly(true)
2929
);
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package net.javadiscord.javabot.systems.qotw.commands.view;
2+
3+
import java.util.List;
4+
import java.util.stream.Collectors;
5+
6+
import com.dynxsty.dih4jda.interactions.commands.SlashCommand;
7+
8+
import net.dv8tion.jda.api.EmbedBuilder;
9+
import net.dv8tion.jda.api.entities.TextChannel;
10+
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
11+
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
12+
import net.dv8tion.jda.api.interactions.commands.OptionType;
13+
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
14+
import net.javadiscord.javabot.Bot;
15+
import net.javadiscord.javabot.data.h2db.DbActions;
16+
import net.javadiscord.javabot.systems.qotw.submissions.SubmissionStatus;
17+
import net.javadiscord.javabot.systems.qotw.submissions.dao.QOTWSubmissionRepository;
18+
import net.javadiscord.javabot.systems.qotw.submissions.model.QOTWSubmission;
19+
import net.javadiscord.javabot.systems.qotw.submissions.subcommands.MarkBestAnswerSubcommand;
20+
import net.javadiscord.javabot.util.Responses;
21+
22+
/**
23+
* Represents the `/qotw-view list-answers` subcommand. It allows for listing answers to a specific QOTW.
24+
*/
25+
public class QOTWListAnswersSubcommand extends SlashCommand.Subcommand {
26+
public QOTWListAnswersSubcommand() {
27+
setSubcommandData(new SubcommandData("list-answers", "Lists answers to (previous) questions of the week")
28+
.addOption(OptionType.INTEGER, "question", "The question number",true));
29+
}
30+
31+
@Override
32+
public void execute(SlashCommandInteractionEvent event) {
33+
if (!event.isFromGuild()) {
34+
Responses.replyGuildOnly(event).setEphemeral(true).queue();
35+
return;
36+
}
37+
OptionMapping questionNumOption = event.getOption("question");
38+
if (questionNumOption==null) {
39+
Responses.error(event, "Missing question number").setEphemeral(true).queue();
40+
return;
41+
}
42+
int questionNum = questionNumOption.getAsInt();
43+
44+
event.deferReply(true).queue();
45+
DbActions.doAsyncDaoAction(QOTWSubmissionRepository::new, repo -> {
46+
List<QOTWSubmission> submissions = repo.getSubmissionsByQuestionNumber(event.getGuild().getIdLong(), questionNum);
47+
EmbedBuilder eb = new EmbedBuilder();
48+
eb.setDescription("**answers of Question of the week #"+questionNum+"**\n");
49+
TextChannel submissionChannel = Bot.getConfig().get(event.getGuild()).getQotwConfig().getSubmissionChannel();
50+
String allAnswers = submissions
51+
.stream()
52+
.filter(submission -> isSubmissionVisible(submission, event.getUser().getIdLong()))
53+
.filter(submission -> submission.getQuestionNumber() == questionNum)
54+
.map(s -> (isBestAnswer(submissionChannel,s)?
55+
"best ":s.getStatus() == SubmissionStatus.ACCEPTED?"accepted ":"")+
56+
"Answer by <@"+s.getAuthorId()+">")
57+
.collect(Collectors.joining("\n"));
58+
if (allAnswers.isEmpty()) {
59+
allAnswers = "No accepted answers found.";
60+
}
61+
eb.appendDescription(allAnswers);
62+
eb.setFooter("Results may not be accurate due to historic data.");
63+
eb.setColor(Responses.Type.DEFAULT.getColor());
64+
event.getHook().sendMessageEmbeds(eb.build()).queue();
65+
});
66+
}
67+
68+
/**
69+
* Checks whether a submission is visible to a specific user.
70+
* @param submission the {@link QOTWSubmission} to be checked
71+
* @param viewerID the user to check against
72+
* @return {@code true} if the submission is visible, else {@code false}}
73+
*/
74+
public static boolean isSubmissionVisible(QOTWSubmission submission, long viewerID) {
75+
return submission.getStatus() == SubmissionStatus.ACCEPTED || submission.getAuthorId() == viewerID;
76+
}
77+
78+
private boolean isBestAnswer(TextChannel submissionChannel, QOTWSubmission submission) {
79+
return submissionChannel
80+
.retrieveArchivedPrivateThreadChannels()
81+
.stream()
82+
.filter(t -> t.getIdLong() == submission.getThreadId())
83+
.findAny()
84+
.map(t -> MarkBestAnswerSubcommand.isSubmissionThreadABestAnswer(t))
85+
.orElse(false);
86+
}
87+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package net.javadiscord.javabot.systems.qotw.commands.view;
2+
3+
import java.util.Comparator;
4+
import java.util.List;
5+
6+
import com.dynxsty.dih4jda.interactions.commands.SlashCommand;
7+
8+
import net.dv8tion.jda.api.EmbedBuilder;
9+
import net.dv8tion.jda.api.entities.MessageEmbed;
10+
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
11+
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
12+
import net.dv8tion.jda.api.interactions.commands.OptionType;
13+
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
14+
import net.javadiscord.javabot.data.h2db.DbActions;
15+
import net.javadiscord.javabot.systems.qotw.dao.QuestionQueueRepository;
16+
import net.javadiscord.javabot.systems.qotw.model.QOTWQuestion;
17+
import net.javadiscord.javabot.util.Responses;
18+
19+
/**
20+
* Represents the `/qotw-view query` subcommand. It allows for listing filtering QOTWs.
21+
*/
22+
public class QOTWQuerySubcommand extends SlashCommand.Subcommand {
23+
public QOTWQuerySubcommand() {
24+
setSubcommandData(new SubcommandData("list-questions", "Lists previous questions of the week")
25+
.addOption(OptionType.STRING, "query", "Only queries questions that contain a specific query", false));
26+
}
27+
28+
@Override
29+
public void execute(SlashCommandInteractionEvent event) {
30+
if(!event.isFromGuild()) {
31+
Responses.replyGuildOnly(event).setEphemeral(true).queue();
32+
return;
33+
}
34+
String query = event.getOption("query", ()->"", OptionMapping::getAsString);
35+
event.deferReply(true).queue();
36+
DbActions.doAsyncDaoAction(QuestionQueueRepository::new, repo->{
37+
List<QOTWQuestion> questions = repo.getUsedQuestionsWithQuery(event.getGuild().getIdLong(), query, 0, 20);
38+
EmbedBuilder eb = new EmbedBuilder();
39+
eb.setDescription("Questions of the week"+(query.isEmpty()?"":" matching '"+query+"'"));
40+
questions
41+
.stream()
42+
.sorted(Comparator.comparingInt(QOTWQuestion::getQuestionNumber))
43+
.map(q -> new MessageEmbed.Field("Question #" + q.getQuestionNumber(), q.getText(), true))
44+
.forEach(eb::addField);
45+
event.getHook().sendMessageEmbeds(eb.build()).queue();
46+
});
47+
}
48+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package net.javadiscord.javabot.systems.qotw.commands.view;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
5+
import com.dynxsty.dih4jda.interactions.commands.SlashCommand;
6+
7+
import net.dv8tion.jda.api.EmbedBuilder;
8+
import net.dv8tion.jda.api.entities.MessageEmbed;
9+
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
10+
import net.dv8tion.jda.api.interactions.commands.OptionMapping;
11+
import net.dv8tion.jda.api.interactions.commands.OptionType;
12+
import net.dv8tion.jda.api.interactions.commands.build.SubcommandData;
13+
import net.javadiscord.javabot.Bot;
14+
import net.javadiscord.javabot.data.h2db.DbActions;
15+
import net.javadiscord.javabot.systems.qotw.submissions.dao.QOTWSubmissionRepository;
16+
import net.javadiscord.javabot.systems.qotw.submissions.model.QOTWSubmission;
17+
import net.javadiscord.javabot.util.MessageActionUtils;
18+
import net.javadiscord.javabot.util.Responses;
19+
20+
/**
21+
* Represents the `/qotw-view answer` subcommand. It allows for viewing an answer to a QOTW.
22+
*/
23+
public class QOTWViewAnswerSubcommand extends SlashCommand.Subcommand{
24+
25+
/**
26+
* The constructor of this class, which sets the corresponding {@link SubcommandData}.
27+
*/
28+
public QOTWViewAnswerSubcommand() {
29+
setSubcommandData(new SubcommandData("answer", "Views the content of an answer to the Question of the week")
30+
.addOption(OptionType.INTEGER, "question", "The question number the answer has been submitted to", true)
31+
.addOption(OptionType.USER, "answerer", "The user who answered the question", true));
32+
}
33+
34+
@Override
35+
public void execute(SlashCommandInteractionEvent event) {
36+
if (!event.isFromGuild()) {
37+
Responses.replyGuildOnly(event).setEphemeral(true).queue();
38+
return;
39+
}
40+
OptionMapping questionOption = event.getOption("question");
41+
if (questionOption == null) {
42+
Responses.error(event, "The question option is missing.").queue();
43+
return;
44+
}
45+
OptionMapping answerOwnerOption = event.getOption("answerer");
46+
if (answerOwnerOption == null) {
47+
Responses.error(event, "The answerer option is missing.").queue();
48+
return;
49+
}
50+
event.deferReply(true).queue();
51+
DbActions.doAsyncDaoAction(QOTWSubmissionRepository::new, repo -> {
52+
QOTWSubmission submission = repo.getSubmissionByQuestionNumberAndAuthorID(event.getGuild().getIdLong(), questionOption.getAsInt(), answerOwnerOption.getAsUser().getIdLong());
53+
if (submission == null || !QOTWListAnswersSubcommand.isSubmissionVisible(submission, event.getUser().getIdLong())) {
54+
Responses.error(event.getHook(), "No answer to the question was found from the specific user.").queue();
55+
return;
56+
}
57+
Bot.getConfig().get(event.getGuild()).getQotwConfig()
58+
.getSubmissionChannel().retrieveArchivedPrivateThreadChannels().queue(threadChannels->{
59+
threadChannels
60+
.stream()
61+
.filter(c->c.getIdLong() == submission.getThreadId())
62+
.findAny()
63+
.ifPresentOrElse(submissionChannel->
64+
submissionChannel.getHistoryFromBeginning(100).queue(history->
65+
MessageActionUtils.copyMessagesToNewThread(event.getGuildChannel().asStandardGuildMessageChannel(),
66+
buildQOTWInfoEmbed(submission, event.getMember()==null?event.getUser().getName():event.getMember().getEffectiveName()),
67+
"QOTW #"+submission.getQuestionNumber(),
68+
history.getRetrievedHistory(),
69+
()->Responses.success(event.getHook(), "View Answer", "Answer copied successfully").setEphemeral(true))),
70+
()->Responses.error(event.getHook(), "The QOTW submission thread was not found.").queue());
71+
});
72+
});
73+
}
74+
75+
private @NotNull MessageEmbed buildQOTWInfoEmbed(QOTWSubmission submission, String requester) {
76+
return new EmbedBuilder()
77+
.setTitle("Answer to Question of the Week #"+submission.getQuestionNumber())
78+
.setDescription("Answer by <@"+submission.getAuthorId()+">")
79+
.setFooter("Requested by "+requester)
80+
.build();
81+
}
82+
83+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package net.javadiscord.javabot.systems.qotw.commands.view;
2+
3+
import com.dynxsty.dih4jda.interactions.commands.SlashCommand;
4+
5+
import net.dv8tion.jda.api.interactions.commands.DefaultMemberPermissions;
6+
import net.dv8tion.jda.api.interactions.commands.build.Commands;
7+
8+
/**
9+
* Represents the `/qotw-view` command.
10+
* It allows to view previous QOTWs and their answers.
11+
*/
12+
public class QOTWViewCommand extends SlashCommand{
13+
/**
14+
* This classes constructor which sets the {@link net.dv8tion.jda.api.interactions.commands.build.SlashCommandData} and
15+
* adds the corresponding {@link net.dv8tion.jda.api.interactions.commands.Command.SubcommandGroup}s.
16+
*/
17+
public QOTWViewCommand() {
18+
setSlashCommandData(Commands.slash("qotw-view", "Query questions of the week and their answers")
19+
.setDefaultPermissions(DefaultMemberPermissions.ENABLED)
20+
.setGuildOnly(true));
21+
addSubcommands(new QOTWQuerySubcommand(), new QOTWListAnswersSubcommand(), new QOTWViewAnswerSubcommand());
22+
23+
}
24+
}

src/main/java/net/javadiscord/javabot/systems/qotw/dao/QuestionQueueRepository.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,36 @@ public void markUsed(QOTWQuestion question) throws SQLException {
108108
* @throws SQLException If an error occurs.
109109
*/
110110
public List<QOTWQuestion> getQuestions(long guildId, int page, int size) throws SQLException {
111-
String sql = "SELECT * FROM qotw_question WHERE guild_id = ? AND used = FALSE ORDER BY priority DESC, created_at ASC LIMIT %d OFFSET %d";
112-
try (PreparedStatement stmt = con.prepareStatement(String.format(sql, size, page))) {
111+
String sql = "SELECT * FROM qotw_question WHERE guild_id = ? AND used = FALSE ORDER BY priority DESC, created_at ASC LIMIT ? OFFSET ?";
112+
try (PreparedStatement stmt = con.prepareStatement(sql)) {
113113
stmt.setLong(1, guildId);
114+
stmt.setInt(2, size);
115+
stmt.setInt(3, page);
116+
ResultSet rs = stmt.executeQuery();
117+
List<QOTWQuestion> questions = new ArrayList<>(size);
118+
while (rs.next()) {
119+
questions.add(this.read(rs));
120+
}
121+
return questions;
122+
}
123+
}
124+
125+
/**
126+
* Gets as many questions matching a query as specified.
127+
* @param guildId The current guild's id..
128+
* @param query The query to match questions against.
129+
* @param page The page.
130+
* @param size The amount of questions to return.
131+
* @return A {@link List} containing the specified amount (or less) of {@link QOTWQuestion} matching the query.
132+
* @throws SQLException If an error occurs.
133+
*/
134+
public List<QOTWQuestion> getUsedQuestionsWithQuery(long guildId, String query, int page, int size) throws SQLException {
135+
String sql = "SELECT * FROM qotw_question WHERE guild_id = ? AND \"text\" LIKE ? AND used = TRUE ORDER BY question_number DESC, created_at ASC LIMIT ? OFFSET ?";
136+
try (PreparedStatement stmt = con.prepareStatement(sql)) {
137+
stmt.setLong(1, guildId);
138+
stmt.setString(2, "%"+query.toLowerCase()+"%");
139+
stmt.setInt(3, size);
140+
stmt.setInt(4, page);
114141
ResultSet rs = stmt.executeQuery();
115142
List<QOTWQuestion> questions = new ArrayList<>(size);
116143
while (rs.next()) {

src/main/java/net/javadiscord/javabot/systems/qotw/submissions/dao/QOTWSubmissionRepository.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,15 @@ public int getCurrentQuestionNumber() throws SQLException {
126126
/**
127127
* Returns all {@link QOTWSubmission}s based on the given question number.
128128
*
129+
* @param guildId the ID of the guild
129130
* @param questionNumber The week's number.
130131
* @return All {@link QOTWSubmission}s, as a {@link List}.
131132
* @throws SQLException If an error occurs.
132133
*/
133-
public List<QOTWSubmission> getSubmissionByQuestionNumber(int questionNumber) throws SQLException {
134-
try (PreparedStatement s = con.prepareStatement("SELECT * FROM qotw_submissions WHERE question_number = ?")) {
135-
s.setInt(1, questionNumber);
134+
public List<QOTWSubmission> getSubmissionsByQuestionNumber(long guildId, int questionNumber) throws SQLException {
135+
try (PreparedStatement s = con.prepareStatement("SELECT * FROM qotw_submissions WHERE guild_id = ? AND question_number = ?")) {
136+
s.setLong(1, guildId);
137+
s.setInt(2, questionNumber);
136138
ResultSet rs = s.executeQuery();
137139
List<QOTWSubmission> submissions = new ArrayList<>();
138140
while (rs.next()) {
@@ -142,6 +144,28 @@ public List<QOTWSubmission> getSubmissionByQuestionNumber(int questionNumber) th
142144
}
143145
}
144146

147+
/**
148+
* Returns the {@link QOTWSubmission} of a specific question by a specific user.
149+
*
150+
* @param guildId the ID of the guild
151+
* @param questionNumber The week's number.
152+
* @param authorID The ID of the user who created the submission.
153+
* @return The {@link QOTWSubmission} or {@code null} if the user has not submitted any answer to the question.
154+
* @throws SQLException If an error occurs.
155+
*/
156+
public QOTWSubmission getSubmissionByQuestionNumberAndAuthorID(long guildId,int questionNumber, long authorID) throws SQLException {
157+
try (PreparedStatement s = con.prepareStatement("SELECT * FROM qotw_submissions WHERE guild_id = ? AND question_number = ? AND author_id = ?")) {
158+
s.setLong(1, guildId);
159+
s.setInt(2, questionNumber);
160+
s.setLong(3, authorID);
161+
ResultSet rs = s.executeQuery();
162+
if (rs.next()) {
163+
return this.read(rs);
164+
}
165+
return null;
166+
}
167+
}
168+
145169
/**
146170
* Reads a {@link ResultSet} and returns a new {@link QOTWSubmission} object.
147171
*

0 commit comments

Comments
 (0)