From 414ace3dcccc7fc68563679362a07de3307edcf2 Mon Sep 17 00:00:00 2001 From: Toastie Date: Thu, 26 Dec 2024 23:23:25 +1300 Subject: [PATCH] Added extra validation for templates --- Commands/AdminCommands.cs | 16 ++++++++ Interviewer.cs | 86 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/Commands/AdminCommands.cs b/Commands/AdminCommands.cs index 7daead7..452bd80 100644 --- a/Commands/AdminCommands.cs +++ b/Commands/AdminCommands.cs @@ -279,6 +279,22 @@ public class AdminCommands } }); + if (interview != null) + { + foreach (KeyValuePair interviewRoot in interview) + { + interviewRoot.Value.Validate(ref errors, out int summaryCount, out int summaryMaxLength); + if (summaryCount > 25) + { + errors.Add("A summary cannot contain more than 25 fields, but you have " + summaryCount + " fields in one of your interview branches."); + } + if (summaryMaxLength >= 6000) + { + errors.Add("A summary cannot contain more than 6000 characters, but one of your branches has the possibility of its summary reaching " + summaryMaxLength + " characters."); + } + } + } + if (errors.Count != 0) { string errorString = string.Join("\n\n", errors); diff --git a/Interviewer.cs b/Interviewer.cs index 4e581c0..dcf5f94 100644 --- a/Interviewer.cs +++ b/Interviewer.cs @@ -46,6 +46,8 @@ public static class Interviewer // The entire interview tree is serialized and stored in the database in order to record responses as they are made. public class InterviewQuestion { + // TODO: String selector entry description + // Title of the message embed. [JsonProperty("title")] public string title; @@ -236,6 +238,85 @@ public static class Interviewer [JsonProperty("paths", Required = Required.Always)] public Dictionary paths; + + public void Validate(ref List errors, out int summaryCount, out int summaryMaxLength) + { + if (string.IsNullOrWhiteSpace(message)) + { + errors.Add("Message cannot be empty."); + } + + if (type is QuestionType.ERROR or QuestionType.END_WITH_SUMMARY or QuestionType.END_WITHOUT_SUMMARY) + { + if (paths.Count > 0) + { + errors.Add("'" + type + "' questions cannot have child paths."); + } + } + else if (paths.Count == 0) + { + errors.Add("'" + type + "' questions must have at least one child path."); + } + + List summaryCounts = new List(); + Dictionary childMaxLengths = new Dictionary(); + foreach (KeyValuePair path in paths) + { + path.Value.Validate(ref errors, out int summaries, out int maxLen); + summaryCounts.Add(summaries); + childMaxLengths.Add(path.Key, maxLen); + } + + summaryCount = summaryCounts.Count == 0 ? 0 : summaryCounts.Max(); + + string childPathString = ""; + int childMaxLength = 0; + if (childMaxLengths.Count != 0) + { + (childPathString, childMaxLength) = childMaxLengths.ToArray().MaxBy(x => x.Key.Length + x.Value); + } + + summaryMaxLength = childMaxLength; + + if (string.IsNullOrWhiteSpace(summaryField)) + { + ++summaryCount; + } + + // Only count summaries that end in a summary question. + if (type == QuestionType.END_WITH_SUMMARY) + { + summaryMaxLength = message?.Length ?? 0; + summaryMaxLength += title?.Length ?? 0; + } + // Only add to the total max length if the summary field is not empty. That way we know this branch ends in a summary. + else if (summaryMaxLength > 0 && !string.IsNullOrEmpty(summaryField)) + { + summaryMaxLength += summaryField.Length; + switch (type) + { + case QuestionType.BUTTONS: + case QuestionType.TEXT_SELECTOR: + summaryMaxLength += childPathString.Length; + break; + case QuestionType.USER_SELECTOR: + case QuestionType.ROLE_SELECTOR: + case QuestionType.MENTIONABLE_SELECTOR: + case QuestionType.CHANNEL_SELECTOR: + // Approximate length of a mention + summaryMaxLength += 23; + break; + case QuestionType.TEXT_INPUT: + summaryMaxLength += (maxLength == 0 ? 1024 : Math.Min(maxLength, 1024)); + break; + case QuestionType.END_WITH_SUMMARY: + case QuestionType.END_WITHOUT_SUMMARY: + case QuestionType.ERROR: + default: + break; + } + } + } } private static Dictionary activeInterviews = []; @@ -335,7 +416,6 @@ public static class Interviewer try { - // TODO: Debug this with the new selectors await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage); } catch (Exception e) @@ -376,8 +456,10 @@ public static class Interviewer case DiscordComponentType.Button: componentID = interaction.Data.CustomId.Replace("supportchild_interviewbutton ", ""); break; + case DiscordComponentType.ActionRow: + case DiscordComponentType.FormInput: default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException("Tried to process an invalid component type: " + interaction.Data.ComponentType); } // The different mentionable selectors provide the actual answer, while the others just return the ID.