Compare commits

..

No commits in common. "main" and "4.0.0-RC1" have entirely different histories.

39 changed files with 613 additions and 337 deletions

View file

@ -74,6 +74,22 @@ public class AddCategoryCommand
}, true); }, true);
} }
await LogChannel.Success(command.User.Mention + " added `" + category.Name + "` as `" + title + "` to the category list."); try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.User.Mention + " added `" + category.Name + "` to the category list.",
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Category: " + title
}
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
} }

View file

@ -62,7 +62,24 @@ public class AddCommand
Description = "Added " + member.Mention + " to ticket." Description = "Added " + member.Mention + " to ticket."
}); });
await LogChannel.Success(member.Mention + " was added to " + command.Channel.Mention + " by " + command.User.Mention + ".", ticket.id); try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = member.Mention + " was added to " + command.Channel.Mention +
" by " + command.User.Mention + ".",
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + ticket.id.ToString("00000")
}
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
catch (Exception) catch (Exception)
{ {

View file

@ -1,5 +1,4 @@
using System.ComponentModel; using System.ComponentModel;
using System.Globalization;
using DSharpPlus.Entities; using DSharpPlus.Entities;
using System.Threading.Tasks; using System.Threading.Tasks;
using DSharpPlus.Commands; using DSharpPlus.Commands;
@ -28,7 +27,7 @@ public class AddMessageCommand
return; return;
} }
if (Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _)) if (Database.TryGetMessage(identifier.ToLower(), out Database.Message _))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -55,6 +54,22 @@ public class AddMessageCommand
}, true); }, true);
} }
await LogChannel.Success(command.User.Mention + " added or updated `" + identifier + "` in the /say command.\n\nContent:\n\n" + message); try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.User.Mention + " added or updated `" + identifier + "` in the /say command.\n\nContent:\n\n" + message,
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Identifier: " + identifier
}
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
} }

View file

@ -43,10 +43,8 @@ public class AddStaffCommand
return; return;
} }
bool alreadyStaff = Database.IsStaff(staffMember.Id);
await using MySqlConnection c = Database.GetConnection(); await using MySqlConnection c = Database.GetConnection();
MySqlCommand cmd = alreadyStaff ? 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); 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(); c.Open();
cmd.Parameters.AddWithValue("@user_id", staffMember.Id); cmd.Parameters.AddWithValue("@user_id", staffMember.Id);
@ -54,23 +52,25 @@ public class AddStaffCommand
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
cmd.Dispose(); cmd.Dispose();
if (alreadyStaff)
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = staffMember.Mention + " is already a staff member, refreshed username in database."
}, true);
}
else
{
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
Color = DiscordColor.Green, Color = DiscordColor.Green,
Description = staffMember.Mention + " was added to staff." Description = staffMember.Mention + " was added to staff."
}, true); });
await LogChannel.Success(staffMember.Mention + " was added to staff by " + command.User.Mention + "."); try
{
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = staffMember.Mention + " was added to staff by " + command.User.Mention + "."
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
} }
} }
} }

View file

@ -18,7 +18,7 @@ public class AdminCommands
[Command("setticket")] [Command("setticket")]
[Description("Turns a channel into a ticket. WARNING: Anyone will be able to delete the channel using /close.")] [Description("Turns a channel into a ticket. WARNING: Anyone will be able to delete the channel using /close.")]
public async Task SetTicket(SlashCommandContext command, public async Task SetTicket(SlashCommandContext command,
[Parameter("user")][Description("(Optional) The owner of the ticket.")] DiscordUser user = null) [Parameter("user")] [Description("(Optional) The owner of the ticket.")] DiscordUser user = null)
{ {
// Check if ticket exists in the database // Check if ticket exists in the database
if (Database.IsOpenTicket(command.Channel.Id)) if (Database.IsOpenTicket(command.Channel.Id))
@ -40,14 +40,31 @@ public class AdminCommands
Description = "Channel has been designated ticket " + id.ToString("00000") + "." Description = "Channel has been designated ticket " + id.ToString("00000") + "."
}); });
await LogChannel.Success(command.Channel.Mention + " has been designated ticket " + id.ToString("00000") + " by " + command.Member?.Mention + ".", (uint)id); try
{
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.Channel.Mention + " has been designated ticket " + id.ToString("00000") + " by " + command.Member?.Mention + ".",
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + id.ToString("00000")
}
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
[RequireGuild] [RequireGuild]
[Command("unsetticket")] [Command("unsetticket")]
[Description("Deletes a ticket from the ticket system without deleting the channel.")] [Description("Deletes a ticket from the ticket system without deleting the channel.")]
public async Task UnsetTicket(SlashCommandContext command, public async Task UnsetTicket(SlashCommandContext command,
[Parameter("ticket-id")][Description("(Optional) Ticket to unset. Uses the channel you are in by default. Use ticket ID, not channel ID!")] long ticketID = 0) [Parameter("ticket-id")] [Description("(Optional) Ticket to unset. Uses the channel you are in by default. Use ticket ID, not channel ID!")] long ticketID = 0)
{ {
Database.Ticket ticket; Database.Ticket ticket;
DiscordChannel channel = null; DiscordChannel channel = null;
@ -104,7 +121,24 @@ public class AdminCommands
Description = "Channel has been undesignated as a ticket." Description = "Channel has been undesignated as a ticket."
}); });
await LogChannel.Success(command.Channel.Mention + " has been undesignated as a ticket by " + command.User.Mention + ".", ticket.id); try
{
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.Channel.Mention + " has been undesignated as a ticket by " + command.User.Mention + ".",
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + ticket.id.ToString("00000")
}
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
else else
{ {
@ -125,9 +159,21 @@ public class AdminCommands
{ {
Color = DiscordColor.Green, Color = DiscordColor.Green,
Description = "Reloading bot application..." Description = "Reloading bot application..."
}, true); });
await LogChannel.Success(command.Channel.Mention + " reloaded the bot."); try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.Channel.Mention + " reloaded the bot.",
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
Logger.Log("Reloading bot..."); Logger.Log("Reloading bot...");
await SupportChild.Reload(); await SupportChild.Reload();

View file

@ -89,9 +89,26 @@ public class AssignCommand
Description = "You have been assigned to a support ticket: " + command.Channel.Mention Description = "You have been assigned to a support ticket: " + command.Channel.Mention
}); });
} }
catch (UnauthorizedException) { /* ignore */ } catch (UnauthorizedException) { }
} }
await LogChannel.Success(member.Mention + " was assigned to " + command.Channel.Mention + " by " + command.User.Mention + ".", ticket.id); try
{
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = member.Mention + " was assigned to " + command.Channel.Mention + " by " + command.User.Mention + ".",
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + ticket.id.ToString("00000")
}
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
} }

View file

@ -35,7 +35,20 @@ public class BlacklistCommand
Description = "Blocked " + user.Mention + " from opening new tickets." Description = "Blocked " + user.Mention + " from opening new tickets."
}, true); }, true);
await LogChannel.Success(user.Mention + " was blocked from opening tickets by " + command.User.Mention + "."); try
{
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = user.Mention + " was blocked from opening tickets by " + command.User.Mention + "."
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
catch (Exception) catch (Exception)
{ {

View file

@ -141,12 +141,28 @@ public class CloseCommand
try try
{ {
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read); await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read);
await LogChannel.Success("Ticket " + ticket.id.ToString("00000") + " closed by " + interaction.User.Mention + ".\n" + closeReason, ticket.id, new Utilities.File(fileName, file)); DiscordMessageBuilder message = new DiscordMessageBuilder();
} message.AddEmbed(new DiscordEmbedBuilder
catch (Exception e)
{ {
Logger.Error("Error occurred sending transcript log message. ", e); Color = DiscordColor.Green,
Description = "Ticket " + ticket.id.ToString("00000") + " closed by " +
interaction.User.Mention + ".\n" + closeReason,
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + ticket.id.ToString("00000")
}
});
message.AddFiles(new Dictionary<string, Stream> { { fileName, file } });
await logChannel.SendMessageAsync(message);
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
} }
if (Config.closingNotifications) if (Config.closingNotifications)
@ -184,12 +200,13 @@ public class CloseCommand
}); });
} }
message.AddFiles(new Dictionary<string, Stream> { { fileName, file } }); message.AddFiles(new Dictionary<string, Stream> { { fileName, file } });
await staffMember.SendMessageAsync(message); await staffMember.SendMessageAsync(message);
} }
catch (NotFoundException) { /* ignore */ } catch (NotFoundException) { }
catch (UnauthorizedException) { /* ignore */ } catch (UnauthorizedException) { }
} }
Database.ArchiveTicket(ticket); Database.ArchiveTicket(ticket);

View file

@ -51,6 +51,18 @@ public class CreateButtonPanelCommand
Description = "Successfully created message, make sure to run this command again if you add new categories to the bot." Description = "Successfully created message, make sure to run this command again if you add new categories to the bot."
}, true); }, true);
await LogChannel.Success(command.User.Mention + " created a new button panel in " + command.Channel.Mention + "."); try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.User.Mention + " created a new button panel in " + command.Channel.Mention + "."
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
} }

View file

@ -16,7 +16,7 @@ public class CreateSelectionBoxPanelCommand
[Command("createselectionboxpanel")] [Command("createselectionboxpanel")]
[Description("Creates a selection box which users can use to open new tickets in specific categories.")] [Description("Creates a selection box which users can use to open new tickets in specific categories.")]
public async Task OnExecute(SlashCommandContext command, public async Task OnExecute(SlashCommandContext command,
[Parameter("placeholder")][Description("(Optional) The message to show in the selection box.")] string message = null) [Parameter("message")][Description("(Optional) The message to show in the selection box.")] string message = null)
{ {
DiscordMessageBuilder builder = new DiscordMessageBuilder() DiscordMessageBuilder builder = new DiscordMessageBuilder()
.WithContent(" ") .WithContent(" ")
@ -29,7 +29,19 @@ public class CreateSelectionBoxPanelCommand
Description = "Successfully created message, make sure to run this command again if you add new categories to the bot." Description = "Successfully created message, make sure to run this command again if you add new categories to the bot."
}, true); }, true);
await LogChannel.Success(command.User.Mention + " created a new selector panel in " + command.Channel.Mention + "."); try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.User.Mention + " created a new selector panel in " + command.Channel.Mention + "."
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
public static async Task<List<DiscordSelectComponent>> GetSelectComponents(SlashCommandContext command, string placeholder) public static async Task<List<DiscordSelectComponent>> GetSelectComponents(SlashCommandContext command, string placeholder)

View file

@ -45,7 +45,23 @@ public class InterviewCommands
}, true); }, true);
} }
await LogChannel.Success(command.User.Mention + " restarted interview in " + command.Channel.Mention + ".", ticket.id); try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.User.Mention + " restarted interview in " + command.Channel.Mention + ".",
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + ticket.id.ToString("00000")
}
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
[Command("stop")] [Command("stop")]
@ -91,6 +107,22 @@ public class InterviewCommands
}, true); }, true);
} }
await LogChannel.Success(command.User.Mention + " stopped the interview in " + command.Channel.Mention + ".", ticket.id); try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.User.Mention + " stopped the interview in " + command.Channel.Mention + ".",
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + ticket.id.ToString("00000")
}
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
} }

View file

@ -43,7 +43,7 @@ public class InterviewTemplateCommands
{ {
string defaultTemplate = string defaultTemplate =
"{\n" + "{\n" +
" \"category-id\": " + category.Id + ",\n" + " \"category-id\": \"" + category.Id + "\",\n" +
" \"interview\":\n" + " \"interview\":\n" +
" {\n" + " {\n" +
" \"message\": \"\",\n" + " \"message\": \"\",\n" +
@ -147,7 +147,7 @@ public class InterviewTemplateCommands
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
Color = DiscordColor.Green, Color = DiscordColor.Green,
Description = "Uploaded interview template for `" + category.Name + "`." Description = "Uploaded interview template."
}, true); }, true);
} }
else else
@ -168,12 +168,18 @@ public class InterviewTemplateCommands
try try
{ {
MemoryStream memStream = new(Encoding.UTF8.GetBytes(Database.GetInterviewTemplateJSON(template.categoryID))); MemoryStream memStream = new(Encoding.UTF8.GetBytes(Database.GetInterviewTemplateJSON(template.categoryID)));
await LogChannel.Success(command.User.Mention + " uploaded a new interview template for the `" + category.Name + "` category.", 0,
new Utilities.File("interview-template-" + template.categoryID + ".json", memStream)); // Log it if the log channel exists
} DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
catch (Exception e) await logChannel.SendMessageAsync(new DiscordMessageBuilder().AddEmbed(new DiscordEmbedBuilder
{ {
Logger.Error("Unable to log interview template upload.", e); Color = DiscordColor.Green,
Description = command.User.Mention + " uploaded a new interview template for the `" + category.Name + "` category."
}).AddFile("interview-template-" + template.categoryID + ".json", memStream));
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
} }
} }
catch (Exception e) catch (Exception e)
@ -235,7 +241,19 @@ public class InterviewTemplateCommands
Description = "Deleted interview template." Description = "Deleted interview template."
}, true); }, true);
await LogChannel.Success(command.User.Mention + " deleted the interview template for the `" + category.Name + "` category.", 0, try
new Utilities.File("interview-template-" + category.Id + ".json", memStream)); {
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordMessageBuilder().AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.User.Mention + " deleted the interview template for the `" + category.Name + "` category."
}).AddFile("interview-template-" + category.Id + ".json", memStream));
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
} }

View file

@ -16,7 +16,7 @@ public class ListCommand
[Command("list")] [Command("list")]
[Description("Lists tickets opened by a user.")] [Description("Lists tickets opened by a user.")]
public async Task OnExecute(SlashCommandContext command, public async Task OnExecute(SlashCommandContext command,
[Parameter("user")][Description("(Optional) The user whose tickets to get, yourself by default.")] DiscordUser user = null) [Parameter("user")][Description("(Optional) The user to get tickets by, yourself by default.")] DiscordUser user = null)
{ {
DiscordUser listUser = user == null ? command.User : user; DiscordUser listUser = user == null ? command.User : user;

View file

@ -105,6 +105,22 @@ public class MoveCommand
Description = "Ticket was moved to `" + categoryChannel.Name + "`." Description = "Ticket was moved to `" + categoryChannel.Name + "`."
}); });
await LogChannel.Success(command.User.Mention + " moved " + command.Channel.Mention + " to `" + categoryChannel.Name + "`.", ticket.id); try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.User.Mention + " moved " + command.Channel.Mention + " to `" + categoryChannel.Name + "`.",
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + ticket.id.ToString("00000")
}
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
} }

View file

@ -11,6 +11,7 @@ using DSharpPlus.Exceptions;
using SupportChild.Interviews; using SupportChild.Interviews;
namespace SupportChild.Commands; namespace SupportChild.Commands;
public class NewCommand public class NewCommand
{ {
[RequireGuild] [RequireGuild]
@ -133,11 +134,6 @@ public class NewCommand
return (false, "Error: Could not find the category to place the ticket in."); return (false, "Error: Could not find the category to place the ticket in.");
} }
if (category.Children.Count == 50)
{
return (false, "This ticket category is full, can not create ticket channel.");
}
DiscordMember member = null; DiscordMember member = null;
try try
{ {
@ -156,10 +152,7 @@ public class NewCommand
{ {
ticketChannel = await category.Guild.CreateChannelAsync("ticket", DiscordChannelType.Text, category); ticketChannel = await category.Guild.CreateChannelAsync("ticket", DiscordChannelType.Text, category);
} }
catch (Exception e) catch (Exception) { /* ignored */ }
{
Logger.Error("Error occured while creating ticket.", e);
}
if (ticketChannel == null) if (ticketChannel == null)
{ {
@ -167,13 +160,13 @@ public class NewCommand
"!\nIs the channel limit reached in the server or ticket category?"); "!\nIs the channel limit reached in the server or ticket category?");
} }
DiscordMember assignedStaff = null; ulong staffID = 0;
if (Config.randomAssignment) if (Config.randomAssignment)
{ {
assignedStaff = await RandomAssignCommand.GetRandomVerifiedStaffMember(ticketChannel, userID, 0, null); staffID = Database.GetRandomActiveStaff(0)?.userID ?? 0;
} }
long id = Database.NewTicket(member.Id, assignedStaff?.Id ?? 0, ticketChannel.Id); long id = Database.NewTicket(member.Id, staffID, ticketChannel.Id);
try try
{ {
await ticketChannel.ModifyAsync(modifiedAttributes => modifiedAttributes.Name = "ticket-" + id.ToString("00000")); await ticketChannel.ModifyAsync(modifiedAttributes => modifiedAttributes.Name = "ticket-" + id.ToString("00000"));
@ -181,7 +174,7 @@ public class NewCommand
catch (DiscordException e) catch (DiscordException e)
{ {
Logger.Error("Exception occurred trying to modify channel: " + e); Logger.Error("Exception occurred trying to modify channel: " + e);
Logger.Error("JsonMessage: " + e.JsonMessage); Logger.Error("JsomMessage: " + e.JsonMessage);
} }
try try
@ -191,7 +184,7 @@ public class NewCommand
catch (DiscordException e) catch (DiscordException e)
{ {
Logger.Error("Exception occurred trying to add channel permissions: " + e); Logger.Error("Exception occurred trying to add channel permissions: " + e);
Logger.Error("JsonMessage: " + e.JsonMessage); Logger.Error("JsomMessage: " + e.JsonMessage);
} }
DiscordMessage message = await ticketChannel.SendMessageAsync("Hello, " + member.Mention + "!\n" + Config.welcomeMessage); DiscordMessage message = await ticketChannel.SendMessageAsync("Hello, " + member.Mention + "!\n" + Config.welcomeMessage);
@ -216,19 +209,20 @@ public class NewCommand
await Interviewer.StartInterview(ticketChannel); await Interviewer.StartInterview(ticketChannel);
} }
if (assignedStaff != null) if (staffID != 0)
{ {
await ticketChannel.SendMessageAsync(new DiscordEmbedBuilder await ticketChannel.SendMessageAsync(new DiscordEmbedBuilder
{ {
Color = DiscordColor.Green, Color = DiscordColor.Green,
Description = "Ticket was randomly assigned to " + assignedStaff.Mention + "." Description = "Ticket was randomly assigned to <@" + staffID + ">."
}); });
if (Config.assignmentNotifications) if (Config.assignmentNotifications)
{ {
try try
{ {
await assignedStaff.SendMessageAsync(new DiscordEmbedBuilder DiscordMember staffMember = await category.Guild.GetMemberAsync(staffID);
await staffMember.SendMessageAsync(new DiscordEmbedBuilder
{ {
Color = DiscordColor.Green, Color = DiscordColor.Green,
Description = "You have been randomly assigned to a newly opened support ticket: " + Description = "You have been randomly assigned to a newly opened support ticket: " +
@ -238,12 +232,30 @@ public class NewCommand
catch (DiscordException e) catch (DiscordException e)
{ {
Logger.Error("Exception occurred assign random staff member: " + e); Logger.Error("Exception occurred assign random staff member: " + e);
Logger.Error("JsonMessage: " + e.JsonMessage); Logger.Error("JsomMessage: " + e.JsonMessage);
} }
} }
} }
await LogChannel.Success("Ticket " + ticketChannel.Mention + " opened by " + member.Mention + ".", (uint)id); try
{
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = "Ticket " + ticketChannel.Mention + " opened by " + member.Mention + ".",
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + id.ToString("00000")
}
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
return (true, "Ticket opened, " + member.Mention + "!\n" + ticketChannel.Mention); return (true, "Ticket opened, " + member.Mention + "!\n" + ticketChannel.Mention);
} }
} }

View file

@ -31,14 +31,9 @@ public class RandomAssignCommand
} }
// Get a random staff member who is verified to have the correct role if applicable // Get a random staff member who is verified to have the correct role if applicable
DiscordMember staffMember = await GetRandomVerifiedStaffMember(command.Channel, ticket.creatorID, ticket.assignedStaffID, role); DiscordMember staffMember = await GetRandomVerifiedStaffMember(command, role, ticket);
if (staffMember == null) if (staffMember == null)
{ {
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "Error: Could not find an applicable staff member with access to this channel."
}, true);
return; return;
} }
@ -71,56 +66,85 @@ public class RandomAssignCommand
Description = "You have been randomly assigned to a support ticket: " + command.Channel.Mention Description = "You have been randomly assigned to a support ticket: " + command.Channel.Mention
}); });
} }
catch (UnauthorizedException) { /* ignore */ } catch (UnauthorizedException) { }
} }
await LogChannel.Success(staffMember.Mention + " was randomly assigned to " + command.Channel.Mention + " by " + command.User.Mention + ".", ticket.id); try
}
internal static async Task<DiscordMember> GetRandomVerifiedStaffMember(DiscordChannel channel, ulong creatorID, ulong currentStaffID, DiscordRole targetRole)
{ {
List<Database.StaffMember> staffMembers; // Log it if the log channel exists
ulong[] ignoredUserIDs = [creatorID, currentStaffID]; DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
if (targetRole == null)
{ {
// No role was specified, any active staff will be picked Color = DiscordColor.Green,
staffMembers = Database.GetActiveStaff(ignoredUserIDs); Description = staffMember.Mention + " was randomly assigned to " + command.Channel.Mention + " by " + command.User.Mention + ".",
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + ticket.id.ToString("00000")
} }
else });
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
}
private static async Task<DiscordMember> GetRandomVerifiedStaffMember(SlashCommandContext command, DiscordRole targetRole, Database.Ticket ticket)
{
if (targetRole != null) // A role was provided
{ {
// Check if role rassign should override staff's active status // Check if role rassign should override staff's active status
staffMembers = Config.randomAssignRoleOverride List<Database.StaffMember> staffMembers = Config.randomAssignRoleOverride
? Database.GetAllStaff(ignoredUserIDs) ? Database.GetAllStaff(ticket.assignedStaffID, ticket.creatorID)
: Database.GetActiveStaff(ignoredUserIDs); : Database.GetActiveStaff(ticket.assignedStaffID, ticket.creatorID);
}
// Randomize the list before checking for roles in order to reduce number of API calls // Randomize the list before checking for roles in order to reduce number of API calls
staffMembers.Shuffle(); staffMembers.Shuffle();
// Get the first staff member that has the role // Get the first staff member that has the role
foreach (Database.StaffMember staffMember in staffMembers) foreach (Database.StaffMember sm in staffMembers)
{ {
try try
{ {
DiscordMember verifiedMember = await channel.Guild.GetMemberAsync(staffMember.userID); DiscordMember verifiedMember = await command.Guild.GetMemberAsync(sm.userID);
if (verifiedMember?.Roles?.Any(role => role.Id == targetRole.Id) ?? false)
// If a role is set filter to only members with that role
if (targetRole == null || verifiedMember.Roles.Any(role => role.Id == targetRole.Id))
{
// Only assign staff members with access to this channel
if (verifiedMember.PermissionsIn(channel).HasFlag(DiscordPermissions.AccessChannels))
{ {
return verifiedMember; return verifiedMember;
} }
} }
}
catch (Exception e) catch (Exception e)
{ {
Logger.Error("Error occured trying to find a staff member for random assignment. User ID: " + staffMember.userID, 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.RespondAsync(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.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "Error: Could not find an applicable staff member."
}, true);
return null; return null;
} }
} }

View file

@ -43,6 +43,19 @@ public class RemoveCategoryCommand
}, true); }, true);
} }
await LogChannel.Success("`" + category.Name + "` was removed by " + command.User.Mention + "."); try
{
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = "`" + category.Name + "` was removed by " + command.User.Mention + "."
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
} }

View file

@ -1,5 +1,4 @@
using System.ComponentModel; using System.ComponentModel;
using System.Globalization;
using DSharpPlus.Entities; using DSharpPlus.Entities;
using System.Threading.Tasks; using System.Threading.Tasks;
using DSharpPlus.Commands; using DSharpPlus.Commands;
@ -17,7 +16,7 @@ public class RemoveMessageCommand
public async Task OnExecute(SlashCommandContext command, public async Task OnExecute(SlashCommandContext command,
[Parameter("identifier")][Description("The identifier word used in the /say command.")] string identifier) [Parameter("identifier")][Description("The identifier word used in the /say command.")] string identifier)
{ {
if (!Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _)) if (!Database.TryGetMessage(identifier.ToLower(), out Database.Message _))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -44,6 +43,18 @@ public class RemoveMessageCommand
}, true); }, true);
} }
await LogChannel.Success("`" + identifier + "` was removed from the /say command by " + command.User.Mention + "."); try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = "`" + identifier + "` was removed from the /say command by " + command.User.Mention + "."
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
} }

View file

@ -1,11 +1,10 @@
using System; using System.ComponentModel;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks; using System.Threading.Tasks;
using DSharpPlus.Commands; using DSharpPlus.Commands;
using DSharpPlus.Commands.ContextChecks; using DSharpPlus.Commands.ContextChecks;
using DSharpPlus.Commands.Processors.SlashCommands; using DSharpPlus.Commands.Processors.SlashCommands;
using DSharpPlus.Entities; using DSharpPlus.Entities;
using DSharpPlus.Exceptions;
using MySqlConnector; using MySqlConnector;
namespace SupportChild.Commands; namespace SupportChild.Commands;
@ -28,31 +27,6 @@ public class RemoveStaffCommand
return; return;
} }
await command.DeferResponseAsync(true);
if (Database.TryGetAssignedTickets(user.Id, out List<Database.Ticket> assignedTickets))
{
foreach (Database.Ticket assignedTicket in assignedTickets)
{
Database.UnassignStaff(assignedTicket);
try
{
DiscordChannel ticketChannel = await SupportChild.client.GetChannelAsync(assignedTicket.channelID);
await ticketChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = "Unassigned <@" + assignedTicket.assignedStaffID + "> from ticket."
});
}
catch (Exception e)
{
Logger.Error("Error when trying to send message about unassigning staff member from ticket-" + assignedTicket.id.ToString("00000"), e);
}
await LogChannel.Success("<@" + assignedTicket.assignedStaffID + "> was unassigned from <#" + assignedTicket.channelID + "> by " + command.User.Mention + ".", assignedTicket.id);
}
}
await using MySqlConnection c = Database.GetConnection(); await using MySqlConnection c = Database.GetConnection();
c.Open(); c.Open();
MySqlCommand deletion = new MySqlCommand(@"DELETE FROM staff WHERE user_id=@user_id", c); MySqlCommand deletion = new MySqlCommand(@"DELETE FROM staff WHERE user_id=@user_id", c);
@ -66,6 +40,19 @@ public class RemoveStaffCommand
Description = "User was removed from staff." Description = "User was removed from staff."
}, true); }, true);
await LogChannel.Success(user.Mention + " was removed from staff by " + command.User.Mention + "."); try
{
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = user.Mention + " was removed from staff by " + command.User.Mention + "."
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
} }

View file

@ -1,7 +1,6 @@
using DSharpPlus.Entities; using DSharpPlus.Entities;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Globalization;
using System.Threading.Tasks; using System.Threading.Tasks;
using DSharpPlus.Commands; using DSharpPlus.Commands;
using DSharpPlus.Commands.ContextChecks; using DSharpPlus.Commands.ContextChecks;
@ -23,12 +22,11 @@ public class SayCommand
// Print list of all messages if no identifier is provided // Print list of all messages if no identifier is provided
if (identifier == null) if (identifier == null)
{ {
await SendMessageList(command); SendMessageList(command);
return; return;
} }
if (!Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), if (!Database.TryGetMessage(identifier.ToLower(), out Database.Message message))
out Database.Message message))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -44,10 +42,22 @@ public class SayCommand
Description = message.message.Replace("\\n", "\n") Description = message.message.Replace("\\n", "\n")
}); });
await LogChannel.Success(command.User.Mention + " posted the " + message.identifier + " message in " + command.Channel.Mention + "."); try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.User.Mention + " posted the " + message.identifier + " message in " + command.Channel.Mention + "."
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
private static async Task SendMessageList(SlashCommandContext command) private static async void SendMessageList(SlashCommandContext command)
{ {
List<Database.Message> messages = Database.GetAllMessages(); List<Database.Message> messages = Database.GetAllMessages();
if (messages.Count == 0) if (messages.Count == 0)

View file

@ -43,6 +43,22 @@ public class SetSummaryCommand
Description = "Summary set." Description = "Summary set."
}, true); }, true);
await LogChannel.Success(command.User.Mention + " set the summary for " + command.Channel.Mention + " to:\n\n" + summary, ticket.id); try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.User.Mention + " set the summary for " + command.Channel.Mention + " to:\n\n" + summary,
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + ticket.id.ToString("00000")
}
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
} }

View file

@ -25,7 +25,7 @@ public class StatusCommand
.AddField("Open tickets:", openTickets + "", true) .AddField("Open tickets:", openTickets + "", true)
.AddField("Closed tickets:", closedTickets + " ", true) .AddField("Closed tickets:", closedTickets + " ", true)
.AddField("Report bugs:", "[Toastielab Issues](https://toastielab.dev/Emotions-stuff/SupportChild/issues)", true) .AddField("Report bugs:", "[Toastielab Issues](https://toastielab.dev/Emotions-stuff/SupportChild/issues)", true)
.AddField("Commands:", "[Toastielab Repository](https://toastielab.dev/Emotions-stuff/SupportChild/src/branch/main/docs/Commands.md)", true); .AddField("Commands:", "[Toastielab Repository](https://toastielab.dev/Emotions-stuff/SupportChild)", true);
await command.RespondAsync(botInfo); await command.RespondAsync(botInfo);
} }
} }

View file

@ -58,13 +58,30 @@ public class ToggleActiveCommand
}, true); }, true);
} }
try
{
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
if (user != null && user.Id != command.User.Id) if (user != null && user.Id != command.User.Id)
{ {
await LogChannel.Success(staffUser.Mention + " set " + command.Channel.Mention + "'s status to " + (staffMember.active ? "active" : "inactive")); await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = staffUser.Mention + " set " + command.Channel.Mention + "'s status to " + (staffMember.active ? "active" : "inactive")
});
} }
else else
{ {
await LogChannel.Success(staffUser.Mention + " set their own status to " + (staffMember.active ? "active" : "inactive")); await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = staffUser.Mention + " set their own status to " + (staffMember.active ? "active" : "inactive")
});
}
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
} }
} }
} }

View file

@ -121,12 +121,27 @@ public class TranscriptCommand
try try
{ {
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read); await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read);
await LogChannel.Success("Transcript generated by " + command.User.Mention + ".", ticket.id, new Utilities.File(fileName, file));
} DiscordMessageBuilder message = new DiscordMessageBuilder();
catch (Exception e) message.AddEmbed(new DiscordEmbedBuilder
{ {
Logger.Error("Failed to log transcript generation.", e); Color = DiscordColor.Green,
Description = "Transcript generated by " + command.User.Mention + ".",
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + ticket.id.ToString("00000")
}
});
message.AddFiles(new Dictionary<string, Stream> { { fileName, file } });
await logChannel.SendMessageAsync(message);
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
} }
if (await SendDirectMessage(command, fileName, filePath, zipSize, ticket.id)) if (await SendDirectMessage(command, fileName, filePath, zipSize, ticket.id))

View file

@ -39,9 +39,26 @@ public class UnassignCommand
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
Color = DiscordColor.Green, Color = DiscordColor.Green,
Description = "Unassigned <@" + ticket.assignedStaffID + "> from ticket." Description = "Unassigned staff member from ticket."
}); });
await LogChannel.Success("<@" + ticket.assignedStaffID + "> was unassigned from <#" + ticket.channelID + "> by " + command.User.Mention + ".", ticket.id); try
{
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = "Staff member was unassigned from " + command.Channel.Mention + " by " + command.User.Mention + ".",
Footer = new DiscordEmbedBuilder.EmbedFooter
{
Text = "Ticket: " + ticket.id.ToString("00000")
}
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
} }

View file

@ -34,7 +34,20 @@ public class UnblacklistCommand
Description = "Unblocked " + user.Mention + " from opening new tickets." Description = "Unblocked " + user.Mention + " from opening new tickets."
}, true); }, true);
await LogChannel.Success(user.Mention + " was unblocked from opening tickets by " + command.User.Mention + "."); try
{
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = user.Mention + " was unblocked from opening tickets by " + command.User.Mention + "."
});
}
catch (NotFoundException)
{
Logger.Error("Could not send message in log channel.");
}
} }
catch (Exception) catch (Exception)
{ {

View file

@ -20,7 +20,6 @@ internal static class Config
internal static bool newCommandUsesSelector = false; internal static bool newCommandUsesSelector = false;
internal static int ticketLimit = 5; internal static int ticketLimit = 5;
internal static bool pinFirstMessage = false; internal static bool pinFirstMessage = false;
internal static string transcriptDir = "";
internal static bool ticketUpdatedNotifications = false; internal static bool ticketUpdatedNotifications = false;
internal static double ticketUpdatedNotificationDelay = 0.0; internal static double ticketUpdatedNotificationDelay = 0.0;
@ -92,7 +91,6 @@ internal static class Config
newCommandUsesSelector = json.SelectToken("bot.new-command-uses-selector")?.Value<bool>() ?? false; newCommandUsesSelector = json.SelectToken("bot.new-command-uses-selector")?.Value<bool>() ?? false;
ticketLimit = json.SelectToken("bot.ticket-limit")?.Value<int>() ?? 5; ticketLimit = json.SelectToken("bot.ticket-limit")?.Value<int>() ?? 5;
pinFirstMessage = json.SelectToken("bot.pin-first-message")?.Value<bool>() ?? false; pinFirstMessage = json.SelectToken("bot.pin-first-message")?.Value<bool>() ?? false;
transcriptDir = json.SelectToken("bot.transcript-dir")?.Value<string>() ?? "";
ticketUpdatedNotifications = json.SelectToken("notifications.ticket-updated")?.Value<bool>() ?? false; ticketUpdatedNotifications = json.SelectToken("notifications.ticket-updated")?.Value<bool>() ?? false;
ticketUpdatedNotificationDelay = json.SelectToken("notifications.ticket-updated-delay")?.Value<double>() ?? 0.0; ticketUpdatedNotificationDelay = json.SelectToken("notifications.ticket-updated-delay")?.Value<double>() ?? 0.0;

View file

@ -7,6 +7,8 @@ using MySqlConnector;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
using SupportChild.Interviews; using SupportChild.Interviews;
using SupportChild.Interviews;
using SupportChild;
namespace SupportChild; namespace SupportChild;
@ -466,6 +468,12 @@ public static class Database
} }
} }
public static StaffMember GetRandomActiveStaff(params ulong[] ignoredUserIDs)
{
List<StaffMember> staffMembers = GetActiveStaff(ignoredUserIDs);
return staffMembers.Any() ? staffMembers[RandomNumberGenerator.GetInt32(staffMembers.Count)] : null;
}
public static List<StaffMember> GetActiveStaff(params ulong[] ignoredUserIDs) public static List<StaffMember> GetActiveStaff(params ulong[] ignoredUserIDs)
{ {
using MySqlConnection c = GetConnection(); using MySqlConnection c = GetConnection();

View file

@ -18,8 +18,6 @@ namespace SupportChild;
public static class EventHandler public static class EventHandler
{ {
internal static bool hasLoggedGuilds = false;
public static Task OnReady(DiscordClient client, GuildDownloadCompletedEventArgs e) public static Task OnReady(DiscordClient client, GuildDownloadCompletedEventArgs e)
{ {
Logger.Log("Connected to Discord."); Logger.Log("Connected to Discord.");
@ -37,12 +35,6 @@ public static class EventHandler
public static async Task OnGuildAvailable(DiscordClient discordClient, GuildAvailableEventArgs e) public static async Task OnGuildAvailable(DiscordClient discordClient, GuildAvailableEventArgs e)
{ {
if (hasLoggedGuilds)
{
return;
}
hasLoggedGuilds = true;
Logger.Log("Found Discord server: " + e.Guild.Name + " (" + e.Guild.Id + ")"); Logger.Log("Found Discord server: " + e.Guild.Name + " (" + e.Guild.Id + ")");
if (SupportChild.commandLineArgs.serversToLeave.Contains(e.Guild.Id)) if (SupportChild.commandLineArgs.serversToLeave.Contains(e.Guild.Id))
@ -112,8 +104,8 @@ public static class EventHandler
Description = "A ticket you are assigned to has been updated: " + e.Channel.Mention Description = "A ticket you are assigned to has been updated: " + e.Channel.Mention
}); });
} }
catch (NotFoundException) { /* ignored */ } catch (NotFoundException) { }
catch (UnauthorizedException) { /* ignored */ } catch (UnauthorizedException) { }
} }
} }
@ -174,7 +166,10 @@ public static class EventHandler
} }
} }
if (LogChannel.IsEnabled && Database.TryGetAssignedTickets(e.Member.Id, out List<Database.Ticket> assignedTickets)) if (Database.TryGetAssignedTickets(e.Member.Id, out List<Database.Ticket> assignedTickets) && Config.logChannel != 0)
{
DiscordChannel logChannel = await client.GetChannelAsync(Config.logChannel);
if (logChannel != null)
{ {
foreach (Database.Ticket ticket in assignedTickets) foreach (Database.Ticket ticket in assignedTickets)
{ {
@ -183,13 +178,18 @@ public static class EventHandler
DiscordChannel channel = await client.GetChannelAsync(ticket.channelID); DiscordChannel channel = await client.GetChannelAsync(ticket.channelID);
if (channel?.GuildId == e.Guild.Id) if (channel?.GuildId == e.Guild.Id)
{ {
await LogChannel.Warn("Assigned staff member '" + e.Member.Username + "#" + e.Member.Discriminator + "' has left the server: <#" + channel.Id + ">"); await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "Assigned staff member '" + e.Member.Username + "#" + e.Member.Discriminator + "' has left the server: <#" + channel.Id + ">"
});
} }
} }
catch (Exception) { /* ignored */ } catch (Exception) { /* ignored */ }
} }
} }
} }
}
public static async Task OnComponentInteractionCreated(DiscordClient client, ComponentInteractionCreatedEventArgs e) public static async Task OnComponentInteractionCreated(DiscordClient client, ComponentInteractionCreatedEventArgs e)
{ {
@ -245,41 +245,45 @@ public static class EventHandler
Logger.Warn("Unknown form input received! '" + e.Id + "'"); Logger.Warn("Unknown form input received! '" + e.Id + "'");
return; return;
case DiscordComponentType.UserSelect: case DiscordComponentType.UserSelect:
if (e.Id.StartsWith("supportchild_interviewuserselector")) switch (e.Id)
{ {
case not null when e.Id.StartsWith("supportchild_interviewuserselector"):
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction); await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
return; return;
} default:
Logger.Warn("Unknown selection box option received! '" + e.Id + "'"); Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
return; return;
}
case DiscordComponentType.RoleSelect: case DiscordComponentType.RoleSelect:
if (e.Id.StartsWith("supportchild_interviewroleselector")) switch (e.Id)
{ {
case not null when e.Id.StartsWith("supportchild_interviewroleselector"):
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction); await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
return; return;
} default:
Logger.Warn("Unknown selection box option received! '" + e.Id + "'"); Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
return; return;
}
case DiscordComponentType.MentionableSelect: case DiscordComponentType.MentionableSelect:
if (e.Id.StartsWith("supportchild_interviewmentionableselector")) switch (e.Id)
{ {
case not null when e.Id.StartsWith("supportchild_interviewmentionableselector"):
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction); await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
return; return;
} default:
Logger.Warn("Unknown selection box option received! '" + e.Id + "'"); Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
return; return;
}
case DiscordComponentType.ChannelSelect: case DiscordComponentType.ChannelSelect:
if (e.Id.StartsWith("supportchild_interviewchannelselector")) switch (e.Id)
{ {
case not null when e.Id.StartsWith("supportchild_interviewchannelselector"):
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction); await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
return; return;
} default:
Logger.Warn("Unknown selection box option received! '" + e.Id + "'"); Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
return; return;
}
default: default:
Logger.Warn("Unknown interaction type received! '" + e.Interaction.Data.ComponentType + "'"); Logger.Warn("Unknown interaction type received! '" + e.Interaction.Data.ComponentType + "'");
break; break;
@ -288,7 +292,7 @@ public static class EventHandler
catch (DiscordException ex) catch (DiscordException ex)
{ {
Logger.Error("Interaction Exception occurred: " + ex); Logger.Error("Interaction Exception occurred: " + ex);
Logger.Error("JsonMessage: " + ex.JsonMessage); Logger.Error("JsomMessage: " + ex.JsonMessage);
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -168,7 +168,7 @@
{ {
"category-id": "category-id":
{ {
"type": "number", "type": "string",
"title": "Category ID", "title": "Category ID",
"description": "The id of the category this template applies to. You can change this and re-upload the template to apply it to a different category." "description": "The id of the category this template applies to. You can change this and re-upload the template to apply it to a different category."
}, },

10
Jenkinsfile vendored
View file

@ -10,16 +10,12 @@ pipeline {
parallel { parallel {
stage('Linux') { stage('Linux') {
steps { steps {
sh 'dotnet publish -r linux-x64 -c Release -p:PublishTrimmed=true --self-contained true --no-restore --output Linux-x64/' sh 'dotnet publish -r linux-x64 -c Release --self-contained true --no-restore --output Linux-x64/'
sh 'mv Linux-x64/SupportChild Linux-x64/SupportChild-SC'
sh 'dotnet publish -r linux-x64 -c Release --self-contained false --no-restore --output Linux-x64/'
} }
} }
stage('Windows') { stage('Windows') {
steps { steps {
sh 'dotnet publish -r win-x64 -c Release -p:PublishTrimmed=true --self-contained true --no-restore --output Windows-x64/' sh 'dotnet publish -r win-x64 -c Release --self-contained true --no-restore --output Windows-x64/'
sh 'mv Windows-x64/SupportChild.exe Windows-x64/SupportChild-SC.exe'
sh 'dotnet publish -r win-x64 -c Release --self-contained false --no-restore --output Windows-x64/'
} }
} }
} }
@ -29,13 +25,11 @@ pipeline {
stage('Linux') { stage('Linux') {
steps { steps {
archiveArtifacts(artifacts: 'Linux-x64/SupportChild', caseSensitive: true) archiveArtifacts(artifacts: 'Linux-x64/SupportChild', caseSensitive: true)
archiveArtifacts(artifacts: 'Linux-x64/SupportChild-SC', caseSensitive: true)
} }
} }
stage('Windows') { stage('Windows') {
steps { steps {
archiveArtifacts(artifacts: 'Windows-x64/SupportChild.exe', caseSensitive: true) archiveArtifacts(artifacts: 'Windows-x64/SupportChild.exe', caseSensitive: true)
archiveArtifacts(artifacts: 'Windows-x64/SupportChild-SC.exe', caseSensitive: true)
} }
} }
} }

View file

@ -1,74 +0,0 @@
using System;
using System.Threading.Tasks;
using DSharpPlus.Entities;
using DSharpPlus.Exceptions;
namespace SupportChild;
public static class LogChannel
{
public static bool IsEnabled => Config.logChannel != 0;
public static async Task Log(string message, uint ticketID = 0, Utilities.File file = null)
{
await Log(DiscordColor.Cyan, message, ticketID, file);
}
public static async Task Success(string message, uint ticketID = 0, Utilities.File file = null)
{
await Log(DiscordColor.Green, message, ticketID, file);
}
public static async Task Warn(string message, uint ticketID = 0, Utilities.File file = null)
{
await Log(DiscordColor.Orange, message, ticketID, file);
}
public static async Task Error(string message, uint ticketID = 0, Utilities.File file = null)
{
await Log(DiscordColor.Red, message, ticketID, file);
}
private static async Task Log(DiscordColor color, string message, uint ticketID = 0, Utilities.File file = null)
{
if (!IsEnabled)
{
return;
}
try
{
DiscordEmbedBuilder embedBuilder = new()
{
Color = color,
Description = message
};
if (ticketID != 0)
{
embedBuilder.WithFooter("Ticket: " + ticketID.ToString("00000"));
}
DiscordMessageBuilder messageBuilder = new();
messageBuilder.AddEmbed(embedBuilder);
if (file != null)
{
messageBuilder.AddFile(file.fileName, file.contents);
}
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(messageBuilder);
}
catch (NotFoundException)
{
Logger.Error("Log channel does not exist. Channel ID: " + Config.logChannel);
}
catch (UnauthorizedException)
{
Logger.Error("No permissions to send message in log channel. Channel ID: " + Config.logChannel);
}
catch (Exception e)
{
Logger.Error("Error occured trying to send message in log channel. Channel ID: " + Config.logChannel, e);
}
}
}

View file

@ -1,3 +1 @@
[![Build Status](https://ci.toastielab.dev/buildStatus/icon?job=Toastie-stuff%2FSupportChild%2Fmain)](https://ci.toastielab.dev/job/Toastie-stuff/job/SupportChild/job/main/)
# SupportChild # SupportChild

View file

@ -27,6 +27,7 @@ internal static class SupportChild
"config", "config",
Required = false, Required = false,
HelpText = "Select a config file to use.", HelpText = "Select a config file to use.",
Default = "config.yml",
MetaValue = "PATH")] MetaValue = "PATH")]
public string configPath { get; set; } public string configPath { get; set; }
@ -34,6 +35,7 @@ internal static class SupportChild
"transcripts", "transcripts",
Required = false, Required = false,
HelpText = "Select directory to store transcripts in.", HelpText = "Select directory to store transcripts in.",
Default = "./transcripts",
MetaValue = "PATH")] MetaValue = "PATH")]
public string transcriptDir { get; set; } public string transcriptDir { get; set; }
@ -224,7 +226,6 @@ internal static class SupportChild
client = clientBuilder.Build(); client = clientBuilder.Build();
Logger.Log("Connecting to Discord..."); Logger.Log("Connecting to Discord...");
EventHandler.hasLoggedGuilds = false;
await client.ConnectAsync(); await client.ConnectAsync();
return true; return true;
} }

View file

@ -10,7 +10,7 @@
<PublishSingleFile>true</PublishSingleFile> <PublishSingleFile>true</PublishSingleFile>
<TrimMode>partial</TrimMode> <TrimMode>partial</TrimMode>
<Authors>Toastie_t0ast</Authors> <Authors>EmotionChild</Authors>
<Description>A Discord support ticket bot built for the Ellie's home server</Description> <Description>A Discord support ticket bot built for the Ellie's home server</Description>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
@ -36,7 +36,7 @@
<PackageReference Include="DSharpPlus.Commands" Version="5.0.0-nightly-02405" /> <PackageReference Include="DSharpPlus.Commands" Version="5.0.0-nightly-02405" />
<PackageReference Include="DSharpPlus.Interactivity" Version="5.0.0-nightly-02405" /> <PackageReference Include="DSharpPlus.Interactivity" Version="5.0.0-nightly-02405" />
<PackageReference Include="EmbeddedBuildTime" Version="1.0.3" /> <PackageReference Include="EmbeddedBuildTime" Version="1.0.3" />
<PackageReference Include="GitInfo" Version="3.3.5"> <PackageReference Include="GitInfo" Version="3.5.0">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>

View file

@ -13,11 +13,23 @@ namespace SupportChild;
internal static class Transcriber internal static class Transcriber
{ {
private static string transcriptDir = "./transcripts"; // TODO: Should be local variable (or come from the config class) when added to config to avoid race conditions when reloading
internal static async Task ExecuteAsync(ulong channelID, uint ticketID) internal static async Task ExecuteAsync(ulong channelID, uint ticketID)
{ {
DiscordClient discordClient = new DiscordClient(Config.token); DiscordClient discordClient = new DiscordClient(Config.token);
ChannelExporter exporter = new ChannelExporter(discordClient); ChannelExporter exporter = new ChannelExporter(discordClient);
if (!string.IsNullOrEmpty(SupportChild.commandLineArgs.transcriptDir))
{
transcriptDir = SupportChild.commandLineArgs.transcriptDir;
}
if (!Directory.Exists(transcriptDir))
{
Directory.CreateDirectory(transcriptDir);
}
string htmlPath = GetHtmlPath(ticketID); string htmlPath = GetHtmlPath(ticketID);
string zipPath = GetZipPath(ticketID); string zipPath = GetZipPath(ticketID);
string assetDirPath = GetAssetDirPath(ticketID); string assetDirPath = GetAssetDirPath(ticketID);
@ -76,10 +88,10 @@ internal static class Transcriber
{ {
using (ZipArchive zip = ZipFile.Open(zipPath, ZipArchiveMode.Create)) using (ZipArchive zip = ZipFile.Open(zipPath, ZipArchiveMode.Create))
{ {
zip.CreateEntryFromFile(htmlPath, htmlFilename, CompressionLevel.SmallestSize); zip.CreateEntryFromFile(htmlPath, htmlFilename);
foreach (string assetFile in assetFiles) foreach (string assetFile in assetFiles)
{ {
zip.CreateEntryFromFile(assetFile, assetDirName + "/" + Path.GetFileName(assetFile), CompressionLevel.SmallestSize); zip.CreateEntryFromFile(assetFile, assetDirName + "/" + Path.GetFileName(assetFile));
} }
} }
@ -87,39 +99,19 @@ internal static class Transcriber
} }
} }
private static string GetTranscriptDir()
{
string transcriptDir = "./transcripts";
if (!string.IsNullOrEmpty(SupportChild.commandLineArgs.transcriptDir))
{
transcriptDir = SupportChild.commandLineArgs.transcriptDir;
}
else if (!string.IsNullOrEmpty(Config.transcriptDir))
{
transcriptDir = Config.transcriptDir;
}
if (!Directory.Exists(transcriptDir))
{
Directory.CreateDirectory(transcriptDir);
}
return transcriptDir;
}
internal static string GetHtmlPath(uint ticketNumber) internal static string GetHtmlPath(uint ticketNumber)
{ {
return GetTranscriptDir() + "/" + GetHTMLFilename(ticketNumber); return transcriptDir + "/" + GetHTMLFilename(ticketNumber);
} }
internal static string GetZipPath(uint ticketNumber) internal static string GetZipPath(uint ticketNumber)
{ {
return GetTranscriptDir() + "/" + GetZipFilename(ticketNumber); return transcriptDir + "/" + GetZipFilename(ticketNumber);
} }
internal static string GetAssetDirPath(uint ticketNumber) internal static string GetAssetDirPath(uint ticketNumber)
{ {
return GetTranscriptDir() + "/" + GetAssetDirName(ticketNumber); return transcriptDir + "/" + GetAssetDirName(ticketNumber);
} }
internal static string GetAssetDirName(uint ticketNumber) internal static string GetAssetDirName(uint ticketNumber)

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -15,7 +14,6 @@ public static class Extensions
{ {
return needles.Any(haystack.Contains); return needles.Any(haystack.Contains);
} }
public static bool ContainsAny(this string haystack, params char[] needles) public static bool ContainsAny(this string haystack, params char[] needles)
{ {
return needles.Any(haystack.Contains); return needles.Any(haystack.Contains);
@ -26,12 +24,6 @@ public static class Utilities
{ {
private static readonly Random rng = new Random(); private static readonly Random rng = new Random();
public class File(string fileName, Stream contents)
{
public string fileName = fileName;
public Stream contents = contents;
}
public static void Shuffle<T>(this IList<T> list) public static void Shuffle<T>(this IList<T> list)
{ {
int n = list.Count; int n = list.Count;
@ -103,7 +95,7 @@ public static class Utilities
public static DiscordColor StringToColor(string color) public static DiscordColor StringToColor(string color)
{ {
switch (color.ToLower(CultureInfo.InvariantCulture)) switch (color.ToLower())
{ {
case "black": case "black":
return DiscordColor.Black; return DiscordColor.Black;

View file

@ -39,9 +39,6 @@
# Pins the first message in a ticket to allow for quick navigation to the top in large tickets. # Pins the first message in a ticket to allow for quick navigation to the top in large tickets.
pin-first-message: true pin-first-message: true
# Ticket transcript location, can be overridden using command line arguments.
transcript-dir: "./transcripts"
notifications: 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. # 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. # Other staff members and bots do not trigger this.

View file

@ -196,7 +196,7 @@ except for selection boxes and buttons where each step becomes a button or selec
<td> <td>
`heading` `heading`
</td> </td>
<td>No</td> <td>Yes</td>
<td>String</td> <td>String</td>
<td> <td>
The title of the embed message. The title of the embed message.
@ -206,7 +206,7 @@ The title of the embed message.
<td> <td>
`summary-field` `summary-field`
</td> </td>
<td>No</td> <td>Yes</td>
<td>String</td> <td>String</td>
<td> <td>
When an interview ends all previous answers with this property will be put in a summary. When an interview ends all previous answers with this property will be put in a summary.
@ -218,7 +218,7 @@ The value of this property is the name which will be displayed next to the answe
<td> <td>
`button-style` `button-style`
</td> </td>
<td>No</td> <td>Yes</td>
<td>String</td> <td>String</td>
<td> <td>
The style of this step's button. Requires that the parent step is a `BUTTONS` step. The style of this step's button. Requires that the parent step is a `BUTTONS` step.
@ -237,7 +237,7 @@ Default style is `SECONDARY`.
<td> <td>
`selector-description` `selector-description`
</td> </td>
<td>No</td> <td>Yes</td>
<td>String</td> <td>String</td>
<td> <td>
Description for this option in the parent step's selection box. Requires that the parent step is a `TEXT_SELECTOR`. Description for this option in the parent step's selection box. Requires that the parent step is a `TEXT_SELECTOR`.
@ -247,7 +247,7 @@ Description for this option in the parent step's selection box. Requires that th
<td> <td>
`selector-placeholder` `selector-placeholder`
</td> </td>
<td>No</td> <td>Yes</td>
<td>String</td> <td>String</td>
<td> <td>
The placeholder text shown before a value is selected in the selection box. Requires that this step is a `TEXT_SELECTOR`. The placeholder text shown before a value is selected in the selection box. Requires that this step is a `TEXT_SELECTOR`.
@ -257,7 +257,7 @@ The placeholder text shown before a value is selected in the selection box. Requ
<td> <td>
`max-length` `max-length`
</td> </td>
<td>No</td> <td>Yes</td>
<td>Number</td> <td>Number</td>
<td> <td>
The maximum length of the user's response message. Requires that this step is a `TEXT_INPUT`. The maximum length of the user's response message. Requires that this step is a `TEXT_INPUT`.
@ -267,7 +267,7 @@ The maximum length of the user's response message. Requires that this step is a
<td> <td>
`min-length` `min-length`
</td> </td>
<td>No</td> <td>Yes</td>
<td>Number</td> <td>Number</td>
<td> <td>
The minimum length of the user's response message. Requires that this step is a `TEXT_INPUT`. The minimum length of the user's response message. Requires that this step is a `TEXT_INPUT`.