From 7f13161853556f6ceea1f3bd28b717bd056f1e82 Mon Sep 17 00:00:00 2001 From: Toastie Date: Thu, 26 Dec 2024 17:55:00 +1300 Subject: [PATCH] Started interview system PoC --- Commands/NewCommand.cs | 5 ++ Config.cs | 3 + Interviewer.cs | 137 +++++++++++++++++++++++++++++++++++++++++ default_config.yml | 23 ++++++- 4 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 Interviewer.cs diff --git a/Commands/NewCommand.cs b/Commands/NewCommand.cs index c914f3a..c86a9eb 100644 --- a/Commands/NewCommand.cs +++ b/Commands/NewCommand.cs @@ -238,6 +238,11 @@ public class NewCommand : ApplicationCommandModule // Refreshes the channel as changes were made to it above ticketChannel = await SupportChild.discordClient.GetChannelAsync(ticketChannel.Id); + if (ticketChannel.Parent?.IsCategory ?? false) + { + Interviewer.StartInterview(ticketChannel); + } + if (staffID != 0) { await ticketChannel.SendMessageAsync(new DiscordEmbedBuilder diff --git a/Config.cs b/Config.cs index fd58e92..9d993d0 100644 --- a/Config.cs +++ b/Config.cs @@ -98,5 +98,8 @@ internal static class Config database = json.SelectToken("database.name")?.Value() ?? "supportchild"; username = json.SelectToken("database.user")?.Value() ?? "supportchild"; password = json.SelectToken("database.password")?.Value() ?? ""; + + // Set up interviewer + Interviewer.ParseConfig(json.SelectToken("interviews")); } } \ No newline at end of file diff --git a/Interviewer.cs b/Interviewer.cs new file mode 100644 index 0000000..737fa95 --- /dev/null +++ b/Interviewer.cs @@ -0,0 +1,137 @@ +using System.Collections.Generic; +using System.Linq; +using DSharpPlus; +using DSharpPlus.Entities; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace SupportChild; + +public static class Interviewer +{ + public enum QuestionType + { + DONE, + FAIL, + BUTTONS, + SELECTOR, + TEXT_INPUT + } + + // A tree of questions representing an interview. + // The tree is generated by the config file when a new ticket is opened or the restart interview command is used. + // Additional components not specified in the config file are populated as the interview progresses. + // The entire interview tree is serialized and stored in the database in order to record responses as they are made. + public struct InterviewQuestion + { + // Message contents sent to the user. + public string message; + + // The type of question. + public QuestionType type; + + // Colour of the message embed. + public string color; + + // The ID of this message, populated after it has been sent. + public ulong messageID; + + // Used as label for this question in the post-interview summary. + public string summaryField; + + // The user's response to the question. + public string answer; + + // The ID of the user's answer message, populated after it has been received. + public ulong answerID; + + // Possible questions to ask next, or DONE/FAIL type in order to finish interview. + public Dictionary paths; + } + + private static Dictionary categoryInterviews = []; + + public static void ParseConfig(JToken interviewConfig) + { + categoryInterviews = JsonConvert.DeserializeObject>(interviewConfig.ToString()); + } + + public static void StartInterview(DiscordChannel channel) + { + if (channel.Parent == null) + { + return; + } + + if (categoryInterviews.TryGetValue(channel.Parent.Id, out InterviewQuestion interview)) + { + CreateQuestion(channel, interview); + } + } + + public static void ProcessResponse(DiscordChannel channel, string response) + { + + } + + private static async void CreateQuestion(DiscordChannel channel, InterviewQuestion question) + { + DiscordMessageBuilder msgBuilder = new DiscordMessageBuilder + { + Embed = new DiscordEmbedBuilder + { + Color = Utilities.StringToColor(question.color), + Description = question.message + } + }; + + switch (question.type) + { + case QuestionType.BUTTONS: + int nrOfButtons = 0; + for (int nrOfButtonRows = 0; nrOfButtonRows < 5 && nrOfButtons < question.paths.Count; nrOfButtonRows++) + { + List buttonRow = []; + for (; nrOfButtons < 5 * (nrOfButtonRows + 1) && nrOfButtons < question.paths.Count; nrOfButtons++) + { + buttonRow.Add(new DiscordButtonComponent(ButtonStyle.Primary, "supportboi_interviewbutton " + nrOfButtons, question.paths.ToArray()[nrOfButtons].Key)); + } + msgBuilder.AddComponents(buttonRow); + } + break; + case QuestionType.SELECTOR: + List selectionComponents = []; + + int selectionOptions = 0; + for (int selectionBoxes = 0; selectionBoxes < 5 && selectionOptions < question.paths.Count; selectionBoxes++) + { + List categoryOptions = []; + for (; selectionOptions < 25 * (selectionBoxes + 1) && selectionOptions < question.paths.Count; selectionOptions++) + { + categoryOptions.Add(new DiscordSelectComponentOption(question.paths.ToArray()[selectionOptions].Key, selectionOptions.ToString())); + } + selectionComponents.Add(new DiscordSelectComponent("supportboi_interviewselector " + selectionBoxes, "Select an option...", categoryOptions, false, 0, 1)); + } + + msgBuilder.AddComponents(selectionComponents); + break; + case QuestionType.TEXT_INPUT: + // TODO: Add embed footer with response info + break; + case QuestionType.DONE: + case QuestionType.FAIL: + default: + break; + } + + DiscordMessage message = await channel.SendMessageAsync(msgBuilder); + question.messageID = message.Id; + + // TODO: Save interview progress + } + + public static void CreateSummary() + { + + } +} \ No newline at end of file diff --git a/default_config.yml b/default_config.yml index 51b51b5..0e4dd04 100644 --- a/default_config.yml +++ b/default_config.yml @@ -16,7 +16,7 @@ # More info: https://dsharpplus.github.io/api/DSharpPlus.TimestampFormat.html timestamp-format: "RelativeTime" - # Whether or not staff members should be randomly assigned tickets when they are made. Individual staff members can opt out using the toggleactive command. + # Whether staff members should be randomly assigned tickets when they are made. Individual staff members can opt out using the toggleactive command. random-assignment: true # If set to true the rasssign command will include staff members set as inactive if a specific role is specified in the command. @@ -60,4 +60,23 @@ database: # Username and password for authentication user: "" - password: "" \ No newline at end of file + password: "" + +# TODO: May want to move interview entries to subkey to make room for additional interview settings +interviews: + 000000000000000000: + message: "Are you appealing your own ban or on behalf of another user?" + type: "BUTTONS" + color: "CYAN" + paths: + - "My own ban": # TODO: Can I add button color support somehow? + text: "What is your user name?" + type: "TEXT_INPUT" + summary-field: "Username" + paths: + - ".*": + text: "Please write your appeal below, motivate why you think you should be unbanned." + - "Another user's ban": + text: "You can only appeal your own ban. Please close this ticket." + type: "FAIL" + color: "CYAN" \ No newline at end of file