2024-12-26 20:12:50 +13:00
using System ;
using System.Collections ;
using System.Collections.Generic ;
using System.Collections.Specialized ;
2024-12-26 17:55:00 +13:00
using System.Linq ;
2024-12-26 20:12:50 +13:00
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
2024-12-26 17:55:00 +13:00
using DSharpPlus.Entities ;
2024-12-27 01:54:28 +13:00
namespace SupportChild.Interviews ;
2024-12-26 17:55:00 +13:00
public static class Interviewer
{
2024-12-27 16:43:55 +13:00
public static async Task < bool > StartInterview ( DiscordChannel channel )
2024-12-26 17:55:00 +13:00
{
2025-02-06 15:15:18 +13:00
if ( ! Database . Interviews . TryGetInterviewFromTemplate ( channel . Parent . Id , channel . Id , out Interview interview ) )
2024-12-26 19:50:47 +13:00
{
2024-12-27 16:43:55 +13:00
return false ;
2024-12-26 20:12:50 +13:00
}
2025-02-04 19:42:02 +13:00
if ( ! ConvertReferences ( interview , interview . interviewRoot , out string errID ) )
{
DiscordMessage errorMessage = await channel . SendMessageAsync ( new DiscordEmbedBuilder
{
Description = "Error: Could not start interview, the referenced step id '" + errID + "' does not exist in the interview template step definitions." ,
Color = DiscordColor . Red
} ) ;
interview . interviewRoot . AddRelatedMessageIDs ( errorMessage . Id ) ;
2025-02-06 15:15:18 +13:00
Database . Interviews . SaveInterview ( interview ) ;
2025-02-04 19:42:02 +13:00
return false ;
}
2025-02-04 19:27:20 +13:00
await SendNextMessage ( interview , channel , interview . interviewRoot ) ;
2025-02-06 15:15:18 +13:00
return Database . Interviews . SaveInterview ( interview ) ;
2024-12-27 16:43:55 +13:00
}
public static async Task < bool > RestartInterview ( DiscordChannel channel )
{
2025-02-04 19:42:02 +13:00
if ( ! await StopInterview ( channel ) )
2024-12-26 20:38:17 +13:00
{
2025-02-04 19:42:02 +13:00
Logger . Error ( "Failed to stop interview in channel '" + channel . Id + "'." ) ;
return false ;
2024-12-26 20:38:17 +13:00
}
2024-12-27 16:43:55 +13:00
return await StartInterview ( channel ) ;
2024-12-26 17:55:00 +13:00
}
2024-12-27 16:43:55 +13:00
public static async Task < bool > StopInterview ( DiscordChannel channel )
2024-12-26 20:47:07 +13:00
{
2025-02-06 15:15:18 +13:00
if ( Database . Interviews . TryGetInterview ( channel . Id , out Interview interview ) )
2024-12-26 20:47:07 +13:00
{
2025-02-04 19:27:20 +13:00
if ( Config . deleteMessagesAfterInterviewEnd )
2024-12-26 23:47:55 +13:00
{
2025-02-04 16:32:21 +13:00
await DeletePreviousMessages ( interview , channel ) ;
2024-12-26 23:47:55 +13:00
}
2025-02-06 15:15:18 +13:00
if ( ! Database . Interviews . TryDeleteInterview ( channel . Id ) )
2024-12-26 20:47:07 +13:00
{
2024-12-27 16:43:55 +13:00
Logger . Warn ( "Could not delete interview from database. Channel ID: " + channel . Id ) ;
2024-12-26 20:47:07 +13:00
}
}
2024-12-27 16:43:55 +13:00
return true ;
2024-12-26 20:47:07 +13:00
}
2024-12-26 20:16:18 +13:00
public static async Task ProcessButtonOrSelectorResponse ( DiscordInteraction interaction )
2024-12-26 17:55:00 +13:00
{
2025-02-04 16:32:21 +13:00
if ( interaction ? . Channel = = null | | interaction . Message = = null )
2024-12-26 17:55:00 +13:00
{
return ;
}
2025-02-06 15:15:18 +13:00
if ( ! Database . Ticket . TryGetOpenTicket ( interaction . Channel . Id , out Database . Ticket ticket ) )
2025-02-04 20:38:34 +13:00
{
await interaction . CreateResponseAsync ( DiscordInteractionResponseType . ChannelMessageWithSource , new DiscordInteractionResponseBuilder ( )
. AddEmbed ( new DiscordEmbedBuilder ( )
. WithColor ( DiscordColor . Red )
. WithDescription ( "Error: This doesn't seem to be in a ticket channel." ) )
. AsEphemeral ( ) ) ;
return ;
}
if ( interaction . User . Id ! = ticket . creatorID )
{
await interaction . CreateResponseAsync ( DiscordInteractionResponseType . ChannelMessageWithSource , new DiscordInteractionResponseBuilder ( )
. AddEmbed ( new DiscordEmbedBuilder ( )
. WithColor ( DiscordColor . Red )
. WithDescription ( "Only the user who opened this ticket can answer interview questions." ) )
. AsEphemeral ( ) ) ;
return ;
}
2024-12-26 20:30:56 +13:00
// Ignore if option was deselected.
2024-12-26 20:51:41 +13:00
if ( interaction . Data . ComponentType is not DiscordComponentType . Button & & interaction . Data . Values . Length = = 0 )
2024-12-26 20:16:18 +13:00
{
2024-12-26 20:30:56 +13:00
await interaction . CreateResponseAsync ( DiscordInteractionResponseType . UpdateMessage ) ;
2024-12-26 20:16:18 +13:00
return ;
}
2024-12-26 20:28:39 +13:00
// Return if there is no active interview in this channel
2025-02-06 15:15:18 +13:00
if ( ! Database . Interviews . TryGetInterview ( interaction . Channel . Id , out Interview interview ) )
2024-12-26 17:55:00 +13:00
{
2024-12-26 20:30:56 +13:00
await interaction . CreateResponseAsync ( DiscordInteractionResponseType . ChannelMessageWithSource , new DiscordInteractionResponseBuilder ( )
. AddEmbed ( new DiscordEmbedBuilder ( )
. WithColor ( DiscordColor . Red )
. WithDescription ( "Error: There is no active interview in this ticket, ask an admin to check the bot logs if this seems incorrect." ) )
. AsEphemeral ( ) ) ;
2024-12-26 20:12:50 +13:00
return ;
2024-12-26 17:55:00 +13:00
}
2024-12-26 20:12:50 +13:00
2024-12-26 20:28:39 +13:00
// Return if the current question cannot be found in the interview.
2025-02-04 16:32:21 +13:00
if ( ! interview . interviewRoot . TryGetCurrentStep ( out InterviewStep currentStep ) )
2024-12-26 20:12:50 +13:00
{
2024-12-26 20:30:56 +13:00
await interaction . CreateResponseAsync ( DiscordInteractionResponseType . ChannelMessageWithSource , new DiscordInteractionResponseBuilder ( )
. AddEmbed ( new DiscordEmbedBuilder ( )
. WithColor ( DiscordColor . Red )
. WithDescription ( "Error: Something seems to have broken in this interview, you may want to restart it." ) )
. AsEphemeral ( ) ) ;
2024-12-27 17:23:03 +13:00
Logger . Error ( "The interview for channel " + interaction . Channel . Id + " exists but does not have a message ID set for it's root interview step" ) ;
2024-12-26 20:12:50 +13:00
return ;
}
2024-12-26 20:28:39 +13:00
// Check if this button/selector is for an older question.
2024-12-27 17:23:03 +13:00
if ( interaction . Message . Id ! = currentStep . messageID )
2024-12-26 20:12:50 +13:00
{
2024-12-26 20:30:56 +13:00
await interaction . CreateResponseAsync ( DiscordInteractionResponseType . ChannelMessageWithSource , new DiscordInteractionResponseBuilder ( )
. AddEmbed ( new DiscordEmbedBuilder ( )
. WithColor ( DiscordColor . Red )
. WithDescription ( "Error: You have already replied to this question, you have to reply to the latest one." ) )
. AsEphemeral ( ) ) ;
2024-12-26 20:12:50 +13:00
return ;
}
2024-12-26 20:51:41 +13:00
try
{
await interaction . CreateResponseAsync ( DiscordInteractionResponseType . UpdateMessage ) ;
}
catch ( Exception e )
{
Logger . Error ( "Could not update original message:" , e ) ;
}
2024-12-26 20:32:34 +13:00
2024-12-26 20:28:39 +13:00
// Parse the response index from the button/selector.
2024-12-26 20:16:18 +13:00
string componentID = "" ;
2024-12-26 20:51:41 +13:00
string answer = "" ;
2024-12-26 20:16:18 +13:00
switch ( interaction . Data . ComponentType )
2024-12-26 20:12:50 +13:00
{
2024-12-26 20:51:41 +13:00
case DiscordComponentType . UserSelect :
case DiscordComponentType . RoleSelect :
case DiscordComponentType . ChannelSelect :
case DiscordComponentType . MentionableSelect :
2025-02-04 20:14:16 +13:00
if ( interaction ? . Data ? . Resolved ? . Roles ? . Any ( ) ? ? false )
2024-12-26 20:51:41 +13:00
{
answer = interaction . Data . Resolved . Roles . First ( ) . Value . Mention ;
}
2025-02-04 20:14:16 +13:00
else if ( interaction . Data ? . Resolved ? . Users ? . Any ( ) ? ? false )
2024-12-26 20:51:41 +13:00
{
answer = interaction . Data . Resolved . Users . First ( ) . Value . Mention ;
}
2025-02-04 20:14:16 +13:00
else if ( interaction . Data ? . Resolved ? . Channels ? . Any ( ) ? ? false )
2024-12-26 20:51:41 +13:00
{
answer = interaction . Data . Resolved . Channels . First ( ) . Value . Mention ;
}
2025-02-04 20:14:16 +13:00
else if ( interaction . Data ? . Resolved ? . Messages ? . Any ( ) ? ? false )
2024-12-26 20:51:41 +13:00
{
answer = interaction . Data . Resolved . Messages . First ( ) . Value . Id . ToString ( ) ;
}
break ;
2024-12-26 20:16:18 +13:00
case DiscordComponentType . StringSelect :
componentID = interaction . Data . Values [ 0 ] ;
break ;
case DiscordComponentType . Button :
2025-02-06 15:15:18 +13:00
componentID = interaction . Data . CustomId . Replace ( "supportboi_interviewbutton " , "" ) ;
2024-12-26 20:16:18 +13:00
break ;
2024-12-26 23:23:25 +13:00
case DiscordComponentType . ActionRow :
case DiscordComponentType . FormInput :
2024-12-26 20:16:18 +13:00
default :
2024-12-26 23:23:25 +13:00
throw new ArgumentOutOfRangeException ( "Tried to process an invalid component type: " + interaction . Data . ComponentType ) ;
2024-12-26 20:12:50 +13:00
}
2024-12-26 20:51:41 +13:00
// The different mentionable selectors provide the actual answer, while the others just return the ID.
if ( componentID = = "" )
2024-12-26 20:12:50 +13:00
{
2025-02-04 16:32:21 +13:00
foreach ( KeyValuePair < string , ReferencedInterviewStep > reference in currentStep . references )
{
// Skip to the first matching step.
if ( Regex . IsMatch ( answer , reference . Key ) )
{
if ( TryGetStepFromReference ( interview , reference . Value , out InterviewStep referencedStep ) )
{
currentStep . steps . Add ( reference . Key , referencedStep ) ;
await HandleAnswer ( answer , referencedStep , interview , currentStep , interaction . Channel ) ;
}
currentStep . references . Remove ( reference . Key ) ;
return ;
}
}
2024-12-27 17:23:03 +13:00
foreach ( KeyValuePair < string , InterviewStep > step in currentStep . steps )
2024-12-26 20:51:41 +13:00
{
2024-12-27 17:23:03 +13:00
// Skip to the first matching step.
if ( Regex . IsMatch ( answer , step . Key ) )
2024-12-26 23:27:36 +13:00
{
2025-02-04 16:32:21 +13:00
await HandleAnswer ( answer , step . Value , interview , currentStep , interaction . Channel ) ;
2024-12-26 23:27:36 +13:00
return ;
}
2024-12-26 20:51:41 +13:00
}
2024-12-26 20:12:50 +13:00
2025-02-04 19:55:25 +13:00
Logger . Error ( "The interview for channel " + interaction . Channel . Id + " reached a step of type " + currentStep . stepType + " which has no valid next step. Their selection was:\n" + answer ) ;
2024-12-27 01:54:28 +13:00
DiscordMessage followupMessage = await interaction . CreateFollowupMessageAsync ( new DiscordFollowupMessageBuilder ( ) . AddEmbed ( new DiscordEmbedBuilder
2024-12-26 23:27:36 +13:00
{
Color = DiscordColor . Red ,
Description = "Error: Could not determine the next question based on your answer. Check your response and ask an admin to check the bot logs if this seems incorrect."
} ) . AsEphemeral ( ) ) ;
2024-12-27 17:23:03 +13:00
currentStep . AddRelatedMessageIDs ( followupMessage . Id ) ;
2025-02-06 15:15:18 +13:00
Database . Interviews . SaveInterview ( interview ) ;
2024-12-26 20:12:50 +13:00
}
2024-12-26 20:51:41 +13:00
else
{
2024-12-27 17:23:03 +13:00
if ( ! int . TryParse ( componentID , out int stepIndex ) )
2024-12-26 20:51:41 +13:00
{
Logger . Error ( "Invalid interview button/selector index: " + componentID ) ;
return ;
}
2024-12-26 20:12:50 +13:00
2024-12-27 17:23:03 +13:00
if ( stepIndex > = currentStep . steps . Count | | stepIndex < 0 )
2024-12-26 20:51:41 +13:00
{
2024-12-27 17:23:03 +13:00
Logger . Error ( "Invalid interview button/selector index: " + stepIndex ) ;
2024-12-26 20:51:41 +13:00
return ;
}
2024-12-27 17:23:03 +13:00
( string stepString , InterviewStep nextStep ) = currentStep . steps . ElementAt ( stepIndex ) ;
2025-02-04 16:32:21 +13:00
await HandleAnswer ( stepString , nextStep , interview , currentStep , interaction . Channel ) ;
2024-12-26 20:51:41 +13:00
}
2024-12-26 17:55:00 +13:00
}
2025-02-04 19:42:02 +13:00
private static bool TryGetStepFromReference ( Interview interview , ReferencedInterviewStep reference , out InterviewStep step )
2025-02-04 16:32:21 +13:00
{
foreach ( KeyValuePair < string , InterviewStep > definition in interview . definitions )
{
if ( reference . id = = definition . Key )
{
step = definition . Value ;
step . buttonStyle = reference . buttonStyle ;
step . selectorDescription = reference . selectorDescription ;
2025-02-04 19:55:25 +13:00
if ( step . stepType ! = StepType . ERROR )
2025-02-04 16:32:21 +13:00
{
step . afterReferenceStep = reference . afterReferenceStep ;
}
return true ;
}
}
step = null ;
return false ;
}
2024-12-26 20:32:34 +13:00
public static async Task ProcessResponseMessage ( DiscordMessage answerMessage )
2024-12-26 17:55:00 +13:00
{
2024-12-26 20:12:50 +13:00
// Either the message or the referenced message is null.
2024-12-26 20:32:34 +13:00
if ( answerMessage . Channel = = null | | answerMessage . ReferencedMessage ? . Channel = = null )
2024-12-26 20:12:50 +13:00
{
return ;
}
// The channel does not have an active interview.
2025-02-06 15:15:18 +13:00
if ( ! Database . Interviews . TryGetInterview ( answerMessage . ReferencedMessage . Channel . Id ,
2025-02-04 16:32:21 +13:00
out Interview interview ) )
2024-12-26 20:12:50 +13:00
{
return ;
}
2025-02-04 16:32:21 +13:00
if ( ! interview . interviewRoot . TryGetCurrentStep ( out InterviewStep currentStep ) )
2024-12-26 20:12:50 +13:00
{
return ;
}
// The user responded to something other than the latest interview question.
2024-12-27 17:23:03 +13:00
if ( answerMessage . ReferencedMessage . Id ! = currentStep . messageID )
2024-12-26 20:12:50 +13:00
{
return ;
}
// The user responded to a question which does not take a text response.
2025-02-04 19:55:25 +13:00
if ( currentStep . stepType ! = StepType . TEXT_INPUT )
2024-12-26 20:12:50 +13:00
{
return ;
}
2024-12-26 20:32:34 +13:00
// The length requirement is less than 1024 characters, and must be less than the configurable limit if it is set.
2025-02-04 20:41:01 +13:00
int maxLength = Math . Min ( currentStep . maxLength ? ? InterviewStep . DEFAULT_MAX_FIELD_LENGTH , InterviewStep . DEFAULT_MAX_FIELD_LENGTH ) ;
2024-12-26 20:32:34 +13:00
if ( answerMessage . Content . Length > maxLength )
{
DiscordMessage lengthMessage = await answerMessage . RespondAsync ( new DiscordEmbedBuilder
{
Description = "Error: Your answer cannot be more than " + maxLength + " characters (" + answerMessage . Content . Length + "/" + maxLength + ")." ,
Color = DiscordColor . Red
} ) ;
2024-12-27 17:23:03 +13:00
currentStep . AddRelatedMessageIDs ( answerMessage . Id , lengthMessage . Id ) ;
2025-02-06 15:15:18 +13:00
Database . Interviews . SaveInterview ( interview ) ;
2024-12-26 20:32:34 +13:00
return ;
}
2025-02-04 20:41:01 +13:00
if ( answerMessage . Content . Length < ( currentStep . minLength ? ? InterviewStep . DEFAULT_MIN_FIELD_LENGTH ) )
2024-12-26 20:32:34 +13:00
{
DiscordMessage lengthMessage = await answerMessage . RespondAsync ( new DiscordEmbedBuilder
{
2024-12-27 17:23:03 +13:00
Description = "Error: Your answer must be at least " + currentStep . minLength + " characters (" + answerMessage . Content . Length + "/" + currentStep . minLength + ")." ,
2024-12-26 20:32:34 +13:00
Color = DiscordColor . Red
} ) ;
2024-12-27 17:23:03 +13:00
currentStep . AddRelatedMessageIDs ( answerMessage . Id , lengthMessage . Id ) ;
2025-02-06 15:15:18 +13:00
Database . Interviews . SaveInterview ( interview ) ;
2024-12-26 20:32:34 +13:00
return ;
}
2024-12-27 17:23:03 +13:00
foreach ( ( string stepPattern , InterviewStep nextStep ) in currentStep . steps )
2024-12-26 20:12:50 +13:00
{
2024-12-27 17:23:03 +13:00
// Skip to the first matching step.
if ( ! Regex . IsMatch ( answerMessage . Content , stepPattern ) )
2024-12-27 01:54:28 +13:00
{
continue ;
}
2024-12-26 20:12:50 +13:00
2025-02-04 16:32:21 +13:00
await HandleAnswer ( answerMessage . Content , nextStep , interview , currentStep , answerMessage . Channel , answerMessage ) ;
2024-12-26 20:16:18 +13:00
return ;
}
2024-12-26 20:12:50 +13:00
2025-02-04 19:55:25 +13:00
Logger . Error ( "The interview for channel " + answerMessage . Channel . Id + " reached a step of type " + currentStep . stepType + " which has no valid next step. Their message was:\n" + answerMessage . Content ) ;
2024-12-26 20:32:34 +13:00
DiscordMessage errorMessage = await answerMessage . RespondAsync ( new DiscordEmbedBuilder
2024-12-26 20:30:56 +13:00
{
2024-12-26 23:27:36 +13:00
Description = "Error: Could not determine the next question based on your answer. Check your response and ask an admin to check the bot logs if this seems incorrect." ,
2024-12-26 20:30:56 +13:00
Color = DiscordColor . Red
} ) ;
2024-12-27 17:23:03 +13:00
currentStep . AddRelatedMessageIDs ( answerMessage . Id , errorMessage . Id ) ;
2025-02-06 15:15:18 +13:00
Database . Interviews . SaveInterview ( interview ) ;
2024-12-26 20:16:18 +13:00
}
2024-12-26 20:12:50 +13:00
2024-12-26 20:51:41 +13:00
private static async Task HandleAnswer ( string answer ,
2024-12-27 17:23:03 +13:00
InterviewStep nextStep ,
2025-02-04 16:32:21 +13:00
Interview interview ,
2024-12-27 17:23:03 +13:00
InterviewStep previousStep ,
2024-12-26 20:16:18 +13:00
DiscordChannel channel ,
2024-12-26 20:28:39 +13:00
DiscordMessage answerMessage = null )
2024-12-26 20:16:18 +13:00
{
2024-12-27 00:38:35 +13:00
// The error message type should not alter anything about the interview.
2025-02-04 19:55:25 +13:00
if ( nextStep . stepType ! = StepType . ERROR )
2024-12-26 20:16:18 +13:00
{
2024-12-27 17:23:03 +13:00
previousStep . answer = answer ;
2024-12-27 00:38:35 +13:00
2024-12-27 17:23:03 +13:00
// There is no message ID if the step is not a text input.
previousStep . answerID = answerMessage = = null ? 0 : answerMessage . Id ;
2024-12-26 20:28:39 +13:00
}
2024-12-26 20:12:50 +13:00
2024-12-27 17:23:03 +13:00
// Create next step, or finish the interview.
2025-02-04 19:55:25 +13:00
switch ( nextStep . stepType )
{
case StepType . TEXT_INPUT :
case StepType . BUTTONS :
case StepType . TEXT_SELECTOR :
case StepType . ROLE_SELECTOR :
case StepType . USER_SELECTOR :
case StepType . CHANNEL_SELECTOR :
case StepType . MENTIONABLE_SELECTOR :
2025-02-04 19:42:02 +13:00
if ( ! ConvertReferences ( interview , nextStep , out string errID ) )
2025-02-04 16:32:21 +13:00
{
2025-02-04 19:42:02 +13:00
if ( answerMessage ! = null )
2025-02-04 16:32:21 +13:00
{
2025-02-04 19:42:02 +13:00
DiscordMessage errorMessage = await answerMessage . RespondAsync ( new DiscordEmbedBuilder
2025-02-04 16:32:21 +13:00
{
2025-02-04 19:42:02 +13:00
Description = "Error: The referenced step id '" + errID + "' does not exist in the step definitions." ,
Color = DiscordColor . Red
} ) ;
nextStep . AddRelatedMessageIDs ( answerMessage . Id , errorMessage . Id ) ;
previousStep . answer = null ;
previousStep . answerID = 0 ;
2025-02-06 15:15:18 +13:00
Database . Interviews . SaveInterview ( interview ) ;
2025-02-04 16:32:21 +13:00
}
2025-02-04 19:42:02 +13:00
return ;
2025-02-04 16:32:21 +13:00
}
2025-02-04 19:27:20 +13:00
await SendNextMessage ( interview , channel , nextStep ) ;
2025-02-06 15:15:18 +13:00
Database . Interviews . SaveInterview ( interview ) ;
2024-12-26 20:16:18 +13:00
break ;
2025-02-04 19:55:25 +13:00
case StepType . INTERVIEW_END :
2025-02-04 19:27:20 +13:00
DiscordEmbedBuilder endEmbed = new ( )
2024-12-26 20:16:18 +13:00
{
2024-12-27 17:23:03 +13:00
Color = Utilities . StringToColor ( nextStep . color ) ,
Title = nextStep . heading ,
Description = nextStep . message ,
2024-12-26 20:16:18 +13:00
} ;
2024-12-26 20:12:50 +13:00
2025-02-04 19:27:20 +13:00
if ( nextStep . addSummary ? ? false )
2024-12-26 20:16:18 +13:00
{
2025-02-04 19:27:20 +13:00
AddSummary ( interview , ref endEmbed ) ;
2024-12-26 20:16:18 +13:00
}
2024-12-26 20:12:50 +13:00
2025-02-04 19:27:20 +13:00
await channel . SendMessageAsync ( endEmbed ) ;
2024-12-26 20:12:50 +13:00
2025-02-04 19:27:20 +13:00
if ( Config . deleteMessagesAfterInterviewEnd )
2024-12-26 23:47:55 +13:00
{
2025-02-04 16:32:21 +13:00
await DeletePreviousMessages ( interview , channel ) ;
2024-12-26 23:47:55 +13:00
}
2025-02-06 15:15:18 +13:00
if ( ! Database . Interviews . TryDeleteInterview ( channel . Id ) )
2024-12-26 20:16:18 +13:00
{
2024-12-26 20:28:39 +13:00
Logger . Error ( "Could not delete interview from database. Channel ID: " + channel . Id ) ;
2024-12-26 20:16:18 +13:00
}
2024-12-26 20:28:39 +13:00
return ;
2025-02-04 19:55:25 +13:00
case StepType . REFERENCE_END :
2025-02-04 16:32:21 +13:00
if ( interview . interviewRoot . TryGetTakenSteps ( out List < InterviewStep > previousSteps ) )
{
foreach ( InterviewStep step in previousSteps )
{
if ( step . afterReferenceStep ! = null )
{
// If the referenced step is also a reference end, skip it and try to find another.
2025-02-04 19:55:25 +13:00
if ( step . afterReferenceStep . stepType = = StepType . REFERENCE_END )
2025-02-04 16:32:21 +13:00
{
step . afterReferenceStep = null ;
}
else
{
nextStep = step . afterReferenceStep ;
step . afterReferenceStep = null ;
previousStep . steps . Clear ( ) ;
previousStep . steps . Add ( answer , nextStep ) ;
2025-02-04 19:27:20 +13:00
await HandleAnswer ( answer , nextStep , interview , previousStep , channel , answerMessage ) ;
2025-02-04 16:32:21 +13:00
return ;
}
}
}
}
2025-02-04 19:27:20 +13:00
DiscordEmbedBuilder error = new ( )
2025-02-04 16:32:21 +13:00
{
Color = DiscordColor . Red ,
Description = "An error occured while trying to find the next interview step."
} ;
if ( answerMessage = = null )
{
DiscordMessage errorMessage = await channel . SendMessageAsync ( error ) ;
previousStep . AddRelatedMessageIDs ( errorMessage . Id ) ;
}
else
{
DiscordMessage errorMessage = await answerMessage . RespondAsync ( error ) ;
previousStep . AddRelatedMessageIDs ( errorMessage . Id , answerMessage . Id ) ;
}
2025-02-06 15:15:18 +13:00
Database . Interviews . SaveInterview ( interview ) ;
2025-02-04 16:32:21 +13:00
Logger . Error ( "Could not find a step to return to after a reference step in channel " + channel . Id ) ;
return ;
2025-02-04 19:55:25 +13:00
case StepType . ERROR :
2024-12-26 20:16:18 +13:00
default :
2025-02-04 19:27:20 +13:00
DiscordEmbedBuilder errorEmbed = new ( )
2025-02-04 16:32:21 +13:00
{
Color = Utilities . StringToColor ( nextStep . color ) ,
Title = nextStep . heading ,
Description = nextStep . message
} ;
2025-02-04 19:27:20 +13:00
if ( nextStep . addSummary ? ? false )
{
AddSummary ( interview , ref errorEmbed ) ;
}
2024-12-26 20:28:39 +13:00
if ( answerMessage = = null )
{
2025-02-04 19:27:20 +13:00
DiscordMessage errorMessage = await channel . SendMessageAsync ( errorEmbed ) ;
2024-12-27 17:23:03 +13:00
previousStep . AddRelatedMessageIDs ( errorMessage . Id ) ;
2024-12-26 20:28:39 +13:00
}
else
{
2025-02-04 19:27:20 +13:00
DiscordMessage errorMessage = await answerMessage . RespondAsync ( errorEmbed ) ;
2024-12-27 17:23:03 +13:00
previousStep . AddRelatedMessageIDs ( errorMessage . Id , answerMessage . Id ) ;
2024-12-26 20:28:39 +13:00
}
2024-12-27 01:24:42 +13:00
2025-02-06 15:15:18 +13:00
Database . Interviews . SaveInterview ( interview ) ;
2025-02-04 19:27:20 +13:00
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 ? ? "-" ) ;
2024-12-26 20:16:18 +13:00
}
2024-12-26 17:55:00 +13:00
}
2025-02-04 19:42:02 +13:00
private static bool ConvertReferences ( Interview interview , InterviewStep step , out string errorID )
{
foreach ( ( string stepPattern , ReferencedInterviewStep reference ) in step . references )
{
if ( ! reference . TryGetReferencedStep ( interview . definitions , out InterviewStep referencedStep ) )
{
errorID = reference . id ;
return false ;
}
step . steps . Add ( stepPattern , referencedStep ) ;
}
step . references . Clear ( ) ;
errorID = "" ;
return true ;
}
2025-02-04 16:32:21 +13:00
private static async Task DeletePreviousMessages ( Interview interview , DiscordChannel channel )
2024-12-26 20:28:39 +13:00
{
2024-12-27 00:38:35 +13:00
List < ulong > previousMessages = [ ] ;
2025-02-04 16:32:21 +13:00
interview . interviewRoot . GetMessageIDs ( ref previousMessages ) ;
2024-12-26 20:28:39 +13:00
foreach ( ulong previousMessageID in previousMessages )
{
try
{
DiscordMessage previousMessage = await channel . GetMessageAsync ( previousMessageID ) ;
await channel . DeleteMessageAsync ( previousMessage , "Deleting old interview message." ) ;
}
catch ( Exception e )
{
Logger . Warn ( "Failed to delete old interview message: " , e ) ;
}
}
}
2025-02-04 19:27:20 +13:00
private static async Task SendNextMessage ( Interview interview , DiscordChannel channel , InterviewStep step )
2024-12-26 17:55:00 +13:00
{
2024-12-26 22:24:42 +13:00
DiscordMessageBuilder msgBuilder = new ( ) ;
DiscordEmbedBuilder embed = new ( )
2024-12-26 17:55:00 +13:00
{
2024-12-27 17:23:03 +13:00
Color = Utilities . StringToColor ( step . color ) ,
Title = step . heading ,
Description = step . message
2024-12-26 17:55:00 +13:00
} ;
2025-02-04 19:27:20 +13:00
if ( step . addSummary ? ? false )
{
AddSummary ( interview , ref embed ) ;
}
2025-02-04 19:55:25 +13:00
switch ( step . stepType )
2024-12-26 17:55:00 +13:00
{
2025-02-04 19:55:25 +13:00
case StepType . BUTTONS :
2024-12-26 17:55:00 +13:00
int nrOfButtons = 0 ;
2024-12-27 17:23:03 +13:00
for ( int nrOfButtonRows = 0 ; nrOfButtonRows < 5 & & nrOfButtons < step . steps . Count ; nrOfButtonRows + + )
2024-12-26 17:55:00 +13:00
{
List < DiscordButtonComponent > buttonRow = [ ] ;
2024-12-27 17:23:03 +13:00
for ( ; nrOfButtons < 5 * ( nrOfButtonRows + 1 ) & & nrOfButtons < step . steps . Count ; nrOfButtons + + )
2024-12-26 17:55:00 +13:00
{
2024-12-27 17:23:03 +13:00
( string stepPattern , InterviewStep nextStep ) = step . steps . ToArray ( ) [ nrOfButtons ] ;
2025-02-06 15:15:18 +13:00
buttonRow . Add ( new DiscordButtonComponent ( nextStep . GetButtonStyle ( ) , "supportboi_interviewbutton " + nrOfButtons , stepPattern ) ) ;
2024-12-26 17:55:00 +13:00
}
msgBuilder . AddComponents ( buttonRow ) ;
}
break ;
2025-02-04 19:55:25 +13:00
case StepType . TEXT_SELECTOR :
2024-12-26 17:55:00 +13:00
List < DiscordSelectComponent > selectionComponents = [ ] ;
int selectionOptions = 0 ;
2024-12-27 17:23:03 +13:00
for ( int selectionBoxes = 0 ; selectionBoxes < 5 & & selectionOptions < step . steps . Count ; selectionBoxes + + )
2024-12-26 17:55:00 +13:00
{
List < DiscordSelectComponentOption > categoryOptions = [ ] ;
2024-12-27 17:23:03 +13:00
for ( ; selectionOptions < 25 * ( selectionBoxes + 1 ) & & selectionOptions < step . steps . Count ; selectionOptions + + )
2024-12-26 17:55:00 +13:00
{
2024-12-27 17:23:03 +13:00
( string stepPattern , InterviewStep nextStep ) = step . steps . ToArray ( ) [ selectionOptions ] ;
categoryOptions . Add ( new DiscordSelectComponentOption ( stepPattern , selectionOptions . ToString ( ) , nextStep . selectorDescription ) ) ;
2024-12-26 17:55:00 +13:00
}
2024-12-26 22:24:42 +13:00
2025-02-06 15:15:18 +13:00
selectionComponents . Add ( new DiscordSelectComponent ( "supportboi_interviewselector " + selectionBoxes ,
2025-02-04 19:27:20 +13:00
string . IsNullOrWhiteSpace ( step . selectorPlaceholder ) ? "Select an option..." : step . selectorPlaceholder , categoryOptions ) ) ;
2024-12-26 17:55:00 +13:00
}
msgBuilder . AddComponents ( selectionComponents ) ;
break ;
2025-02-04 19:55:25 +13:00
case StepType . ROLE_SELECTOR :
2025-02-06 15:15:18 +13:00
msgBuilder . AddComponents ( new DiscordRoleSelectComponent ( "supportboi_interviewroleselector" ,
2025-02-04 19:27:20 +13:00
string . IsNullOrWhiteSpace ( step . selectorPlaceholder ) ? "Select a role..." : step . selectorPlaceholder ) ) ;
2024-12-26 20:51:41 +13:00
break ;
2025-02-04 19:55:25 +13:00
case StepType . USER_SELECTOR :
2025-02-06 15:15:18 +13:00
msgBuilder . AddComponents ( new DiscordUserSelectComponent ( "supportboi_interviewuserselector" ,
2025-02-04 19:27:20 +13:00
string . IsNullOrWhiteSpace ( step . selectorPlaceholder ) ? "Select a user..." : step . selectorPlaceholder ) ) ;
2024-12-26 20:51:41 +13:00
break ;
2025-02-04 19:55:25 +13:00
case StepType . CHANNEL_SELECTOR :
2025-02-06 15:15:18 +13:00
msgBuilder . AddComponents ( new DiscordChannelSelectComponent ( "supportboi_interviewchannelselector" ,
2025-02-04 19:27:20 +13:00
string . IsNullOrWhiteSpace ( step . selectorPlaceholder ) ? "Select a channel..." : step . selectorPlaceholder ) ) ;
2024-12-26 20:51:41 +13:00
break ;
2025-02-04 19:55:25 +13:00
case StepType . MENTIONABLE_SELECTOR :
2025-02-06 15:15:18 +13:00
msgBuilder . AddComponents ( new DiscordMentionableSelectComponent ( "supportboi_interviewmentionableselector" ,
2025-02-04 19:27:20 +13:00
string . IsNullOrWhiteSpace ( step . selectorPlaceholder ) ? "Select a user or role..." : step . selectorPlaceholder ) ) ;
2024-12-26 20:51:41 +13:00
break ;
2025-02-04 19:55:25 +13:00
case StepType . TEXT_INPUT :
2025-02-04 20:41:01 +13:00
string lengthInfo ;
if ( step . minLength ! = null )
{
lengthInfo = " (" + step . minLength + "-" + ( step . maxLength ? ? InterviewStep . DEFAULT_MAX_FIELD_LENGTH ) + " characters)" ;
}
else
{
2025-02-06 15:15:18 +13:00
lengthInfo = " (Maximum " + ( step . maxLength ? ? InterviewStep . DEFAULT_MAX_FIELD_LENGTH ) + " characters)" ;
2025-02-04 20:41:01 +13:00
}
embed . WithFooter ( "Reply to this message with your answer" + lengthInfo + ". You cannot include images or files." ) ;
2024-12-26 17:55:00 +13:00
break ;
2025-02-04 19:55:25 +13:00
case StepType . INTERVIEW_END :
case StepType . ERROR :
2024-12-26 17:55:00 +13:00
default :
break ;
}
2024-12-26 18:07:36 +13:00
msgBuilder . AddEmbed ( embed ) ;
2024-12-26 17:55:00 +13:00
DiscordMessage message = await channel . SendMessageAsync ( msgBuilder ) ;
2024-12-27 17:23:03 +13:00
step . messageID = message . Id ;
2024-12-26 17:55:00 +13:00
}
}