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 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>() ?? "";

View file

@ -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;

View file

@ -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;

View file

@ -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
}

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.
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.