Added selection box support

This commit is contained in:
Toastie 2024-12-26 20:16:18 +13:00
parent 32776826ba
commit 280d18c80b
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
2 changed files with 123 additions and 123 deletions

View file

@ -204,7 +204,7 @@ public static class EventHandler
await OnNewTicketButtonUsed(e.Interaction); await OnNewTicketButtonUsed(e.Interaction);
return; return;
case not null when e.Id.StartsWith("supportchild_interviewbutton"): case not null when e.Id.StartsWith("supportchild_interviewbutton"):
await Interviewer.ProcessButtonResponse(e.Interaction); await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
return; return;
case "right": case "right":
case "left": case "left":
@ -225,6 +225,9 @@ public static class EventHandler
case not null when e.Id.StartsWith("supportchild_newticketselector"): case not null when e.Id.StartsWith("supportchild_newticketselector"):
await CreateSelectionBoxPanelCommand.OnSelectionMenuUsed(e.Interaction); await CreateSelectionBoxPanelCommand.OnSelectionMenuUsed(e.Interaction);
return; return;
case not null when e.Id.StartsWith("supportchild_interviewselector"):
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
return;
default: default:
Logger.Warn("Unknown selection box option received! '" + e.Id + "'"); Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
return; return;

View file

@ -13,13 +13,14 @@ namespace SupportChild;
public static class Interviewer public static class Interviewer
{ {
// TODO: Investigate other types of selectors
public enum QuestionType public enum QuestionType
{ {
ERROR, ERROR,
CANCEL, CANCEL,
DONE, DONE,
BUTTONS, BUTTONS,
SELECTOR, TEXT_SELECTOR,
TEXT_INPUT TEXT_INPUT
} }
@ -182,9 +183,7 @@ public static class Interviewer
return activeInterviews.ContainsKey(channelID); return activeInterviews.ContainsKey(channelID);
} }
// TODO: Add selection box handling. public static async Task ProcessButtonOrSelectorResponse(DiscordInteraction interaction)
public static async Task ProcessButtonResponse(DiscordInteraction interaction)
{ {
// TODO: Add error responses. // TODO: Add error responses.
@ -193,6 +192,12 @@ public static class Interviewer
return; return;
} }
// The user selected nothing.
if (interaction.Data.ComponentType == DiscordComponentType.StringSelect && interaction.Data.Values.Length == 0)
{
return;
}
await interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredMessageUpdate); await interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredMessageUpdate);
// Could not find active interview. // Could not find active interview.
@ -214,69 +219,46 @@ public static class Interviewer
} }
// Parse the response index from the button. // Parse the response index from the button.
if (!int.TryParse(interaction.Data.CustomId.Replace("supportchild_interviewbutton ", ""), out int pathIndex)) string componentID = "";
switch (interaction.Data.ComponentType)
{ {
Logger.Error("Invalid interview button index: " + interaction.Data.CustomId.Replace("supportchild_interviewbutton ", "")); case DiscordComponentType.StringSelect:
componentID = interaction.Data.Values[0];
break;
case DiscordComponentType.Button:
componentID = interaction.Data.CustomId.Replace("supportchild_interviewbutton ", "");
break;
default:
throw new ArgumentOutOfRangeException();
}
if (!int.TryParse(componentID, out int pathIndex))
{
Logger.Error("Invalid interview button/selector index: " + componentID);
return; return;
} }
if (pathIndex >= currentQuestion.paths.Count || pathIndex < 0) if (pathIndex >= currentQuestion.paths.Count || pathIndex < 0)
{ {
Logger.Error("Invalid interview button index: " + pathIndex); Logger.Error("Invalid interview button/selector index: " + pathIndex);
return; return;
} }
KeyValuePair<string, InterviewQuestion> questionPath = currentQuestion.paths.ElementAt(pathIndex); (string questionString, InterviewQuestion nextQuestion) = currentQuestion.paths.ElementAt(pathIndex);
await HandleAnswer(questionString, nextQuestion, interviewRoot, currentQuestion, interaction.Channel);
currentQuestion.answer = interaction.Data.Name; // Edit message to remove buttons/selectors.
currentQuestion.answerID = 0; // TODO: Add footer with answer.
// Create next question, or finish the interview.
InterviewQuestion nextQuestion = questionPath.Value;
switch (questionPath.Value.type)
{
case QuestionType.TEXT_INPUT:
await CreateQuestion(interaction.Channel, nextQuestion);
break;
case QuestionType.BUTTONS:
await CreateQuestion(interaction.Channel, nextQuestion);
// TODO: Remove buttons
break;
case QuestionType.SELECTOR:
await CreateQuestion(interaction.Channel, nextQuestion);
// TODO: Remove selector
break;
case QuestionType.DONE:
// TODO: Create summary.
// TODO: Remove previous interview messages.
// TODO: Remove active interview.
Logger.Error("INTERVIEW DONE");
break;
case QuestionType.CANCEL:
default:
// TODO: Post fail message.
// TODO: Remove active interview.
Logger.Error("INTERVIEW FAILED");
break;
}
// Remove other paths.
currentQuestion.paths = new Dictionary<string, InterviewQuestion>
{
{ questionPath.Key, nextQuestion }
};
await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(interaction.Message.Embeds[0])); await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(interaction.Message.Embeds[0]));
Database.SaveInterview(interaction.Channel.Id, interviewRoot);
} }
public static async Task ProcessResponseMessage(DiscordMessage message) public static async Task ProcessResponseMessage(DiscordMessage message)
{ {
// TODO: Handle other interactions like button presses. // TODO: Handle other interactions like button presses.
// TODO: Handle FAIL event, cancelling the interview. // TODO: Handle FAIL event, cancelling the interview.
// TODO: Handle DONE event, creating a summary.
// Either the message or the referenced message is null. // Either the message or the referenced message is null.
if (message.Channel == null || message.ReferencedMessage?.Channel == null) if (message.Channel == null || message.ReferencedMessage?.Channel == null)
@ -312,78 +294,7 @@ public static class Interviewer
// Skip to the matching path. // Skip to the matching path.
if (!Regex.IsMatch(message.Content, questionString)) continue; if (!Regex.IsMatch(message.Content, questionString)) continue;
// TODO: Refactor this into separate function to reduce duplication await HandleAnswer(questionString, nextQuestion, interviewRoot, currentQuestion, message.Channel, message);
currentQuestion.answer = message.Content;
currentQuestion.answerID = message.Id;
// Create next question, or finish the interview.
switch (nextQuestion.type)
{
case QuestionType.ERROR:
break;
case QuestionType.TEXT_INPUT:
case QuestionType.BUTTONS:
case QuestionType.SELECTOR:
await CreateQuestion(message.Channel, nextQuestion);
// Remove other paths.
currentQuestion.paths = new Dictionary<string, InterviewQuestion>
{
{ questionString, nextQuestion }
};
Database.SaveInterview(message.Channel.Id, interviewRoot);
break;
case QuestionType.DONE:
// TODO: Remove previous interview messages.
OrderedDictionary summaryFields = new OrderedDictionary();
interviewRoot.GetSummary(ref summaryFields);
DiscordEmbedBuilder embed = new DiscordEmbedBuilder()
{
Color = Utilities.StringToColor(nextQuestion.color),
Title = "Summary:",
Description = nextQuestion.message,
};
foreach (DictionaryEntry entry in summaryFields)
{
embed.AddField((string)entry.Key, (string)entry.Value);
}
await message.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 message.Channel.GetMessageAsync(previousMessageID);
await message.Channel.DeleteMessageAsync(previousMessage, "Deleting old interview message.");
}
catch (Exception e)
{
Logger.Error("Failed to delete old interview message: " + e.Message);
}
}
if (!Database.TryDeleteInterview(message.Channel.Id))
{
Logger.Error("Could not delete interview from database. Channel ID: " + message.Channel.Id);
}
Reload();
return;
case QuestionType.CANCEL:
default:
// TODO: Post fail message.
// TODO: Remove active interview.
break;
}
return; return;
} }
@ -391,6 +302,92 @@ public static class Interviewer
} }
private static async Task HandleAnswer(string questionString,
InterviewQuestion nextQuestion,
InterviewQuestion interviewRoot,
InterviewQuestion previousQuestion,
DiscordChannel channel,
DiscordMessage message = null)
{
// The answer was provided using a button or selector
if (message == null)
{
previousQuestion.answer = questionString;
previousQuestion.answerID = 0;
}
else
{
previousQuestion.answer = message.Content;
previousQuestion.answerID = message.Id;
}
// 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)
{
case QuestionType.TEXT_INPUT:
case QuestionType.BUTTONS:
case QuestionType.TEXT_SELECTOR:
await CreateQuestion(channel, nextQuestion);
Database.SaveInterview(channel.Id, interviewRoot);
break;
case QuestionType.DONE:
OrderedDictionary summaryFields = new OrderedDictionary();
interviewRoot.GetSummary(ref summaryFields);
DiscordEmbedBuilder embed = new DiscordEmbedBuilder()
{
Color = Utilities.StringToColor(nextQuestion.color),
Title = "Summary:",
Description = nextQuestion.message,
};
foreach (DictionaryEntry entry in summaryFields)
{
embed.AddField((string)entry.Key, (string)entry.Value);
}
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);
}
}
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.
break;
case QuestionType.ERROR:
default:
break;
}
}
private static async Task CreateQuestion(DiscordChannel channel, InterviewQuestion question) private static async Task CreateQuestion(DiscordChannel channel, InterviewQuestion question)
{ {
DiscordMessageBuilder msgBuilder = new DiscordMessageBuilder(); DiscordMessageBuilder msgBuilder = new DiscordMessageBuilder();
@ -414,7 +411,7 @@ public static class Interviewer
msgBuilder.AddComponents(buttonRow); msgBuilder.AddComponents(buttonRow);
} }
break; break;
case QuestionType.SELECTOR: case QuestionType.TEXT_SELECTOR:
List<DiscordSelectComponent> selectionComponents = []; List<DiscordSelectComponent> selectionComponents = [];
int selectionOptions = 0; int selectionOptions = 0;