From d8e186318294152fc90cfdb2e65d003b7fe7bc62 Mon Sep 17 00:00:00 2001 From: Toastie <toastie@toastiet0ast.com> Date: Tue, 4 Feb 2025 19:27:20 +1300 Subject: [PATCH] Made it possible to add a summary to any message instead of only interview ends --- Config.cs | 6 +- Interviews/Interview.cs | 23 ++--- Interviews/Interviewer.cs | 104 ++++++++++------------ Interviews/interview_template.schema.json | 23 +++-- default_config.yml | 7 +- 5 files changed, 75 insertions(+), 88 deletions(-) diff --git a/Config.cs b/Config.cs index 80a85cd..8ecd93a 100644 --- a/Config.cs +++ b/Config.cs @@ -28,8 +28,7 @@ internal static class Config internal static bool closingNotifications = false; internal static bool interviewsEnabled = false; - internal static bool deleteMessagesAfterSummary = false; - internal static bool deleteMessagesAfterNoSummary = false; + internal static bool deleteMessagesAfterInterviewEnd = false; internal static string hostName = "127.0.0.1"; internal static int port = 3306; @@ -100,8 +99,7 @@ internal static class Config closingNotifications = json.SelectToken("notifications.closing")?.Value<bool>() ?? false; interviewsEnabled = json.SelectToken("interviews.enabled")?.Value<bool>() ?? false; - deleteMessagesAfterSummary = json.SelectToken("interviews.delete-messages-after-summary")?.Value<bool>() ?? false; - deleteMessagesAfterNoSummary = json.SelectToken("interviews.delete-messages-after-no-summary")?.Value<bool>() ?? false; + deleteMessagesAfterInterviewEnd = json.SelectToken("interviews.delete-messages-after-interview-end")?.Value<bool>() ?? false; // Reads database info hostName = json.SelectToken("database.address")?.Value<string>() ?? ""; diff --git a/Interviews/Interview.cs b/Interviews/Interview.cs index e102ceb..97a8aaa 100644 --- a/Interviews/Interview.cs +++ b/Interviews/Interview.cs @@ -15,8 +15,9 @@ public enum MessageType { // TODO: Support multiselector as separate type ERROR, - END_WITH_SUMMARY, - END_WITHOUT_SUMMARY, + INTERVIEW_END = 1, + [Obsolete("Use INTERVIEW_END instead")] END_WITH_SUMMARY = 1, + [Obsolete("Use INTERVIEW_END instead")] END_WITHOUT_SUMMARY = 1, BUTTONS, TEXT_SELECTOR, USER_SELECTOR, @@ -125,6 +126,10 @@ public class InterviewStep [JsonProperty("min-length")] public int? minLength; + // Adds a summary to the message. + [JsonProperty("add-summary")] + public bool? addSummary; + // References to steps defined elsewhere in the template [JsonProperty("step-references")] public Dictionary<string, ReferencedInterviewStep> references = new(); @@ -185,7 +190,7 @@ public class InterviewStep } // This object is the deepest object with a message ID set, meaning it is the latest asked question. - previousSteps = new List<InterviewStep> { this }; + previousSteps = [this]; return true; } @@ -196,7 +201,7 @@ public class InterviewStep return; } - if (!string.IsNullOrWhiteSpace(summaryField)) + if (!string.IsNullOrWhiteSpace(summaryField) && !string.IsNullOrWhiteSpace(answer)) { // TODO: Add option to merge answers summary[summaryField] = answer; @@ -292,8 +297,7 @@ public class InterviewStep case MessageType.TEXT_INPUT: summaryMaxLength += Math.Min(maxLength ?? 1024, 1024); break; - case MessageType.END_WITH_SUMMARY: - case MessageType.END_WITHOUT_SUMMARY: + case MessageType.INTERVIEW_END: case MessageType.ERROR: case MessageType.REFERENCE_END: default: @@ -317,7 +321,7 @@ public class InterviewStep } } - if (messageType is MessageType.ERROR or MessageType.END_WITH_SUMMARY or MessageType.END_WITHOUT_SUMMARY or MessageType.REFERENCE_END) + if (messageType is MessageType.ERROR or MessageType.INTERVIEW_END or MessageType.REFERENCE_END) { if (steps.Count > 0 || references.Count > 0) { @@ -334,7 +338,6 @@ public class InterviewStep errors.Add("Steps of the type '" + messageType + "' must have at least one child step.\n\n> " + stepID + ".message-type"); } - // TODO: Test this foreach (KeyValuePair<string, ReferencedInterviewStep> reference in references) { if (!reference.Value.TryGetReferencedStep(definitions, out InterviewStep referencedStep, true)) @@ -343,7 +346,7 @@ public class InterviewStep } else if (reference.Value.afterReferenceStep == null) { - List<InterviewStep> allChildSteps = new List<InterviewStep>(); + List<InterviewStep> allChildSteps = []; referencedStep.GetAllSteps(ref allChildSteps); if (allChildSteps.Any(s => s.messageType == MessageType.REFERENCE_END)) { @@ -352,7 +355,7 @@ public class InterviewStep } } - if (messageType is MessageType.END_WITH_SUMMARY) + if (addSummary ?? false) { summaryMaxLength += message?.Length ?? 0; summaryMaxLength += heading?.Length ?? 0; diff --git a/Interviews/Interviewer.cs b/Interviews/Interviewer.cs index 6d091d0..4c25e58 100644 --- a/Interviews/Interviewer.cs +++ b/Interviews/Interviewer.cs @@ -19,7 +19,7 @@ public static class Interviewer return false; } - await SendNextMessage(channel, interview.interviewRoot); + await SendNextMessage(interview, channel, interview.interviewRoot); return Database.SaveInterview(interview); } @@ -27,7 +27,7 @@ public static class Interviewer { if (Database.TryGetInterview(channel.Id, out Interview interview)) { - if (Config.deleteMessagesAfterNoSummary) + if (Config.deleteMessagesAfterInterviewEnd) { await DeletePreviousMessages(interview, channel); } @@ -45,7 +45,7 @@ public static class Interviewer { if (Database.TryGetInterview(channel.Id, out Interview interview)) { - if (Config.deleteMessagesAfterNoSummary) + if (Config.deleteMessagesAfterInterviewEnd) { await DeletePreviousMessages(interview, channel); } @@ -362,28 +362,25 @@ public static class Interviewer } nextStep.references.Clear(); - await SendNextMessage(channel, nextStep); + await SendNextMessage(interview, channel, nextStep); Database.SaveInterview(interview); break; - case MessageType.END_WITH_SUMMARY: - OrderedDictionary summaryFields = new OrderedDictionary(); - interview.interviewRoot.GetSummary(ref summaryFields); - - DiscordEmbedBuilder embed = new DiscordEmbedBuilder + case MessageType.INTERVIEW_END: + DiscordEmbedBuilder endEmbed = new() { Color = Utilities.StringToColor(nextStep.color), Title = nextStep.heading, Description = nextStep.message, }; - foreach (DictionaryEntry entry in summaryFields) + if (nextStep.addSummary ?? false) { - embed.AddField((string)entry.Key, (string)entry.Value ?? string.Empty); + AddSummary(interview, ref endEmbed); } - await channel.SendMessageAsync(embed); + await channel.SendMessageAsync(endEmbed); - if (Config.deleteMessagesAfterSummary) + if (Config.deleteMessagesAfterInterviewEnd) { await DeletePreviousMessages(interview, channel); } @@ -393,26 +390,7 @@ public static class Interviewer Logger.Error("Could not delete interview from database. Channel ID: " + channel.Id); } return; - case MessageType.END_WITHOUT_SUMMARY: - await channel.SendMessageAsync(new DiscordEmbedBuilder - { - Color = Utilities.StringToColor(nextStep.color), - Title = nextStep.heading, - Description = nextStep.message - }); - - if (Config.deleteMessagesAfterNoSummary) - { - await DeletePreviousMessages(interview, channel); - } - - if (!Database.TryDeleteInterview(channel.Id)) - { - Logger.Error("Could not delete interview from database. Channel ID: " + channel.Id); - } - break; case MessageType.REFERENCE_END: - // TODO: What is happening with the summaries? if (interview.interviewRoot.TryGetTakenSteps(out List<InterviewStep> previousSteps)) { foreach (InterviewStep step in previousSteps) @@ -431,19 +409,14 @@ public static class Interviewer previousStep.steps.Clear(); previousStep.steps.Add(answer, nextStep); - await HandleAnswer(answer, - nextStep, - interview, - previousStep, - channel, - answerMessage); + await HandleAnswer(answer, nextStep, interview, previousStep, channel, answerMessage); return; } } } } - DiscordEmbedBuilder error = new DiscordEmbedBuilder + DiscordEmbedBuilder error = new() { Color = DiscordColor.Red, Description = "An error occured while trying to find the next interview step." @@ -466,26 +439,41 @@ public static class Interviewer return; case MessageType.ERROR: default: - DiscordEmbedBuilder err = new DiscordEmbedBuilder + DiscordEmbedBuilder errorEmbed = new() { Color = Utilities.StringToColor(nextStep.color), Title = nextStep.heading, Description = nextStep.message }; + if (nextStep.addSummary ?? false) + { + AddSummary(interview, ref errorEmbed); + } + if (answerMessage == null) { - DiscordMessage errorMessage = await channel.SendMessageAsync(err); + DiscordMessage errorMessage = await channel.SendMessageAsync(errorEmbed); previousStep.AddRelatedMessageIDs(errorMessage.Id); } else { - DiscordMessage errorMessage = await answerMessage.RespondAsync(err); + DiscordMessage errorMessage = await answerMessage.RespondAsync(errorEmbed); previousStep.AddRelatedMessageIDs(errorMessage.Id, answerMessage.Id); } Database.SaveInterview(interview); - break; + return; + } + } + + private static void AddSummary(Interview interview, ref DiscordEmbedBuilder embed) + { + OrderedDictionary summaryFields = new(); + interview.interviewRoot.GetSummary(ref summaryFields); + foreach (DictionaryEntry entry in summaryFields) + { + embed.AddField((string)entry.Key, (string)entry.Value ?? "-"); } } @@ -508,7 +496,7 @@ public static class Interviewer } } - private static async Task SendNextMessage(DiscordChannel channel, InterviewStep step) + private static async Task SendNextMessage(Interview interview, DiscordChannel channel, InterviewStep step) { DiscordMessageBuilder msgBuilder = new(); DiscordEmbedBuilder embed = new() @@ -518,6 +506,11 @@ public static class Interviewer Description = step.message }; + if (step.addSummary ?? false) + { + AddSummary(interview, ref embed); + } + switch (step.messageType) { case MessageType.BUTTONS: @@ -546,33 +539,32 @@ public static class Interviewer categoryOptions.Add(new DiscordSelectComponentOption(stepPattern, selectionOptions.ToString(), nextStep.selectorDescription)); } - selectionComponents.Add(new DiscordSelectComponent("supportboi_interviewselector " + selectionBoxes, string.IsNullOrWhiteSpace(step.selectorPlaceholder) - ? "Select an option..." : step.selectorPlaceholder, categoryOptions)); + selectionComponents.Add(new DiscordSelectComponent("supportboi_interviewselector " + selectionBoxes, + string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select an option..." : step.selectorPlaceholder, categoryOptions)); } msgBuilder.AddComponents(selectionComponents); break; case MessageType.ROLE_SELECTOR: - msgBuilder.AddComponents(new DiscordRoleSelectComponent("supportboi_interviewroleselector", string.IsNullOrWhiteSpace(step.selectorPlaceholder) - ? "Select a role..." : step.selectorPlaceholder)); + msgBuilder.AddComponents(new DiscordRoleSelectComponent("supportboi_interviewroleselector", + string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a role..." : step.selectorPlaceholder)); break; case MessageType.USER_SELECTOR: - msgBuilder.AddComponents(new DiscordUserSelectComponent("supportboi_interviewuserselector", string.IsNullOrWhiteSpace(step.selectorPlaceholder) - ? "Select a user..." : step.selectorPlaceholder)); + msgBuilder.AddComponents(new DiscordUserSelectComponent("supportboi_interviewuserselector", + string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a user..." : step.selectorPlaceholder)); break; case MessageType.CHANNEL_SELECTOR: - msgBuilder.AddComponents(new DiscordChannelSelectComponent("supportboi_interviewchannelselector", string.IsNullOrWhiteSpace(step.selectorPlaceholder) - ? "Select a channel..." : step.selectorPlaceholder)); + msgBuilder.AddComponents(new DiscordChannelSelectComponent("supportboi_interviewchannelselector", + string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a channel..." : step.selectorPlaceholder)); break; case MessageType.MENTIONABLE_SELECTOR: - msgBuilder.AddComponents(new DiscordMentionableSelectComponent("supportboi_interviewmentionableselector", string.IsNullOrWhiteSpace(step.selectorPlaceholder) - ? "Select a user or role..." : step.selectorPlaceholder)); + msgBuilder.AddComponents(new DiscordMentionableSelectComponent("supportboi_interviewmentionableselector", + string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a user or role..." : step.selectorPlaceholder)); break; case MessageType.TEXT_INPUT: embed.WithFooter("Reply to this message with your answer. You cannot include images or files."); break; - case MessageType.END_WITH_SUMMARY: - case MessageType.END_WITHOUT_SUMMARY: + case MessageType.INTERVIEW_END: case MessageType.ERROR: default: break; diff --git a/Interviews/interview_template.schema.json b/Interviews/interview_template.schema.json index 995e041..0440bbf 100644 --- a/Interviews/interview_template.schema.json +++ b/Interviews/interview_template.schema.json @@ -39,9 +39,7 @@ "$ref": "#/definitions/step" } }, - "required": [ - "id" - ], + "required": [ "id" ], "unevaluatedProperties": false }, "step": { @@ -58,7 +56,7 @@ "message": { "type": "string", "title": "Message", - "description": "The text in the embed message that will be sent to the user when they reach this step.", + "description": "The text in the embed message that will be sent to the user when they reach this step. Required for all message types except 'REFERENCE_END'.", "minLength": 1 }, "message-type": { @@ -67,8 +65,7 @@ "description": "The type of message, decides what the bot will do when the user gets to this step.", "enum": [ "ERROR", - "END_WITH_SUMMARY", - "END_WITHOUT_SUMMARY", + "INTERVIEW_END", "BUTTONS", "TEXT_SELECTOR", "USER_SELECTOR", @@ -189,11 +186,14 @@ "title": "Min Length", "description": "The minimum length of the user's response message. Requires that this step is a 'TEXT_INPUT'.", "minimum": 0 + }, + "add-summary": { + "type": "boolean", + "title": "Add Summary", + "description": "This adds a summary field to the end of the message." } }, - "required": [ - "message-type" - ], + "required": [ "message-type" ], "unevaluatedProperties": false } }, @@ -218,9 +218,6 @@ } } }, - "required": [ - "category-id", - "interview" - ], + "required": [ "category-id", "interview" ], "unevaluatedProperties": false } \ No newline at end of file diff --git a/default_config.yml b/default_config.yml index b05cced..fde5bce 100644 --- a/default_config.yml +++ b/default_config.yml @@ -61,11 +61,8 @@ interviews: # Any existing interviews can still be completed while interviews are disabled, but new ones will not be created. enabled: true - # Whether to delete the interview question and answer messages after an interview summary is posted. - delete-messages-after-summary: true - - # Whether to delete the interview question and answer messages after an interview ends without a summary. - delete-messages-after-no-summary: true + # Whether to delete the interview question and answer messages after an interview ends. + delete-messages-after-interview-end: true database: # Address and port of the mysql server.