Started interview system PoC

This commit is contained in:
Toastie 2024-12-26 17:55:00 +13:00
parent 32f5518891
commit 7f13161853
Signed by: toastie_t0ast
GPG key ID: 27F3B6855AFD40A4
4 changed files with 166 additions and 2 deletions

View file

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

View file

@ -98,5 +98,8 @@ internal static class Config
database = json.SelectToken("database.name")?.Value<string>() ?? "supportchild";
username = json.SelectToken("database.user")?.Value<string>() ?? "supportchild";
password = json.SelectToken("database.password")?.Value<string>() ?? "";
// Set up interviewer
Interviewer.ParseConfig(json.SelectToken("interviews"));
}
}

137
Interviewer.cs Normal file
View file

@ -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<string, InterviewQuestion> paths;
}
private static Dictionary<ulong, InterviewQuestion> categoryInterviews = [];
public static void ParseConfig(JToken interviewConfig)
{
categoryInterviews = JsonConvert.DeserializeObject<Dictionary<ulong, InterviewQuestion>>(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<DiscordButtonComponent> 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<DiscordSelectComponent> selectionComponents = [];
int selectionOptions = 0;
for (int selectionBoxes = 0; selectionBoxes < 5 && selectionOptions < question.paths.Count; selectionBoxes++)
{
List<DiscordSelectComponentOption> 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()
{
}
}

View file

@ -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.
@ -61,3 +61,22 @@ database:
# Username and password for authentication
user: ""
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"