Made it possible to add a summary to any message instead of only interview ends

This commit is contained in:
Toastie 2025-02-04 19:27:20 +13:00
parent abe0e3397e
commit d8e1863182
Signed by: toastie_t0ast
GPG key ID: 0861BE54AD481DC7
5 changed files with 75 additions and 88 deletions

View file

@ -28,8 +28,7 @@ internal static class Config
internal static bool closingNotifications = false; internal static bool closingNotifications = false;
internal static bool interviewsEnabled = false; internal static bool interviewsEnabled = false;
internal static bool deleteMessagesAfterSummary = false; internal static bool deleteMessagesAfterInterviewEnd = false;
internal static bool deleteMessagesAfterNoSummary = false;
internal static string hostName = "127.0.0.1"; internal static string hostName = "127.0.0.1";
internal static int port = 3306; internal static int port = 3306;
@ -100,8 +99,7 @@ internal static class Config
closingNotifications = json.SelectToken("notifications.closing")?.Value<bool>() ?? false; closingNotifications = json.SelectToken("notifications.closing")?.Value<bool>() ?? false;
interviewsEnabled = json.SelectToken("interviews.enabled")?.Value<bool>() ?? false; interviewsEnabled = json.SelectToken("interviews.enabled")?.Value<bool>() ?? false;
deleteMessagesAfterSummary = json.SelectToken("interviews.delete-messages-after-summary")?.Value<bool>() ?? false; deleteMessagesAfterInterviewEnd = json.SelectToken("interviews.delete-messages-after-interview-end")?.Value<bool>() ?? false;
deleteMessagesAfterNoSummary = json.SelectToken("interviews.delete-messages-after-no-summary")?.Value<bool>() ?? false;
// Reads database info // Reads database info
hostName = json.SelectToken("database.address")?.Value<string>() ?? ""; hostName = json.SelectToken("database.address")?.Value<string>() ?? "";

View file

@ -15,8 +15,9 @@ public enum MessageType
{ {
// TODO: Support multiselector as separate type // TODO: Support multiselector as separate type
ERROR, ERROR,
END_WITH_SUMMARY, INTERVIEW_END = 1,
END_WITHOUT_SUMMARY, [Obsolete("Use INTERVIEW_END instead")] END_WITH_SUMMARY = 1,
[Obsolete("Use INTERVIEW_END instead")] END_WITHOUT_SUMMARY = 1,
BUTTONS, BUTTONS,
TEXT_SELECTOR, TEXT_SELECTOR,
USER_SELECTOR, USER_SELECTOR,
@ -125,6 +126,10 @@ public class InterviewStep
[JsonProperty("min-length")] [JsonProperty("min-length")]
public int? minLength; public int? minLength;
// Adds a summary to the message.
[JsonProperty("add-summary")]
public bool? addSummary;
// References to steps defined elsewhere in the template // References to steps defined elsewhere in the template
[JsonProperty("step-references")] [JsonProperty("step-references")]
public Dictionary<string, ReferencedInterviewStep> references = new(); 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. // 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; return true;
} }
@ -196,7 +201,7 @@ public class InterviewStep
return; return;
} }
if (!string.IsNullOrWhiteSpace(summaryField)) if (!string.IsNullOrWhiteSpace(summaryField) && !string.IsNullOrWhiteSpace(answer))
{ {
// TODO: Add option to merge answers // TODO: Add option to merge answers
summary[summaryField] = answer; summary[summaryField] = answer;
@ -292,8 +297,7 @@ public class InterviewStep
case MessageType.TEXT_INPUT: case MessageType.TEXT_INPUT:
summaryMaxLength += Math.Min(maxLength ?? 1024, 1024); summaryMaxLength += Math.Min(maxLength ?? 1024, 1024);
break; break;
case MessageType.END_WITH_SUMMARY: case MessageType.INTERVIEW_END:
case MessageType.END_WITHOUT_SUMMARY:
case MessageType.ERROR: case MessageType.ERROR:
case MessageType.REFERENCE_END: case MessageType.REFERENCE_END:
default: 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) 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"); 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) foreach (KeyValuePair<string, ReferencedInterviewStep> reference in references)
{ {
if (!reference.Value.TryGetReferencedStep(definitions, out InterviewStep referencedStep, true)) if (!reference.Value.TryGetReferencedStep(definitions, out InterviewStep referencedStep, true))
@ -343,7 +346,7 @@ public class InterviewStep
} }
else if (reference.Value.afterReferenceStep == null) else if (reference.Value.afterReferenceStep == null)
{ {
List<InterviewStep> allChildSteps = new List<InterviewStep>(); List<InterviewStep> allChildSteps = [];
referencedStep.GetAllSteps(ref allChildSteps); referencedStep.GetAllSteps(ref allChildSteps);
if (allChildSteps.Any(s => s.messageType == MessageType.REFERENCE_END)) 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 += message?.Length ?? 0;
summaryMaxLength += heading?.Length ?? 0; summaryMaxLength += heading?.Length ?? 0;

View file

@ -19,7 +19,7 @@ public static class Interviewer
return false; return false;
} }
await SendNextMessage(channel, interview.interviewRoot); await SendNextMessage(interview, channel, interview.interviewRoot);
return Database.SaveInterview(interview); return Database.SaveInterview(interview);
} }
@ -27,7 +27,7 @@ public static class Interviewer
{ {
if (Database.TryGetInterview(channel.Id, out Interview interview)) if (Database.TryGetInterview(channel.Id, out Interview interview))
{ {
if (Config.deleteMessagesAfterNoSummary) if (Config.deleteMessagesAfterInterviewEnd)
{ {
await DeletePreviousMessages(interview, channel); await DeletePreviousMessages(interview, channel);
} }
@ -45,7 +45,7 @@ public static class Interviewer
{ {
if (Database.TryGetInterview(channel.Id, out Interview interview)) if (Database.TryGetInterview(channel.Id, out Interview interview))
{ {
if (Config.deleteMessagesAfterNoSummary) if (Config.deleteMessagesAfterInterviewEnd)
{ {
await DeletePreviousMessages(interview, channel); await DeletePreviousMessages(interview, channel);
} }
@ -362,28 +362,25 @@ public static class Interviewer
} }
nextStep.references.Clear(); nextStep.references.Clear();
await SendNextMessage(channel, nextStep); await SendNextMessage(interview, channel, nextStep);
Database.SaveInterview(interview); Database.SaveInterview(interview);
break; break;
case MessageType.END_WITH_SUMMARY: case MessageType.INTERVIEW_END:
OrderedDictionary summaryFields = new OrderedDictionary(); DiscordEmbedBuilder endEmbed = new()
interview.interviewRoot.GetSummary(ref summaryFields);
DiscordEmbedBuilder embed = new DiscordEmbedBuilder
{ {
Color = Utilities.StringToColor(nextStep.color), Color = Utilities.StringToColor(nextStep.color),
Title = nextStep.heading, Title = nextStep.heading,
Description = nextStep.message, 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); await DeletePreviousMessages(interview, channel);
} }
@ -393,26 +390,7 @@ public static class Interviewer
Logger.Error("Could not delete interview from database. Channel ID: " + channel.Id); Logger.Error("Could not delete interview from database. Channel ID: " + channel.Id);
} }
return; 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: case MessageType.REFERENCE_END:
// TODO: What is happening with the summaries?
if (interview.interviewRoot.TryGetTakenSteps(out List<InterviewStep> previousSteps)) if (interview.interviewRoot.TryGetTakenSteps(out List<InterviewStep> previousSteps))
{ {
foreach (InterviewStep step in previousSteps) foreach (InterviewStep step in previousSteps)
@ -431,19 +409,14 @@ public static class Interviewer
previousStep.steps.Clear(); previousStep.steps.Clear();
previousStep.steps.Add(answer, nextStep); previousStep.steps.Add(answer, nextStep);
await HandleAnswer(answer, await HandleAnswer(answer, nextStep, interview, previousStep, channel, answerMessage);
nextStep,
interview,
previousStep,
channel,
answerMessage);
return; return;
} }
} }
} }
} }
DiscordEmbedBuilder error = new DiscordEmbedBuilder DiscordEmbedBuilder error = new()
{ {
Color = DiscordColor.Red, Color = DiscordColor.Red,
Description = "An error occured while trying to find the next interview step." Description = "An error occured while trying to find the next interview step."
@ -466,26 +439,41 @@ public static class Interviewer
return; return;
case MessageType.ERROR: case MessageType.ERROR:
default: default:
DiscordEmbedBuilder err = new DiscordEmbedBuilder DiscordEmbedBuilder errorEmbed = new()
{ {
Color = Utilities.StringToColor(nextStep.color), Color = Utilities.StringToColor(nextStep.color),
Title = nextStep.heading, Title = nextStep.heading,
Description = nextStep.message Description = nextStep.message
}; };
if (nextStep.addSummary ?? false)
{
AddSummary(interview, ref errorEmbed);
}
if (answerMessage == null) if (answerMessage == null)
{ {
DiscordMessage errorMessage = await channel.SendMessageAsync(err); DiscordMessage errorMessage = await channel.SendMessageAsync(errorEmbed);
previousStep.AddRelatedMessageIDs(errorMessage.Id); previousStep.AddRelatedMessageIDs(errorMessage.Id);
} }
else else
{ {
DiscordMessage errorMessage = await answerMessage.RespondAsync(err); DiscordMessage errorMessage = await answerMessage.RespondAsync(errorEmbed);
previousStep.AddRelatedMessageIDs(errorMessage.Id, answerMessage.Id); previousStep.AddRelatedMessageIDs(errorMessage.Id, answerMessage.Id);
} }
Database.SaveInterview(interview); 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(); DiscordMessageBuilder msgBuilder = new();
DiscordEmbedBuilder embed = new() DiscordEmbedBuilder embed = new()
@ -518,6 +506,11 @@ public static class Interviewer
Description = step.message Description = step.message
}; };
if (step.addSummary ?? false)
{
AddSummary(interview, ref embed);
}
switch (step.messageType) switch (step.messageType)
{ {
case MessageType.BUTTONS: case MessageType.BUTTONS:
@ -546,33 +539,32 @@ public static class Interviewer
categoryOptions.Add(new DiscordSelectComponentOption(stepPattern, selectionOptions.ToString(), nextStep.selectorDescription)); categoryOptions.Add(new DiscordSelectComponentOption(stepPattern, selectionOptions.ToString(), nextStep.selectorDescription));
} }
selectionComponents.Add(new DiscordSelectComponent("supportboi_interviewselector " + selectionBoxes, string.IsNullOrWhiteSpace(step.selectorPlaceholder) selectionComponents.Add(new DiscordSelectComponent("supportboi_interviewselector " + selectionBoxes,
? "Select an option..." : step.selectorPlaceholder, categoryOptions)); string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select an option..." : step.selectorPlaceholder, categoryOptions));
} }
msgBuilder.AddComponents(selectionComponents); msgBuilder.AddComponents(selectionComponents);
break; break;
case MessageType.ROLE_SELECTOR: case MessageType.ROLE_SELECTOR:
msgBuilder.AddComponents(new DiscordRoleSelectComponent("supportboi_interviewroleselector", string.IsNullOrWhiteSpace(step.selectorPlaceholder) msgBuilder.AddComponents(new DiscordRoleSelectComponent("supportboi_interviewroleselector",
? "Select a role..." : step.selectorPlaceholder)); string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a role..." : step.selectorPlaceholder));
break; break;
case MessageType.USER_SELECTOR: case MessageType.USER_SELECTOR:
msgBuilder.AddComponents(new DiscordUserSelectComponent("supportboi_interviewuserselector", string.IsNullOrWhiteSpace(step.selectorPlaceholder) msgBuilder.AddComponents(new DiscordUserSelectComponent("supportboi_interviewuserselector",
? "Select a user..." : step.selectorPlaceholder)); string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a user..." : step.selectorPlaceholder));
break; break;
case MessageType.CHANNEL_SELECTOR: case MessageType.CHANNEL_SELECTOR:
msgBuilder.AddComponents(new DiscordChannelSelectComponent("supportboi_interviewchannelselector", string.IsNullOrWhiteSpace(step.selectorPlaceholder) msgBuilder.AddComponents(new DiscordChannelSelectComponent("supportboi_interviewchannelselector",
? "Select a channel..." : step.selectorPlaceholder)); string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a channel..." : step.selectorPlaceholder));
break; break;
case MessageType.MENTIONABLE_SELECTOR: case MessageType.MENTIONABLE_SELECTOR:
msgBuilder.AddComponents(new DiscordMentionableSelectComponent("supportboi_interviewmentionableselector", string.IsNullOrWhiteSpace(step.selectorPlaceholder) msgBuilder.AddComponents(new DiscordMentionableSelectComponent("supportboi_interviewmentionableselector",
? "Select a user or role..." : step.selectorPlaceholder)); string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a user or role..." : step.selectorPlaceholder));
break; break;
case MessageType.TEXT_INPUT: case MessageType.TEXT_INPUT:
embed.WithFooter("Reply to this message with your answer. You cannot include images or files."); embed.WithFooter("Reply to this message with your answer. You cannot include images or files.");
break; break;
case MessageType.END_WITH_SUMMARY: case MessageType.INTERVIEW_END:
case MessageType.END_WITHOUT_SUMMARY:
case MessageType.ERROR: case MessageType.ERROR:
default: default:
break; break;

View file

@ -39,9 +39,7 @@
"$ref": "#/definitions/step" "$ref": "#/definitions/step"
} }
}, },
"required": [ "required": [ "id" ],
"id"
],
"unevaluatedProperties": false "unevaluatedProperties": false
}, },
"step": { "step": {
@ -58,7 +56,7 @@
"message": { "message": {
"type": "string", "type": "string",
"title": "Message", "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 "minLength": 1
}, },
"message-type": { "message-type": {
@ -67,8 +65,7 @@
"description": "The type of message, decides what the bot will do when the user gets to this step.", "description": "The type of message, decides what the bot will do when the user gets to this step.",
"enum": [ "enum": [
"ERROR", "ERROR",
"END_WITH_SUMMARY", "INTERVIEW_END",
"END_WITHOUT_SUMMARY",
"BUTTONS", "BUTTONS",
"TEXT_SELECTOR", "TEXT_SELECTOR",
"USER_SELECTOR", "USER_SELECTOR",
@ -189,11 +186,14 @@
"title": "Min Length", "title": "Min Length",
"description": "The minimum length of the user's response message. Requires that this step is a 'TEXT_INPUT'.", "description": "The minimum length of the user's response message. Requires that this step is a 'TEXT_INPUT'.",
"minimum": 0 "minimum": 0
},
"add-summary": {
"type": "boolean",
"title": "Add Summary",
"description": "This adds a summary field to the end of the message."
} }
}, },
"required": [ "required": [ "message-type" ],
"message-type"
],
"unevaluatedProperties": false "unevaluatedProperties": false
} }
}, },
@ -218,9 +218,6 @@
} }
} }
}, },
"required": [ "required": [ "category-id", "interview" ],
"category-id",
"interview"
],
"unevaluatedProperties": false "unevaluatedProperties": false
} }

View file

@ -61,11 +61,8 @@ interviews:
# Any existing interviews can still be completed while interviews are disabled, but new ones will not be created. # Any existing interviews can still be completed while interviews are disabled, but new ones will not be created.
enabled: true enabled: true
# Whether to delete the interview question and answer messages after an interview summary is posted. # Whether to delete the interview question and answer messages after an interview ends.
delete-messages-after-summary: true delete-messages-after-interview-end: true
# Whether to delete the interview question and answer messages after an interview ends without a summary.
delete-messages-after-no-summary: true
database: database:
# Address and port of the mysql server. # Address and port of the mysql server.