diff --git a/.github/workflows/auto-approve.yml b/.github/workflows/auto-approve.yml new file mode 100644 index 0000000..a1bb62f --- /dev/null +++ b/.github/workflows/auto-approve.yml @@ -0,0 +1,26 @@ +name: Auto approve + +on: pull_request + +permissions: + contents: read + +jobs: + auto-approve: + + name: Auto approve Pull Request + runs-on: ubuntu-latest + + # for hmarr/auto-approve-action to approve PRs + permissions: + pull-requests: write + + # Only run this on the main repo + if: github.event.pull_request.head.repo.full_name == 'EmotionChild/SupportChild' + + steps: + - name: Approve via actions + uses: hmarr/auto-approve-action@v2.2.1 + if: github.actor == 'EmotionChild' || github.actor == 'dependabot[bot]' + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/SupportChild/Commands/AddCategoryCommand.cs b/SupportChild/Commands/AddCategoryCommand.cs new file mode 100644 index 0000000..3656fdb --- /dev/null +++ b/SupportChild/Commands/AddCategoryCommand.cs @@ -0,0 +1,71 @@ +using System.Threading.Tasks; +using DSharpPlus.Entities; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; + +namespace SupportChild.Commands; + +public class AddCategoryCommand : ApplicationCommandModule +{ + [SlashRequireGuild] + [SlashCommand("addcategory", "Adds a category to the ticket bot letting users open tickets in them.")] + public async Task OnExecute(InteractionContext command, [Option("Title", "The name to display on buttons and in selection boxes.")] string title, [Option("Category", "The category to add.")] DiscordChannel category) + { + if (!category.IsCategory) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "That channel is not a category." + }, true); + return; + } + + if (string.IsNullOrWhiteSpace(title)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Invalid category title specified." + }, true); + return; + } + + if (Database.TryGetCategory(category.Id, out Database.Category _)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "That category is already registered." + }, true); + return; + } + + if (Database.TryGetCategory(title, out Database.Category _)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "There is already a category with that title." + }, true); + return; + } + + if (Database.AddCategory(title, category.Id)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Category added." + }, true); + } + else + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: Failed adding the category to the database." + }, true); + } + } +} \ No newline at end of file diff --git a/SupportChild/Commands/AddCommand.cs b/SupportChild/Commands/AddCommand.cs index 64e5935..47b92f8 100644 --- a/SupportChild/Commands/AddCommand.cs +++ b/SupportChild/Commands/AddCommand.cs @@ -1,107 +1,82 @@ using System; using System.Threading.Tasks; using DSharpPlus; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class AddCommand : ApplicationCommandModule { - public class AddCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("add", "Adds a user to a ticket")] + public async Task OnExecute(InteractionContext command, [Option("User", "User to add to ticket.")] DiscordUser user) { - [Command("add")] - [Description("Adds a user to a ticket.")] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + // Check if ticket exists in the database + if (!Database.IsOpenTicket(command.Channel.Id)) { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "add")) + await command.CreateResponseAsync(new DiscordEmbedBuilder { - DiscordEmbed error = new DiscordEmbedBuilder + Color = DiscordColor.Red, + Description = "This channel is not a ticket." + }, true); + return; + } + + DiscordMember member; + try + { + member = (user == null ? command.Member : await command.Guild.GetMemberAsync(user.Id)); + + if (member == null) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder { Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the add command but did not have permission."); + Description = "Could not find that user in this server." + }, true); return; } - - // Check if ticket exists in the database - if (!Database.IsOpenTicket(command.Channel.Id)) + } + catch (Exception) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "This channel is not a ticket." - }; - await command.RespondAsync(error); - return; - } + Color = DiscordColor.Red, + Description = "Could not find that user in this server." + }, true); + return; + } - string[] parsedArgs = Utilities.ParseIDs(command.RawArgumentString); - foreach (string parsedArg in parsedArgs) + try + { + await command.Channel.AddOverwriteAsync(member, Permissions.AccessChannels); + await command.CreateResponseAsync(new DiscordEmbedBuilder { - if (!ulong.TryParse(parsedArg, out ulong userID)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Invalid ID/Mention. (Could not convert to numerical)" - }; - await command.RespondAsync(error); - continue; - } + Color = DiscordColor.Green, + Description = "Added " + member.Mention + " to ticket." + }); - DiscordMember mentionedMember; - try + // Log it if the log channel exists + DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + await logChannel.SendMessageAsync(new DiscordEmbedBuilder { - mentionedMember = await command.Guild.GetMemberAsync(userID); - } - catch (Exception) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Invalid ID/Mention. (Could not find user on this server)" - }; - await command.RespondAsync(error); - continue; - } - - try - { - await command.Channel.AddOverwriteAsync(mentionedMember, Permissions.AccessChannels, Permissions.None); - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Added " + mentionedMember.Mention + " to ticket." - }; - await command.RespondAsync(message); - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed logMessage = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = mentionedMember.Mention + " was added to " + command.Channel.Mention + - " by " + command.Member.Mention + "." - }; - await logChannel.SendMessageAsync(logMessage); - } - } - catch (Exception) - { - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Could not add <@" + parsedArg + "> to ticket, unknown error occured." - }; - await command.RespondAsync(message); - } + Color = DiscordColor.Green, + Description = member.Mention + " was added to " + command.Channel.Mention + + " by " + command.Member.Mention + "." + }); } } + catch (Exception) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Could not add " + member.Mention + " to ticket, unknown error occured." + }, true); + } } } \ No newline at end of file diff --git a/SupportChild/Commands/AddMessageCommand.cs b/SupportChild/Commands/AddMessageCommand.cs index a2e6606..6ffa491 100644 --- a/SupportChild/Commands/AddMessageCommand.cs +++ b/SupportChild/Commands/AddMessageCommand.cs @@ -1,76 +1,53 @@ -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using DSharpPlus.Entities; using System.Threading.Tasks; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class AddMessageCommand : ApplicationCommandModule { - public class AddMessageCommand : BaseCommandModule - { - [Command("addmessage")] - [Description("Adds a new message for the 'say' command.")] - public async Task OnExecute(CommandContext command, string identifier, [RemainingText] string message) - { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "addmessage")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the addmessage command but did not have permission."); - return; - } + [SlashRequireGuild] + [SlashCommand("addmessage", "Adds a new message for the 'say' command.")] + public async Task OnExecute(InteractionContext command, + [Option("Identifier", "The identifier word used in the /say command.")] string identifier, + [Option("Message", "The message the /say command will return.")] string message) + { + if (string.IsNullOrEmpty(message)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "No message specified." + }, true); + return; + } - if (string.IsNullOrEmpty(message)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "No message specified." - }; - await command.RespondAsync(error); - return; - } - - if (Database.TryGetMessage(identifier.ToLower(), out Database.Message _)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "There is already a message with that identifier." - }; - await command.RespondAsync(error); - return; - } + if (Database.TryGetMessage(identifier.ToLower(), out Database.Message _)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "There is already a message with that identifier." + }, true); + return; + } - if(Database.AddMessage(identifier, command.Member.Id, message)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Message added." - }; - await command.RespondAsync(error); - return; - } - else - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: Failed adding the message to the database." - }; - await command.RespondAsync(error); - return; - } - } - } + if (Database.AddMessage(identifier, command.Member.Id, message)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Message added." + }, true); + } + else + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: Failed adding the message to the database." + }, true); + } + } } \ No newline at end of file diff --git a/SupportChild/Commands/AddStaffCommand.cs b/SupportChild/Commands/AddStaffCommand.cs index 03f5948..f0e48ea 100644 --- a/SupportChild/Commands/AddStaffCommand.cs +++ b/SupportChild/Commands/AddStaffCommand.cs @@ -1,96 +1,67 @@ using System; -using System.Linq; using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; using MySql.Data.MySqlClient; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class AddStaffCommand : ApplicationCommandModule { - public class AddStaffCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("addstaff", "Adds a new staff member.")] + public async Task OnExecute(InteractionContext command, [Option("User", "User to add to staff.")] DiscordUser user) { - [Command("addstaff")] - [Cooldown(1, 5, CooldownBucketType.User)] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + DiscordMember staffMember = null; + try { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "addstaff")) + staffMember = user == null ? command.Member : await command.Guild.GetMemberAsync(user.Id); + + if (staffMember == null) { - DiscordEmbed error = new DiscordEmbedBuilder + await command.CreateResponseAsync(new DiscordEmbedBuilder { Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the addstaff command but did not have permission."); + Description = "Could not find that user in this server." + }, true); return; } - - ulong userID; - string[] parsedArgs = Utilities.ParseIDs(commandArgs); - - if (!parsedArgs.Any()) + } + catch (Exception) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder { - userID = command.Member.Id; - } - else if (!ulong.TryParse(parsedArgs[0], out userID)) + Color = DiscordColor.Red, + Description = "Could not find that user in this server." + }, true); + return; + } + + await using MySqlConnection c = Database.GetConnection(); + MySqlCommand cmd = Database.IsStaff(staffMember.Id) ? new MySqlCommand(@"UPDATE staff SET name = @name WHERE user_id = @user_id", c) : new MySqlCommand(@"INSERT INTO staff (user_id, name) VALUES (@user_id, @name);", c); + + c.Open(); + cmd.Parameters.AddWithValue("@user_id", staffMember.Id); + cmd.Parameters.AddWithValue("@name", staffMember.DisplayName); + cmd.ExecuteNonQuery(); + cmd.Dispose(); + + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = staffMember.Mention + " was added to staff." + }); + + // Log it if the log channel exists + DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + await logChannel.SendMessageAsync(new DiscordEmbedBuilder { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Invalid ID/Mention. (Could not convert to numerical)" - }; - await command.RespondAsync(error); - return; - } - - DiscordMember member; - try - { - member = await command.Guild.GetMemberAsync(userID); - } - catch (Exception) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Invalid ID/Mention. (Could not find user on this server)" - }; - await command.RespondAsync(error); - return; - } - - using (MySqlConnection c = Database.GetConnection()) - { - MySqlCommand cmd = Database.IsStaff(userID) ? new MySqlCommand(@"UPDATE staff SET name = @name WHERE user_id = @user_id", c) : new MySqlCommand(@"INSERT INTO staff (user_id, name) VALUES (@user_id, @name);", c); - - c.Open(); - cmd.Parameters.AddWithValue("@user_id", userID); - cmd.Parameters.AddWithValue("@name", member.DisplayName); - cmd.ExecuteNonQuery(); - cmd.Dispose(); - - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = member.Mention + " was added to staff." - }; - await command.RespondAsync(message); - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed logMessage = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = member.Mention + " was added to staff.\n", - }; - await logChannel.SendMessageAsync(logMessage); - } - } + Color = DiscordColor.Green, + Description = staffMember.Mention + " was added to staff.\n" + }); } } } \ No newline at end of file diff --git a/SupportChild/Commands/AdminCommands.cs b/SupportChild/Commands/AdminCommands.cs new file mode 100644 index 0000000..bf90847 --- /dev/null +++ b/SupportChild/Commands/AdminCommands.cs @@ -0,0 +1,200 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DSharpPlus.Entities; +using DSharpPlus.Interactivity; +using DSharpPlus.Interactivity.Extensions; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; + +namespace SupportChild.Commands; + +[SlashCommandGroup("admin", "Administrative commands.")] +public class AdminCommands : ApplicationCommandModule +{ + [SlashRequireGuild] + [SlashCommand("listinvalid", "List tickets which channels have been deleted. Use /admin unsetticket to remove them.")] + public async Task ListInvalid(InteractionContext command) + { + if (!Database.TryGetOpenTickets(out List openTickets)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + 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 allChannels = new List(); + foreach (KeyValuePair guild in SupportChild.discordClient.Guilds) + { + try + { + allChannels.AddRange(await guild.Value.GetChannelsAsync()); + } + catch (Exception) { /*ignored*/ } + } + + // Check which tickets channels no longer exist + List listItems = new List(); + 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) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "All tickets are valid!" + }, true); + return; + } + + List embeds = new List(); + 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 listPages = new List(); + foreach (DiscordEmbedBuilder embed in embeds) + { + listPages.Add(new Page("", embed)); + } + + await command.Interaction.SendPaginatedResponseAsync(true, command.User, listPages); + } + + [SlashRequireGuild] + [SlashCommand("setticket", "Turns a channel into a ticket WARNING: Anyone will be able to delete the channel using /close.")] + public async Task SetTicket(InteractionContext command, [Option("User", "(Optional) The owner of the ticket.")] DiscordUser user = null) + { + // Check if ticket exists in the database + if (Database.IsOpenTicket(command.Channel.Id)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + 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); + string ticketID = id.ToString("00000"); + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Channel has been designated ticket " + ticketID + "." + }); + + // Log it if the log channel exists + DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + await logChannel.SendMessageAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = command.Channel.Mention + " has been designated ticket " + ticketID + " by " + command.Member.Mention + "." + }); + } + } + + [SlashRequireGuild] + [SlashCommand("unsetticket", "Deletes a ticket from the ticket system without deleting the channel.")] + public async Task UnsetTicket(InteractionContext command, [Option("TicketID", "(Optional) Ticket to unset. Uses the channel you are in by default.")] long ticketID = 0) + { + Database.Ticket ticket; + + if (ticketID == 0) + { + // Check if ticket exists in the database + if (!Database.TryGetOpenTicket(command.Channel.Id, out ticket)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + 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)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "There is no ticket with this ticket ID." + }, true); + return; + } + } + + + if (Database.DeleteOpenTicket(ticket.id)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Channel has been undesignated as a ticket." + }); + + // Log it if the log channel exists + DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + await logChannel.SendMessageAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = command.Channel.Mention + " has been undesignated as a ticket by " + command.Member.Mention + "." + }); + } + } + else + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: Failed removing ticket from database." + }, true); + } + } + + [SlashCommand("reload", "Reloads the bot config.")] + public async Task Reload(InteractionContext command) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Reloading bot application..." + }); + Logger.Log("Reloading bot..."); + SupportChild.Reload(); + } +} \ No newline at end of file diff --git a/SupportChild/Commands/AssignCommand.cs b/SupportChild/Commands/AssignCommand.cs index a4660f6..171760a 100644 --- a/SupportChild/Commands/AssignCommand.cs +++ b/SupportChild/Commands/AssignCommand.cs @@ -1,135 +1,102 @@ -using System.Linq; +using System; using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; using DSharpPlus.Exceptions; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class AssignCommand : ApplicationCommandModule { - public class AssignCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("assign", "Assigns a staff member to this ticket.")] + public async Task OnExecute(InteractionContext command, [Option("User", "(Optional) User to assign to this ticket.")] DiscordUser user = null) { - [Command("assign")] - [Description("Assigns a staff member to a ticket.")] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + DiscordMember member = null; + try { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "assign")) + member = user == null ? command.Member : await command.Guild.GetMemberAsync(user.Id); + + if (member == null) { - DiscordEmbed error = new DiscordEmbedBuilder + await command.CreateResponseAsync(new DiscordEmbedBuilder { Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the assign command but did not have permission."); + Description = "Could not find that user in this server." + }, true); return; } - - // Check if ticket exists in the database - if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) + } + catch (Exception) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "This channel is not a ticket." - }; - await command.RespondAsync(error); - return; - } + Color = DiscordColor.Red, + Description = "Could not find that user in this server." + }, true); + return; + } - ulong staffID; - string[] parsedMessage = Utilities.ParseIDs(command.RawArgumentString); - - if (!parsedMessage.Any()) + // Check if ticket exists in the database + if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder { - staffID = command.Member.Id; - } - else if (!ulong.TryParse(parsedMessage[0], out staffID)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Invalid ID/Mention. (Could not convert to numerical)" - }; - await command.RespondAsync(error); - return; - } + Color = DiscordColor.Red, + Description = "This channel is not a ticket." + }, true); + return; + } - DiscordMember staffMember = null; + if (!Database.IsStaff(member.Id)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: User is not registered as staff." + }, true); + return; + } + + if (!Database.AssignStaff(ticket, member.Id)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: Failed to assign " + member.Mention + " to ticket." + }, true); + return; + } + + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Assigned " + member.Mention + " to ticket." + }); + + if (Config.assignmentNotifications) + { try { - staffMember = await command.Guild.GetMemberAsync(staffID); - } - catch (NotFoundException) { } - - if (staffMember == null) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: Could not find user." - }; - await command.RespondAsync(error); - return; - } - - if (!Database.IsStaff(staffMember.Id)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: User is not registered as staff." - }; - await command.RespondAsync(error); - return; - } - - if (!Database.AssignStaff(ticket, staffID)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: Failed to assign " + staffMember.Mention + " to ticket." - }; - await command.RespondAsync(error); - return; - } - - DiscordEmbed feedback = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Assigned " + staffMember.Mention + " to ticket." - }; - await command.RespondAsync(feedback); - - if (Config.assignmentNotifications) - { - try - { - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "You have been assigned to a support ticket: " + command.Channel.Mention - }; - await staffMember.SendMessageAsync(message); - } - catch (UnauthorizedException) {} - - } - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed logMessage = new DiscordEmbedBuilder + await member.SendMessageAsync(new DiscordEmbedBuilder { Color = DiscordColor.Green, - Description = staffMember.Mention + " was assigned to " + command.Channel.Mention + " by " + command.Member.Mention + "." - }; - await logChannel.SendMessageAsync(logMessage); + Description = "You have been assigned to a support ticket: " + command.Channel.Mention + }); } + catch (UnauthorizedException) { } + } + + // Log it if the log channel exists + DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + await logChannel.SendMessageAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = member.Mention + " was assigned to " + command.Channel.Mention + " by " + command.Member.Mention + "." + }); } } } \ No newline at end of file diff --git a/SupportChild/Commands/BlacklistCommand.cs b/SupportChild/Commands/BlacklistCommand.cs index dedf9ad..33a6262 100644 --- a/SupportChild/Commands/BlacklistCommand.cs +++ b/SupportChild/Commands/BlacklistCommand.cs @@ -1,99 +1,54 @@ using System; using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using DSharpPlus.Exceptions; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class BlacklistCommand : ApplicationCommandModule { - public class BlacklistCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("blacklist", "Blacklists a user from opening tickets.")] + public async Task OnExecute(InteractionContext command, [Option("User", "User to blacklist.")] DiscordUser user) { - [Command("blacklist")] - [Description("Blacklists a user from opening tickets.")] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + try { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "blacklist")) + if (!Database.Blacklist(user.Id, command.User.Id)) { - DiscordEmbed error = new DiscordEmbedBuilder + await command.CreateResponseAsync(new DiscordEmbedBuilder { Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the blacklist command but did not have permission."); + Description = user.Mention + " is already blacklisted." + }, true); return; } - string[] parsedArgs = Utilities.ParseIDs(command.RawArgumentString); - foreach (string parsedArg in parsedArgs) + await command.CreateResponseAsync(new DiscordEmbedBuilder { - if (ulong.TryParse(parsedArg, out ulong userId)) + Color = DiscordColor.Green, + Description = "Blacklisted " + user.Mention + "." + }, true); + + // Log it if the log channel exists + DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + await logChannel.SendMessageAsync(new DiscordEmbedBuilder { - DiscordUser blacklistedUser = null; - try - { - blacklistedUser = await command.Client.GetUserAsync(userId); - } - catch (NotFoundException) { } - - if (blacklistedUser == null) - { - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: Could not find user." - }; - await command.RespondAsync(message); - continue; - } - - try - { - if (!Database.Blacklist(blacklistedUser.Id, command.User.Id)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = blacklistedUser.Mention + " is already blacklisted." - }; - await command.RespondAsync(error); - continue; - } - - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Blacklisted " + blacklistedUser.Mention + "." - }; - await command.RespondAsync(message); - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed logMessage = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = blacklistedUser.Mention + " was blacklisted from opening tickets by " + command.Member.Mention + "." - }; - await logChannel.SendMessageAsync(logMessage); - } - } - catch (Exception) - { - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error occured while blacklisting " + blacklistedUser.Mention + "." - }; - await command.RespondAsync(message); - throw; - } - } + Color = DiscordColor.Green, + Description = user.Mention + " was blacklisted from opening tickets by " + command.Member.Mention + "." + }); } } + catch (Exception) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error occured while blacklisting " + user.Mention + "." + }, true); + throw; + } } } \ No newline at end of file diff --git a/SupportChild/Commands/CloseCommand.cs b/SupportChild/Commands/CloseCommand.cs index 51e5324..059d34f 100644 --- a/SupportChild/Commands/CloseCommand.cs +++ b/SupportChild/Commands/CloseCommand.cs @@ -2,117 +2,133 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; +using DSharpPlus; using DSharpPlus.Entities; using DSharpPlus.Exceptions; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class CloseCommand : ApplicationCommandModule { - public class CloseCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("close", "Closes a ticket.")] + public async Task OnExecute(InteractionContext command) { - [Command("close")] - [Cooldown(1, 5, CooldownBucketType.User)] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + // Check if ticket exists in the database + if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket _)) { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "close")) + await command.CreateResponseAsync(new DiscordEmbedBuilder { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the close command but did not have permission."); - return; - } + Color = DiscordColor.Red, + Description = "This channel is not a ticket." + }); + return; + } - ulong channelID = command.Channel.Id; - string channelName = command.Channel.Name; - - // Check if ticket exists in the database - if (!Database.TryGetOpenTicket(channelID, out Database.Ticket ticket)) + DiscordInteractionResponseBuilder confirmation = new DiscordInteractionResponseBuilder() + .AddEmbed(new DiscordEmbedBuilder { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "This channel is not a ticket." - }; - await command.RespondAsync(error); - return; - } + Color = DiscordColor.Cyan, + Description = "Are you sure you wish to close this ticket? You cannot re-open it again later." + }) + .AddComponents(new DiscordButtonComponent(ButtonStyle.Danger, "supportboi_closeconfirm", "Confirm")); + + + await command.CreateResponseAsync(confirmation); + } + + public static async Task OnConfirmed(DiscordInteraction interaction) + { + await interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate); + ulong channelID = interaction.Channel.Id; + string channelName = interaction.Channel.Name; + + // Check if ticket exists in the database + if (!Database.TryGetOpenTicket(channelID, out Database.Ticket ticket)) + { + await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "This channel is not a ticket." + })); + return; + } + + // Build transcript + try + { + await Transcriber.ExecuteAsync(interaction.Channel.Id, ticket.id); + } + catch (Exception e) + { + Logger.Error("Exception occured when trying to save transcript while closing ticket: " + e); + await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "ERROR: Could not save transcript file. Aborting..." + })); + return; + } + + // Log it if the log channel exists + DiscordChannel logChannel = interaction.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + DiscordEmbed embed = new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Ticket " + ticket.id.ToString("00000") + " closed by " + interaction.User.Mention + ".\n", + Footer = new DiscordEmbedBuilder.EmbedFooter { Text = '#' + channelName } + }; + + await using FileStream file = new FileStream(Transcriber.GetPath(ticket.id), FileMode.Open, FileAccess.Read); + DiscordMessageBuilder message = new DiscordMessageBuilder(); + message.WithEmbed(embed); + message.WithFiles(new Dictionary { { Transcriber.GetFilename(ticket.id), file } }); + + await logChannel.SendMessageAsync(message); + } + + if (Config.closingNotifications) + { + DiscordEmbed embed = new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Ticket " + ticket.id.ToString("00000") + " which you opened has now been closed, check the transcript for more info.\n", + Footer = new DiscordEmbedBuilder.EmbedFooter { Text = '#' + channelName } + }; - // Build transcript try { - await Transcriber.ExecuteAsync(command.Channel.Id, ticket.id); + DiscordMember staffMember = await interaction.Guild.GetMemberAsync(ticket.creatorID); + await using FileStream file = new FileStream(Transcriber.GetPath(ticket.id), FileMode.Open, FileAccess.Read); + + DiscordMessageBuilder message = new DiscordMessageBuilder(); + message.WithEmbed(embed); + message.WithFiles(new Dictionary { { Transcriber.GetFilename(ticket.id), file } }); + + await staffMember.SendMessageAsync(message); } - catch (Exception) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "ERROR: Could not save transcript file. Aborting..." - }; - await command.RespondAsync(error); - throw; - } - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed embed = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Ticket " + ticket.id.ToString("00000") + " closed by " + command.Member.Mention + ".\n", - Footer = new DiscordEmbedBuilder.EmbedFooter { Text = '#' + channelName } - }; - - using (FileStream file = new FileStream(Transcriber.GetPath(ticket.id), FileMode.Open, FileAccess.Read)) - { - DiscordMessageBuilder message = new DiscordMessageBuilder(); - message.WithEmbed(embed); - message.WithFiles(new Dictionary() { { Transcriber.GetFilename(ticket.id), file } }); - - await logChannel.SendMessageAsync(message); - } - } - - if (Config.closingNotifications) - { - DiscordEmbed embed = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Ticket " + ticket.id.ToString("00000") + " which you opened has now been closed, check the transcript for more info.\n", - Footer = new DiscordEmbedBuilder.EmbedFooter { Text = '#' + channelName } - }; - - try - { - DiscordMember staffMember = await command.Guild.GetMemberAsync(ticket.creatorID); - - using (FileStream file = new FileStream(Transcriber.GetPath(ticket.id), FileMode.Open, FileAccess.Read)) - { - DiscordMessageBuilder message = new DiscordMessageBuilder(); - message.WithEmbed(embed); - message.WithFiles(new Dictionary() { { Transcriber.GetFilename(ticket.id), file } }); - - await staffMember.SendMessageAsync(message); - } - } - catch (NotFoundException) { } - catch (UnauthorizedException) { } - } - - Database.ArchiveTicket(ticket); - - // Delete the channel and database entry - await command.Channel.DeleteAsync("Ticket closed."); - - Database.DeleteOpenTicket(ticket.id); + catch (NotFoundException) { } + catch (UnauthorizedException) { } } + + Database.ArchiveTicket(ticket); + + await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Channel will be deleted in 3 seconds..." + })); + + + await Task.Delay(3000); + + // Delete the channel and database entry + await interaction.Channel.DeleteAsync("Ticket closed."); + + Database.DeleteOpenTicket(ticket.id); } } \ No newline at end of file diff --git a/SupportChild/Commands/CreateButtonPanelCommand.cs b/SupportChild/Commands/CreateButtonPanelCommand.cs new file mode 100644 index 0000000..15ee739 --- /dev/null +++ b/SupportChild/Commands/CreateButtonPanelCommand.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; + +namespace SupportChild.Commands; + +public class CreateButtonPanelCommand : ApplicationCommandModule +{ + [SlashRequireGuild] + [SlashCommand("createbuttonpanel", "Creates a series of buttons which users can use to open new tickets in specific categories.")] + public async Task OnExecute(InteractionContext command) + { + DiscordMessageBuilder builder = new DiscordMessageBuilder().WithContent(" "); + List verifiedCategories = await Utilities.GetVerifiedChannels(); + + if (verifiedCategories.Count == 0) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: No registered categories found." + }, true); + return; + } + + verifiedCategories = verifiedCategories.OrderBy(x => x.name).ToList(); + + int nrOfButtons = 0; + for (int nrOfButtonRows = 0; nrOfButtonRows < 5 && nrOfButtons < verifiedCategories.Count; nrOfButtonRows++) + { + List buttonRow = new List(); + + for (; nrOfButtons < 5 * (nrOfButtonRows + 1) && nrOfButtons < verifiedCategories.Count; nrOfButtons++) + { + buttonRow.Add(new DiscordButtonComponent(ButtonStyle.Primary, "supportboi_newticketbutton " + verifiedCategories[nrOfButtons].id, verifiedCategories[nrOfButtons].name)); + } + builder.AddComponents(buttonRow); + } + + await command.Channel.SendMessageAsync(builder); + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Successfully created message, make sure to run this command again if you add new categories to the bot." + }, true); + } + + public static async Task OnButtonUsed(DiscordInteraction interaction) + { + await interaction.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral()); + + if (!ulong.TryParse(interaction.Data.CustomId.Replace("supportboi_newticketbutton ", ""), out ulong categoryID) || categoryID == 0) + { + Logger.Warn("Invalid ID: " + interaction.Data.CustomId.Replace("supportboi_newticketbutton ", "")); + return; + } + + (bool success, string message) = await NewCommand.OpenNewTicket(interaction.User.Id, interaction.ChannelId, categoryID); + + if (success) + { + await interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = message + })); + } + else + { + await interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = message + })); + } + } +} \ No newline at end of file diff --git a/SupportChild/Commands/CreateSelectionBoxPanelCommand.cs b/SupportChild/Commands/CreateSelectionBoxPanelCommand.cs new file mode 100644 index 0000000..395d1e5 --- /dev/null +++ b/SupportChild/Commands/CreateSelectionBoxPanelCommand.cs @@ -0,0 +1,79 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DSharpPlus; +using DSharpPlus.Entities; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; + +namespace SupportChild.Commands; + +public class CreateSelectionBoxPanelCommand : ApplicationCommandModule +{ + [SlashRequireGuild] + [SlashCommand("createselectionboxpanel", "Creates a selection box which users can use to open new tickets in specific categories.")] + public async Task OnExecute(InteractionContext command, [Option("Message", "(Optional) The message to show in the selection box.")] string message = null) + { + DiscordMessageBuilder builder = new DiscordMessageBuilder() + .WithContent(" ") + .AddComponents(await GetSelectComponents(command, message ?? "Open new ticket...")); + + await command.Channel.SendMessageAsync(builder); + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Successfully created message, make sure to run this command again if you add new categories to the bot." + }, true); + } + + public static async Task> GetSelectComponents(InteractionContext command, string placeholder) + { + List verifiedCategories = await Utilities.GetVerifiedChannels(); + + if (verifiedCategories.Count == 0) return new List(); + + verifiedCategories = verifiedCategories.OrderBy(x => x.name).ToList(); + List selectionComponents = new List(); + int selectionOptions = 0; + for (int selectionBoxes = 0; selectionBoxes < 5 && selectionOptions < verifiedCategories.Count; selectionBoxes++) + { + List categoryOptions = new List(); + + for (; selectionOptions < 25 * (selectionBoxes + 1) && selectionOptions < verifiedCategories.Count; selectionOptions++) + { + categoryOptions.Add(new DiscordSelectComponentOption(verifiedCategories[selectionOptions].name, verifiedCategories[selectionOptions].id.ToString())); + } + selectionComponents.Add(new DiscordSelectComponent("supportboi_newticketselector" + selectionBoxes, placeholder, categoryOptions, false, 0, 1)); + } + + return selectionComponents; + } + + public static async Task OnSelectionMenuUsed(DiscordInteraction interaction) + { + if (interaction.Data.Values == null || interaction.Data.Values.Length <= 0) return; + + if (!ulong.TryParse(interaction.Data.Values[0], out ulong categoryID) || categoryID == 0) return; + + await interaction.CreateResponseAsync(InteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral()); + + (bool success, string message) = await NewCommand.OpenNewTicket(interaction.User.Id, interaction.ChannelId, categoryID); + + if (success) + { + await interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = message + }).AsEphemeral()); + } + else + { + await interaction.CreateFollowupMessageAsync(new DiscordFollowupMessageBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = message + }).AsEphemeral()); + } + } +} \ No newline at end of file diff --git a/SupportChild/Commands/ListAssignedCommand.cs b/SupportChild/Commands/ListAssignedCommand.cs index 351df6c..b9ce36f 100644 --- a/SupportChild/Commands/ListAssignedCommand.cs +++ b/SupportChild/Commands/ListAssignedCommand.cs @@ -1,76 +1,63 @@ using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; +using DSharpPlus.Interactivity; +using DSharpPlus.Interactivity.Extensions; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class ListAssignedCommand : ApplicationCommandModule { - public class ListAssignedCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("listassigned", "Lists tickets assigned to a user.")] + public async Task OnExecute(InteractionContext command, [Option("User", "(Optional) User to list tickets for.")] DiscordUser user = null) { - [Command("listassigned")] - [Aliases("la")] - [Cooldown(1, 5, CooldownBucketType.User)] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + DiscordUser listUser = user == null ? command.User : user; + + if (!Database.TryGetAssignedTickets(listUser.Id, out List assignedTickets)) { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "listassigned")) + await command.CreateResponseAsync(new DiscordEmbedBuilder { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the listassigned command but did not have permission."); - return; - } - - ulong staffID; - string[] parsedIDs = Utilities.ParseIDs(command.RawArgumentString); - - if (!parsedIDs.Any()) - { - staffID = command.Member.Id; - } - else if (!ulong.TryParse(parsedIDs[0], out staffID)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Invalid ID/Mention. (Could not convert to numerical)" - }; - await command.RespondAsync(error); - return; - } - - if (!Database.TryGetAssignedTickets(staffID, out List assignedTickets)) - { - DiscordEmbed error = new DiscordEmbedBuilder() - .WithColor(DiscordColor.Red) - .WithDescription("User does not have any assigned tickets."); - await command.RespondAsync(error); - return; - } - - List listItems = new List(); - foreach (Database.Ticket ticket in assignedTickets) - { - listItems.Add("**" + ticket.FormattedCreatedTime() + ":** <#" + ticket.channelID + "> by <@" + ticket.creatorID + ">\n"); - } - - LinkedList messages = Utilities.ParseListIntoMessages(listItems); - foreach (string message in messages) - { - DiscordEmbed channelInfo = new DiscordEmbedBuilder() - .WithTitle("Assigned tickets: ") - .WithColor(DiscordColor.Green) - .WithDescription(message); - await command.RespondAsync(channelInfo); - } - + Color = DiscordColor.Red, + Description = "User does not have any assigned tickets." + }); + return; } + + List listItems = new List(); + foreach (Database.Ticket ticket in assignedTickets) + { + listItems.Add("**" + ticket.DiscordRelativeTime() + ":** <#" + ticket.channelID + "> by <@" + ticket.creatorID + ">\n"); + } + + List embeds = new List(); + foreach (string message in Utilities.ParseListIntoMessages(listItems)) + { + embeds.Add(new DiscordEmbedBuilder + { + Title = "Assigned tickets: ", + Color = DiscordColor.Green, + 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 listPages = new List(); + foreach (DiscordEmbedBuilder embed in embeds) + { + listPages.Add(new Page("", embed)); + } + + await command.Interaction.SendPaginatedResponseAsync(true, command.User, listPages); } } \ No newline at end of file diff --git a/SupportChild/Commands/ListCommand.cs b/SupportChild/Commands/ListCommand.cs index 3916ed4..e2811b5 100644 --- a/SupportChild/Commands/ListCommand.cs +++ b/SupportChild/Commands/ListCommand.cs @@ -1,101 +1,99 @@ using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; +using DSharpPlus.Interactivity; +using DSharpPlus.Interactivity.Extensions; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class ListCommand : ApplicationCommandModule { - public class ListCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("list", "Lists tickets opened by a user.")] + public async Task OnExecute(InteractionContext command, [Option("User", "(Optional) The user to get tickets by.")] DiscordUser user = null) { - [Command("list")] - [Cooldown(1, 5, CooldownBucketType.User)] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + DiscordUser listUser = user == null ? command.User : user; + + List openEmbeds = new List(); + if (Database.TryGetOpenTickets(listUser.Id, out List openTickets)) { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "list")) + List listItems = new List(); + foreach (Database.Ticket ticket in openTickets) { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the list command but did not have permission."); - return; + listItems.Add("**" + ticket.DiscordRelativeTime() + ":** <#" + ticket.channelID + ">\n"); } - ulong userID; - string[] parsedMessage = Utilities.ParseIDs(command.RawArgumentString); - - if (!parsedMessage.Any()) + foreach (string message in Utilities.ParseListIntoMessages(listItems)) { - userID = command.Member.Id; - } - else if (!ulong.TryParse(parsedMessage[0], out userID)) - { - DiscordEmbed error = new DiscordEmbedBuilder + openEmbeds.Add(new DiscordEmbedBuilder { - Color = DiscordColor.Red, - Description = "Invalid ID/Mention. (Could not convert to numerical)" - }; - await command.RespondAsync(error); - return; + Color = DiscordColor.Green, + Description = message + }); } - if (Database.TryGetOpenTickets(userID, out List openTickets)) + // Add the titles + for (int i = 0; i < openEmbeds.Count; i++) { - List listItems = new List(); - foreach (Database.Ticket ticket in openTickets) - { - listItems.Add("**" + ticket.FormattedCreatedTime() + ":** <#" + ticket.channelID + ">\n"); - } - - LinkedList messages = Utilities.ParseListIntoMessages(listItems); - foreach (string message in messages) - { - DiscordEmbed channelInfo = new DiscordEmbedBuilder() - .WithTitle("Open tickets: ") - .WithColor(DiscordColor.Green) - .WithDescription(message); - await command.RespondAsync(channelInfo); - } - } - else - { - DiscordEmbed channelInfo = new DiscordEmbedBuilder() - .WithColor(DiscordColor.Green) - .WithDescription("User does not have any open tickets."); - await command.RespondAsync(channelInfo); - } - - if (Database.TryGetClosedTickets(userID, out List closedTickets)) - { - List listItems = new List(); - foreach (Database.Ticket ticket in closedTickets) - { - listItems.Add("**" + ticket.FormattedCreatedTime() + ":** Ticket " + ticket.id.ToString("00000") + "\n"); - } - - LinkedList messages = Utilities.ParseListIntoMessages(listItems); - foreach (string message in messages) - { - DiscordEmbed channelInfo = new DiscordEmbedBuilder() - .WithTitle("Closed tickets: ") - .WithColor(DiscordColor.Red) - .WithDescription(message); - await command.RespondAsync(channelInfo); - } - } - else - { - DiscordEmbed channelInfo = new DiscordEmbedBuilder() - .WithColor(DiscordColor.Red) - .WithDescription("User does not have any closed tickets."); - await command.RespondAsync(channelInfo); + openEmbeds[i].Title = $"Open tickets ({i + 1}/{openEmbeds.Count})"; } } + + List closedEmbeds = new List(); + if (Database.TryGetClosedTickets(listUser.Id, out List closedTickets)) + { + List listItems = new List(); + foreach (Database.Ticket ticket in closedTickets) + { + listItems.Add("**" + ticket.DiscordRelativeTime() + ":** Ticket " + ticket.id.ToString("00000") + "\n"); + } + + foreach (string message in Utilities.ParseListIntoMessages(listItems)) + { + closedEmbeds.Add(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = message + }); + } + + // Add the titles + for (int i = 0; i < closedEmbeds.Count; i++) + { + closedEmbeds[i].Title = $"Closed tickets ({i + 1}/{closedEmbeds.Count})"; + } + } + + // Merge the embed lists and add the footers + List embeds = new List(); + embeds.AddRange(openEmbeds); + embeds.AddRange(closedEmbeds); + for (int i = 0; i < embeds.Count; i++) + { + embeds[i].Footer = new DiscordEmbedBuilder.EmbedFooter + { + Text = $"Page {i + 1} / {embeds.Count}" + }; + } + + if (embeds.Count == 0) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Cyan, + Description = "User does not have any open or closed tickets." + }); + return; + } + + List listPages = new List(); + foreach (DiscordEmbedBuilder embed in embeds) + { + listPages.Add(new Page("", embed)); + } + + await command.Interaction.SendPaginatedResponseAsync(true, command.User, listPages); } } \ No newline at end of file diff --git a/SupportChild/Commands/ListOldestCommand.cs b/SupportChild/Commands/ListOldestCommand.cs deleted file mode 100644 index 394b8d0..0000000 --- a/SupportChild/Commands/ListOldestCommand.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; - -namespace SupportChild.Commands -{ - public class ListOldestCommand : BaseCommandModule - { - [Command("listoldest")] - [Aliases("lo")] - [Cooldown(1, 5, CooldownBucketType.User)] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) - { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "listoldest")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the listoldest command but did not have permission."); - return; - } - - int listLimit = 20; - if (!string.IsNullOrEmpty(command.RawArgumentString?.Trim() ?? "")) - { - if (!int.TryParse(command.RawArgumentString?.Trim(), out listLimit) || listLimit < 5 || listLimit > 100) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Invalid list amount. (Must be integer between 5 and 100)" - }; - await command.RespondAsync(error); - return; - } - } - - if (!Database.TryGetOldestTickets(command.Member.Id, out List openTickets, listLimit)) - { - DiscordEmbed channelInfo = new DiscordEmbedBuilder() - .WithColor(DiscordColor.Red) - .WithDescription("Could not fetch any open tickets."); - await command.RespondAsync(channelInfo); - return; - } - - List listItems = new List(); - foreach (Database.Ticket ticket in openTickets) - { - listItems.Add("**" + ticket.FormattedCreatedTime() + ":** <#" + ticket.channelID + "> by <@" + ticket.creatorID + ">\n"); - } - - LinkedList messages = Utilities.ParseListIntoMessages(listItems); - foreach (string message in messages) - { - DiscordEmbed channelInfo = new DiscordEmbedBuilder() - .WithTitle("The " + openTickets.Count + " oldest open tickets: ") - .WithColor(DiscordColor.Green) - .WithDescription(message?.Trim()); - await command.RespondAsync(channelInfo); - } - } - } -} \ No newline at end of file diff --git a/SupportChild/Commands/ListOpen.cs b/SupportChild/Commands/ListOpen.cs new file mode 100644 index 0000000..7775e17 --- /dev/null +++ b/SupportChild/Commands/ListOpen.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using DSharpPlus.Entities; +using DSharpPlus.Interactivity; +using DSharpPlus.Interactivity.Extensions; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; + +namespace SupportChild.Commands; + +public class ListOpen : ApplicationCommandModule +{ + [SlashRequireGuild] + [SlashCommand("listopen", "Lists all open tickets, oldest first.")] + public async Task OnExecute(InteractionContext command) + { + if (!Database.TryGetOpenTickets(out List openTickets)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Could not fetch any open tickets." + }); + return; + } + + List listItems = new List(); + foreach (Database.Ticket ticket in openTickets) + { + listItems.Add("**" + ticket.DiscordRelativeTime() + ":** <#" + ticket.channelID + "> by <@" + ticket.creatorID + ">\n"); + } + + List embeds = new List(); + foreach (string message in Utilities.ParseListIntoMessages(listItems)) + { + embeds.Add(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + 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 listPages = new List(); + foreach (DiscordEmbedBuilder embed in embeds) + { + listPages.Add(new Page("", embed)); + } + + await command.Interaction.SendPaginatedResponseAsync(true, command.User, listPages); + } +} \ No newline at end of file diff --git a/SupportChild/Commands/ListUnassignedCommand.cs b/SupportChild/Commands/ListUnassignedCommand.cs index 19236cf..bb0bba7 100644 --- a/SupportChild/Commands/ListUnassignedCommand.cs +++ b/SupportChild/Commands/ListUnassignedCommand.cs @@ -1,55 +1,61 @@ using System.Collections.Generic; using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; +using DSharpPlus.Interactivity; +using DSharpPlus.Interactivity.Extensions; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class ListUnassignedCommand : ApplicationCommandModule { - public class ListUnassignedCommand : BaseCommandModule - { - [Command("listunassigned")] - [Aliases("lu")] - [Cooldown(1, 5, CooldownBucketType.User)] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) - { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "listunassigned")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the listunassigned command but did not have permission."); - return; - } + [SlashRequireGuild] + [SlashCommand("listunassigned", "Lists unassigned tickets.")] + public async Task OnExecute(InteractionContext command) + { + if (!Database.TryGetAssignedTickets(0, out List unassignedTickets)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "There are no unassigned tickets." + }); + return; + } - if (!Database.TryGetAssignedTickets(0, out List unassignedTickets)) - { - DiscordEmbed response = new DiscordEmbedBuilder() - .WithColor(DiscordColor.Green) - .WithDescription("There are no unassigned tickets."); - await command.RespondAsync(response); - } + List listItems = new List(); + foreach (Database.Ticket ticket in unassignedTickets) + { + listItems.Add("**" + ticket.DiscordRelativeTime() + ":** <#" + ticket.channelID + "> by <@" + ticket.creatorID + ">\n"); + } - List listItems = new List(); - foreach (Database.Ticket ticket in unassignedTickets) - { - listItems.Add("**" + ticket.FormattedCreatedTime() + ":** <#" + ticket.channelID + "> by <@" + ticket.creatorID + ">\n"); - } + List embeds = new List(); + foreach (string message in Utilities.ParseListIntoMessages(listItems)) + { + embeds.Add(new DiscordEmbedBuilder + { + Title = "Unassigned tickets: ", + Color = DiscordColor.Green, + Description = message + }); + } - LinkedList messages = Utilities.ParseListIntoMessages(listItems); - foreach (string message in messages) - { - DiscordEmbed channelInfo = new DiscordEmbedBuilder() - .WithTitle("Unassigned tickets: ") - .WithColor(DiscordColor.Green) - .WithDescription(message?.Trim()); - await command.RespondAsync(channelInfo); - } - } - } + // Add the footers + for (int i = 0; i < embeds.Count; i++) + { + embeds[i].Footer = new DiscordEmbedBuilder.EmbedFooter + { + Text = $"Page {i + 1} / {embeds.Count}" + }; + } + + List listPages = new List(); + foreach (DiscordEmbedBuilder embed in embeds) + { + listPages.Add(new Page("", embed)); + } + + await command.Interaction.SendPaginatedResponseAsync(true, command.User, listPages); + } } \ No newline at end of file diff --git a/SupportChild/Commands/MoveCommand.cs b/SupportChild/Commands/MoveCommand.cs index 4ef0737..e81cf70 100644 --- a/SupportChild/Commands/MoveCommand.cs +++ b/SupportChild/Commands/MoveCommand.cs @@ -2,103 +2,82 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; using DSharpPlus.Exceptions; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class MoveCommand : ApplicationCommandModule { - public class MoveCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("move", "Moves a ticket to another category.")] + public async Task OnExecute(InteractionContext command, [Option("Category", "The category to move the ticket to. Only has to be the beginning of the name.")] string category) { - [Command("move")] - [Description("Moves a ticket to another category.")] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + // Check if ticket exists in the database + if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket _)) { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "move")) + await command.CreateResponseAsync(new DiscordEmbedBuilder { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the move command but did not have permission."); - return; - } - - // Check if ticket exists in the database - if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "This channel is not a ticket." - }; - await command.RespondAsync(error); - return; - } - - if (string.IsNullOrEmpty(command.RawArgumentString)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: No category provided." - }; - await command.RespondAsync(error); - return; - } - - IReadOnlyList channels = await command.Guild.GetChannelsAsync(); - IEnumerable categories = channels.Where(x => x.IsCategory); - DiscordChannel category = categories.FirstOrDefault(x => x.Name.StartsWith(command.RawArgumentString.Trim(), StringComparison.OrdinalIgnoreCase)); - - if (category == null) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: Could not find a category by that name." - }; - await command.RespondAsync(error); - return; - } - - if (command.Channel.Id == category.Id) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: The ticket is already in that category." - }; - await command.RespondAsync(error); - return; - } - - try - { - await command.Channel.ModifyAsync(modifiedAttributes => modifiedAttributes.Parent = category); - } - catch (UnauthorizedException) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: Not authorized to move this ticket to that category." - }; - await command.RespondAsync(error); - return; - } - - DiscordEmbed feedback = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Ticket was moved to " + category.Mention - }; - await command.RespondAsync(feedback); + Color = DiscordColor.Red, + Description = "This channel is not a ticket." + }, true); + return; } + + if (string.IsNullOrEmpty(category)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: No category provided." + }, true); + return; + } + + IReadOnlyList channels = await command.Guild.GetChannelsAsync(); + IEnumerable categories = channels.Where(x => x.IsCategory); + DiscordChannel categoryChannel = categories.FirstOrDefault(x => x.Name.StartsWith(category.Trim(), StringComparison.OrdinalIgnoreCase)); + + if (categoryChannel == null) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: Could not find a category by that name." + }, true); + return; + } + + if (command.Channel.Id == categoryChannel.Id) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: The ticket is already in that category." + }, true); + return; + } + + try + { + await command.Channel.ModifyAsync(modifiedAttributes => modifiedAttributes.Parent = categoryChannel); + } + catch (UnauthorizedException) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: Not authorized to move this ticket to that category." + }, true); + return; + } + + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Ticket was moved to " + categoryChannel.Mention + }); } } \ No newline at end of file diff --git a/SupportChild/Commands/NewCommand.cs b/SupportChild/Commands/NewCommand.cs index 1345a00..0b77184 100644 --- a/SupportChild/Commands/NewCommand.cs +++ b/SupportChild/Commands/NewCommand.cs @@ -1,150 +1,276 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using DSharpPlus; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; using DSharpPlus.Exceptions; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class NewCommand : ApplicationCommandModule { - public class NewCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("new", "Opens a new ticket.")] + public async Task OnExecute(InteractionContext command) { - [Command("new")] - [Cooldown(1, 5, CooldownBucketType.User)] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + List verifiedCategories = await Utilities.GetVerifiedChannels(); + switch (verifiedCategories.Count) { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "new")) - { - DiscordEmbed error = new DiscordEmbedBuilder + case 0: + await command.CreateResponseAsync(new DiscordEmbedBuilder { Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the new command but did not have permission."); + Description = "Error: No registered categories found." + }, true); return; - } + case 1: + await command.DeferAsync(true); + (bool success, string message) = await OpenNewTicket(command.User.Id, command.Channel.Id, verifiedCategories[0].id); - // Check if user is blacklisted - if (Database.IsBlacklisted(command.User.Id)) - { - DiscordEmbed error = new DiscordEmbedBuilder + if (success) { - Color = DiscordColor.Red, - Description = "You are banned from opening tickets." - }; - await command.RespondAsync(error); - return; - } - - if (Database.IsOpenTicket(command.Channel.Id)) - { - DiscordEmbed error = new DiscordEmbedBuilder + await command.FollowUpAsync(new DiscordFollowupMessageBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = message + }).AsEphemeral()); + } + else { - Color = DiscordColor.Red, - Description = "You cannot use this command in a ticket channel." - }; - await command.RespondAsync(error); + await command.FollowUpAsync(new DiscordFollowupMessageBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = message + }).AsEphemeral()); + } return; - } - - DiscordChannel category = command.Guild.GetChannel(Config.ticketCategory); - DiscordChannel ticketChannel; - - try - { - ticketChannel = await command.Guild.CreateChannelAsync("ticket", ChannelType.Text, category); - } - catch (Exception) - { - DiscordEmbed error = new DiscordEmbedBuilder + default: + if (Config.newCommandUsesSelector) { - Color = DiscordColor.Red, - Description = "Error occured while creating ticket, " + command.Member.Mention + - "!\nIs the channel limit reached in the server or ticket category?" - }; - await command.RespondAsync(error); - return; - } - - if (ticketChannel == null) - { - DiscordEmbed error = new DiscordEmbedBuilder + await CreateSelector(command, verifiedCategories); + } + else { - Color = DiscordColor.Red, - Description = "Error occured while creating ticket, " + command.Member.Mention + - "!\nIs the channel limit reached in the server or ticket category?" - }; - await command.RespondAsync(error); + await CreateButtons(command, verifiedCategories); + } return; - } + } + } - ulong staffID = 0; - if (Config.randomAssignment) + public static async Task CreateButtons(InteractionContext command, List verifiedCategories) + { + DiscordInteractionResponseBuilder builder = new DiscordInteractionResponseBuilder().WithContent(" "); + int nrOfButtons = 0; + for (int nrOfButtonRows = 0; nrOfButtonRows < 5 && nrOfButtons < verifiedCategories.Count; nrOfButtonRows++) + { + List buttonRow = new List(); + + for (; nrOfButtons < 5 * (nrOfButtonRows + 1) && nrOfButtons < verifiedCategories.Count; nrOfButtons++) { - staffID = Database.GetRandomActiveStaff(0)?.userID ?? 0; + buttonRow.Add(new DiscordButtonComponent(ButtonStyle.Primary, "supportboi_newcommandbutton " + verifiedCategories[nrOfButtons].id, verifiedCategories[nrOfButtons].name)); } + builder.AddComponents(buttonRow); + } - long id = Database.NewTicket(command.Member.Id, staffID, ticketChannel.Id); - string ticketID = id.ToString("00000"); + await command.CreateResponseAsync(builder.AsEphemeral()); + } + + public static async Task CreateSelector(InteractionContext command, List verifiedCategories) + { + verifiedCategories = verifiedCategories.OrderBy(x => x.name).ToList(); + List selectionComponents = new List(); + int selectionOptions = 0; + for (int selectionBoxes = 0; selectionBoxes < 5 && selectionOptions < verifiedCategories.Count; selectionBoxes++) + { + List categoryOptions = new List(); + + for (; selectionOptions < 25 * (selectionBoxes + 1) && selectionOptions < verifiedCategories.Count; selectionOptions++) + { + categoryOptions.Add(new DiscordSelectComponentOption(verifiedCategories[selectionOptions].name, verifiedCategories[selectionOptions].id.ToString())); + } + selectionComponents.Add(new DiscordSelectComponent("supportboi_newcommandselector" + selectionBoxes, "Open new ticket...", categoryOptions, false, 0, 1)); + } + + await command.CreateResponseAsync(new DiscordInteractionResponseBuilder().AddComponents(selectionComponents).AsEphemeral()); + } + + public static async Task OnCategorySelection(DiscordInteraction interaction) + { + string stringID; + switch (interaction.Data.ComponentType) + { + case ComponentType.Button: + stringID = interaction.Data.CustomId.Replace("supportboi_newcommandbutton ", ""); + break; + case ComponentType.Select: + if (interaction.Data.Values == null || interaction.Data.Values.Length <= 0) return; + stringID = interaction.Data.Values[0]; + break; + + case ComponentType.ActionRow: + case ComponentType.FormInput: + default: + return; + } + + if (!ulong.TryParse(stringID, out ulong categoryID) || categoryID == 0) return; + + await interaction.CreateResponseAsync(InteractionResponseType.DeferredMessageUpdate, new DiscordInteractionResponseBuilder().AsEphemeral()); + + (bool success, string message) = await OpenNewTicket(interaction.User.Id, interaction.ChannelId, categoryID); + + if (success) + { + await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = message + })); + } + else + { + await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = message + })); + } + } + + public static async Task<(bool, string)> OpenNewTicket(ulong userID, ulong commandChannelID, ulong categoryID) + { + // Check if user is blacklisted + if (Database.IsBlacklisted(userID)) + { + return (false, "You are banned from opening tickets."); + } + + if (Database.IsOpenTicket(commandChannelID)) + { + return (false, "You cannot use this command in a ticket channel."); + } + + if (!Database.IsStaff(userID) && Database.TryGetOpenTickets(userID, out List ownTickets) && ownTickets.Count >= Config.ticketLimit) + { + return (false, "You have reached the limit for maximum open tickets."); + } + + DiscordChannel category = null; + try + { + category = await SupportChild.discordClient.GetChannelAsync(categoryID); + } + catch (Exception) { /*ignored*/ } + + if (category == null) + { + return (false, "Error: Could not find the category to place the ticket in."); + } + + DiscordMember member = null; + try + { + member = await category.Guild.GetMemberAsync(userID); + } + catch (Exception) { /*ignored*/ } + + if (member == null) + { + return (false, "Error: Could not find you on the Discord server."); + } + + DiscordChannel ticketChannel = null; + + try + { + ticketChannel = await category.Guild.CreateChannelAsync("ticket", ChannelType.Text, category); + } + catch (Exception) { /* ignored */ } + + if (ticketChannel == null) + { + return (false, "Error occured while creating ticket, " + member.Mention + + "!\nIs the channel limit reached in the server or ticket category?"); + } + + ulong staffID = 0; + if (Config.randomAssignment) + { + staffID = Database.GetRandomActiveStaff(0)?.userID ?? 0; + } + + long id = Database.NewTicket(member.Id, staffID, ticketChannel.Id); + string ticketID = id.ToString("00000"); + + try + { await ticketChannel.ModifyAsync(modifiedAttributes => modifiedAttributes.Name = "ticket-" + ticketID); - await ticketChannel.AddOverwriteAsync(command.Member, Permissions.AccessChannels, Permissions.None); + } + catch (DiscordException e) + { + Logger.Error("Exception occurred trying to modify channel: " + e); + Logger.Error("JsomMessage: " + e.JsonMessage); + } - await ticketChannel.SendMessageAsync("Hello, " + command.Member.Mention + "!\n" + Config.welcomeMessage); + try + { + await ticketChannel.AddOverwriteAsync(member, Permissions.AccessChannels); + } + catch (DiscordException e) + { + Logger.Error("Exception occurred trying to add channel permissions: " + e); + Logger.Error("JsomMessage: " + e.JsonMessage); + } - // Refreshes the channel as changes were made to it above - ticketChannel = command.Guild.GetChannel(ticketChannel.Id); + await ticketChannel.SendMessageAsync("Hello, " + member.Mention + "!\n" + Config.welcomeMessage); - if (staffID != 0) + // Refreshes the channel as changes were made to it above + ticketChannel = await SupportChild.discordClient.GetChannelAsync(ticketChannel.Id); + + if (staffID != 0) + { + await ticketChannel.SendMessageAsync(new DiscordEmbedBuilder { - DiscordEmbed assignmentMessage = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Ticket was randomly assigned to <@" + staffID + ">." - }; - await ticketChannel.SendMessageAsync(assignmentMessage); + Color = DiscordColor.Green, + Description = "Ticket was randomly assigned to <@" + staffID + ">." + }); - if (Config.assignmentNotifications) + if (Config.assignmentNotifications) + { + try { - DiscordEmbed message = new DiscordEmbedBuilder + DiscordMember staffMember = await category.Guild.GetMemberAsync(staffID); + await staffMember.SendMessageAsync(new DiscordEmbedBuilder { Color = DiscordColor.Green, Description = "You have been randomly assigned to a newly opened support ticket: " + - ticketChannel.Mention - }; - - try - { - DiscordMember staffMember = await command.Guild.GetMemberAsync(staffID); - await staffMember.SendMessageAsync(message); - } - catch (NotFoundException) {} - catch (UnauthorizedException) {} + ticketChannel.Mention + }); + } + catch (DiscordException e) + { + Logger.Error("Exception occurred assign random staff member: " + e); + Logger.Error("JsomMessage: " + e.JsonMessage); } } + } - DiscordEmbed response = new DiscordEmbedBuilder + // Log it if the log channel exists + DiscordChannel logChannel = category.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + DiscordEmbed logMessage = new DiscordEmbedBuilder { Color = DiscordColor.Green, - Description = "Ticket opened, " + command.Member.Mention + "!\n" + ticketChannel.Mention + Description = "Ticket " + ticketChannel.Mention + " opened by " + member.Mention + ".\n", + Footer = new DiscordEmbedBuilder.EmbedFooter { Text = "Ticket " + ticketID } }; - await command.RespondAsync(response); - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed logMessage = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Ticket " + ticketChannel.Mention + " opened by " + command.Member.Mention + ".\n", - Footer = new DiscordEmbedBuilder.EmbedFooter {Text = "Ticket " + ticketID} - }; - await logChannel.SendMessageAsync(logMessage); - } + await logChannel.SendMessageAsync(logMessage); } + + return (true, "Ticket opened, " + member.Mention + "!\n" + ticketChannel.Mention); } } \ No newline at end of file diff --git a/SupportChild/Commands/RandomAssignCommand.cs b/SupportChild/Commands/RandomAssignCommand.cs index 7b2a3e3..36d633d 100644 --- a/SupportChild/Commands/RandomAssignCommand.cs +++ b/SupportChild/Commands/RandomAssignCommand.cs @@ -2,181 +2,138 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; using DSharpPlus.Exceptions; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; using Microsoft.Extensions.Logging; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class RandomAssignCommand : ApplicationCommandModule { - public class RandomAssignCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("rassign", "Randomly assigns a staff member to a ticket.")] + public async Task OnExecute(InteractionContext command, [Option("Role", "(Optional) Limit the random assignment to a specific role.")] DiscordRole role = null) { - [Command("rassign")] - [Description("Randomly assigns a staff member to a ticket.")] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArguments) + // Check if ticket exists in the database + if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "rassign")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the rassign command but did not have permission."); - return; - } - - // Check if ticket exists in the database - if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "This channel is not a ticket." - }; - await command.RespondAsync(error); - return; - } - - // Get a random staff member who is verified to have the correct role if applicable - DiscordMember staffMember = await GetRandomVerifiedStaffMember(command, ticket); - if (staffMember == null) - { - return; - } - - // Attempt to assign the staff member to the ticket - if (!Database.AssignStaff(ticket, staffMember.Id)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: Failed to assign " + staffMember.Mention + " to ticket." - }; - await command.RespondAsync(error); - return; - } - - // Respond that the command was successful - DiscordEmbed feedback = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Randomly assigned " + staffMember.Mention + " to ticket." - }; - await command.RespondAsync(feedback); - - // Send a notification to the staff member if applicable - if (Config.assignmentNotifications) - { - try - { - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "You have been randomly assigned to a support ticket: " + command.Channel.Mention - }; - await staffMember.SendMessageAsync(message); - } - catch (UnauthorizedException) {} - } - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed logMessage = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = staffMember.Mention + " was assigned to " + command.Channel.Mention + " by " + command.Member.Mention + "." - }; - await logChannel.SendMessageAsync(logMessage); - } - } - - private async Task GetRandomVerifiedStaffMember(CommandContext command, Database.Ticket ticket) - { - if (command.RawArguments.Any()) // An argument was provided, check if this can be parsed into a role - { - ulong roleID = 0; - - // Try to parse either discord mention or ID - string[] parsedMessage = Utilities.ParseIDs(command.RawArgumentString); - if (!ulong.TryParse(parsedMessage[0], out roleID)) - { - // Try to find role by name - roleID = Utilities.GetRoleByName(command.Guild, command.RawArgumentString)?.Id ?? 0; - } - - // Check if a role was found - if (roleID == 0) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Could not find a role by that name/ID." - }; - await command.RespondAsync(error); - return null; - } - - // Check if role rassign should override staff's active status - List staffMembers = Config.randomAssignRoleOverride - ? Database.GetAllStaff(ticket.assignedStaffID) - : Database.GetActiveStaff(ticket.assignedStaffID); - - // Randomize the list before checking for roles in order to reduce number of API calls - staffMembers = Utilities.RandomizeList(staffMembers); - - // Get the first staff member that has the role - foreach (Database.StaffMember sm in staffMembers) - { - try - { - DiscordMember verifiedMember = await command.Guild.GetMemberAsync(sm.userID); - if (verifiedMember?.Roles?.Any(role => role.Id == roleID) ?? false) - { - return verifiedMember; - } - } - catch (Exception e) - { - command.Client.Logger.Log(LogLevel.Information, e, "Error occured trying to find a staff member in the rassign command."); - } - } - } - else // No role was specified, any active staff will be picked - { - Database.StaffMember staffEntry = Database.GetRandomActiveStaff(ticket.assignedStaffID); - if (staffEntry == null) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: There are no other staff members to choose from." - }; - await command.RespondAsync(error); - return null; - } - - // Get the staff member from discord - try - { - return await command.Guild.GetMemberAsync(staffEntry.userID); - } - catch (NotFoundException) { } - } - - // Send a more generic error if we get to this point and still haven't found the staff member - DiscordEmbed err = new DiscordEmbedBuilder + await command.CreateResponseAsync(new DiscordEmbedBuilder { Color = DiscordColor.Red, - Description = "Error: Could not find an applicable staff member." - }; - await command.RespondAsync(err); - return null; + Description = "Error: This channel is not a ticket." + }, true); + return; + } + + // Get a random staff member who is verified to have the correct role if applicable + DiscordMember staffMember = await GetRandomVerifiedStaffMember(command, role, ticket); + if (staffMember == null) + { + return; + } + + // Attempt to assign the staff member to the ticket + if (!Database.AssignStaff(ticket, staffMember.Id)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: Failed to assign " + staffMember.Mention + " to ticket." + }, true); + return; + } + + // Respond that the command was successful + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Randomly assigned " + staffMember.Mention + " to ticket." + }); + + // Send a notification to the staff member if applicable + if (Config.assignmentNotifications) + { + try + { + await staffMember.SendMessageAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "You have been randomly assigned to a support ticket: " + command.Channel.Mention + }); + } + catch (UnauthorizedException) { } + } + + // Log it if the log channel exists + DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + await logChannel.SendMessageAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = staffMember.Mention + " was randomly assigned to " + command.Channel.Mention + " by " + command.Member.Mention + "." + }); } } + + private static async Task GetRandomVerifiedStaffMember(InteractionContext command, DiscordRole targetRole, Database.Ticket ticket) + { + if (targetRole != null) // A role was provided + { + // Check if role rassign should override staff's active status + List staffMembers = Config.randomAssignRoleOverride + ? Database.GetAllStaff(ticket.assignedStaffID, ticket.creatorID) + : Database.GetActiveStaff(ticket.assignedStaffID, ticket.creatorID); + + // Randomize the list before checking for roles in order to reduce number of API calls + staffMembers.Shuffle(); + + // Get the first staff member that has the role + foreach (Database.StaffMember sm in staffMembers) + { + try + { + DiscordMember verifiedMember = await command.Guild.GetMemberAsync(sm.userID); + if (verifiedMember?.Roles?.Any(role => role.Id == targetRole.Id) ?? false) + { + return verifiedMember; + } + } + catch (Exception e) + { + command.Client.Logger.Log(LogLevel.Information, e, "Error occured trying to find a staff member in the rassign command."); + } + } + } + else // No role was specified, any active staff will be picked + { + Database.StaffMember staffEntry = Database.GetRandomActiveStaff(ticket.assignedStaffID, ticket.creatorID); + if (staffEntry == null) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: There are no other staff members to choose from." + }, true); + return null; + } + + // Get the staff member from discord + try + { + return await command.Guild.GetMemberAsync(staffEntry.userID); + } + catch (NotFoundException) { } + } + + // Send a more generic error if we get to this point and still haven't found the staff member + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: Could not find an applicable staff member." + }, true); + return null; + } } \ No newline at end of file diff --git a/SupportChild/Commands/ReloadCommand.cs b/SupportChild/Commands/ReloadCommand.cs deleted file mode 100644 index de426c0..0000000 --- a/SupportChild/Commands/ReloadCommand.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; - -namespace SupportChild.Commands -{ - public class ReloadCommand : BaseCommandModule - { - [Command("reload")] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) - { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "reload")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the reload command but did not have permission."); - return; - } - - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Reloading bot application..." - }; - await command.RespondAsync(message); - Console.WriteLine("Reloading bot..."); - SupportChild.instance.Reload(); - } - } -} \ No newline at end of file diff --git a/SupportChild/Commands/RemoveCategoryCommand.cs b/SupportChild/Commands/RemoveCategoryCommand.cs new file mode 100644 index 0000000..0cef6a4 --- /dev/null +++ b/SupportChild/Commands/RemoveCategoryCommand.cs @@ -0,0 +1,41 @@ +using System.Threading.Tasks; +using DSharpPlus.Entities; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; + +namespace SupportChild.Commands; + +public class RemoveCategoryCommand : ApplicationCommandModule +{ + [SlashRequireGuild] + [SlashCommand("removecategory", "Removes the ability for users to open tickets in a specific category.")] + public async Task OnExecute(InteractionContext command, [Option("Category", "The category to remove.")] DiscordChannel channel) + { + if (!Database.TryGetCategory(channel.Id, out Database.Category _)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "That category is not registered." + }, true); + return; + } + + if (Database.RemoveCategory(channel.Id)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Category removed." + }, true); + } + else + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: Failed removing the category from the database." + }, true); + } + } +} \ No newline at end of file diff --git a/SupportChild/Commands/RemoveMessageCommand.cs b/SupportChild/Commands/RemoveMessageCommand.cs index 96794e4..fc14a0f 100644 --- a/SupportChild/Commands/RemoveMessageCommand.cs +++ b/SupportChild/Commands/RemoveMessageCommand.cs @@ -1,66 +1,41 @@ -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using DSharpPlus.Entities; using System.Threading.Tasks; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class RemoveMessageCommand : ApplicationCommandModule { - public class RemoveMessageCommand : BaseCommandModule - { - [Command("removemessage")] - [Description("Removes a message from the 'say' command.")] - public async Task OnExecute(CommandContext command, string identifier) - { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "removemessage")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the removemessage command but did not have permission."); - return; - } + [SlashRequireGuild] + [SlashCommand("removemessage", "Removes a message from the 'say' command.")] + public async Task OnExecute(InteractionContext command, [Option("Identifier", "The identifier word used in the /say command.")] string identifier) + { + if (!Database.TryGetMessage(identifier.ToLower(), out Database.Message _)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "There is no message with that identifier." + }, true); + return; + } - if (!Database.TryGetMessage(identifier.ToLower(), out Database.Message _)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "There is no message with that identifier." - }; - await command.RespondAsync(error); - return; - } - - if(Database.RemoveMessage(identifier)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Message removed." - }; - await command.RespondAsync(error); - return; - } - else - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: Failed removing the message from the database." - }; - await command.RespondAsync(error); - return; - } - - } - } + if (Database.RemoveMessage(identifier)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Message removed." + }, true); + } + else + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: Failed removing the message from the database." + }, true); + } + } } \ No newline at end of file diff --git a/SupportChild/Commands/RemoveStaffCommand.cs b/SupportChild/Commands/RemoveStaffCommand.cs index a48f96f..70e2162 100644 --- a/SupportChild/Commands/RemoveStaffCommand.cs +++ b/SupportChild/Commands/RemoveStaffCommand.cs @@ -1,104 +1,49 @@ -using System; -using System.Linq; -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; +using System.Threading.Tasks; using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; using MySql.Data.MySqlClient; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class RemoveStaffCommand : ApplicationCommandModule { - public class RemoveStaffCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("removestaff", "Removes a staff member.")] + public async Task OnExecute(InteractionContext command, [Option("User", "User to remove from staff.")] DiscordUser user) { - [Command("removestaff")] - [Cooldown(1, 5, CooldownBucketType.User)] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + if (!Database.IsStaff(user.Id)) { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "removestaff")) + await command.CreateResponseAsync(new DiscordEmbedBuilder { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the removestaff command but did not have permission."); - return; - } + Color = DiscordColor.Red, + Description = "User is already not registered as staff." + }, true); + return; + } - ulong userID; - string[] parsedMessage = Utilities.ParseIDs(command.RawArgumentString); + await using MySqlConnection c = Database.GetConnection(); + c.Open(); + MySqlCommand deletion = new MySqlCommand(@"DELETE FROM staff WHERE user_id=@user_id", c); + deletion.Parameters.AddWithValue("@user_id", user.Id); + deletion.Prepare(); + deletion.ExecuteNonQuery(); - if (!parsedMessage.Any()) + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "User was removed from staff." + }, true); + + // Log it if the log channel exists + DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + await logChannel.SendMessageAsync(new DiscordEmbedBuilder { - userID = command.Member.Id; - } - else if (!ulong.TryParse(parsedMessage[0], out userID)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Invalid ID/Mention. (Could not convert to numerical)" - }; - await command.RespondAsync(error); - return; - } - - try - { - await command.Client.GetUserAsync(userID); - } - catch (Exception) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Invalid ID/Mention. (Could not find user on Discord)" - }; - await command.RespondAsync(error); - return; - } - - if (!Database.IsStaff(userID)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "User is already not registered as staff." - }; - await command.RespondAsync(error); - return; - } - - using (MySqlConnection c = Database.GetConnection()) - { - c.Open(); - MySqlCommand deletion = new MySqlCommand(@"DELETE FROM staff WHERE user_id=@user_id", c); - deletion.Parameters.AddWithValue("@user_id", userID); - deletion.Prepare(); - deletion.ExecuteNonQuery(); - - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "User was removed from staff." - }; - await command.RespondAsync(message); - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed logMessage = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "User was removed from staff.\n", - }; - await logChannel.SendMessageAsync(logMessage); - } - } + Color = DiscordColor.Green, + Description = "User was removed from staff.\n" + }); } } } \ No newline at end of file diff --git a/SupportChild/Commands/SayCommand.cs b/SupportChild/Commands/SayCommand.cs index 5eaedd1..b198b9c 100644 --- a/SupportChild/Commands/SayCommand.cs +++ b/SupportChild/Commands/SayCommand.cs @@ -1,78 +1,29 @@ -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; +using DSharpPlus.Entities; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class SayCommand : ApplicationCommandModule { - public class SayCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("say", "Prints a message with information from staff. Use without identifier to list all identifiers.")] + public async Task OnExecute(InteractionContext command, [Option("Identifier", "(Optional) The identifier word to summon a message.")] string identifier = null) { - [Command("say")] - [Cooldown(1, 2, CooldownBucketType.Channel)] - [Description("Prints a message with information from staff.")] - public async Task OnExecute(CommandContext command, string identifier) + // Print list of all messages if no identifier is provided + if (identifier == null) { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "say")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the say command but did not have permission."); - return; - } - - if (!Database.TryGetMessage(identifier.ToLower(), out Database.Message message)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "There is no message with that identifier." - }; - await command.RespondAsync(error); - return; - } - - DiscordEmbed reply = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = message.message - }; - await command.RespondAsync(reply); - } - - [Command("say")] - [Cooldown(1, 2.0, CooldownBucketType.Channel)] - [Description("Prints a list of staff messages.")] - public async Task OnExecute(CommandContext command) - { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "say")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the say command but did not have permission."); - return; - } - - List messages = Database.GetAllMessages(); if (!messages.Any()) { - DiscordEmbed error = new DiscordEmbedBuilder() - .WithColor(DiscordColor.Red) - .WithDescription("There are no messages registered."); - await command.RespondAsync(error); + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "There are no messages registered." + }, true); return; } @@ -85,12 +36,32 @@ namespace SupportChild.Commands LinkedList listMessages = Utilities.ParseListIntoMessages(listItems); foreach (string listMessage in listMessages) { - DiscordEmbed channelInfo = new DiscordEmbedBuilder() - .WithTitle("Available messages: ") - .WithColor(DiscordColor.Green) - .WithDescription(listMessage); - await command.RespondAsync(channelInfo); + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Title = "Available messages: ", + Color = DiscordColor.Green, + Description = listMessage + }, true); } } + // Print specific message + else + { + if (!Database.TryGetMessage(identifier.ToLower(), out Database.Message message)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "There is no message with that identifier." + }, true); + return; + } + + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Cyan, + Description = message.message.Replace("\\n", "\n") + }); + } } } \ No newline at end of file diff --git a/SupportChild/Commands/SetSummaryCommand.cs b/SupportChild/Commands/SetSummaryCommand.cs index 81b0ca5..6d9e87e 100644 --- a/SupportChild/Commands/SetSummaryCommand.cs +++ b/SupportChild/Commands/SetSummaryCommand.cs @@ -1,63 +1,42 @@ using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; using MySql.Data.MySqlClient; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class SetSummaryCommand : ApplicationCommandModule { - public class SetSummaryCommand : BaseCommandModule - { - [Command("setsummary")] - [Cooldown(1, 5, CooldownBucketType.User)] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) - { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "setsummary")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the setsummary command but did not have permission."); - return; - } + [SlashRequireGuild] + [SlashCommand("setsummary", "Sets a ticket's summary for the summary command.")] + public async Task OnExecute(InteractionContext command, [Option("Summary", "The ticket summary text.")] string summary) + { + ulong channelID = command.Channel.Id; + // Check if ticket exists in the database + if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket _)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "This channel is not a ticket." + }); + return; + } - ulong channelID = command.Channel.Id; - // Check if ticket exists in the database - if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "This channel is not a ticket." - }; - await command.RespondAsync(error); - return; - } + await using MySqlConnection c = Database.GetConnection(); + c.Open(); + MySqlCommand update = new MySqlCommand(@"UPDATE tickets SET summary = @summary WHERE channel_id = @channel_id", c); + update.Parameters.AddWithValue("@summary", summary); + update.Parameters.AddWithValue("@channel_id", channelID); + update.Prepare(); + update.ExecuteNonQuery(); + update.Dispose(); - string summary = command.Message.Content.Replace(Config.prefix + "setsummary", "").Trim(); - - using (MySqlConnection c = Database.GetConnection()) - { - c.Open(); - MySqlCommand update = new MySqlCommand(@"UPDATE tickets SET summary = @summary WHERE channel_id = @channel_id", c); - update.Parameters.AddWithValue("@summary", summary); - update.Parameters.AddWithValue("@channel_id", channelID); - update.Prepare(); - update.ExecuteNonQuery(); - update.Dispose(); - - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Summary set." - }; - await command.RespondAsync(message); - } - } - } + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Summary set." + }, true); + } } \ No newline at end of file diff --git a/SupportChild/Commands/SetTicketCommand.cs b/SupportChild/Commands/SetTicketCommand.cs deleted file mode 100644 index 11797ec..0000000 --- a/SupportChild/Commands/SetTicketCommand.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; -using MySql.Data.MySqlClient; - -namespace SupportChild.Commands -{ - public class SetTicketCommand :BaseCommandModule - { - [Command("setticket")] - [Description("Turns a channel into a ticket, warning: this will let anyone with write access delete the channel using the close command.")] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) - { - using (MySqlConnection c = Database.GetConnection()) - { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "setticket")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the setticket command but did not have permission."); - return; - } - - // Check if ticket exists in the database - if (Database.IsOpenTicket(command.Channel.Id)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "This channel is already a ticket." - }; - await command.RespondAsync(error); - return; - } - - ulong userID; - string[] parsedMessage = Utilities.ParseIDs(command.RawArgumentString); - - if (!parsedMessage.Any()) - { - userID = command.Member.Id; - } - else if (!ulong.TryParse(parsedMessage[0], out userID)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Invalid ID/Mention. (Could not convert to numerical)" - }; - await command.RespondAsync(error); - return; - } - - DiscordUser user = await command.Client.GetUserAsync(userID); - - if (user == null) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Invalid ID/Mention." - }; - await command.RespondAsync(error); - return; - } - - long id = Database.NewTicket(userID, 0, command.Channel.Id); - string ticketID = id.ToString("00000"); - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Channel has been designated ticket " + ticketID + "." - }; - await command.RespondAsync(message); - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed logMessage = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = command.Channel.Mention + " has been designated ticket " + ticketID + " by " + command.Member.Mention + "." - }; - await logChannel.SendMessageAsync(logMessage); - } - } - } - } -} \ No newline at end of file diff --git a/SupportChild/Commands/StatusCommand.cs b/SupportChild/Commands/StatusCommand.cs index 8521b80..dec688b 100644 --- a/SupportChild/Commands/StatusCommand.cs +++ b/SupportChild/Commands/StatusCommand.cs @@ -1,41 +1,26 @@ using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class StatusCommand : ApplicationCommandModule { - public class StatusCommand : BaseCommandModule - { - [Command("status")] - [Cooldown(1, 5, CooldownBucketType.User)] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) - { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "status")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the status command but did not have permission."); - return; - } + [SlashRequireGuild] + [SlashCommand("status", "Shows bot status and information.")] + public async Task OnExecute(InteractionContext command) + { + long openTickets = Database.GetNumberOfTickets(); + long closedTickets = Database.GetNumberOfClosedTickets(); - long openTickets = Database.GetNumberOfTickets(); - long closedTickets = Database.GetNumberOfClosedTickets(); - - DiscordEmbed botInfo = new DiscordEmbedBuilder() - .WithAuthor("EmotionChild/SupportChild @ GitHub", "https://github.com/EmotionChild/SupportChild", "https://cdn.emotionchild.com/Ellie.png") - .WithTitle("Bot information") - .WithColor(DiscordColor.Cyan) - .AddField("Version:", SupportChild.GetVersion(), false) - .AddField("Open tickets:", openTickets + "", true) - .AddField("Closed tickets (1.1.0+ tickets only):", closedTickets + " ", true); - await command.RespondAsync(botInfo); - } - } + DiscordEmbed botInfo = new DiscordEmbedBuilder() + .WithAuthor("KarlofDuty/SupportBoi @ GitHub", "https://github.com/EmotionChild/SupportChild", "https://cdn.discordapp.com/attachments/765441543100170271/914327948667011132/Ellie_Concept_2_transparent_ver.png") + .WithTitle("Bot information") + .WithColor(DiscordColor.Cyan) + .AddField("Version:", SupportChild.GetVersion()) + .AddField("Open tickets:", openTickets + "", true) + .AddField("Closed tickets:", closedTickets + " ", true); + await command.CreateResponseAsync(botInfo); + } } \ No newline at end of file diff --git a/SupportChild/Commands/SummaryCommand.cs b/SupportChild/Commands/SummaryCommand.cs index 360db00..69a0a3b 100644 --- a/SupportChild/Commands/SummaryCommand.cs +++ b/SupportChild/Commands/SummaryCommand.cs @@ -1,51 +1,35 @@ using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class SummaryCommand : ApplicationCommandModule { - public class SummaryCommand : BaseCommandModule - { - [Command("summary")] - [Cooldown(1, 5, CooldownBucketType.User)] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) - { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "summary")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the summary command but did not have permission."); - return; - } - - if (Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) - { - DiscordEmbed channelInfo = new DiscordEmbedBuilder() - .WithTitle("Channel information") - .WithColor(DiscordColor.Cyan) - .AddField("Ticket number:", ticket.id.ToString(), true) - .AddField("Ticket creator:", $"<@{ticket.creatorID}>", true) - .AddField("Assigned staff:", ticket.assignedStaffID == 0 ? "Unassigned." : $"<@{ticket.assignedStaffID}>", true) - .AddField("Creation time:", ticket.createdTime.ToString(Config.timestampFormat), true) - .AddField("Summary:", string.IsNullOrEmpty(ticket.summary) ? "No summary." : ticket.summary, false); - await command.RespondAsync(channelInfo); - } - else - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "This channel is not a ticket." - }; - await command.RespondAsync(error); - } - } - } + [SlashRequireGuild] + [SlashCommand("summary", "Lists tickets assigned to a user.")] + public async Task OnExecute(InteractionContext command) + { + if (Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) + { + DiscordEmbed channelInfo = new DiscordEmbedBuilder() + .WithTitle("Channel information") + .WithColor(DiscordColor.Cyan) + .AddField("Ticket number:", ticket.id.ToString("00000"), true) + .AddField("Ticket creator:", $"<@{ticket.creatorID}>", true) + .AddField("Assigned staff:", ticket.assignedStaffID == 0 ? "Unassigned." : $"<@{ticket.assignedStaffID}>", true) + .AddField("Creation time:", ticket.DiscordRelativeTime(), true) + .AddField("Summary:", string.IsNullOrEmpty(ticket.summary) ? "No summary." : ticket.summary.Replace("\\n", "\n")); + await command.CreateResponseAsync(channelInfo); + } + else + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "This channel is not a ticket." + }, true); + } + } } \ No newline at end of file diff --git a/SupportChild/Commands/ToggleActiveCommand.cs b/SupportChild/Commands/ToggleActiveCommand.cs index b953367..1e07cfa 100644 --- a/SupportChild/Commands/ToggleActiveCommand.cs +++ b/SupportChild/Commands/ToggleActiveCommand.cs @@ -1,78 +1,44 @@ -using System.Linq; -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; +using System.Threading.Tasks; using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; -using MySql.Data.MySqlClient; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class ToggleActiveCommand : ApplicationCommandModule { - public class ToggleActiveCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("toggleactive", "Toggles active status for a staff member.")] + public async Task OnExecute(InteractionContext command, [Option("User", "(Optional) Staff member to toggle activity for.")] DiscordUser user = null) { - [Command("toggleactive")] - [Aliases("ta")] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + DiscordUser staffUser = user == null ? command.User : user; + + // Check if ticket exists in the database + if (!Database.TryGetStaff(staffUser.Id, out Database.StaffMember staffMember)) { - using (MySqlConnection c = Database.GetConnection()) + await command.CreateResponseAsync(new DiscordEmbedBuilder { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "toggleactive")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the toggleactive command but did not have permission."); - return; - } + Color = DiscordColor.Red, + Description = user == null ? "You have not been registered as staff." : "The user is not registered as staff." + }, true); + return; + } - ulong staffID; - string[] parsedMessage = Utilities.ParseIDs(command.RawArgumentString); - - if (!parsedMessage.Any()) - { - staffID = command.Member.Id; - } - else if (!ulong.TryParse(parsedMessage[0], out staffID)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Invalid ID/Mention. (Could not convert to numerical)" - }; - await command.RespondAsync(error); - return; - } - - // Check if ticket exists in the database - if (!Database.TryGetStaff(staffID, out Database.StaffMember staffMember)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You have not been registered as staff." - }; - await command.RespondAsync(error); - return; - } - - c.Open(); - MySqlCommand update = new MySqlCommand(@"UPDATE staff SET active = @active WHERE user_id = @user_id", c); - update.Parameters.AddWithValue("@user_id", staffID); - update.Parameters.AddWithValue("@active", !staffMember.active); - update.Prepare(); - update.ExecuteNonQuery(); - - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = staffMember.active ? "Staff member is now set as inactive and will no longer be randomly assigned any support tickets." : "Staff member is now set as active and will be randomly assigned support tickets again." - }; - await command.RespondAsync(message); - } + if (Database.SetStaffActive(staffUser.Id, !staffMember.active)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = staffMember.active ? "Staff member is now set as inactive and will no longer be randomly assigned any support tickets." : "Staff member is now set as active and will be randomly assigned support tickets again." + }, true); + } + else + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error: Unable to update active status in database." + }, true); } } } \ No newline at end of file diff --git a/SupportChild/Commands/TranscriptCommand.cs b/SupportChild/Commands/TranscriptCommand.cs index 8fb24cd..0eb4bfc 100644 --- a/SupportChild/Commands/TranscriptCommand.cs +++ b/SupportChild/Commands/TranscriptCommand.cs @@ -2,171 +2,128 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; using DSharpPlus.Exceptions; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class TranscriptCommand : ApplicationCommandModule { - public class TranscriptCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("transcript", "Creates a transcript of a ticket.")] + public async Task OnExecute(InteractionContext command, [Option("Ticket", "(Optional) Ticket number to get transcript of.")] long ticketID = 0) { - [Command("transcript")] - [Cooldown(1, 5, CooldownBucketType.User)] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + await command.DeferAsync(true); + Database.Ticket ticket; + if (ticketID == 0) // If there are no arguments use current channel { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "transcript")) + if (Database.TryGetOpenTicket(command.Channel.Id, out ticket)) { - DiscordEmbed error = new DiscordEmbedBuilder + try + { + await Transcriber.ExecuteAsync(command.Channel.Id, ticket.id); + } + catch (Exception) + { + await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "ERROR: Could not save transcript file. Aborting..." + })); + throw; + } + } + else + { + await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder { Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the transcript command but did not have permission."); + Description = "This channel is not a ticket." + })); return; } - - Database.Ticket ticket; - string strippedMessage = command.Message.Content.Replace(Config.prefix, ""); - string[] parsedMessage = strippedMessage.Replace("<@!", "").Replace("<@", "").Replace(">", "").Split(); - - // If there are no arguments use current channel - if (parsedMessage.Length < 2) + } + else + { + // If the ticket is still open, generate a new fresh transcript + if (Database.TryGetOpenTicketByID((uint)ticketID, out ticket) && ticket?.creatorID == command.Member.Id) { - if (Database.TryGetOpenTicket(command.Channel.Id, out ticket)) + try { - try - { - await Transcriber.ExecuteAsync(command.Channel.Id, ticket.id); - } - catch (Exception) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "ERROR: Could not save transcript file. Aborting..." - }; - await command.RespondAsync(error); - throw; - } + await Transcriber.ExecuteAsync(command.Channel.Id, ticket.id); } - else + catch (Exception) { - DiscordEmbed error = new DiscordEmbedBuilder + await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder { Color = DiscordColor.Red, - Description = "This channel is not a ticket." - }; - await command.RespondAsync(error); - return; + Description = "ERROR: Could not save transcript file. Aborting..." + })); + throw; } + } - else + // If there is no open or closed ticket, send an error. If there is a closed ticket we will simply use the old transcript from when the ticket was closed. + else if (!Database.TryGetClosedTicket((uint)ticketID, out ticket) || (ticket?.creatorID != command.Member.Id && !Database.IsStaff(command.Member.Id))) { - // Check if argument is numerical, if not abort - if (!uint.TryParse(parsedMessage[1], out uint ticketID)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Argument must be a number." - }; - await command.RespondAsync(error); - return; - } - - // If the ticket is still open, generate a new fresh transcript - if (Database.TryGetOpenTicketByID(ticketID, out ticket) && ticket?.creatorID == command.Member.Id) - { - try - { - await Transcriber.ExecuteAsync(command.Channel.Id, ticket.id); - } - catch (Exception) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "ERROR: Could not save transcript file. Aborting..." - }; - await command.RespondAsync(error); - throw; - } - - } - // If there is no open or closed ticket, send an error. If there is a closed ticket we will simply use the old transcript from when the ticket was closed. - else if (!Database.TryGetClosedTicket(ticketID, out ticket) || (ticket?.creatorID != command.Member.Id && !Database.IsStaff(command.Member.Id))) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Could not find a closed ticket with that number which you opened." + (Config.HasPermission(command.Member, "list") ? "\n(Use the " + Config.prefix + "list command to see all your tickets)" : "") - }; - await command.RespondAsync(error); - return; - } - } - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed embed = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Ticket " + ticket.id.ToString("00000") + " transcript generated by " + command.Member.Mention + ".\n", - Footer = new DiscordEmbedBuilder.EmbedFooter { Text = '#' + command.Channel.Name } - }; - - using (FileStream file = new FileStream(Transcriber.GetPath(ticket.id), FileMode.Open, FileAccess.Read)) - { - DiscordMessageBuilder message = new DiscordMessageBuilder(); - message.WithEmbed(embed); - message.WithFiles(new Dictionary() { { Transcriber.GetFilename(ticket.id), file } }); - - await logChannel.SendMessageAsync(message); - } - } - - try - { - // Send transcript privately - DiscordEmbed directMessageEmbed = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Transcript generated, " + command.Member.Mention + "!\n" - }; - - using (FileStream file = new FileStream(Transcriber.GetPath(ticket.id), FileMode.Open, FileAccess.Read)) - { - DiscordMessageBuilder directMessage = new DiscordMessageBuilder(); - directMessage.WithEmbed(directMessageEmbed); - directMessage.WithFiles(new Dictionary() { { Transcriber.GetFilename(ticket.id), file } }); - - await command.Member.SendMessageAsync(directMessage); - } - - // Respond to message directly - DiscordEmbed response = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Transcript sent, " + command.Member.Mention + "!\n" - }; - await command.RespondAsync(response); - } - catch (UnauthorizedException) - { - // Send transcript privately - DiscordEmbed error = new DiscordEmbedBuilder + await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder { Color = DiscordColor.Red, - Description = "Not allowed to send direct message to you, " + command.Member.Mention + ", please check your privacy settings.\n" - }; - await command.RespondAsync(error); + Description = "Could not find a closed ticket with that number which you opened.\n(Use the /list command to see all your tickets)" + })); + return; } } + + // Log it if the log channel exists + DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + await using FileStream file = new FileStream(Transcriber.GetPath(ticket.id), FileMode.Open, FileAccess.Read); + + DiscordMessageBuilder message = new DiscordMessageBuilder(); + message.WithEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Ticket " + ticket.id.ToString("00000") + " transcript generated by " + command.Member.Mention + ".\n", + Footer = new DiscordEmbedBuilder.EmbedFooter { Text = '#' + command.Channel.Name } + }); + message.WithFiles(new Dictionary { { Transcriber.GetFilename(ticket.id), file } }); + + await logChannel.SendMessageAsync(message); + } + + try + { + // Send transcript in a direct message + await using FileStream file = new FileStream(Transcriber.GetPath(ticket.id), FileMode.Open, FileAccess.Read); + + DiscordMessageBuilder directMessage = new DiscordMessageBuilder(); + directMessage.WithEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Transcript generated!\n" + }); + directMessage.WithFiles(new Dictionary { { Transcriber.GetFilename(ticket.id), file } }); + + await command.Member.SendMessageAsync(directMessage); + } + catch (UnauthorizedException) + { + await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Not allowed to send direct message to you, please check your privacy settings.\n" + })); + return; + } + + await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Transcript sent!\n" + })); } } \ No newline at end of file diff --git a/SupportChild/Commands/UnassignComand.cs b/SupportChild/Commands/UnassignComand.cs index 5b892a8..42e272c 100644 --- a/SupportChild/Commands/UnassignComand.cs +++ b/SupportChild/Commands/UnassignComand.cs @@ -1,71 +1,52 @@ using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class UnassignCommand : ApplicationCommandModule { - public class UnassignCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("unassign", "Unassigns a staff member from a ticket.")] + public async Task OnExecute(InteractionContext command) { - [Command("unassign")] - [Description("Unassigns a staff member from a ticket.")] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + // Check if ticket exists in the database + if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "unassign")) + await command.CreateResponseAsync(new DiscordEmbedBuilder { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the unassign command but did not have permission."); - return; - } + Color = DiscordColor.Red, + Description = "This channel is not a ticket." + }, true); + return; + } - // Check if ticket exists in the database - if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) + if (!Database.UnassignStaff(ticket)) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "This channel is not a ticket." - }; - await command.RespondAsync(error); - return; - } + Color = DiscordColor.Red, + Description = "Error: Failed to unassign staff member from ticket." + }, true); + return; + } - if (!Database.UnassignStaff(ticket)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: Failed to unassign staff from ticket." - }; - await command.RespondAsync(error); - return; - } + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Unassigned staff member from ticket." + }); - DiscordEmbed message = new DiscordEmbedBuilder + // Log it if the log channel exists + DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + await logChannel.SendMessageAsync(new DiscordEmbedBuilder { Color = DiscordColor.Green, - Description = "Unassigned staff from ticket." - }; - await command.RespondAsync(message); - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed logMessage = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Staff was unassigned from " + command.Channel.Mention + " by " + command.Member.Mention + "." - }; - await logChannel.SendMessageAsync(logMessage); - } + Description = "Staff member was unassigned from " + command.Channel.Mention + " by " + command.Member.Mention + "." + }); } } } \ No newline at end of file diff --git a/SupportChild/Commands/UnblacklistCommand.cs b/SupportChild/Commands/UnblacklistCommand.cs index e73e5b3..30d591a 100644 --- a/SupportChild/Commands/UnblacklistCommand.cs +++ b/SupportChild/Commands/UnblacklistCommand.cs @@ -1,99 +1,54 @@ using System; using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; using DSharpPlus.Entities; -using DSharpPlus.Exceptions; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; -namespace SupportChild.Commands +namespace SupportChild.Commands; + +public class UnblacklistCommand : ApplicationCommandModule { - public class UnblacklistCommand : BaseCommandModule + [SlashRequireGuild] + [SlashCommand("unblacklist", "Unblacklists a user from opening tickets.")] + public async Task OnExecute(InteractionContext command, [Option("User", "User to remove from blacklist.")] DiscordUser user) { - [Command("unblacklist")] - [Description("Un-blacklists a user from opening tickets.")] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) + try { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "unblacklist")) + if (!Database.Unblacklist(user.Id)) { - DiscordEmbed error = new DiscordEmbedBuilder + await command.CreateResponseAsync(new DiscordEmbedBuilder { Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the unblacklist command but did not have permission."); + Description = user.Mention + " is not blacklisted." + }, true); return; } - string[] words = Utilities.ParseIDs(command.RawArgumentString); - foreach (string word in words) + await command.CreateResponseAsync(new DiscordEmbedBuilder { - if (ulong.TryParse(word, out ulong userId)) + Color = DiscordColor.Green, + Description = "Removed " + user.Mention + " from blacklist." + }, true); + + // Log it if the log channel exists + DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); + if (logChannel != null) + { + await logChannel.SendMessageAsync(new DiscordEmbedBuilder { - DiscordUser blacklistedUser = null; - try - { - blacklistedUser = await command.Client.GetUserAsync(userId); - } - catch (NotFoundException) { } - - if (blacklistedUser == null) - { - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error: Could not find user." - }; - await command.RespondAsync(message); - continue; - } - - try - { - if (!Database.Unblacklist(blacklistedUser.Id)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = blacklistedUser.Mention + " is not blacklisted." - }; - await command.RespondAsync(error); - continue; - } - - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Removed " + blacklistedUser.Mention + " from blacklist." - }; - await command.RespondAsync(message); - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed logMessage = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = blacklistedUser.Mention + " was unblacklisted from opening tickets by " + command.Member.Mention + "." - }; - await logChannel.SendMessageAsync(logMessage); - } - } - catch (Exception) - { - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "Error occured while removing " + blacklistedUser.Mention + " from blacklist." - }; - await command.RespondAsync(message); - throw; - } - } + Color = DiscordColor.Green, + Description = user.Mention + " was unblacklisted from opening tickets by " + command.Member.Mention + "." + }); } } + catch (Exception) + { + await command.CreateResponseAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Error occured while removing " + user.Mention + " from blacklist." + }, true); + throw; + } } } \ No newline at end of file diff --git a/SupportChild/Commands/UnsetTicketCommand.cs b/SupportChild/Commands/UnsetTicketCommand.cs deleted file mode 100644 index 1ead160..0000000 --- a/SupportChild/Commands/UnsetTicketCommand.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Threading.Tasks; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.Entities; -using Microsoft.Extensions.Logging; -using MySql.Data.MySqlClient; - -namespace SupportChild.Commands -{ - public class UnsetTicketCommand : BaseCommandModule - { - [Command("unsetticket")] - [Description( - "Deletes a channel from the ticket system without deleting the channel.")] - public async Task OnExecute(CommandContext command, [RemainingText] string commandArgs) - { - using (MySqlConnection c = Database.GetConnection()) - { - // Check if the user has permission to use this command. - if (!Config.HasPermission(command.Member, "unsetticket")) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "You do not have permission to use this command." - }; - await command.RespondAsync(error); - command.Client.Logger.Log(LogLevel.Information, "User tried to use the unsetticket command but did not have permission."); - return; - } - - // Check if ticket exists in the database - if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = "This channel is not a ticket." - }; - await command.RespondAsync(error); - return; - } - - c.Open(); - MySqlCommand deletion = new MySqlCommand(@"DELETE FROM tickets WHERE channel_id=@channel_id", c); - deletion.Parameters.AddWithValue("@channel_id", command.Channel.Id); - deletion.Prepare(); - deletion.ExecuteNonQuery(); - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Channel has been undesignated as a ticket." - }; - await command.RespondAsync(message); - - // Log it if the log channel exists - DiscordChannel logChannel = command.Guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed logMessage = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = command.Channel.Mention + " has been undesignated as a ticket by " + command.Member.Mention + "." - }; - await logChannel.SendMessageAsync(logMessage); - } - } - } - } -} \ No newline at end of file diff --git a/SupportChild/Config.cs b/SupportChild/Config.cs index 858e17b..69ad566 100644 --- a/SupportChild/Config.cs +++ b/SupportChild/Config.cs @@ -1,143 +1,95 @@ using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; -using DSharpPlus.Entities; +using DSharpPlus; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; using SupportChild.Properties; using YamlDotNet.Serialization; -namespace SupportChild +namespace SupportChild; + +internal static class Config { - internal static class Config + internal static string token = ""; + internal static ulong logChannel; + internal static string welcomeMessage = ""; + internal static LogLevel logLevel = LogLevel.Information; + internal static TimestampFormat timestampFormat = TimestampFormat.RelativeTime; + internal static bool randomAssignment = false; + internal static bool randomAssignRoleOverride = false; + internal static string presenceType = "Playing"; + internal static string presenceText = ""; + internal static bool newCommandUsesSelector = false; + internal static int ticketLimit = 5; + + internal static bool ticketUpdatedNotifications = false; + internal static double ticketUpdatedNotificationDelay = 0.0; + internal static bool assignmentNotifications = false; + internal static bool closingNotifications = false; + + internal static string hostName = "127.0.0.1"; + internal static int port = 3306; + internal static string database = "supportchild"; + internal static string username = "supportchild"; + internal static string password = ""; + + public static void LoadConfig() { - internal static string token = ""; - internal static string prefix = ""; - internal static ulong logChannel; - internal static ulong ticketCategory; - internal static ulong reactionMessage; - internal static string welcomeMessage = ""; - internal static string logLevel = "Information"; - internal static string timestampFormat = "yyyy-MMM-dd HH:mm"; - internal static bool randomAssignment = false; - internal static bool randomAssignRoleOverride = false; - internal static string presenceType = "Playing"; - internal static string presenceText = ""; - - internal static bool ticketUpdatedNotifications = false; - internal static double ticketUpdatedNotificationDelay = 0.0; - internal static bool assignmentNotifications = false; - internal static bool closingNotifications = false; - - internal static string hostName = "127.0.0.1"; - internal static int port = 3306; - internal static string database = "supportbot"; - internal static string username = "supportbot"; - internal static string password = ""; - - private static readonly Dictionary permissions = new Dictionary + // Writes default config to file if it does not already exist + if (!File.Exists("./config.yml")) { - // Public commands - { "close", new ulong[]{ } }, - { "list", new ulong[]{ } }, - { "new", new ulong[]{ } }, - { "say", new ulong[]{ } }, - { "status", new ulong[]{ } }, - { "summary", new ulong[]{ } }, - { "transcript", new ulong[]{ } }, - // Moderator commands - { "add", new ulong[]{ } }, - { "addmessage", new ulong[]{ } }, - { "assign", new ulong[]{ } }, - { "blacklist", new ulong[]{ } }, - { "listassigned", new ulong[]{ } }, - { "listoldest", new ulong[]{ } }, - { "listunassigned", new ulong[]{ } }, - { "move", new ulong[]{ } }, - { "rassign", new ulong[]{ } }, - { "removemessage", new ulong[]{ } }, - { "setsummary", new ulong[]{ } }, - { "toggleactive", new ulong[]{ } }, - { "unassign", new ulong[]{ } }, - { "unblacklist", new ulong[]{ } }, - // Admin commands - { "addstaff", new ulong[]{ } }, - { "reload", new ulong[]{ } }, - { "removestaff", new ulong[]{ } }, - { "setticket", new ulong[]{ } }, - { "unsetticket", new ulong[]{ } }, - }; - - public static void LoadConfig() - { - // Writes default config to file if it does not already exist - if (!File.Exists("./config.yml")) - { - File.WriteAllText("./config.yml", Encoding.UTF8.GetString(Resources.default_config)); - } - - // Reads config contents into FileStream - FileStream stream = File.OpenRead("./config.yml"); - - // Converts the FileStream into a YAML object - IDeserializer deserializer = new DeserializerBuilder().Build(); - object yamlObject = deserializer.Deserialize(new StreamReader(stream)); - - // Converts the YAML object into a JSON object as the YAML ones do not support traversal or selection of nodes by name - ISerializer serializer = new SerializerBuilder().JsonCompatible().Build(); - JObject json = JObject.Parse(serializer.Serialize(yamlObject)); - - // Sets up the bot - token = json.SelectToken("bot.token").Value() ?? ""; - prefix = json.SelectToken("bot.prefix").Value() ?? ""; - logChannel = json.SelectToken("bot.log-channel").Value(); - ticketCategory = json.SelectToken("bot.ticket-category")?.Value() ?? 0; - reactionMessage = json.SelectToken("bot.reaction-message")?.Value() ?? 0; - welcomeMessage = json.SelectToken("bot.welcome-message").Value() ?? ""; - logLevel = json.SelectToken("bot.console-log-level").Value() ?? ""; - timestampFormat = json.SelectToken("bot.timestamp-format").Value() ?? "yyyy-MM-dd HH:mm"; - randomAssignment = json.SelectToken("bot.random-assignment")?.Value() ?? false; - randomAssignRoleOverride = json.SelectToken("bot.random-assign-role-override")?.Value() ?? false; - presenceType = json.SelectToken("bot.presence-type")?.Value() ?? "Playing"; - presenceText = json.SelectToken("bot.presence-text")?.Value() ?? ""; - - ticketUpdatedNotifications = json.SelectToken("notifications.ticket-updated")?.Value() ?? false; - ticketUpdatedNotificationDelay = json.SelectToken("notifications.ticket-updated-delay")?.Value() ?? 0.0; - assignmentNotifications = json.SelectToken("notifications.assignment")?.Value() ?? false; - closingNotifications = json.SelectToken("notifications.closing")?.Value() ?? false; - - // Reads database info - hostName = json.SelectToken("database.address")?.Value() ?? ""; - port = json.SelectToken("database.port")?.Value() ?? 3306; - database = json.SelectToken("database.name")?.Value() ?? "supportchild"; - username = json.SelectToken("database.user")?.Value() ?? "supportchild"; - password = json.SelectToken("database.password")?.Value() ?? ""; - - timestampFormat = timestampFormat.Trim(); - - foreach (KeyValuePair node in permissions.ToList()) - { - try - { - permissions[node.Key] = json.SelectToken("permissions." + node.Key).Value().Values().ToArray(); - } - catch (ArgumentNullException) - { - Console.WriteLine("Permission node '" + node.Key + "' was not found in the config, using default value: []"); - } - } + File.WriteAllText("./config.yml", Encoding.UTF8.GetString(Resources.default_config)); } - /// - /// Checks whether a user has a specific permission. - /// - /// The Discord user to check. - /// The permission name to check. - /// - public static bool HasPermission(DiscordMember member, string permission) + // Reads config contents into FileStream + FileStream stream = File.OpenRead("./config.yml"); + + // Converts the FileStream into a YAML object + IDeserializer deserializer = new DeserializerBuilder().Build(); + object yamlObject = deserializer.Deserialize(new StreamReader(stream)) ?? ""; + + // Converts the YAML object into a JSON object as the YAML ones do not support traversal or selection of nodes by name + ISerializer serializer = new SerializerBuilder().JsonCompatible().Build(); + JObject json = JObject.Parse(serializer.Serialize(yamlObject)); + + // Sets up the bot + token = json.SelectToken("bot.token")?.Value() ?? ""; + logChannel = json.SelectToken("bot.log-channel")?.Value() ?? 0; + welcomeMessage = json.SelectToken("bot.welcome-message")?.Value() ?? ""; + string stringLogLevel = json.SelectToken("bot.console-log-level")?.Value() ?? ""; + + if (!Enum.TryParse(stringLogLevel, true, out logLevel)) { - return member.Roles.Any(role => permissions[permission].Contains(role.Id)) || permissions[permission].Contains(member.Guild.Id); + logLevel = LogLevel.Information; + Logger.Warn("Log level '" + stringLogLevel + "' invalid, using 'Information' instead."); } + + string stringTimestampFormat = json.SelectToken("bot.timestamp-format")?.Value() ?? "RelativeTime"; + + if (!Enum.TryParse(stringTimestampFormat, true, out timestampFormat)) + { + timestampFormat = TimestampFormat.RelativeTime; + Logger.Warn("Timestamp '" + stringTimestampFormat + "' invalid, using 'RelativeTime' instead."); + } + + randomAssignment = json.SelectToken("bot.random-assignment")?.Value() ?? false; + randomAssignRoleOverride = json.SelectToken("bot.random-assign-role-override")?.Value() ?? false; + presenceType = json.SelectToken("bot.presence-type")?.Value() ?? "Playing"; + presenceText = json.SelectToken("bot.presence-text")?.Value() ?? ""; + newCommandUsesSelector = json.SelectToken("bot.new-command-uses-selector")?.Value() ?? false; + ticketLimit = json.SelectToken("bot.ticket-limit")?.Value() ?? 5; + + ticketUpdatedNotifications = json.SelectToken("notifications.ticket-updated")?.Value() ?? false; + ticketUpdatedNotificationDelay = json.SelectToken("notifications.ticket-updated-delay")?.Value() ?? 0.0; + assignmentNotifications = json.SelectToken("notifications.assignment")?.Value() ?? false; + closingNotifications = json.SelectToken("notifications.closing")?.Value() ?? false; + + // Reads database info + hostName = json.SelectToken("database.address")?.Value() ?? ""; + port = json.SelectToken("database.port")?.Value() ?? 3306; + database = json.SelectToken("database.name")?.Value() ?? "supportchild"; + username = json.SelectToken("database.user")?.Value() ?? "supportchild"; + password = json.SelectToken("database.password")?.Value() ?? ""; } } \ No newline at end of file diff --git a/SupportChild/Database.cs b/SupportChild/Database.cs index 57ecbca..e78160c 100644 --- a/SupportChild/Database.cs +++ b/SupportChild/Database.cs @@ -1,691 +1,818 @@ using System; using System.Linq; using System.Collections.Generic; +using DSharpPlus; using MySql.Data.MySqlClient; -namespace SupportChild +namespace SupportChild; + +public static class Database { - public static class Database + private static string connectionString = ""; + + private static readonly Random random = new Random(); + + public static void SetConnectionString(string host, int port, string database, string username, string password) { - private static string connectionString = ""; + connectionString = "server=" + host + + ";database=" + database + + ";port=" + port + + ";userid=" + username + + ";password=" + password; + } - private static Random random = new Random(); + public static MySqlConnection GetConnection() + { + return new MySqlConnection(connectionString); + } - public static void SetConnectionString(string host, int port, string database, string username, string password) + public static long GetNumberOfTickets() + { + try { - connectionString = "server=" + host + - ";database=" + database + - ";port=" + port + - ";userid=" + username + - ";password=" + password; + using MySqlConnection c = GetConnection(); + using MySqlCommand countTickets = new MySqlCommand("SELECT COUNT(*) FROM tickets", c); + c.Open(); + return (long)countTickets.ExecuteScalar(); } - public static MySqlConnection GetConnection() + catch (Exception e) { - return new MySqlConnection(connectionString); - } - public static long GetNumberOfTickets() - { - try - { - using (MySqlConnection c = GetConnection()) - { - MySqlCommand countTickets = new MySqlCommand("SELECT COUNT(*) FROM tickets", c); - c.Open(); - return (long)countTickets.ExecuteScalar(); - } - } - catch (Exception e) - { - Console.WriteLine("Error occured when attempting to count number of open tickets: " + e); - } - - return -1; - } - public static long GetNumberOfClosedTickets() - { - try - { - using (MySqlConnection c = GetConnection()) - { - MySqlCommand countTickets = new MySqlCommand("SELECT COUNT(*) FROM ticket_history", c); - c.Open(); - return (long)countTickets.ExecuteScalar(); - } - } - catch (Exception e) - { - Console.WriteLine("Error occured when attempting to count number of open tickets: " + e); - } - - return -1; - } - public static void SetupTables() - { - using (MySqlConnection c = GetConnection()) - { - MySqlCommand createTickets = new MySqlCommand( - "CREATE TABLE IF NOT EXISTS tickets(" + - "id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT," + - "created_time DATETIME NOT NULL," + - "creator_id BIGINT UNSIGNED NOT NULL," + - "assigned_staff_id BIGINT UNSIGNED NOT NULL DEFAULT 0," + - "summary VARCHAR(5000) NOT NULL," + - "channel_id BIGINT UNSIGNED NOT NULL UNIQUE," + - "INDEX(created_time, assigned_staff_id, channel_id))", - c); - MySqlCommand createTicketHistory = new MySqlCommand( - "CREATE TABLE IF NOT EXISTS ticket_history(" + - "id INT UNSIGNED NOT NULL PRIMARY KEY," + - "created_time DATETIME NOT NULL," + - "closed_time DATETIME NOT NULL," + - "creator_id BIGINT UNSIGNED NOT NULL," + - "assigned_staff_id BIGINT UNSIGNED NOT NULL DEFAULT 0," + - "summary VARCHAR(5000) NOT NULL," + - "channel_id BIGINT UNSIGNED NOT NULL UNIQUE," + - "INDEX(created_time, closed_time, channel_id))", - c); - MySqlCommand createBlacklisted = new MySqlCommand( - "CREATE TABLE IF NOT EXISTS blacklisted_users(" + - "user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," + - "time DATETIME NOT NULL," + - "moderator_id BIGINT UNSIGNED NOT NULL," + - "INDEX(user_id, time))", - c); - MySqlCommand createStaffList = new MySqlCommand( - "CREATE TABLE IF NOT EXISTS staff(" + - "user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," + - "name VARCHAR(256) NOT NULL," + - "active BOOLEAN NOT NULL DEFAULT true)", - c); - MySqlCommand createMessages = new MySqlCommand( - "CREATE TABLE IF NOT EXISTS messages(" + - "identifier VARCHAR(256) NOT NULL PRIMARY KEY," + - "user_id BIGINT UNSIGNED NOT NULL," + - "message VARCHAR(5000) NOT NULL)", - c); - c.Open(); - createTickets.ExecuteNonQuery(); - createBlacklisted.ExecuteNonQuery(); - createTicketHistory.ExecuteNonQuery(); - createStaffList.ExecuteNonQuery(); - createMessages.ExecuteNonQuery(); - } - } - public static bool IsOpenTicket(ulong channelID) - { - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE channel_id=@channel_id", c); - selection.Parameters.AddWithValue("@channel_id", channelID); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - // Check if ticket exists in the database - if (!results.Read()) - { - return false; - } - results.Close(); - } - return true; - } - public static bool TryGetOpenTicket(ulong channelID, out Ticket ticket) - { - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE channel_id=@channel_id", c); - selection.Parameters.AddWithValue("@channel_id", channelID); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - // Check if ticket exists in the database - if (!results.Read()) - { - ticket = null; - return false; - } - - ticket = new Ticket(results); - results.Close(); - return true; - } - } - public static bool TryGetOpenTicketByID(uint id, out Ticket ticket) - { - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE id=@id", c); - selection.Parameters.AddWithValue("@id", id); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - // Check if open ticket exists in the database - if (results.Read()) - { - ticket = new Ticket(results); - return true; - } - - ticket = null; - return false; - } - } - public static bool TryGetClosedTicket(uint id, out Ticket ticket) - { - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM ticket_history WHERE id=@id", c); - selection.Parameters.AddWithValue("@id", id); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - // Check if closed ticket exists in the database - if (results.Read()) - { - ticket = new Ticket(results); - return true; - } - - ticket = null; - return false; - } - } - public static bool TryGetOpenTickets(ulong userID, out List tickets) - { - tickets = null; - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE creator_id=@creator_id", c); - selection.Parameters.AddWithValue("@creator_id", userID); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - if (!results.Read()) - { - return false; - } - - tickets = new List { new Ticket(results) }; - while (results.Read()) - { - tickets.Add(new Ticket(results)); - } - results.Close(); - return true; - } - } - public static bool TryGetOldestTickets(ulong userID, out List tickets, int listLimit) - { - tickets = null; - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets ORDER BY created_time ASC LIMIT @limit", c); - selection.Parameters.AddWithValue("@creator_id", userID); - selection.Parameters.AddWithValue("@limit", listLimit); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - if (!results.Read()) - { - return false; - } - - tickets = new List { new Ticket(results) }; - while (results.Read()) - { - tickets.Add(new Ticket(results)); - } - results.Close(); - return true; - } - } - public static bool TryGetClosedTickets(ulong userID, out List tickets) - { - tickets = null; - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM ticket_history WHERE creator_id=@creator_id", c); - selection.Parameters.AddWithValue("@creator_id", userID); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - if (!results.Read()) - { - return false; - } - - tickets = new List { new Ticket(results) }; - while (results.Read()) - { - tickets.Add(new Ticket(results)); - } - results.Close(); - return true; - } - } - public static bool TryGetAssignedTickets(ulong staffID, out List tickets) - { - tickets = null; - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE assigned_staff_id=@assigned_staff_id", c); - selection.Parameters.AddWithValue("@assigned_staff_id", staffID); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - if (!results.Read()) - { - return false; - } - - tickets = new List { new Ticket(results) }; - while (results.Read()) - { - tickets.Add(new Ticket(results)); - } - results.Close(); - return true; - } - } - public static long NewTicket(ulong memberID, ulong staffID, ulong ticketID) - { - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand cmd = new MySqlCommand( - @"INSERT INTO tickets (created_time, creator_id, assigned_staff_id, summary, channel_id) VALUES (UTC_TIMESTAMP(), @creator_id, @assigned_staff_id, @summary, @channel_id);", - c); - cmd.Parameters.AddWithValue("@creator_id", memberID); - cmd.Parameters.AddWithValue("@assigned_staff_id", staffID); - cmd.Parameters.AddWithValue("@summary", ""); - cmd.Parameters.AddWithValue("@channel_id", ticketID); - cmd.ExecuteNonQuery(); - return cmd.LastInsertedId; - } + Logger.Error("Error occured when attempting to count number of open tickets: " + e); } - public static void ArchiveTicket(Ticket ticket) + return -1; + } + + public static long GetNumberOfClosedTickets() + { + try { - // Check if ticket already exists in the archive - if (TryGetClosedTicket(ticket.id, out Ticket _)) - { - using (MySqlConnection c = GetConnection()) - { - MySqlCommand deleteTicket = new MySqlCommand(@"DELETE FROM ticket_history WHERE id=@id OR channel_id=@channel_id", c); - deleteTicket.Parameters.AddWithValue("@id", ticket.id); - deleteTicket.Parameters.AddWithValue("@channel_id", ticket.channelID); - - c.Open(); - deleteTicket.Prepare(); - deleteTicket.ExecuteNonQuery(); - } - } - - using (MySqlConnection c = GetConnection()) - { - // Create an entry in the ticket history database - MySqlCommand archiveTicket = new MySqlCommand(@"INSERT INTO ticket_history (id, created_time, closed_time, creator_id, assigned_staff_id, summary, channel_id) VALUES (@id, @created_time, UTC_TIMESTAMP(), @creator_id, @assigned_staff_id, @summary, @channel_id);", c); - archiveTicket.Parameters.AddWithValue("@id", ticket.id); - archiveTicket.Parameters.AddWithValue("@created_time", ticket.createdTime); - archiveTicket.Parameters.AddWithValue("@creator_id", ticket.creatorID); - archiveTicket.Parameters.AddWithValue("@assigned_staff_id", ticket.assignedStaffID); - archiveTicket.Parameters.AddWithValue("@summary", ticket.summary); - archiveTicket.Parameters.AddWithValue("@channel_id", ticket.channelID); - - c.Open(); - archiveTicket.Prepare(); - archiveTicket.ExecuteNonQuery(); - } + using MySqlConnection c = GetConnection(); + using MySqlCommand countTickets = new MySqlCommand("SELECT COUNT(*) FROM ticket_history", c); + c.Open(); + return (long)countTickets.ExecuteScalar(); + } + catch (Exception e) + { + Logger.Error("Error occured when attempting to count number of open tickets: " + e); } - public static void DeleteOpenTicket(uint ticketID) + return -1; + } + + public static void SetupTables() + { + using MySqlConnection c = GetConnection(); + using MySqlCommand createTickets = new MySqlCommand( + "CREATE TABLE IF NOT EXISTS tickets(" + + "id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT," + + "created_time DATETIME NOT NULL," + + "creator_id BIGINT UNSIGNED NOT NULL," + + "assigned_staff_id BIGINT UNSIGNED NOT NULL DEFAULT 0," + + "summary VARCHAR(5000) NOT NULL," + + "channel_id BIGINT UNSIGNED NOT NULL UNIQUE," + + "INDEX(created_time, assigned_staff_id, channel_id))", + c); + using MySqlCommand createTicketHistory = new MySqlCommand( + "CREATE TABLE IF NOT EXISTS ticket_history(" + + "id INT UNSIGNED NOT NULL PRIMARY KEY," + + "created_time DATETIME NOT NULL," + + "closed_time DATETIME NOT NULL," + + "creator_id BIGINT UNSIGNED NOT NULL," + + "assigned_staff_id BIGINT UNSIGNED NOT NULL DEFAULT 0," + + "summary VARCHAR(5000) NOT NULL," + + "channel_id BIGINT UNSIGNED NOT NULL UNIQUE," + + "INDEX(created_time, closed_time, channel_id))", + c); + using MySqlCommand createBlacklisted = new MySqlCommand( + "CREATE TABLE IF NOT EXISTS blacklisted_users(" + + "user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," + + "time DATETIME NOT NULL," + + "moderator_id BIGINT UNSIGNED NOT NULL," + + "INDEX(user_id, time))", + c); + using MySqlCommand createStaffList = new MySqlCommand( + "CREATE TABLE IF NOT EXISTS staff(" + + "user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," + + "name VARCHAR(256) NOT NULL," + + "active BOOLEAN NOT NULL DEFAULT true)", + c); + using MySqlCommand createMessages = new MySqlCommand( + "CREATE TABLE IF NOT EXISTS messages(" + + "identifier VARCHAR(256) NOT NULL PRIMARY KEY," + + "user_id BIGINT UNSIGNED NOT NULL," + + "message VARCHAR(5000) NOT NULL)", + c); + using MySqlCommand createCategories = new MySqlCommand( + "CREATE TABLE IF NOT EXISTS categories(" + + "name VARCHAR(256) NOT NULL UNIQUE," + + "category_id BIGINT UNSIGNED NOT NULL PRIMARY KEY)", + c); + c.Open(); + createTickets.ExecuteNonQuery(); + createBlacklisted.ExecuteNonQuery(); + createTicketHistory.ExecuteNonQuery(); + createStaffList.ExecuteNonQuery(); + createMessages.ExecuteNonQuery(); + createCategories.ExecuteNonQuery(); + } + + public static bool IsOpenTicket(ulong channelID) + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE channel_id=@channel_id", c); + selection.Parameters.AddWithValue("@channel_id", channelID); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if ticket exists in the database + if (!results.Read()) { - using (MySqlConnection c = GetConnection()) - { - MySqlCommand deletion = new MySqlCommand(@"DELETE FROM tickets WHERE id=@id", c); - deletion.Parameters.AddWithValue("@id", ticketID); - - c.Open(); - deletion.Prepare(); - deletion.ExecuteNonQuery(); - } - } - - public static bool IsBlacklisted(ulong userID) - { - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM blacklisted_users WHERE user_id=@user_id", c); - selection.Parameters.AddWithValue("@user_id", userID); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - // Check if user is blacklisted - if (results.Read()) - { - return true; - } - results.Close(); - } - return false; } - public static bool Blacklist(ulong blacklistedID, ulong staffID) + results.Close(); + return true; + } + + public static bool TryGetOpenTicket(ulong channelID, out Ticket ticket) + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE channel_id=@channel_id", c); + selection.Parameters.AddWithValue("@channel_id", channelID); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if ticket exists in the database + if (!results.Read()) { - using (MySqlConnection c = GetConnection()) + ticket = null; + return false; + } + + ticket = new Ticket(results); + results.Close(); + return true; + } + + public static bool TryGetOpenTicketByID(uint id, out Ticket ticket) + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE id=@id", c); + selection.Parameters.AddWithValue("@id", id); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if open ticket exists in the database + if (results.Read()) + { + ticket = new Ticket(results); + results.Close(); + return true; + } + + results.Close(); + ticket = null; + return false; + } + + public static bool TryGetClosedTicket(uint id, out Ticket ticket) + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM ticket_history WHERE id=@id", c); + selection.Parameters.AddWithValue("@id", id); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if closed ticket exists in the database + if (results.Read()) + { + ticket = new Ticket(results); + results.Close(); + return true; + } + + ticket = null; + results.Close(); + return false; + } + + public static bool TryGetOpenTickets(ulong userID, out List tickets) + { + tickets = null; + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE creator_id=@creator_id", c); + selection.Parameters.AddWithValue("@creator_id", userID); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + if (!results.Read()) + { + return false; + } + + tickets = new List { new Ticket(results) }; + while (results.Read()) + { + tickets.Add(new Ticket(results)); + } + results.Close(); + return true; + } + + public static bool TryGetOpenTickets(out List tickets) + { + tickets = null; + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets ORDER BY channel_id ASC", c); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + if (!results.Read()) + { + return false; + } + + tickets = new List { new Ticket(results) }; + while (results.Read()) + { + tickets.Add(new Ticket(results)); + } + results.Close(); + return true; + } + + public static bool TryGetClosedTickets(ulong userID, out List tickets) + { + tickets = null; + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM ticket_history WHERE creator_id=@creator_id", c); + selection.Parameters.AddWithValue("@creator_id", userID); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + if (!results.Read()) + { + return false; + } + + tickets = new List { new Ticket(results) }; + while (results.Read()) + { + tickets.Add(new Ticket(results)); + } + results.Close(); + return true; + } + + public static bool TryGetAssignedTickets(ulong staffID, out List tickets) + { + tickets = null; + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE assigned_staff_id=@assigned_staff_id", c); + selection.Parameters.AddWithValue("@assigned_staff_id", staffID); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + if (!results.Read()) + { + return false; + } + + tickets = new List { new Ticket(results) }; + while (results.Read()) + { + tickets.Add(new Ticket(results)); + } + results.Close(); + return true; + } + + public static long NewTicket(ulong memberID, ulong staffID, ulong ticketID) + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO tickets (created_time, creator_id, assigned_staff_id, summary, channel_id) VALUES (UTC_TIMESTAMP(), @creator_id, @assigned_staff_id, @summary, @channel_id);", c); + cmd.Parameters.AddWithValue("@creator_id", memberID); + cmd.Parameters.AddWithValue("@assigned_staff_id", staffID); + cmd.Parameters.AddWithValue("@summary", ""); + cmd.Parameters.AddWithValue("@channel_id", ticketID); + cmd.ExecuteNonQuery(); + return cmd.LastInsertedId; + } + + public static void ArchiveTicket(Ticket ticket) + { + // Check if ticket already exists in the archive + if (TryGetClosedTicket(ticket.id, out Ticket _)) + { + using MySqlConnection c = GetConnection(); + using MySqlCommand deleteTicket = new MySqlCommand(@"DELETE FROM ticket_history WHERE id=@id OR channel_id=@channel_id", c); + deleteTicket.Parameters.AddWithValue("@id", ticket.id); + deleteTicket.Parameters.AddWithValue("@channel_id", ticket.channelID); + + c.Open(); + deleteTicket.Prepare(); + deleteTicket.ExecuteNonQuery(); + } + + // Create an entry in the ticket history database + using MySqlConnection conn = GetConnection(); + using MySqlCommand archiveTicket = new MySqlCommand(@"INSERT INTO ticket_history (id, created_time, closed_time, creator_id, assigned_staff_id, summary, channel_id) VALUES (@id, @created_time, UTC_TIMESTAMP(), @creator_id, @assigned_staff_id, @summary, @channel_id);", conn); + archiveTicket.Parameters.AddWithValue("@id", ticket.id); + archiveTicket.Parameters.AddWithValue("@created_time", ticket.channelID.GetSnowflakeTime()); + archiveTicket.Parameters.AddWithValue("@creator_id", ticket.creatorID); + archiveTicket.Parameters.AddWithValue("@assigned_staff_id", ticket.assignedStaffID); + archiveTicket.Parameters.AddWithValue("@summary", ticket.summary); + archiveTicket.Parameters.AddWithValue("@channel_id", ticket.channelID); + + conn.Open(); + archiveTicket.Prepare(); + archiveTicket.ExecuteNonQuery(); + } + + public static bool DeleteOpenTicket(uint ticketID) + { + try + { + using MySqlConnection c = GetConnection(); + using MySqlCommand deletion = new MySqlCommand(@"DELETE FROM tickets WHERE id=@id", c); + deletion.Parameters.AddWithValue("@id", ticketID); + + c.Open(); + deletion.Prepare(); + return deletion.ExecuteNonQuery() > 0; + } + catch (MySqlException) + { + return false; + } + } + + public static bool IsBlacklisted(ulong userID) + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM blacklisted_users WHERE user_id=@user_id", c); + selection.Parameters.AddWithValue("@user_id", userID); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if user is blacklisted + if (results.Read()) + { + return true; + } + results.Close(); + + return false; + } + + public static bool Blacklist(ulong blacklistedID, ulong staffID) + { + try + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO blacklisted_users (user_id,time,moderator_id) VALUES (@user_id, UTC_TIMESTAMP(), @moderator_id);", c); + cmd.Parameters.AddWithValue("@user_id", blacklistedID); + cmd.Parameters.AddWithValue("@moderator_id", staffID); + cmd.Prepare(); + return cmd.ExecuteNonQuery() > 0; + } + catch (MySqlException) + { + return false; + } + } + + public static bool Unblacklist(ulong blacklistedID) + { + try + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM blacklisted_users WHERE user_id=@user_id", c); + cmd.Parameters.AddWithValue("@user_id", blacklistedID); + cmd.Prepare(); + return cmd.ExecuteNonQuery() > 0; + } + catch (MySqlException) + { + return false; + } + } + + public static bool AssignStaff(Ticket ticket, ulong staffID) + { + try + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand update = new MySqlCommand(@"UPDATE tickets SET assigned_staff_id = @assigned_staff_id WHERE id = @id", c); + update.Parameters.AddWithValue("@assigned_staff_id", staffID); + update.Parameters.AddWithValue("@id", ticket.id); + update.Prepare(); + return update.ExecuteNonQuery() > 0; + } + catch (MySqlException) + { + return false; + } + } + + public static bool UnassignStaff(Ticket ticket) + { + return AssignStaff(ticket, 0); + } + + public static bool SetStaffActive(ulong staffID, bool active) + { + try + { + using MySqlConnection c = GetConnection(); + c.Open(); + MySqlCommand update = new MySqlCommand(@"UPDATE staff SET active = @active WHERE user_id = @user_id", c); + update.Parameters.AddWithValue("@user_id", staffID); + update.Parameters.AddWithValue("@active", active); + update.Prepare(); + return update.ExecuteNonQuery() > 0; + } + catch (MySqlException) + { + return false; + } + } + + public static StaffMember GetRandomActiveStaff(params ulong[] ignoredUserIDs) + { + List staffMembers = GetActiveStaff(ignoredUserIDs); + return staffMembers.Any() ? staffMembers[random.Next(staffMembers.Count)] : null; + } + + public static List GetActiveStaff(params ulong[] ignoredUserIDs) + { + bool first = true; + string filterString = ""; + foreach (ulong userID in ignoredUserIDs) + { + if (first) { - try - { - c.Open(); - MySqlCommand cmd = new MySqlCommand(@"INSERT INTO blacklisted_users (user_id,time,moderator_id) VALUES (@user_id, UTC_TIMESTAMP(), @moderator_id);", c); - cmd.Parameters.AddWithValue("@user_id", blacklistedID); - cmd.Parameters.AddWithValue("@moderator_id", staffID); - cmd.Prepare(); - return cmd.ExecuteNonQuery() > 0; - } - catch (MySqlException) - { - return false; - } + first = false; + filterString += "AND user_id != " + userID; } - } - public static bool Unblacklist(ulong blacklistedID) - { - using (MySqlConnection c = GetConnection()) + else { - try - { - c.Open(); - MySqlCommand cmd = new MySqlCommand(@"DELETE FROM blacklisted_users WHERE user_id=@user_id", c); - cmd.Parameters.AddWithValue("@user_id", blacklistedID); - cmd.Prepare(); - return cmd.ExecuteNonQuery() > 0; - } - catch (MySqlException) - { - return false; - } - - } - } - public static bool AssignStaff(Ticket ticket, ulong staffID) - { - using (MySqlConnection c = GetConnection()) - { - try - { - c.Open(); - MySqlCommand update = new MySqlCommand(@"UPDATE tickets SET assigned_staff_id = @assigned_staff_id WHERE id = @id", c); - update.Parameters.AddWithValue("@assigned_staff_id", staffID); - update.Parameters.AddWithValue("@id", ticket.id); - update.Prepare(); - return update.ExecuteNonQuery() > 0; - } - catch (MySqlException) - { - return false; - } - - } - } - public static bool UnassignStaff(Ticket ticket) - { - return AssignStaff(ticket, 0); - } - public static StaffMember GetRandomActiveStaff(ulong currentStaffID) - { - List staffMembers = GetActiveStaff(currentStaffID); - if (!staffMembers.Any()) - { - return null; - } - - return staffMembers[random.Next(staffMembers.Count)]; - } - - public static List GetActiveStaff(ulong currentStaffID = 0) - { - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE active = true AND user_id != @user_id", c); - selection.Parameters.AddWithValue("@user_id", currentStaffID); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - // Check if staff exists in the database - if (!results.Read()) - { - return new List(); - } - - List staffMembers = new List { new StaffMember(results) }; - while (results.Read()) - { - staffMembers.Add(new StaffMember(results)); - } - results.Close(); - - return staffMembers; + filterString += "&& user_id != " + userID; } } - public static List GetAllStaff(ulong currentStaffID = 0) + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE active = true " + filterString, c); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if staff exists in the database + if (!results.Read()) { - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE user_id != @user_id", c); - selection.Parameters.AddWithValue("@user_id", currentStaffID); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - // Check if staff exist in the database - if (!results.Read()) - { - return new List(); - } - - List staffMembers = new List { new StaffMember(results) }; - while (results.Read()) - { - staffMembers.Add(new StaffMember(results)); - } - results.Close(); - - return staffMembers; - } + return new List(); } - public static bool IsStaff(ulong staffID) + List staffMembers = new List { new StaffMember(results) }; + while (results.Read()) { - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE user_id=@user_id", c); - selection.Parameters.AddWithValue("@user_id", staffID); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - // Check if ticket exists in the database - if (!results.Read()) - { - return false; - } - results.Close(); - return true; - } + staffMembers.Add(new StaffMember(results)); } - public static bool TryGetStaff(ulong staffID, out StaffMember staffMember) - { - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE user_id=@user_id", c); - selection.Parameters.AddWithValue("@user_id", staffID); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); + results.Close(); - // Check if ticket exists in the database - if (!results.Read()) - { - staffMember = null; - return false; - } - staffMember = new StaffMember(results); - results.Close(); - return true; + return staffMembers; + } + + public static List GetAllStaff(params ulong[] ignoredUserIDs) + { + bool first = true; + string filterString = ""; + foreach (ulong userID in ignoredUserIDs) + { + if (first) + { + first = false; + filterString += "WHERE user_id != " + userID; } + else + { + filterString += "&& user_id != " + userID; + } + } - public static List GetAllMessages() + + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff " + filterString, c); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if staff exist in the database + if (!results.Read()) { - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM messages", c); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); - - // Check if messages exist in the database - if (!results.Read()) - { - return new List(); - } - - List messages = new List { new Message(results) }; - while (results.Read()) - { - messages.Add(new Message(results)); - } - results.Close(); - - return messages; - } + return new List(); } - public static bool TryGetMessage(string identifier, out Message message) + List staffMembers = new List { new StaffMember(results) }; + while (results.Read()) { - using (MySqlConnection c = GetConnection()) - { - c.Open(); - MySqlCommand selection = new MySqlCommand(@"SELECT * FROM messages WHERE identifier=@identifier", c); - selection.Parameters.AddWithValue("@identifier", identifier); - selection.Prepare(); - MySqlDataReader results = selection.ExecuteReader(); + staffMembers.Add(new StaffMember(results)); + } + results.Close(); - // Check if ticket exists in the database - if (!results.Read()) - { - message = null; - return false; - } - message = new Message(results); - results.Close(); - return true; - } + return staffMembers; + } + + public static bool IsStaff(ulong staffID) + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE user_id=@user_id", c); + selection.Parameters.AddWithValue("@user_id", staffID); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if ticket exists in the database + if (!results.Read()) + { + return false; + } + results.Close(); + return true; + } + + public static bool TryGetStaff(ulong staffID, out StaffMember staffMember) + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE user_id=@user_id", c); + selection.Parameters.AddWithValue("@user_id", staffID); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if ticket exists in the database + if (!results.Read()) + { + staffMember = null; + return false; + } + staffMember = new StaffMember(results); + results.Close(); + return true; + } + + public static List GetAllMessages() + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM messages", c); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if messages exist in the database + if (!results.Read()) + { + return new List(); } - public static bool AddMessage(string identifier, ulong userID, string message) + List messages = new List { new Message(results) }; + while (results.Read()) { - using (MySqlConnection c = GetConnection()) - { - try - { - c.Open(); - MySqlCommand cmd = new MySqlCommand(@"INSERT INTO messages (identifier,user_id,message) VALUES (@identifier, @user_id, @message);", c); - cmd.Parameters.AddWithValue("@identifier", identifier); - cmd.Parameters.AddWithValue("@user_id", userID); - cmd.Parameters.AddWithValue("@message", message); - cmd.Prepare(); - return cmd.ExecuteNonQuery() > 0; - } - catch (MySqlException) - { - return false; - } - } + messages.Add(new Message(results)); + } + results.Close(); + + return messages; + } + + public static bool TryGetMessage(string identifier, out Message message) + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM messages WHERE identifier=@identifier", c); + selection.Parameters.AddWithValue("@identifier", identifier); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if ticket exists in the database + if (!results.Read()) + { + message = null; + return false; + } + message = new Message(results); + results.Close(); + return true; + } + + public static bool AddMessage(string identifier, ulong userID, string message) + { + try + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO messages (identifier,user_id,message) VALUES (@identifier, @user_id, @message);", c); + cmd.Parameters.AddWithValue("@identifier", identifier); + cmd.Parameters.AddWithValue("@user_id", userID); + cmd.Parameters.AddWithValue("@message", message); + cmd.Prepare(); + return cmd.ExecuteNonQuery() > 0; + } + catch (MySqlException) + { + return false; + } + } + + public static bool RemoveMessage(string identifier) + { + try + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM messages WHERE identifier=@identifier", c); + cmd.Parameters.AddWithValue("@identifier", identifier); + cmd.Prepare(); + return cmd.ExecuteNonQuery() > 0; + } + catch (MySqlException) + { + return false; + } + } + + public static List GetAllCategories() + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories", c); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if messages exist in the database + if (!results.Read()) + { + return new List(); } - public static bool RemoveMessage(string identifier) + List categories = new List { new Category(results) }; + while (results.Read()) { - using (MySqlConnection c = GetConnection()) - { - try - { - c.Open(); - MySqlCommand cmd = new MySqlCommand(@"DELETE FROM messages WHERE identifier=@identifier", c); - cmd.Parameters.AddWithValue("@identifier", identifier); - cmd.Prepare(); - return cmd.ExecuteNonQuery() > 0; - } - catch (MySqlException) - { - return false; - } + categories.Add(new Category(results)); + } + results.Close(); - } + return categories; + } + + public static bool TryGetCategory(ulong categoryID, out Category message) + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories WHERE category_id=@category_id", c); + selection.Parameters.AddWithValue("@category_id", categoryID); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if ticket exists in the database + if (!results.Read()) + { + message = null; + return false; + } + message = new Category(results); + results.Close(); + return true; + } + + public static bool TryGetCategory(string name, out Category message) + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories WHERE name=@name", c); + selection.Parameters.AddWithValue("@name", name); + selection.Prepare(); + MySqlDataReader results = selection.ExecuteReader(); + + // Check if ticket exists in the database + if (!results.Read()) + { + message = null; + return false; + } + message = new Category(results); + results.Close(); + return true; + } + + public static bool AddCategory(string name, ulong categoryID) + { + try + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO categories (name,category_id) VALUES (@name, @category_id);", c); + cmd.Parameters.AddWithValue("@name", name); + cmd.Parameters.AddWithValue("@category_id", categoryID); + cmd.Prepare(); + return cmd.ExecuteNonQuery() > 0; + } + catch (MySqlException) + { + return false; + } + } + + public static bool RemoveCategory(ulong categoryID) + { + try + { + using MySqlConnection c = GetConnection(); + c.Open(); + using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM categories WHERE category_id=@category_id", c); + cmd.Parameters.AddWithValue("@category_id", categoryID); + cmd.Prepare(); + return cmd.ExecuteNonQuery() > 0; + } + catch (MySqlException) + { + return false; + } + } + + public class Ticket + { + public uint id; + public ulong creatorID; + public ulong assignedStaffID; + public string summary; + public ulong channelID; + + public Ticket(MySqlDataReader reader) + { + id = reader.GetUInt32("id"); + creatorID = reader.GetUInt64("creator_id"); + assignedStaffID = reader.GetUInt64("assigned_staff_id"); + summary = reader.GetString("summary"); + channelID = reader.GetUInt64("channel_id"); } - public class Ticket + public string DiscordRelativeTime() { - public uint id; - public DateTime createdTime; - public ulong creatorID; - public ulong assignedStaffID; - public string summary; - public ulong channelID; - - public Ticket(MySqlDataReader reader) - { - this.id = reader.GetUInt32("id"); - this.createdTime = reader.GetDateTime("created_time"); - this.creatorID = reader.GetUInt64("creator_id"); - this.assignedStaffID = reader.GetUInt64("assigned_staff_id"); - this.summary = reader.GetString("summary"); - this.channelID = reader.GetUInt64("channel_id"); - } - - public string FormattedCreatedTime() - { - return this.createdTime.ToString(Config.timestampFormat); - } + return Formatter.Timestamp(channelID.GetSnowflakeTime(), Config.timestampFormat); } - public class StaffMember - { - public ulong userID; - public string name; - public bool active; + } + public class StaffMember + { + public ulong userID; + public string name; + public bool active; - public StaffMember(MySqlDataReader reader) - { - this.userID = reader.GetUInt64("user_id"); - this.name = reader.GetString("name"); - this.active = reader.GetBoolean("active"); - } + public StaffMember(MySqlDataReader reader) + { + userID = reader.GetUInt64("user_id"); + name = reader.GetString("name"); + active = reader.GetBoolean("active"); } + } - public class Message + public class Message + { + public string identifier; + public ulong userID; + public string message; + + public Message(MySqlDataReader reader) { - public string identifier; - public ulong userID; - public string message; + identifier = reader.GetString("identifier"); + userID = reader.GetUInt64("user_id"); + message = reader.GetString("message"); + } + } - public Message(MySqlDataReader reader) - { - this.identifier = reader.GetString("identifier"); - this.userID = reader.GetUInt64("user_id"); - this.message = reader.GetString("message"); - } + public class Category + { + public string name; + public ulong id; + + public Category(MySqlDataReader reader) + { + name = reader.GetString("name"); + id = reader.GetUInt64("category_id"); } } } \ No newline at end of file diff --git a/SupportChild/EventHandler.cs b/SupportChild/EventHandler.cs index e5f61f8..27b35a6 100644 --- a/SupportChild/EventHandler.cs +++ b/SupportChild/EventHandler.cs @@ -2,223 +2,153 @@ using System.Collections.Generic; using System.Threading.Tasks; using DSharpPlus; -using DSharpPlus.CommandsNext; -using DSharpPlus.CommandsNext.Attributes; -using DSharpPlus.CommandsNext.Exceptions; using DSharpPlus.Entities; using DSharpPlus.EventArgs; using DSharpPlus.Exceptions; -using Microsoft.Extensions.Logging; +using DSharpPlus.SlashCommands; +using DSharpPlus.SlashCommands.Attributes; +using DSharpPlus.SlashCommands.EventArgs; +using SupportChild.Commands; -namespace SupportChild +namespace SupportChild; + +internal static class EventHandler { - internal class EventHandler + internal static Task OnReady(DiscordClient client, ReadyEventArgs e) { - private DiscordClient discordClient; + Logger.Log("Client is ready to process events."); - //DateTime for the end of the cooldown - private static Dictionary reactionTicketCooldowns = new Dictionary(); - - public EventHandler(DiscordClient client) + // Checking activity type + if (!Enum.TryParse(Config.presenceType, true, out ActivityType activityType)) { - this.discordClient = client; + Logger.Log("Presence type '" + Config.presenceType + "' invalid, using 'Playing' instead."); + activityType = ActivityType.Playing; } - internal Task OnReady(DiscordClient client, ReadyEventArgs e) + client.UpdateStatusAsync(new DiscordActivity(Config.presenceText, activityType), UserStatus.Online); + return Task.CompletedTask; + } + + internal static Task OnGuildAvailable(DiscordClient _, GuildCreateEventArgs e) + { + Logger.Log("Guild available: " + e.Guild.Name); + + IReadOnlyDictionary roles = e.Guild.Roles; + + foreach ((ulong roleID, DiscordRole role) in roles) { - discordClient.Logger.Log(LogLevel.Information, "Client is ready to process events."); + Logger.Log(role.Name.PadRight(40, '.') + roleID); + } + return Task.CompletedTask; + } - // Checking activity type - if (!Enum.TryParse(Config.presenceType, true, out ActivityType activityType)) - { - Console.WriteLine("Presence type '" + Config.presenceType + "' invalid, using 'Playing' instead."); - activityType = ActivityType.Playing; - } + internal static Task OnClientError(DiscordClient _, ClientErrorEventArgs e) + { + Logger.Error("Client exception occured:\n" + e.Exception); + switch (e.Exception) + { + case BadRequestException ex: + Logger.Error("JSON Message: " + ex.JsonMessage); + break; + } + return Task.CompletedTask; + } - this.discordClient.UpdateStatusAsync(new DiscordActivity(Config.presenceText, activityType), UserStatus.Online); - return Task.CompletedTask; + internal static async Task OnMessageCreated(DiscordClient client, MessageCreateEventArgs e) + { + if (e.Author.IsBot) + { + return; } - internal Task OnGuildAvailable(DiscordClient client, GuildCreateEventArgs e) + // Check if ticket exists in the database and ticket notifications are enabled + if (!Database.TryGetOpenTicket(e.Channel.Id, out Database.Ticket ticket) || !Config.ticketUpdatedNotifications) { - discordClient.Logger.Log(LogLevel.Information, $"Guild available: {e.Guild.Name}"); - - IReadOnlyDictionary roles = e.Guild.Roles; - - foreach ((ulong roleID, DiscordRole role) in roles) - { - discordClient.Logger.Log(LogLevel.Information, role.Name.PadRight(40, '.') + roleID); - } - return Task.CompletedTask; + return; } - internal Task OnClientError(DiscordClient client, ClientErrorEventArgs e) + // Sends a DM to the assigned staff member if at least a day has gone by since the last message and the user sending the message isn't staff + IReadOnlyList messages = await e.Channel.GetMessagesAsync(2); + if (messages.Count > 1 && messages[1].Timestamp < DateTimeOffset.UtcNow.AddDays(Config.ticketUpdatedNotificationDelay * -1) && !Database.IsStaff(e.Author.Id)) { - discordClient.Logger.Log(LogLevel.Error, $"Exception occured: {e.Exception.GetType()}: {e.Exception}"); - - return Task.CompletedTask; - } - - internal async Task OnMessageCreated(DiscordClient client, MessageCreateEventArgs e) - { - if (e.Author.IsBot) + try { - return; - } - - // Check if ticket exists in the database and ticket notifications are enabled - if (!Database.TryGetOpenTicket(e.Channel.Id, out Database.Ticket ticket) || !Config.ticketUpdatedNotifications) - { - return; - } - - // Sends a DM to the assigned staff member if at least a day has gone by since the last message and the user sending the message isn't staff - IReadOnlyList messages = await e.Channel.GetMessagesAsync(2); - if (messages.Count > 1 && messages[1].Timestamp < DateTimeOffset.UtcNow.AddDays(Config.ticketUpdatedNotificationDelay * -1) && !Database.IsStaff(e.Author.Id)) - { - try + DiscordMember staffMember = await e.Guild.GetMemberAsync(ticket.assignedStaffID); + await staffMember.SendMessageAsync(new DiscordEmbedBuilder { - DiscordEmbed message = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "A ticket you are assigned to has been updated: " + e.Channel.Mention - }; - - DiscordMember staffMember = await e.Guild.GetMemberAsync(ticket.assignedStaffID); - await staffMember.SendMessageAsync(message); - } - catch (NotFoundException) { } - catch (UnauthorizedException) { } + Color = DiscordColor.Green, + Description = "A ticket you are assigned to has been updated: " + e.Channel.Mention + }); } + catch (NotFoundException) { } + catch (UnauthorizedException) { } } + } - internal Task OnCommandError(CommandsNextExtension commandSystem, CommandErrorEventArgs e) + internal static async Task OnCommandError(SlashCommandsExtension commandSystem, SlashCommandErrorEventArgs e) + { + switch (e.Exception) { - switch (e.Exception) - { - case CommandNotFoundException _: - return Task.CompletedTask; - case ChecksFailedException _: + case SlashExecutionChecksFailedException checksFailedException: + { + foreach (SlashCheckBaseAttribute attr in checksFailedException.FailedChecks) { - foreach (CheckBaseAttribute attr in ((ChecksFailedException)e.Exception).FailedChecks) - { - DiscordEmbed error = new DiscordEmbedBuilder - { - Color = DiscordColor.Red, - Description = this.ParseFailedCheck(attr) - }; - e.Context?.Channel?.SendMessageAsync(error); - } - return Task.CompletedTask; - } - - default: - { - discordClient.Logger.Log(LogLevel.Error, $"Exception occured: {e.Exception.GetType()}: {e.Exception}"); - DiscordEmbed error = new DiscordEmbedBuilder + await e.Context.Channel.SendMessageAsync(new DiscordEmbedBuilder { Color = DiscordColor.Red, - Description = "Internal error occured, please report this to the developer." - }; - e.Context?.Channel?.SendMessageAsync(error); - return Task.CompletedTask; + Description = ParseFailedCheck(attr) + }); } - } + return; + } + + case BadRequestException ex: + Logger.Error("Command exception occured:\n" + e.Exception); + Logger.Error("JSON Message: " + ex.JsonMessage); + return; + + default: + { + Logger.Error("Exception occured: " + e.Exception.GetType() + ": " + e.Exception); + await e.Context.Channel.SendMessageAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "Internal error occured, please report this to the developer." + }); + return; + } + } + } + + internal static async Task OnMemberAdded(DiscordClient client, GuildMemberAddEventArgs e) + { + if (!Database.TryGetOpenTickets(e.Member.Id, out List ownTickets)) + { + return; } - internal async Task OnReactionAdded(DiscordClient client, MessageReactionAddEventArgs e) + foreach (Database.Ticket ticket in ownTickets) { - if (e.Message.Id != Config.reactionMessage) return; - - DiscordGuild guild = e.Message.Channel.Guild; - DiscordMember member = await guild.GetMemberAsync(e.User.Id); - - if (!Config.HasPermission(member, "new") || Database.IsBlacklisted(member.Id)) return; - if (reactionTicketCooldowns.ContainsKey(member.Id)) + try { - if (reactionTicketCooldowns[member.Id] > DateTime.Now) return; // cooldown has not expired - else reactionTicketCooldowns.Remove(member.Id); // cooldown exists but has expired, delete it - } - - - DiscordChannel category = guild.GetChannel(Config.ticketCategory); - DiscordChannel ticketChannel = await guild.CreateChannelAsync("ticket", ChannelType.Text, category); - - if (ticketChannel == null) return; - - ulong staffID = 0; - if (Config.randomAssignment) - { - staffID = Database.GetRandomActiveStaff(0)?.userID ?? 0; - } - - long id = Database.NewTicket(member.Id, staffID, ticketChannel.Id); - reactionTicketCooldowns.Add(member.Id, DateTime.Now.AddSeconds(10)); // add a cooldown which expires in 10 seconds - string ticketID = id.ToString("00000"); - - await ticketChannel.ModifyAsync(model => model.Name = "ticket-" + ticketID); - await ticketChannel.AddOverwriteAsync(member, Permissions.AccessChannels, Permissions.None); - await ticketChannel.SendMessageAsync("Hello, " + member.Mention + "!\n" + Config.welcomeMessage); - - // Remove user's reaction - await e.Message.DeleteReactionAsync(e.Emoji, e.User); - - // Refreshes the channel as changes were made to it above - ticketChannel = await SupportChild.GetClient().GetChannelAsync(ticketChannel.Id); - - if (staffID != 0) - { - DiscordEmbed assignmentMessage = new DiscordEmbedBuilder + DiscordChannel channel = await client.GetChannelAsync(ticket.channelID); + if (channel?.GuildId == e.Guild.Id) { - Color = DiscordColor.Green, - Description = "Ticket was randomly assigned to <@" + staffID + ">." - }; - await ticketChannel.SendMessageAsync(assignmentMessage); - - if (Config.assignmentNotifications) - { - DiscordEmbed message = new DiscordEmbedBuilder + await channel.SendMessageAsync(new DiscordEmbedBuilder { Color = DiscordColor.Green, - Description = "You have been randomly assigned to a newly opened support ticket: " + - ticketChannel.Mention - }; - - try - { - DiscordMember staffMember = await guild.GetMemberAsync(staffID); - await staffMember.SendMessageAsync(message); - } - catch (NotFoundException) - { - } - catch (UnauthorizedException) - { - } + Description = "User '" + e.Member.Username + "#" + e.Member.Discriminator + "' has rejoined the server, and has been re-added to the ticket." + }); } } - - // Log it if the log channel exists - DiscordChannel logChannel = guild.GetChannel(Config.logChannel); - if (logChannel != null) - { - DiscordEmbed logMessage = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Ticket " + ticketChannel.Mention + " opened by " + member.Mention + ".\n", - Footer = new DiscordEmbedBuilder.EmbedFooter {Text = "Ticket " + ticketID} - }; - await logChannel.SendMessageAsync(logMessage); - } + catch (Exception) { /* ignored */ } } + } - internal async Task OnMemberAdded(DiscordClient client, GuildMemberAddEventArgs e) + internal static async Task OnMemberRemoved(DiscordClient client, GuildMemberRemoveEventArgs e) + { + if (Database.TryGetOpenTickets(e.Member.Id, out List ownTickets)) { - if (!Database.TryGetOpenTickets(e.Member.Id, out List ownTickets)) - { - return; - } - foreach (Database.Ticket ticket in ownTickets) { try @@ -226,82 +156,120 @@ namespace SupportChild DiscordChannel channel = await client.GetChannelAsync(ticket.channelID); if (channel?.GuildId == e.Guild.Id) { - await channel.AddOverwriteAsync(e.Member, Permissions.AccessChannels, Permissions.None); - DiscordEmbed message = new DiscordEmbedBuilder() - .WithColor(DiscordColor.Green) - .WithDescription("User '" + e.Member.Username + "#" + e.Member.Discriminator + "' has rejoined the server, and has been re-added to the ticket."); - await channel.SendMessageAsync(message); + await channel.SendMessageAsync(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "User '" + e.Member.Username + "#" + e.Member.Discriminator + "' has left the server." + }); } } - catch (Exception) { } + catch (Exception) { /* ignored */ } } } - internal async Task OnMemberRemoved(DiscordClient client, Guild​Member​Remove​Event​Args e) + if (Database.TryGetAssignedTickets(e.Member.Id, out List assignedTickets) && Config.logChannel != 0) { - if (Database.TryGetOpenTickets(e.Member.Id, out List ownTickets)) + DiscordChannel logChannel = await client.GetChannelAsync(Config.logChannel); + if (logChannel != null) { - foreach(Database.Ticket ticket in ownTickets) + foreach (Database.Ticket ticket in assignedTickets) { try { DiscordChannel channel = await client.GetChannelAsync(ticket.channelID); if (channel?.GuildId == e.Guild.Id) { - DiscordEmbed message = new DiscordEmbedBuilder() - .WithColor(DiscordColor.Red) - .WithDescription("User '" + e.Member.Username + "#" + e.Member.Discriminator + "' has left the server."); - await channel.SendMessageAsync(message); - } - } - catch (Exception) { } - } - } - - if (Database.TryGetAssignedTickets(e.Member.Id, out List assignedTickets) && Config.logChannel != 0) - { - DiscordChannel logChannel = await client.GetChannelAsync(Config.logChannel); - if (logChannel != null) - { - - foreach (Database.Ticket ticket in assignedTickets) - { - try - { - DiscordChannel channel = await client.GetChannelAsync(ticket.channelID); - if (channel?.GuildId == e.Guild.Id) + await logChannel.SendMessageAsync(new DiscordEmbedBuilder { - DiscordEmbed message = new DiscordEmbedBuilder() - .WithColor(DiscordColor.Red) - .WithDescription("Assigned staff member '" + e.Member.Username + "#" + e.Member.Discriminator + "' has left the server: <#" + channel.Id + ">"); - await logChannel.SendMessageAsync(message); - } + Color = DiscordColor.Red, + Description = "Assigned staff member '" + e.Member.Username + "#" + e.Member.Discriminator + "' has left the server: <#" + channel.Id + ">" + }); } - catch (Exception) { } } + catch (Exception) { /* ignored */ } } } } - - private string ParseFailedCheck(CheckBaseAttribute attr) - { - switch (attr) - { - case CooldownAttribute _: - return "You cannot use do that so often!"; - case RequireOwnerAttribute _: - return "Only the server owner can use that command!"; - case RequirePermissionsAttribute _: - return "You don't have permission to do that!"; - case RequireRolesAttribute _: - return "You do not have a required role!"; - case RequireUserPermissionsAttribute _: - return "You don't have permission to do that!"; - case RequireNsfwAttribute _: - return "This command can only be used in an NSFW channel!"; - default: - return "Unknown Discord API error occured, please try again later."; - } - } + } + + internal static async Task OnComponentInteractionCreated(DiscordClient client, ComponentInteractionCreateEventArgs e) + { + try + { + switch (e.Interaction.Data.ComponentType) + { + case ComponentType.Button: + switch (e.Id) + { + case "supportchild_closeconfirm": + await CloseCommand.OnConfirmed(e.Interaction); + return; + case { } when e.Id.StartsWith("supportchild_newcommandbutton"): + await NewCommand.OnCategorySelection(e.Interaction); + return; + case { } when e.Id.StartsWith("supportchild_newticketbutton"): + await CreateButtonPanelCommand.OnButtonUsed(e.Interaction); + return; + case "right": + return; + case "left": + return; + case "rightskip": + return; + case "leftskip": + return; + case "stop": + return; + default: + Logger.Warn("Unknown button press received! '" + e.Id + "'"); + return; + } + case ComponentType.Select: + switch (e.Id) + { + case { } when e.Id.StartsWith("supportchild_newcommandselector"): + await NewCommand.OnCategorySelection(e.Interaction); + return; + case { } when e.Id.StartsWith("supportchild_newticketselector"): + await CreateSelectionBoxPanelCommand.OnSelectionMenuUsed(e.Interaction); + return; + default: + Logger.Warn("Unknown selection box option received! '" + e.Id + "'"); + return; + } + case ComponentType.ActionRow: + Logger.Warn("Unknown action row received! '" + e.Id + "'"); + return; + case ComponentType.FormInput: + Logger.Warn("Unknown form input received! '" + e.Id + "'"); + return; + default: + Logger.Warn("Unknown interaction type received! '" + e.Interaction.Data.ComponentType + "'"); + break; + } + } + catch (DiscordException ex) + { + Logger.Error("Interaction Exception occurred: " + ex); + Logger.Error("JsomMessage: " + ex.JsonMessage); + } + catch (Exception ex) + { + Logger.Error("Interaction Exception occured: " + ex.GetType() + ": " + ex); + } + } + + private static string ParseFailedCheck(SlashCheckBaseAttribute attr) + { + return attr switch + { + SlashRequireDirectMessageAttribute => "This command can only be used in direct messages!", + SlashRequireOwnerAttribute => "Only the server owner can use that command!", + SlashRequirePermissionsAttribute => "You don't have permission to do that!", + SlashRequireBotPermissionsAttribute => "The bot doesn't have the required permissions to do that!", + SlashRequireUserPermissionsAttribute => "You don't have permission to do that!", + SlashRequireGuildAttribute => "This command has to be used in a Discord server!", + _ => "Unknown Discord API error occured, please try again later." + }; } } \ No newline at end of file diff --git a/SupportChild/Logger.cs b/SupportChild/Logger.cs new file mode 100644 index 0000000..53d8f5e --- /dev/null +++ b/SupportChild/Logger.cs @@ -0,0 +1,68 @@ +using Microsoft.Extensions.Logging; +using System; +using System.Reflection; + +namespace SupportChild; + +public static class Logger +{ + public static void Debug(string message) + { + try + { + SupportChild.discordClient.Logger.Log(LogLevel.Debug, new EventId(420, Assembly.GetEntryAssembly()?.GetName().Name), message); + } + catch (NullReferenceException) + { + Console.WriteLine("[DEBUG] " + message); + } + } + + public static void Log(string message) + { + try + { + SupportChild.discordClient.Logger.Log(LogLevel.Information, new EventId(420, Assembly.GetEntryAssembly()?.GetName().Name), message); + } + catch (NullReferenceException) + { + Console.WriteLine("[INFO] " + message); + } + } + + public static void Warn(string message) + { + try + { + SupportChild.discordClient.Logger.Log(LogLevel.Warning, new EventId(420, Assembly.GetEntryAssembly()?.GetName().Name), message); + } + catch (NullReferenceException) + { + Console.WriteLine("[WARNING] " + message); + } + } + + public static void Error(string message) + { + try + { + SupportChild.discordClient.Logger.Log(LogLevel.Error, new EventId(420, Assembly.GetEntryAssembly()?.GetName().Name), message); + } + catch (NullReferenceException) + { + Console.WriteLine("[ERROR] " + message); + } + } + + public static void Fatal(string message) + { + try + { + SupportChild.discordClient.Logger.Log(LogLevel.Critical, new EventId(420, Assembly.GetEntryAssembly()?.GetName().Name), message); + } + catch (NullReferenceException) + { + Console.WriteLine("[CRITICAL] " + message); + } + } +} \ No newline at end of file diff --git a/SupportChild/Properties/Resources.Designer.cs b/SupportChild/Properties/Resources.Designer.cs index 4f8fc83..8cdb657 100644 --- a/SupportChild/Properties/Resources.Designer.cs +++ b/SupportChild/Properties/Resources.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -12,13 +11,6 @@ namespace SupportChild.Properties { using System; - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] @@ -32,9 +24,6 @@ namespace SupportChild.Properties { internal Resources() { } - /// - /// Returns the cached ResourceManager instance used by this class. - /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { @@ -46,10 +35,6 @@ namespace SupportChild.Properties { } } - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { @@ -60,9 +45,6 @@ namespace SupportChild.Properties { } } - /// - /// Looks up a localized resource of type System.Byte[]. - /// internal static byte[] default_config { get { object obj = ResourceManager.GetObject("default_config", resourceCulture); diff --git a/SupportChild/SupportChild.cs b/SupportChild/SupportChild.cs index 3f3c1b2..4af79a3 100644 --- a/SupportChild/SupportChild.cs +++ b/SupportChild/SupportChild.cs @@ -3,161 +3,148 @@ using System.IO; using System.Reflection; using System.Threading.Tasks; using DSharpPlus; -using DSharpPlus.CommandsNext; +using DSharpPlus.Interactivity; +using DSharpPlus.Interactivity.Enums; +using DSharpPlus.Interactivity.Extensions; +using DSharpPlus.SlashCommands; using Microsoft.Extensions.Logging; using SupportChild.Commands; -namespace SupportChild +namespace SupportChild; + +internal static class SupportChild { - internal class SupportChild + // Sets up a dummy client to use for logging + public static DiscordClient discordClient = new DiscordClient(new DiscordConfiguration { Token = "DUMMY_TOKEN", TokenType = TokenType.Bot, MinimumLogLevel = LogLevel.Debug }); + private static SlashCommandsExtension commands = null; + + private static void Main() { - internal static SupportChild instance; + MainAsync().GetAwaiter().GetResult(); + } - private DiscordClient discordClient = null; - private CommandsNextExtension commands = null; - private EventHandler eventHandler; - - static void Main(string[] args) + private static async Task MainAsync() + { + Logger.Log("Starting " + Assembly.GetEntryAssembly()?.GetName().Name + " version " + GetVersion() + "..."); + try { - new SupportChild().MainAsync().GetAwaiter().GetResult(); + Reload(); + + // Block this task until the program is closed. + await Task.Delay(-1); } - - private async Task MainAsync() + catch (Exception e) { - instance = this; - - Console.WriteLine("Starting SupportChild version " + GetVersion() + "..."); - try - { - this.Reload(); - - // Block this task until the program is closed. - await Task.Delay(-1); - } - catch (Exception e) - { - Console.WriteLine("Fatal error:"); - Console.WriteLine(e); - Console.ReadLine(); - } - } - - public static DiscordClient GetClient() - { - return instance.discordClient; - } - - public static string GetVersion() - { - Version version = Assembly.GetEntryAssembly()?.GetName().Version; - return version?.Major + "." + version?.Minor + "." + version?.Build + (version?.Revision == 0 ? "" : "-" + (char)(64 + version?.Revision ?? 0)); - } - - public async void Reload() - { - if (this.discordClient != null) - { - await this.discordClient.DisconnectAsync(); - this.discordClient.Dispose(); - Console.WriteLine("Discord client disconnected."); - } - - Console.WriteLine("Loading config \"" + Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "config.yml\""); - Config.LoadConfig(); - - // Check if token is unset - if (Config.token == "" || Config.token == "") - { - Console.WriteLine("You need to set your bot token in the config and start the bot again."); - throw new ArgumentException("Invalid Discord bot token"); - } - - // Database connection and setup - try - { - Console.WriteLine("Connecting to database... (" + Config.hostName + ":" + Config.port + ")"); - Database.SetConnectionString(Config.hostName, Config.port, Config.database, Config.username, Config.password); - Database.SetupTables(); - } - catch (Exception e) - { - Console.WriteLine("Could not set up database tables, please confirm connection settings, status of the server and permissions of MySQL user. Error: " + e); - throw; - } - - Console.WriteLine("Setting up Discord client..."); - - // Checking log level - if (!Enum.TryParse(Config.logLevel, true, out LogLevel logLevel)) - { - Console.WriteLine("Log level '" + Config.logLevel + "' invalid, using 'Information' instead."); - logLevel = LogLevel.Information; - } - - // Setting up client configuration - DiscordConfiguration cfg = new DiscordConfiguration - { - Token = Config.token, - TokenType = TokenType.Bot, - MinimumLogLevel = logLevel, - AutoReconnect = true, - Intents = DiscordIntents.All - }; - - this.discordClient = new DiscordClient(cfg); - - this.eventHandler = new EventHandler(this.discordClient); - - Console.WriteLine("Hooking events..."); - this.discordClient.Ready += this.eventHandler.OnReady; - this.discordClient.GuildAvailable += this.eventHandler.OnGuildAvailable; - this.discordClient.ClientErrored += this.eventHandler.OnClientError; - this.discordClient.MessageCreated += this.eventHandler.OnMessageCreated; - this.discordClient.GuildMemberAdded += this.eventHandler.OnMemberAdded; - this.discordClient.GuildMemberRemoved += this.eventHandler.OnMemberRemoved; - if (Config.reactionMessage != 0) - { - this.discordClient.MessageReactionAdded += this.eventHandler.OnReactionAdded; - } - - Console.WriteLine("Registering commands..."); - commands = discordClient.UseCommandsNext(new CommandsNextConfiguration - { - StringPrefixes = new []{ Config.prefix } - }); - - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - this.commands.RegisterCommands(); - - Console.WriteLine("Hooking command events..."); - this.commands.CommandErrored += this.eventHandler.OnCommandError; - - Console.WriteLine("Connecting to Discord..."); - await this.discordClient.ConnectAsync(); + Logger.Fatal("Fatal error:\n" + e); + Console.ReadLine(); } } + + public static string GetVersion() + { + Version version = Assembly.GetEntryAssembly()?.GetName().Version; + return version?.Major + "." + version?.Minor + "." + version?.Build + (version?.Revision == 0 ? "" : "-" + (char)(64 + version?.Revision ?? 0)); + } + + public static async void Reload() + { + if (discordClient != null) + { + await discordClient.DisconnectAsync(); + discordClient.Dispose(); + Logger.Log("Discord client disconnected."); + } + + Logger.Log("Loading config \"" + Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar + "config.yml\""); + Config.LoadConfig(); + + // Check if token is unset + if (Config.token is "" or "") + { + Logger.Fatal("You need to set your bot token in the config and start the bot again."); + throw new ArgumentException("Invalid Discord bot token"); + } + + // Database connection and setup + try + { + Logger.Log("Connecting to database... (" + Config.hostName + ":" + Config.port + ")"); + Database.SetConnectionString(Config.hostName, Config.port, Config.database, Config.username, Config.password); + Database.SetupTables(); + } + catch (Exception e) + { + Logger.Fatal("Could not set up database tables, please confirm connection settings, status of the server and permissions of MySQL user. Error: " + e); + throw; + } + + Logger.Log("Setting up Discord client..."); + + // Setting up client configuration + DiscordConfiguration cfg = new DiscordConfiguration + { + Token = Config.token, + TokenType = TokenType.Bot, + MinimumLogLevel = Config.logLevel, + AutoReconnect = true, + Intents = DiscordIntents.All + }; + + discordClient = new DiscordClient(cfg); + + Logger.Log("Hooking events..."); + discordClient.Ready += EventHandler.OnReady; + discordClient.GuildAvailable += EventHandler.OnGuildAvailable; + discordClient.ClientErrored += EventHandler.OnClientError; + discordClient.MessageCreated += EventHandler.OnMessageCreated; + discordClient.GuildMemberAdded += EventHandler.OnMemberAdded; + discordClient.GuildMemberRemoved += EventHandler.OnMemberRemoved; + discordClient.ComponentInteractionCreated += EventHandler.OnComponentInteractionCreated; + + discordClient.UseInteractivity(new InteractivityConfiguration + { + AckPaginationButtons = true, + PaginationBehaviour = PaginationBehaviour.Ignore, + PaginationDeletion = PaginationDeletion.DeleteMessage, + Timeout = TimeSpan.FromMinutes(15) + }); + + Logger.Log("Registering commands..."); + commands = discordClient.UseSlashCommands(); + + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + commands.RegisterCommands(); + + Logger.Log("Hooking command events..."); + commands.SlashCommandErrored += EventHandler.OnCommandError; + + Logger.Log("Connecting to Discord..."); + await discordClient.ConnectAsync(); + } } \ No newline at end of file diff --git a/SupportChild/SupportChild.csproj b/SupportChild/SupportChild.csproj index 5b31187..ea42f5f 100644 --- a/SupportChild/SupportChild.csproj +++ b/SupportChild/SupportChild.csproj @@ -5,7 +5,7 @@ ellie_icon.ico net6.0 win-x64;linux-x64 - 1.2.0 + SupportChild.SupportChild EmotionChild @@ -13,58 +13,58 @@ https://github.com/EmotionChild/SupportChild Git LICENSE - https://cdn.emotionchild.com/Ellie.png - A Discord support bot build for the Ellie's Home Discord server - 2.6.1.1 - 2.6.1.1 + https://cdn.discordapp.com/attachments/765441543100170271/914327948667011132/Ellie_Concept_2_transparent_ver.png + A Discord support ticket bot built for the Ellie's home server en - 1.2.0 + 3.0.0.1 + 1.3.0 + 3.0.0.1 + 3.0.0.1 + + + + full - + + - + - + + - - True - True - Resources.resx - + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + - - ResXFileCodeGenerator - Resources.Designer.cs - + + lib\DiscordChatExporter.Core.dll + - - True - - + + True + True + Resources.resx + - - - - - - - lib\DiscordChatExporter.Core.dll - - - - + \ No newline at end of file diff --git a/SupportChild/Transcriber.cs b/SupportChild/Transcriber.cs index 9b10e62..0fd0d5c 100644 --- a/SupportChild/Transcriber.cs +++ b/SupportChild/Transcriber.cs @@ -3,60 +3,51 @@ using System.Threading.Tasks; using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord.Data; -using DiscordChatExporter.Core.Exceptions; using DiscordChatExporter.Core.Exporting; using DiscordChatExporter.Core.Exporting.Filtering; using DiscordChatExporter.Core.Exporting.Partitioning; -using DiscordChatExporter.Core.Utils.Extensions; -namespace SupportChild +namespace SupportChild; + +internal static class Transcriber { - internal static class Transcriber - { - internal static async Task ExecuteAsync(ulong channelID, uint ticketID) - { - DiscordClient discordClient = new DiscordClient(new AuthToken(AuthTokenKind.Bot, Config.token)); - ChannelExporter Exporter = new ChannelExporter(discordClient); + internal static async Task ExecuteAsync(ulong channelID, uint ticketID) + { + DiscordClient discordClient = new DiscordClient(Config.token); + ChannelExporter exporter = new ChannelExporter(discordClient); - if (!Directory.Exists("./transcripts")) - { - Directory.CreateDirectory("./transcripts"); - } + if (!Directory.Exists("./transcripts")) + { + Directory.CreateDirectory("./transcripts"); + } - string dateFormat = "yyyy-MMM-dd HH:mm"; + Channel channel = await discordClient.GetChannelAsync(new Snowflake(channelID)); + Guild guild = await discordClient.GetGuildAsync(channel.GuildId); - // Configure settings - if (Config.timestampFormat != "") - dateFormat = Config.timestampFormat; + ExportRequest request = new ExportRequest( + Guild: guild, + Channel: channel, + OutputPath: GetPath(ticketID), + Format: ExportFormat.HtmlDark, + After: null, + Before: null, + PartitionLimit: PartitionLimit.Null, + MessageFilter: MessageFilter.Null, + ShouldDownloadMedia: false, + ShouldReuseMedia: false, + DateFormat: "yyyy-MMM-dd HH:mm" + ); - Channel channel = await discordClient.GetChannelAsync(new Snowflake(channelID)); - Guild guild = await discordClient.GetGuildAsync(channel.GuildId); + await exporter.ExportChannelAsync(request); + } - ExportRequest request = new ExportRequest( - guild: guild, - channel: channel, - outputPath: GetPath(ticketID), - format: ExportFormat.HtmlDark, - after: null, - before: null, - partitionLimit: PartitionLimit.Null, - messageFilter: MessageFilter.Null, - shouldDownloadMedia: false, - shouldReuseMedia: false, - dateFormat: dateFormat - ); + internal static string GetPath(uint ticketNumber) + { + return "./transcripts/" + GetFilename(ticketNumber); + } - await Exporter.ExportChannelAsync(request); - } - - internal static string GetPath(uint ticketNumber) - { - return "./transcripts/" + GetFilename(ticketNumber); - } - - internal static string GetFilename(uint ticketNumber) - { - return "ticket-" + ticketNumber.ToString("00000") + ".html"; - } - } + internal static string GetFilename(uint ticketNumber) + { + return "ticket-" + ticketNumber.ToString("00000") + ".html"; + } } \ No newline at end of file diff --git a/SupportChild/Utilities.cs b/SupportChild/Utilities.cs index 06099d1..b554cfc 100644 --- a/SupportChild/Utilities.cs +++ b/SupportChild/Utilities.cs @@ -1,71 +1,65 @@ using System; using System.Collections.Generic; -using System.Security.Cryptography; +using System.Threading.Tasks; using DSharpPlus.Entities; -namespace SupportChild +namespace SupportChild; + +public static class Utilities { - public static class Utilities - { - public static List RandomizeList(List list) - { - RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider(); - int n = list.Count; - while (n > 1) - { - byte[] box = new byte[1]; - do provider.GetBytes(box); - while (!(box[0] < n * (Byte.MaxValue / n))); - int k = (box[0] % n); - n--; - T value = list[k]; - list[k] = list[n]; - list[n] = value; - } + private static readonly Random rng = new Random(); - return list; - } + public static void Shuffle(this IList list) + { + int n = list.Count; + while (n > 1) + { + n--; + int k = rng.Next(n + 1); + (list[k], list[n]) = (list[n], list[k]); + } + } - public static string[] ParseIDs(string args) - { - if (string.IsNullOrEmpty(args)) - { - return new string[0]; - } - return args.Trim().Replace("<@!", "").Replace("<@", "").Replace(">", "").Split(); - } + public static LinkedList ParseListIntoMessages(List listItems) + { + LinkedList messages = new LinkedList(); - public static LinkedList ParseListIntoMessages(List listItems) - { - LinkedList messages = new LinkedList(); + foreach (string listItem in listItems) + { + if (messages.Last?.Value?.Length + listItem?.Length < 2048) + { + messages.Last.Value += listItem; + } + else + { + messages.AddLast(listItem); + } + } - foreach (string listItem in listItems) - { - if (messages.Last?.Value?.Length + listItem?.Length < 2048) - { - messages.Last.Value += listItem; - } - else - { - messages.AddLast(listItem); - } - } + return messages; + } - return messages; - } + public static async Task> GetVerifiedChannels() + { + List verifiedCategories = new List(); + foreach (Database.Category category in Database.GetAllCategories()) + { + DiscordChannel channel = null; + try + { + channel = await SupportChild.discordClient.GetChannelAsync(category.id); + } + catch (Exception) { /*ignored*/ } - public static DiscordRole GetRoleByName(DiscordGuild guild, string Name) - { - Name = Name.Trim().ToLower(); - foreach (DiscordRole role in guild.Roles.Values) - { - if (role.Name.ToLower().StartsWith(Name)) - { - return role; - } - } - - return null; - } - } + if (channel != null) + { + verifiedCategories.Add(category); + } + else + { + Logger.Warn("Category '" + category.name + "' (" + category.id + ") no longer exists! Ignoring..."); + } + } + return verifiedCategories; + } } \ No newline at end of file diff --git a/SupportChild/default_config.yml b/SupportChild/default_config.yml index 8dcb6e8..b24019a 100644 --- a/SupportChild/default_config.yml +++ b/SupportChild/default_config.yml @@ -1,84 +1,48 @@ bot: - # Bot token. - token: "" - # Command prefix. - prefix: "-" - # Channel where ticket logs are posted (recommended) - log-channel: 000000000000000000 - # Category where the ticket will be created, it will have the same permissions of that ticket plus read permissions for the user opening the ticket (recommended) - ticket-category: 000000000000000000 - # A message which will open new tickets when users react to it (optional) - reaction-message: 000000000000000000 - # Message posted when a ticket is opened. - welcome-message: "Please describe your issue below, and include all information needed for us to help you." - # Decides what messages are shown in console - # Possible values are: Critical, Error, Warning, Information, Debug. - console-log-level: "Information" - # Format for timestamps in transcripts and google sheets if used - timestamp-format: "yyyy-MM-dd HH:mm" - # Whether or not 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. - # This can be useful if you have admins set as inactive to not automatically recieve tickets and then have moderators elevate tickets when needed. - random-assign-role-override: true - # Sets the type of activity for the bot to display in its presence status - # Possible values are: Playing, Streaming, ListeningTo, Watching, Competing - presence-type: "ListeningTo" - # Sets the activity text shown in the bot's status - presence-text: "-new" + # Bot token. + token: "" + # Channel where ticket logs are posted (recommended) + log-channel: 000000000000000000 + # Message posted when a ticket is opened. + welcome-message: "Please describe your issue below, and include all information needed for us to take action." + # Decides what messages are shown in console + # Possible values are: Critical, Error, Warning, Information, Debug. + console-log-level: "Information" + # One of the following: LongDate, LongDateTime, LongTime, RelativeTime, ShortDate, ShortDateTime, ShortTime + # 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. + 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. + # This can be useful if you have admins set as inactive to not automatically receive tickets and then have moderators elevate tickets when needed. + random-assign-role-override: true + # Sets the type of activity for the bot to display in its presence status + # Possible values are: Playing, Streaming, ListeningTo, Watching, Competing + presence-type: "ListeningTo" + # Sets the activity text shown in the bot's status + presence-text: "/new" + # Set to true if you want the /new command to show a selection box instead of a series of buttons + new-command-uses-selector: false + # Number of tickets a single user can have open at a time, staff members are excluded from this + ticket-limit: 5 notifications: - # Notifies the assigned staff member when a new message is posted in a ticket if the ticket has been silent for a configurable amount of time - # Other staff members and bots do not trigger this. - ticket-updated: true - # The above notification will only be sent if the ticket has been silent for more than this amount of days. Default is 0.5 days. - ticket-updated-delay: 0.5 - # Notifies staff when they are assigned to tickets - assignment: true - # Notifies the user opening the ticket that their ticket was closed and includes the transcript - closing: true + # Notifies the assigned staff member when a new message is posted in a ticket if the ticket has been silent for a configurable amount of time + # Other staff members and bots do not trigger this. + ticket-updated: true + # The above notification will only be sent if the ticket has been silent for more than this amount of days. Default is 0.5 days. + ticket-updated-delay: 0.5 + # Notifies staff when they are assigned to tickets + assignment: true + # Notifies the user opening the ticket that their ticket was closed and includes the transcript + closing: true database: - # Address and port of the mysql server - address: "127.0.0.1" - port: 3306 - # Name of the database to use - name: "supportchild" - # Username and password for authentication - user: "" - password: "" - -# Set up which roles are allowed to use different commands. -# Example: -# new: [ 000000000000000000, 111111111111111111 ] -# They are grouped into suggested command groups below for first time setup. -permissions: - # Public commands - close: [] - list: [] - new: [] - say: [] - status: [] - summary: [] - transcript: [] - # Moderator commands - add: [] - addmessage: [] - assign: [] - blacklist: [] - listassigned: [] - listoldest: [] - listunassigned: [] - move: [] - rassign: [] - removemessage: [] - setsummary: [] - toggleactive: [] - unassign: [] - unblacklist: [] - # Admin commands - addstaff: [] - reload: [] - removestaff: [] - setticket: [] - unsetticket: [] \ No newline at end of file + # Address and port of the mysql server + address: "127.0.0.1" + port: 3306 + # Name of the database to use + name: "supportchild" + # Username and password for authentication + user: "" + password: "" \ No newline at end of file diff --git a/SupportChild/lib/DiscordChatExporter.Core.dll b/SupportChild/lib/DiscordChatExporter.Core.dll index e27417e..938808d 100644 Binary files a/SupportChild/lib/DiscordChatExporter.Core.dll and b/SupportChild/lib/DiscordChatExporter.Core.dll differ