Added support for END_WITHOUT_SUMMARY and ERROR question types

This commit is contained in:
Toastie 2024-12-26 20:28:39 +13:00
parent ebd279da45
commit 91991c10f2
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
2 changed files with 110 additions and 60 deletions

View file

@ -862,7 +862,7 @@ public static class Database
c.Open();
using MySqlCommand cmd = new MySqlCommand(query, c);
cmd.Parameters.AddWithValue("@channel_id", channelID);
cmd.Parameters.AddWithValue("@interview", JsonConvert.SerializeObject(interview));
cmd.Parameters.AddWithValue("@interview", JsonConvert.SerializeObject(interview, Formatting.Indented));
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}

View file

@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
@ -17,8 +18,8 @@ public static class Interviewer
public enum QuestionType
{
ERROR,
CANCEL,
DONE,
END_WITH_SUMMARY,
END_WITHOUT_SUMMARY,
BUTTONS,
TEXT_SELECTOR,
TEXT_INPUT
@ -53,7 +54,9 @@ public static class Interviewer
[JsonProperty("paths")]
public Dictionary<string, InterviewQuestion> paths;
// The following parameters are populated by the bot, not the json template.
// ////////////////////////////////////////////////////////////////////////////
// The following parameters are populated by the bot, not the json template. //
// ////////////////////////////////////////////////////////////////////////////
// The ID of this message where the bot asked this question.
[JsonProperty("message-id")]
@ -67,6 +70,10 @@ public static class Interviewer
[JsonProperty("answer-id")]
public ulong answerID;
// Any extra messages generated by the bot that should be removed when the interview ends.
[JsonProperty("related-message-ids")]
public List<ulong> relatedMessageIDs;
public bool TryGetCurrentQuestion(out InterviewQuestion question)
{
// This object has not been initialized, we have checked too deep.
@ -117,12 +124,29 @@ public static class Interviewer
messageIDs.Add(answerID);
}
if (relatedMessageIDs != null)
{
messageIDs.AddRange(relatedMessageIDs);
}
// This will always contain exactly one or zero children.
foreach (KeyValuePair<string, InterviewQuestion> path in paths)
{
path.Value.GetMessageIDs(ref messageIDs);
}
}
public void AddRelatedMessageIDs(params ulong[] messageIDs)
{
if (relatedMessageIDs == null)
{
relatedMessageIDs = messageIDs.ToList();
}
else
{
relatedMessageIDs.AddRange(messageIDs);
}
}
}
// This class is identical to the one above and just exists as a hack to get JSON validation when
@ -178,11 +202,6 @@ public static class Interviewer
}
}
public static bool IsInterviewActive(ulong channelID)
{
return activeInterviews.ContainsKey(channelID);
}
public static async Task ProcessButtonOrSelectorResponse(DiscordInteraction interaction)
{
// TODO: Add error responses.
@ -192,7 +211,7 @@ public static class Interviewer
return;
}
// The user selected nothing.
// Return if the user didn't select anything
if (interaction.Data.ComponentType == DiscordComponentType.StringSelect && interaction.Data.Values.Length == 0)
{
return;
@ -200,25 +219,25 @@ public static class Interviewer
await interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredMessageUpdate);
// Could not find active interview.
// Return if there is no active interview in this channel
if (!activeInterviews.TryGetValue(interaction.Channel.Id, out InterviewQuestion interviewRoot))
{
return;
}
// Could not find message id in interview.
// Return if the current question cannot be found in the interview.
if (!interviewRoot.TryGetCurrentQuestion(out InterviewQuestion currentQuestion))
{
return;
}
// This button is for an older question.
// Check if this button/selector is for an older question.
if (interaction.Message.Id != currentQuestion.messageID)
{
return;
}
// Parse the response index from the button.
// Parse the response index from the button/selector.
string componentID = "";
switch (interaction.Data.ComponentType)
@ -249,10 +268,6 @@ public static class Interviewer
(string questionString, InterviewQuestion nextQuestion) = currentQuestion.paths.ElementAt(pathIndex);
await HandleAnswer(questionString, nextQuestion, interviewRoot, currentQuestion, interaction.Channel);
// Edit message to remove buttons/selectors.
// TODO: Add footer with answer.
await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(interaction.Message.Embeds[0]));
}
public static async Task ProcessResponseMessage(DiscordMessage message)
@ -304,25 +319,28 @@ public static class Interviewer
InterviewQuestion interviewRoot,
InterviewQuestion previousQuestion,
DiscordChannel channel,
DiscordMessage message = null)
DiscordMessage answerMessage = null)
{
// The answer was provided using a button or selector
if (message == null)
if (nextQuestion.type != QuestionType.ERROR)
{
previousQuestion.answer = questionString;
previousQuestion.answerID = 0;
}
else
{
previousQuestion.answer = message.Content;
previousQuestion.answerID = message.Id;
}
// The answer was provided using a button or selector
if (answerMessage == null)
{
previousQuestion.answer = questionString;
previousQuestion.answerID = 0;
}
else
{
previousQuestion.answer = answerMessage.Content;
previousQuestion.answerID = answerMessage.Id;
}
// Remove any other paths from the previous question.
previousQuestion.paths = new Dictionary<string, InterviewQuestion>
{
{ questionString, nextQuestion }
};
// Remove any other paths from the previous question.
previousQuestion.paths = new Dictionary<string, InterviewQuestion>
{
{ questionString, nextQuestion }
};
}
// Create next question, or finish the interview.
switch (nextQuestion.type)
@ -333,14 +351,14 @@ public static class Interviewer
await CreateQuestion(channel, nextQuestion);
Database.SaveInterview(channel.Id, interviewRoot);
break;
case QuestionType.DONE:
case QuestionType.END_WITH_SUMMARY:
OrderedDictionary summaryFields = new OrderedDictionary();
interviewRoot.GetSummary(ref summaryFields);
DiscordEmbedBuilder embed = new DiscordEmbedBuilder()
{
Color = Utilities.StringToColor(nextQuestion.color),
Title = "Summary:",
Title = "Summary:", // TODO: Set title
Description = nextQuestion.message,
};
@ -351,41 +369,73 @@ public static class Interviewer
await channel.SendMessageAsync(embed);
List<ulong> previousMessages = new List<ulong> { };
interviewRoot.GetMessageIDs(ref previousMessages);
foreach (ulong previousMessageID in previousMessages)
{
try
{
Logger.Debug("Deleting message: " + previousMessageID);
DiscordMessage previousMessage = await channel.GetMessageAsync(previousMessageID);
await channel.DeleteMessageAsync(previousMessage, "Deleting old interview message.");
}
catch (Exception e)
{
Logger.Error("Failed to delete old interview message: " + e.Message);
}
}
await DeletePreviousMessages(interviewRoot, channel);
if (!Database.TryDeleteInterview(channel.Id))
{
Logger.Error("Could not delete interview from database. Channel ID: " + channel.Id);
}
Reload();
return;
case QuestionType.CANCEL:
// TODO: Post fail message.
// TODO: Remove active interview.
case QuestionType.END_WITHOUT_SUMMARY:
// TODO: Add command to restart interview.
await channel.SendMessageAsync(new DiscordEmbedBuilder()
{
Color = Utilities.StringToColor(nextQuestion.color),
Description = nextQuestion.message
});
await DeletePreviousMessages(interviewRoot, channel);
if (!Database.TryDeleteInterview(channel.Id))
{
Logger.Error("Could not delete interview from database. Channel ID: " + channel.Id);
}
Reload();
break;
case QuestionType.ERROR:
default:
// TODO: Post error message.
if (answerMessage == null)
{
DiscordMessage errorMessage = await channel.SendMessageAsync(new DiscordEmbedBuilder()
{
Color = Utilities.StringToColor(nextQuestion.color),
Description = nextQuestion.message
});
previousQuestion.AddRelatedMessageIDs(errorMessage.Id);
}
else
{
DiscordMessageBuilder errorMessageBuilder = new DiscordMessageBuilder()
.AddEmbed(new DiscordEmbedBuilder()
{
Color = Utilities.StringToColor(nextQuestion.color),
Description = nextQuestion.message
}).WithReply(answerMessage.Id);
DiscordMessage errorMessage = await answerMessage.RespondAsync(errorMessageBuilder);
previousQuestion.AddRelatedMessageIDs(errorMessage.Id, answerMessage.Id);
}
break;
}
}
private static async Task DeletePreviousMessages(InterviewQuestion interviewRoot, DiscordChannel channel)
{
List<ulong> previousMessages = new List<ulong> { };
interviewRoot.GetMessageIDs(ref previousMessages);
foreach (ulong previousMessageID in previousMessages)
{
try
{
DiscordMessage previousMessage = await channel.GetMessageAsync(previousMessageID);
await channel.DeleteMessageAsync(previousMessage, "Deleting old interview message.");
}
catch (Exception e)
{
Logger.Warn("Failed to delete old interview message: ", e);
}
}
}
private static async Task CreateQuestion(DiscordChannel channel, InterviewQuestion question)
{
DiscordMessageBuilder msgBuilder = new DiscordMessageBuilder();
@ -428,8 +478,8 @@ public static class Interviewer
case QuestionType.TEXT_INPUT:
embed.WithFooter("Reply to this message with your answer. You cannot include images or files.");
break;
case QuestionType.DONE:
case QuestionType.CANCEL:
case QuestionType.END_WITH_SUMMARY:
case QuestionType.END_WITHOUT_SUMMARY:
case QuestionType.ERROR:
default:
break;