2024-10-29 09:10:37 +00:00
using System ;
2022-08-21 07:34:11 +00:00
using System.Collections.Generic ;
2024-12-26 05:36:20 +00:00
using System.ComponentModel ;
2024-12-26 06:50:47 +00:00
using System.IO ;
2022-08-21 07:34:11 +00:00
using System.Linq ;
2024-12-26 06:50:47 +00:00
using System.Net.Http ;
using System.Text ;
2022-08-21 07:34:11 +00:00
using System.Threading.Tasks ;
2024-12-26 05:36:20 +00:00
using DSharpPlus.Commands ;
using DSharpPlus.Commands.ContextChecks ;
using DSharpPlus.Commands.Processors.SlashCommands ;
2022-08-21 07:34:11 +00:00
using DSharpPlus.Entities ;
2024-12-26 05:57:41 +00:00
using DSharpPlus.Exceptions ;
2022-08-21 07:34:11 +00:00
using DSharpPlus.Interactivity ;
using DSharpPlus.Interactivity.Extensions ;
2024-12-26 06:50:47 +00:00
using Newtonsoft.Json ;
2022-08-21 07:34:11 +00:00
namespace SupportChild.Commands ;
2024-12-26 05:36:20 +00:00
[Command("admin")]
[Description("Administrative commands.")]
public class AdminCommands
2022-08-21 07:34:11 +00:00
{
2024-12-26 05:36:20 +00:00
[RequireGuild]
[Command("listinvalid")]
[Description("List tickets which channels have been deleted. Use /admin unsetticket <id> to remove them.")]
public async Task ListInvalid ( SlashCommandContext command )
2024-10-29 09:10:37 +00:00
{
if ( ! Database . TryGetOpenTickets ( out List < Database . Ticket > openTickets ) )
{
2024-12-26 05:36:20 +00:00
await command . RespondAsync ( new DiscordEmbedBuilder
2024-10-29 09:10:37 +00:00
{
Color = DiscordColor . Red ,
Description = "Could not get any open tickets from database."
} , true ) ;
}
// Get all channels in all guilds the bot is part of
List < DiscordChannel > allChannels = new List < DiscordChannel > ( ) ;
2024-12-26 05:36:20 +00:00
foreach ( KeyValuePair < ulong , DiscordGuild > guild in SupportChild . client . Guilds )
2024-10-29 09:10:37 +00:00
{
try
{
allChannels . AddRange ( await guild . Value . GetChannelsAsync ( ) ) ;
}
catch ( Exception ) { /*ignored*/ }
}
// Check which tickets channels no longer exist
List < string > listItems = new List < string > ( ) ;
foreach ( Database . Ticket ticket in openTickets )
{
if ( allChannels . All ( channel = > channel . Id ! = ticket . channelID ) )
{
listItems . Add ( "ID: **" + ticket . id . ToString ( "00000" ) + ":** <#" + ticket . channelID + ">\n" ) ;
}
}
if ( listItems . Count = = 0 )
{
2024-12-26 05:36:20 +00:00
await command . RespondAsync ( new DiscordEmbedBuilder
2024-10-29 09:10:37 +00:00
{
Color = DiscordColor . Green ,
Description = "All tickets are valid!"
} , true ) ;
return ;
}
List < DiscordEmbedBuilder > embeds = new List < DiscordEmbedBuilder > ( ) ;
foreach ( string message in Utilities . ParseListIntoMessages ( listItems ) )
{
embeds . Add ( new DiscordEmbedBuilder
{
Title = "Invalid tickets:" ,
Color = DiscordColor . Red ,
Description = message
} ) ;
}
// Add the footers
for ( int i = 0 ; i < embeds . Count ; i + + )
{
embeds [ i ] . Footer = new DiscordEmbedBuilder . EmbedFooter
{
Text = $"Page {i + 1} / {embeds.Count}"
} ;
}
List < Page > listPages = new List < Page > ( ) ;
foreach ( DiscordEmbedBuilder embed in embeds )
{
listPages . Add ( new Page ( "" , embed ) ) ;
}
await command . Interaction . SendPaginatedResponseAsync ( true , command . User , listPages ) ;
}
2024-12-26 05:36:20 +00:00
[RequireGuild]
[Command("setticket")]
[Description("Turns a channel into a ticket. WARNING: Anyone will be able to delete the channel using /close.")]
public async Task SetTicket ( SlashCommandContext command ,
[Parameter("user")] [ Description ( "(Optional) The owner of the ticket." ) ] DiscordUser user = null )
2024-10-29 09:10:37 +00:00
{
// Check if ticket exists in the database
if ( Database . IsOpenTicket ( command . Channel . Id ) )
{
2024-12-26 05:36:20 +00:00
await command . RespondAsync ( new DiscordEmbedBuilder
2024-10-29 09:10:37 +00:00
{
Color = DiscordColor . Red ,
Description = "This channel is already a ticket."
} , true ) ;
return ;
}
DiscordUser ticketUser = ( user = = null ? command . User : user ) ;
long id = Database . NewTicket ( ticketUser . Id , 0 , command . Channel . Id ) ;
2024-12-26 05:36:20 +00:00
await command . RespondAsync ( new DiscordEmbedBuilder
2024-10-29 09:10:37 +00:00
{
Color = DiscordColor . Green ,
2024-12-26 12:21:37 +00:00
Description = "Channel has been designated ticket " + id . ToString ( "00000" ) + "."
2024-10-29 09:10:37 +00:00
} ) ;
2024-12-26 05:57:41 +00:00
try
2024-10-29 09:10:37 +00:00
{
2024-12-26 05:57:41 +00:00
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild . client . GetChannelAsync ( Config . logChannel ) ;
2024-10-29 09:10:37 +00:00
await logChannel . SendMessageAsync ( new DiscordEmbedBuilder
{
Color = DiscordColor . Green ,
2024-12-26 12:21:37 +00:00
Description = command . Channel . Mention + " has been designated ticket " + id . ToString ( "00000" ) + " by " + command . Member ? . Mention + "." ,
Footer = new DiscordEmbedBuilder . EmbedFooter
{
Text = "Ticket: " + id . ToString ( "00000" )
}
2024-10-29 09:10:37 +00:00
} ) ;
}
2024-12-26 05:57:41 +00:00
catch ( NotFoundException )
{
Logger . Error ( "Could not find the log channel." ) ;
}
2024-10-29 09:10:37 +00:00
}
2024-12-26 05:36:20 +00:00
[RequireGuild]
[Command("unsetticket")]
[Description("Deletes a ticket from the ticket system without deleting the channel.")]
public async Task UnsetTicket ( SlashCommandContext command ,
[Parameter("ticket-id")] [ Description ( "(Optional) Ticket to unset. Uses the channel you are in by default." ) ] long ticketID = 0 )
2024-10-29 09:10:37 +00:00
{
Database . Ticket ticket ;
if ( ticketID = = 0 )
{
// Check if ticket exists in the database
if ( ! Database . TryGetOpenTicket ( command . Channel . Id , out ticket ) )
{
2024-12-26 05:36:20 +00:00
await command . RespondAsync ( new DiscordEmbedBuilder
2024-10-29 09:10:37 +00:00
{
Color = DiscordColor . Red ,
Description = "This channel is not a ticket!"
} , true ) ;
return ;
}
}
else
{
// Check if ticket exists in the database
if ( ! Database . TryGetOpenTicketByID ( ( uint ) ticketID , out ticket ) )
{
2024-12-26 05:36:20 +00:00
await command . RespondAsync ( new DiscordEmbedBuilder
2024-10-29 09:10:37 +00:00
{
Color = DiscordColor . Red ,
Description = "There is no ticket with this ticket ID."
} , true ) ;
return ;
}
}
if ( Database . DeleteOpenTicket ( ticket . id ) )
{
2024-12-26 05:36:20 +00:00
await command . RespondAsync ( new DiscordEmbedBuilder
2024-10-29 09:10:37 +00:00
{
Color = DiscordColor . Green ,
Description = "Channel has been undesignated as a ticket."
} ) ;
2024-12-26 05:57:41 +00:00
try
2024-10-29 09:10:37 +00:00
{
2024-12-26 05:57:41 +00:00
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild . client . GetChannelAsync ( Config . logChannel ) ;
2024-10-29 09:10:37 +00:00
await logChannel . SendMessageAsync ( new DiscordEmbedBuilder
{
Color = DiscordColor . Green ,
2024-12-26 12:21:37 +00:00
Description = command . Channel . Mention + " has been undesignated as a ticket by " + command . User . Mention + "." ,
Footer = new DiscordEmbedBuilder . EmbedFooter
{
Text = "Ticket: " + ticket . id . ToString ( "00000" )
}
2024-10-29 09:10:37 +00:00
} ) ;
}
2024-12-26 05:57:41 +00:00
catch ( NotFoundException )
{
Logger . Error ( "Could not find the log channel." ) ;
}
2024-10-29 09:10:37 +00:00
}
else
{
2024-12-26 05:36:20 +00:00
await command . RespondAsync ( new DiscordEmbedBuilder
2024-10-29 09:10:37 +00:00
{
Color = DiscordColor . Red ,
Description = "Error: Failed removing ticket from database."
} , true ) ;
}
}
2024-12-26 07:47:07 +00:00
[RequireGuild]
2024-12-26 05:36:20 +00:00
[Command("reload")]
[Description("Reloads the bot config.")]
public async Task Reload ( SlashCommandContext command )
2024-10-29 09:10:37 +00:00
{
2024-12-26 05:36:20 +00:00
await command . RespondAsync ( new DiscordEmbedBuilder
2024-10-29 09:10:37 +00:00
{
Color = DiscordColor . Green ,
Description = "Reloading bot application..."
} ) ;
2024-12-26 12:21:37 +00:00
try
{
DiscordChannel logChannel = await SupportChild . client . GetChannelAsync ( Config . logChannel ) ;
await logChannel . SendMessageAsync ( new DiscordEmbedBuilder
{
Color = DiscordColor . Green ,
Description = command . Channel . Mention + " reloaded the bot." ,
} ) ;
}
catch ( NotFoundException )
{
Logger . Error ( "Could not find the log channel." ) ;
}
2024-10-29 09:10:37 +00:00
Logger . Log ( "Reloading bot..." ) ;
2024-12-26 07:12:50 +00:00
await SupportChild . Reload ( ) ;
2024-10-29 09:10:37 +00:00
}
2024-12-26 06:50:47 +00:00
2024-12-26 07:47:07 +00:00
[RequireGuild]
2024-12-26 12:54:28 +00:00
[Command("getinterviewtemplate")]
[Description("Provides a copy of the interview template for a category which you can edit and then reupload.")]
public async Task GetInterviewTemplate ( SlashCommandContext command ,
[Parameter("category")] [ Description ( "The category to get the template for." ) ] DiscordChannel category )
2024-12-26 06:50:47 +00:00
{
2024-12-26 12:54:28 +00:00
if ( ! category ? . IsCategory ? ? true )
{
await command . RespondAsync ( new DiscordEmbedBuilder
{
Color = DiscordColor . Red ,
Description = "That channel is not a category."
} , true ) ;
return ;
}
if ( ! Database . TryGetCategory ( category . Id , out Database . Category categoryData ) )
{
await command . RespondAsync ( new DiscordEmbedBuilder
{
Color = DiscordColor . Red ,
Description = "That category is not registered with the bot."
} , true ) ;
return ;
}
string interviewTemplateJSON = Database . GetInterviewTemplateJSON ( category . Id ) ;
if ( interviewTemplateJSON = = null )
{
string defaultTemplate =
"{\n" +
" \"category-id\": \"" + category . Id + "\",\n" +
" \"interview\":\n" +
" {\n" +
" \"message\": \"\",\n" +
" \"type\": \"\",\n" +
" \"color\": \"\",\n" +
" \"paths\":\n" +
" {\n" +
" \n" +
" }\n" +
" }\n" +
"}" ;
MemoryStream stream = new ( Encoding . UTF8 . GetBytes ( defaultTemplate ) ) ;
DiscordInteractionResponseBuilder response = new DiscordInteractionResponseBuilder ( ) . AddEmbed ( new DiscordEmbedBuilder
{
Color = DiscordColor . Green ,
Description = "No interview template found for this category. A default template has been generated."
} ) . AddFile ( "interview-template-" + category . Id + ".json" , stream ) . AsEphemeral ( ) ;
await command . RespondAsync ( response ) ;
}
else
{
MemoryStream stream = new ( Encoding . UTF8 . GetBytes ( interviewTemplateJSON ) ) ;
await command . RespondAsync ( new DiscordInteractionResponseBuilder ( ) . AddFile ( "interview-template-" + category . Id + ".json" , stream ) . AsEphemeral ( ) ) ;
}
2024-12-26 06:50:47 +00:00
}
2024-12-26 07:47:07 +00:00
[RequireGuild]
2024-12-26 12:54:28 +00:00
[Command("setinterviewtemplate")]
2024-12-26 06:50:47 +00:00
[Description("Uploads an interview template file.")]
2024-12-26 12:54:28 +00:00
public async Task SetInterviewTemplate ( SlashCommandContext command ,
[Parameter("file")] [ Description ( "The file containing the template." ) ] DiscordAttachment file )
2024-12-26 06:50:47 +00:00
{
2024-12-26 12:54:28 +00:00
await command . DeferResponseAsync ( true ) ;
2024-12-26 06:50:47 +00:00
if ( ! file . MediaType ? . Contains ( "application/json" ) ? ? false )
{
await command . RespondAsync ( new DiscordEmbedBuilder
{
Color = DiscordColor . Red ,
Description = "The uploaded file is not a JSON file according to Discord."
2024-12-26 07:22:05 +00:00
} , true ) ;
2024-12-26 06:50:47 +00:00
return ;
}
Stream stream = await new HttpClient ( ) . GetStreamAsync ( file . Url ) ;
string json = await new StreamReader ( stream ) . ReadToEndAsync ( ) ;
try
{
List < string > errors = [ ] ;
// Convert it to an interview object to validate the template
2024-12-26 12:54:28 +00:00
Interviews . ValidatedTemplate template = JsonConvert . DeserializeObject < Interviews . ValidatedTemplate > ( json , new JsonSerializerSettings ( )
2024-12-26 06:50:47 +00:00
{
//NullValueHandling = NullValueHandling.Include,
MissingMemberHandling = MissingMemberHandling . Error ,
2024-12-26 11:38:35 +00:00
Error = delegate ( object sender , Newtonsoft . Json . Serialization . ErrorEventArgs args )
2024-12-26 06:50:47 +00:00
{
// I noticed the main exception mainly has information for developers, not administrators,
// so I switched to using the inner message if available.
if ( string . IsNullOrEmpty ( args . ErrorContext . Error . InnerException ? . Message ) )
{
errors . Add ( args . ErrorContext . Error . Message ) ;
}
else
{
errors . Add ( args . ErrorContext . Error . InnerException . Message ) ;
}
Logger . Debug ( "Exception occured when trying to upload interview template:\n" + args . ErrorContext . Error ) ;
2024-12-26 12:54:28 +00:00
args . ErrorContext . Handled = false ;
2024-12-26 06:50:47 +00:00
}
} ) ;
2024-12-26 12:54:28 +00:00
DiscordChannel category = await SupportChild . client . GetChannelAsync ( template . categoryID ) ;
if ( ! category . IsCategory )
2024-12-26 10:23:25 +00:00
{
2024-12-26 12:54:28 +00:00
await command . RespondAsync ( new DiscordEmbedBuilder
2024-12-26 10:23:25 +00:00
{
2024-12-26 12:54:28 +00:00
Color = DiscordColor . Red ,
Description = "The category ID in the uploaded JSON structure is not a valid category."
} , true ) ;
return ;
}
2024-12-26 11:47:23 +00:00
2024-12-26 12:54:28 +00:00
if ( ! Database . TryGetCategory ( category . Id , out Database . Category _ ) )
{
await command . RespondAsync ( new DiscordEmbedBuilder
{
Color = DiscordColor . Red ,
Description = "The category ID in the uploaded JSON structure is not a category registered with the bot, use /addcategory first."
} , true ) ;
return ;
}
2024-12-26 11:47:23 +00:00
2024-12-26 12:54:28 +00:00
template . interview . Validate ( ref errors , out int summaryCount , out int summaryMaxLength ) ;
if ( summaryCount > 25 )
{
errors . Add ( "A summary cannot contain more than 25 fields, but you have " + summaryCount + " fields in at least one of your interview branches." ) ;
}
if ( summaryMaxLength > = 6000 )
{
errors . Add ( "A summary cannot contain more than 6000 characters, but at least one of your branches has the possibility of its summary reaching " + summaryMaxLength + " characters." ) ;
2024-12-26 10:23:25 +00:00
}
2024-12-26 06:50:47 +00:00
if ( errors . Count ! = 0 )
{
string errorString = string . Join ( "\n\n" , errors ) ;
if ( errorString . Length > 1500 )
{
errorString = errorString . Substring ( 0 , 1500 ) ;
}
await command . RespondAsync ( new DiscordEmbedBuilder
{
Color = DiscordColor . Red ,
Description = "The uploaded JSON structure could not be converted to an interview template.\n\nErrors:\n```\n" + errorString + "\n```" ,
Footer = new DiscordEmbedBuilder . EmbedFooter ( )
{
Text = "More detailed information may be available as debug messages in the bot logs."
}
2024-12-26 07:22:05 +00:00
} , true ) ;
2024-12-26 06:50:47 +00:00
return ;
}
2024-12-26 12:54:28 +00:00
if ( ! Database . SetInterviewTemplate ( template ) )
{
await command . RespondAsync ( new DiscordEmbedBuilder
{
Color = DiscordColor . Red ,
Description = "An error occured trying to write the new template to database."
} , true ) ;
return ;
}
try
{
MemoryStream memStream = new ( Encoding . UTF8 . GetBytes ( Database . GetInterviewTemplateJSON ( template . categoryID ) ) ) ;
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild . client . GetChannelAsync ( Config . logChannel ) ;
await logChannel . SendMessageAsync ( new DiscordMessageBuilder ( ) . AddEmbed ( new DiscordEmbedBuilder
{
Color = DiscordColor . Green ,
Description = command . User . Mention + " uploaded a new interview template for the `" + category . Name + "` category."
} ) . AddFile ( "interview-template-" + template . categoryID + ".json" , memStream ) ) ;
}
catch ( NotFoundException )
{
Logger . Error ( "Could not find the log channel." ) ;
}
2024-12-26 06:50:47 +00:00
}
catch ( Exception e )
{
await command . RespondAsync ( new DiscordEmbedBuilder
{
Color = DiscordColor . Red ,
Description = "The uploaded JSON structure could not be converted to an interview template.\n\nError message:\n```\n" + e . Message + "\n```"
2024-12-26 07:22:05 +00:00
} , true ) ;
2024-12-26 06:50:47 +00:00
return ;
}
await command . RespondAsync ( new DiscordEmbedBuilder
{
Color = DiscordColor . Green ,
Description = "Uploaded interview template."
2024-12-26 07:22:05 +00:00
} , true ) ;
2024-12-26 12:21:37 +00:00
2024-12-26 06:50:47 +00:00
}
2022-08-21 07:34:11 +00:00
}