Compare commits

...

21 commits

Author SHA1 Message Date
c9ba8b4952 Update README.md
All checks were successful
Toastie-stuff/SupportChild/pipeline/head This commit looks good
2025-01-04 14:34:51 +00:00
5aec1b7e7b Update README.md
All checks were successful
Toastie-stuff/SupportChild/pipeline/head This commit looks good
2025-01-04 14:32:55 +00:00
66d67257be
Fix logging issues
All checks were successful
Toastie-stuff/SupportChild/pipeline/head This commit looks good
2024-12-27 20:22:00 +13:00
c6579beba2
Refactored log channel handling 2024-12-27 20:17:14 +13:00
dac221246c
Refactor random assignment and make it only assign staff with read access 2024-12-27 19:43:06 +13:00
12337101b7
Only log guilds and roles once after connecting 2024-12-27 19:39:52 +13:00
e3984ab968
Added transcript dir to config 2024-12-27 19:36:02 +13:00
0304744d9d
Improved unassign log messages 2024-12-27 19:30:30 +13:00
2b73548b7a
Remove unnecessary parameter 2024-12-27 19:27:51 +13:00
a9c28a04b6
Add non-selfcontained build to jenkins 2024-12-27 19:26:24 +13:00
f5d044512a
Unassign staff member from tickets when they are removed from staff 2024-12-27 19:23:54 +13:00
0ee12cb1ec
Downgrade GitInfo due to some new license issue 2024-12-27 19:18:16 +13:00
586f9c8f07
Improved feedback message for /addstaff when user is already staff 2024-12-27 18:18:23 +13:00
c6c7379f20
Make some messages ephemeral 2024-12-27 18:15:54 +13:00
8e7a21c81a
Fix interview category id type in schema 2024-12-27 18:14:20 +13:00
ad940c6167
Set the compression method to prioritize file size 2024-12-27 18:09:50 +13:00
a88997e8e5
Update InterviewTemplates.md 2024-12-27 18:08:34 +13:00
384f046915
Update command documentation 2024-12-27 18:06:25 +13:00
bb9ef65e42
Update interview set feedback message 2024-12-27 18:01:19 +13:00
c12c17de41
Updated author with my new username 2024-12-27 17:49:47 +13:00
bfd3196374
Updated formatting. 2024-12-27 17:46:22 +13:00
39 changed files with 334 additions and 610 deletions

View file

@ -74,22 +74,6 @@ public class AddCategoryCommand
}, true); }, true);
} }
try await LogChannel.Success(command.User.Mention + " added `" + category.Name + "` as `" + title + "` to the category list.");
{
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,24 +62,7 @@ public class AddCommand
Description = "Added " + member.Mention + " to ticket." Description = "Added " + member.Mention + " to ticket."
}); });
try await LogChannel.Success(member.Mention + " was added to " + command.Channel.Mention + " by " + command.User.Mention + ".", ticket.id);
{
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,4 +1,5 @@
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;
@ -27,7 +28,7 @@ public class AddMessageCommand
return; return;
} }
if (Database.TryGetMessage(identifier.ToLower(), out Database.Message _)) if (Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -54,22 +55,6 @@ public class AddMessageCommand
}, true); }, true);
} }
try await LogChannel.Success(command.User.Mention + " added or updated `" + identifier + "` in the /say command.\n\nContent:\n\n" + message);
{
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,8 +43,10 @@ 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 = 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); 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);
c.Open(); c.Open();
cmd.Parameters.AddWithValue("@user_id", staffMember.Id); cmd.Parameters.AddWithValue("@user_id", staffMember.Id);
@ -52,25 +54,23 @@ public class AddStaffCommand
cmd.ExecuteNonQuery(); cmd.ExecuteNonQuery();
cmd.Dispose(); cmd.Dispose();
await command.RespondAsync(new DiscordEmbedBuilder if (alreadyStaff)
{ {
Color = DiscordColor.Green, await command.RespondAsync(new DiscordEmbedBuilder
Description = staffMember.Mention + " was added to staff."
});
try
{
// Log it if the log channel exists
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordEmbedBuilder
{ {
Color = DiscordColor.Green, Color = DiscordColor.Green,
Description = staffMember.Mention + " was added to staff by " + command.User.Mention + "." Description = staffMember.Mention + " is already a staff member, refreshed username in database."
}); }, true);
} }
catch (NotFoundException) else
{ {
Logger.Error("Could not send message in log channel."); await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = staffMember.Mention + " was added to staff."
}, true);
await LogChannel.Success(staffMember.Mention + " was added to staff by " + command.User.Mention + ".");
} }
} }
} }

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,31 +40,14 @@ public class AdminCommands
Description = "Channel has been designated ticket " + id.ToString("00000") + "." Description = "Channel has been designated ticket " + id.ToString("00000") + "."
}); });
try await LogChannel.Success(command.Channel.Mention + " has been designated ticket " + id.ToString("00000") + " by " + command.Member?.Mention + ".", (uint)id);
{
// 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;
@ -121,24 +104,7 @@ public class AdminCommands
Description = "Channel has been undesignated as a ticket." Description = "Channel has been undesignated as a ticket."
}); });
try await LogChannel.Success(command.Channel.Mention + " has been undesignated as a ticket by " + command.User.Mention + ".", ticket.id);
{
// 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
{ {
@ -159,21 +125,9 @@ public class AdminCommands
{ {
Color = DiscordColor.Green, Color = DiscordColor.Green,
Description = "Reloading bot application..." Description = "Reloading bot application..."
}); }, true);
try await LogChannel.Success(command.Channel.Mention + " reloaded the bot.");
{
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,26 +89,9 @@ 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) { } catch (UnauthorizedException) { /* ignore */ }
} }
try await LogChannel.Success(member.Mention + " was assigned to " + command.Channel.Mention + " by " + command.User.Mention + ".", ticket.id);
{
// 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,20 +35,7 @@ public class BlacklistCommand
Description = "Blocked " + user.Mention + " from opening new tickets." Description = "Blocked " + user.Mention + " from opening new tickets."
}, true); }, true);
try await LogChannel.Success(user.Mention + " was blocked from opening tickets by " + command.User.Mention + ".");
{
// 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,28 +141,12 @@ 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);
DiscordMessageBuilder message = new DiscordMessageBuilder(); await LogChannel.Success("Ticket " + ticket.id.ToString("00000") + " closed by " + interaction.User.Mention + ".\n" + closeReason, ticket.id, new Utilities.File(fileName, file));
message.AddEmbed(new DiscordEmbedBuilder
{
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) catch (Exception e)
{ {
Logger.Error("Could not send message in log channel."); Logger.Error("Error occurred sending transcript log message. ", e);
} }
if (Config.closingNotifications) if (Config.closingNotifications)
@ -200,13 +184,12 @@ 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) { } catch (NotFoundException) { /* ignore */ }
catch (UnauthorizedException) { } catch (UnauthorizedException) { /* ignore */ }
} }
Database.ArchiveTicket(ticket); Database.ArchiveTicket(ticket);

View file

@ -51,18 +51,6 @@ 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);
try await LogChannel.Success(command.User.Mention + " created a new button panel in " + command.Channel.Mention + ".");
{
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("message")][Description("(Optional) The message to show in the selection box.")] string message = null) [Parameter("placeholder")][Description("(Optional) The message to show in the selection box.")] string message = null)
{ {
DiscordMessageBuilder builder = new DiscordMessageBuilder() DiscordMessageBuilder builder = new DiscordMessageBuilder()
.WithContent(" ") .WithContent(" ")
@ -29,19 +29,7 @@ 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);
try await LogChannel.Success(command.User.Mention + " created a new selector panel in " + command.Channel.Mention + ".");
{
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,23 +45,7 @@ public class InterviewCommands
}, true); }, true);
} }
try await LogChannel.Success(command.User.Mention + " restarted interview in " + command.Channel.Mention + ".", ticket.id);
{
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")]
@ -107,22 +91,6 @@ public class InterviewCommands
}, true); }, true);
} }
try await LogChannel.Success(command.User.Mention + " stopped the interview in " + command.Channel.Mention + ".", ticket.id);
{
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." Description = "Uploaded interview template for `" + category.Name + "`."
}, true); }, true);
} }
else else
@ -168,18 +168,12 @@ 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,
// Log it if the log channel exists new Utilities.File("interview-template-" + template.categoryID + ".json", memStream));
DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel);
await logChannel.SendMessageAsync(new DiscordMessageBuilder().AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = command.User.Mention + " uploaded a new interview template for the `" + category.Name + "` category."
}).AddFile("interview-template-" + template.categoryID + ".json", memStream));
} }
catch (NotFoundException) catch (Exception e)
{ {
Logger.Error("Could not send message in log channel."); Logger.Error("Unable to log interview template upload.", e);
} }
} }
catch (Exception e) catch (Exception e)
@ -241,19 +235,7 @@ public class InterviewTemplateCommands
Description = "Deleted interview template." Description = "Deleted interview template."
}, true); }, true);
try await LogChannel.Success(command.User.Mention + " deleted the interview template for the `" + category.Name + "` category.", 0,
{ 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 to get tickets by, yourself by default.")] DiscordUser user = null) [Parameter("user")][Description("(Optional) The user whose tickets to get, yourself by default.")] DiscordUser user = null)
{ {
DiscordUser listUser = user == null ? command.User : user; DiscordUser listUser = user == null ? command.User : user;

View file

@ -105,22 +105,6 @@ public class MoveCommand
Description = "Ticket was moved to `" + categoryChannel.Name + "`." Description = "Ticket was moved to `" + categoryChannel.Name + "`."
}); });
try await LogChannel.Success(command.User.Mention + " moved " + command.Channel.Mention + " to `" + categoryChannel.Name + "`.", ticket.id);
{
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,7 +11,6 @@ using DSharpPlus.Exceptions;
using SupportChild.Interviews; using SupportChild.Interviews;
namespace SupportChild.Commands; namespace SupportChild.Commands;
public class NewCommand public class NewCommand
{ {
[RequireGuild] [RequireGuild]
@ -134,6 +133,11 @@ 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
{ {
@ -152,7 +156,10 @@ public class NewCommand
{ {
ticketChannel = await category.Guild.CreateChannelAsync("ticket", DiscordChannelType.Text, category); ticketChannel = await category.Guild.CreateChannelAsync("ticket", DiscordChannelType.Text, category);
} }
catch (Exception) { /* ignored */ } catch (Exception e)
{
Logger.Error("Error occured while creating ticket.", e);
}
if (ticketChannel == null) if (ticketChannel == null)
{ {
@ -160,13 +167,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?");
} }
ulong staffID = 0; DiscordMember assignedStaff = null;
if (Config.randomAssignment) if (Config.randomAssignment)
{ {
staffID = Database.GetRandomActiveStaff(0)?.userID ?? 0; assignedStaff = await RandomAssignCommand.GetRandomVerifiedStaffMember(ticketChannel, userID, 0, null);
} }
long id = Database.NewTicket(member.Id, staffID, ticketChannel.Id); long id = Database.NewTicket(member.Id, assignedStaff?.Id ?? 0, ticketChannel.Id);
try try
{ {
await ticketChannel.ModifyAsync(modifiedAttributes => modifiedAttributes.Name = "ticket-" + id.ToString("00000")); await ticketChannel.ModifyAsync(modifiedAttributes => modifiedAttributes.Name = "ticket-" + id.ToString("00000"));
@ -174,7 +181,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("JsomMessage: " + e.JsonMessage); Logger.Error("JsonMessage: " + e.JsonMessage);
} }
try try
@ -184,7 +191,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("JsomMessage: " + e.JsonMessage); Logger.Error("JsonMessage: " + e.JsonMessage);
} }
DiscordMessage message = await ticketChannel.SendMessageAsync("Hello, " + member.Mention + "!\n" + Config.welcomeMessage); DiscordMessage message = await ticketChannel.SendMessageAsync("Hello, " + member.Mention + "!\n" + Config.welcomeMessage);
@ -209,20 +216,19 @@ public class NewCommand
await Interviewer.StartInterview(ticketChannel); await Interviewer.StartInterview(ticketChannel);
} }
if (staffID != 0) if (assignedStaff != null)
{ {
await ticketChannel.SendMessageAsync(new DiscordEmbedBuilder await ticketChannel.SendMessageAsync(new DiscordEmbedBuilder
{ {
Color = DiscordColor.Green, Color = DiscordColor.Green,
Description = "Ticket was randomly assigned to <@" + staffID + ">." Description = "Ticket was randomly assigned to " + assignedStaff.Mention + "."
}); });
if (Config.assignmentNotifications) if (Config.assignmentNotifications)
{ {
try try
{ {
DiscordMember staffMember = await category.Guild.GetMemberAsync(staffID); await assignedStaff.SendMessageAsync(new DiscordEmbedBuilder
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: " +
@ -232,30 +238,12 @@ 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("JsomMessage: " + e.JsonMessage); Logger.Error("JsonMessage: " + e.JsonMessage);
} }
} }
} }
try await LogChannel.Success("Ticket " + ticketChannel.Mention + " opened by " + member.Mention + ".", (uint)id);
{
// 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,9 +31,14 @@ 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, role, ticket); DiscordMember staffMember = await GetRandomVerifiedStaffMember(command.Channel, ticket.creatorID, ticket.assignedStaffID, role);
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;
} }
@ -66,85 +71,56 @@ 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) { } catch (UnauthorizedException) { /* ignore */ }
} }
try await LogChannel.Success(staffMember.Mention + " was randomly assigned to " + command.Channel.Mention + " by " + command.User.Mention + ".", ticket.id);
{
// 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 randomly 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.");
}
} }
private static async Task<DiscordMember> GetRandomVerifiedStaffMember(SlashCommandContext command, DiscordRole targetRole, Database.Ticket ticket) internal static async Task<DiscordMember> GetRandomVerifiedStaffMember(DiscordChannel channel, ulong creatorID, ulong currentStaffID, DiscordRole targetRole)
{ {
if (targetRole != null) // A role was provided List<Database.StaffMember> staffMembers;
ulong[] ignoredUserIDs = [creatorID, currentStaffID];
if (targetRole == null)
{
// No role was specified, any active staff will be picked
staffMembers = Database.GetActiveStaff(ignoredUserIDs);
}
else
{ {
// Check if role rassign should override staff's active status // Check if role rassign should override staff's active status
List<Database.StaffMember> staffMembers = Config.randomAssignRoleOverride staffMembers = Config.randomAssignRoleOverride
? Database.GetAllStaff(ticket.assignedStaffID, ticket.creatorID) ? Database.GetAllStaff(ignoredUserIDs)
: Database.GetActiveStaff(ticket.assignedStaffID, ticket.creatorID); : Database.GetActiveStaff(ignoredUserIDs);
}
// 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 sm in staffMembers) foreach (Database.StaffMember staffMember in staffMembers)
{
try
{ {
try DiscordMember verifiedMember = await channel.Guild.GetMemberAsync(staffMember.userID);
// If a role is set filter to only members with that role
if (targetRole == null || verifiedMember.Roles.Any(role => role.Id == targetRole.Id))
{ {
DiscordMember verifiedMember = await command.Guild.GetMemberAsync(sm.userID); // Only assign staff members with access to this channel
if (verifiedMember?.Roles?.Any(role => role.Id == targetRole.Id) ?? false) if (verifiedMember.PermissionsIn(channel).HasFlag(DiscordPermissions.AccessChannels))
{ {
return verifiedMember; return verifiedMember;
} }
} }
catch (Exception e) }
{ catch (Exception e)
command.Client.Logger.Log(LogLevel.Information, e, "Error occured trying to find a staff member in the rassign command."); {
} Logger.Error("Error occured trying to find a staff member for random assignment. User ID: " + staffMember.userID, e);
} }
} }
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,19 +43,6 @@ public class RemoveCategoryCommand
}, true); }, true);
} }
try await LogChannel.Success("`" + category.Name + "` was removed by " + command.User.Mention + ".");
{
// 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,4 +1,5 @@
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;
@ -16,7 +17,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(), out Database.Message _)) if (!Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -43,18 +44,6 @@ public class RemoveMessageCommand
}, true); }, true);
} }
try await LogChannel.Success("`" + identifier + "` was removed from the /say command by " + command.User.Mention + ".");
{
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,10 +1,11 @@
using System.ComponentModel; using System;
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;
@ -27,6 +28,31 @@ 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);
@ -40,19 +66,6 @@ public class RemoveStaffCommand
Description = "User was removed from staff." Description = "User was removed from staff."
}, true); }, true);
try await LogChannel.Success(user.Mention + " was removed from staff by " + command.User.Mention + ".");
{
// 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,6 +1,7 @@
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;
@ -22,11 +23,12 @@ 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)
{ {
SendMessageList(command); await SendMessageList(command);
return; return;
} }
if (!Database.TryGetMessage(identifier.ToLower(), out Database.Message message)) if (!Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture),
out Database.Message message))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -42,22 +44,10 @@ public class SayCommand
Description = message.message.Replace("\\n", "\n") Description = message.message.Replace("\\n", "\n")
}); });
try await LogChannel.Success(command.User.Mention + " posted the " + message.identifier + " message in " + command.Channel.Mention + ".");
{
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 void SendMessageList(SlashCommandContext command) private static async Task 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,22 +43,6 @@ public class SetSummaryCommand
Description = "Summary set." Description = "Summary set."
}, true); }, true);
try await LogChannel.Success(command.User.Mention + " set the summary for " + command.Channel.Mention + " to:\n\n" + summary, ticket.id);
{
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)", true); .AddField("Commands:", "[Toastielab Repository](https://toastielab.dev/Emotions-stuff/SupportChild/src/branch/main/docs/Commands.md)", true);
await command.RespondAsync(botInfo); await command.RespondAsync(botInfo);
} }
} }

View file

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

View file

@ -121,27 +121,12 @@ 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();
message.AddEmbed(new DiscordEmbedBuilder
{
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) catch (Exception e)
{ {
Logger.Error("Could not send message in log channel."); Logger.Error("Failed to log transcript generation.", e);
} }
if (await SendDirectMessage(command, fileName, filePath, zipSize, ticket.id)) if (await SendDirectMessage(command, fileName, filePath, zipSize, ticket.id))

View file

@ -39,26 +39,9 @@ public class UnassignCommand
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
Color = DiscordColor.Green, Color = DiscordColor.Green,
Description = "Unassigned staff member from ticket." Description = "Unassigned <@" + ticket.assignedStaffID + "> from ticket."
}); });
try await LogChannel.Success("<@" + ticket.assignedStaffID + "> was unassigned from <#" + ticket.channelID + "> by " + command.User.Mention + ".", ticket.id);
{
// 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,20 +34,7 @@ public class UnblacklistCommand
Description = "Unblocked " + user.Mention + " from opening new tickets." Description = "Unblocked " + user.Mention + " from opening new tickets."
}, true); }, true);
try await LogChannel.Success(user.Mention + " was unblocked from opening tickets by " + command.User.Mention + ".");
{
// 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,6 +20,7 @@ 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;
@ -91,6 +92,7 @@ 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,8 +7,6 @@ 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;
@ -468,12 +466,6 @@ 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,6 +18,8 @@ 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.");
@ -35,6 +37,12 @@ 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))
@ -104,8 +112,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) { } catch (NotFoundException) { /* ignored */ }
catch (UnauthorizedException) { } catch (UnauthorizedException) { /* ignored */ }
} }
} }
@ -166,27 +174,19 @@ public static class EventHandler
} }
} }
if (Database.TryGetAssignedTickets(e.Member.Id, out List<Database.Ticket> assignedTickets) && Config.logChannel != 0) if (LogChannel.IsEnabled && Database.TryGetAssignedTickets(e.Member.Id, out List<Database.Ticket> assignedTickets))
{ {
DiscordChannel logChannel = await client.GetChannelAsync(Config.logChannel); foreach (Database.Ticket ticket in assignedTickets)
if (logChannel != null)
{ {
foreach (Database.Ticket ticket in assignedTickets) try
{ {
try DiscordChannel channel = await client.GetChannelAsync(ticket.channelID);
if (channel?.GuildId == e.Guild.Id)
{ {
DiscordChannel channel = await client.GetChannelAsync(ticket.channelID); await LogChannel.Warn("Assigned staff member '" + e.Member.Username + "#" + e.Member.Discriminator + "' has left the server: <#" + channel.Id + ">");
if (channel?.GuildId == e.Guild.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 */ }
} }
} }
} }
@ -245,45 +245,41 @@ 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:
switch (e.Id) if (e.Id.StartsWith("supportchild_interviewuserselector"))
{ {
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 + "'");
return;
} }
Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
return;
case DiscordComponentType.RoleSelect: case DiscordComponentType.RoleSelect:
switch (e.Id) if (e.Id.StartsWith("supportchild_interviewroleselector"))
{ {
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 + "'");
return;
} }
Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
return;
case DiscordComponentType.MentionableSelect: case DiscordComponentType.MentionableSelect:
switch (e.Id) if (e.Id.StartsWith("supportchild_interviewmentionableselector"))
{ {
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 + "'");
return;
} }
Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
return;
case DiscordComponentType.ChannelSelect: case DiscordComponentType.ChannelSelect:
switch (e.Id) if (e.Id.StartsWith("supportchild_interviewchannelselector"))
{ {
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 + "'");
return;
} }
Logger.Warn("Unknown selection box option received! '" + e.Id + "'");
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;
@ -292,7 +288,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("JsomMessage: " + ex.JsonMessage); Logger.Error("JsonMessage: " + ex.JsonMessage);
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -168,7 +168,7 @@
{ {
"category-id": "category-id":
{ {
"type": "string", "type": "number",
"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."
}, },

12
Jenkinsfile vendored
View file

@ -10,12 +10,16 @@ pipeline {
parallel { parallel {
stage('Linux') { stage('Linux') {
steps { steps {
sh 'dotnet publish -r linux-x64 -c Release --self-contained true --no-restore --output Linux-x64/' sh 'dotnet publish -r linux-x64 -c Release -p:PublishTrimmed=true --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 --self-contained true --no-restore --output Windows-x64/' sh 'dotnet publish -r win-x64 -c Release -p:PublishTrimmed=true --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/'
} }
} }
} }
@ -25,14 +29,16 @@ 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)
} }
} }
} }
} }
} }
} }

74
LogChannel.cs Normal file
View file

@ -0,0 +1,74 @@
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 +1,3 @@
[![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,7 +27,6 @@ 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; }
@ -35,7 +34,6 @@ 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; }
@ -226,6 +224,7 @@ 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>EmotionChild</Authors> <Authors>Toastie_t0ast</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.5.0"> <PackageReference Include="GitInfo" Version="3.3.5">
<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,23 +13,11 @@ 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);
@ -88,10 +76,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); zip.CreateEntryFromFile(htmlPath, htmlFilename, CompressionLevel.SmallestSize);
foreach (string assetFile in assetFiles) foreach (string assetFile in assetFiles)
{ {
zip.CreateEntryFromFile(assetFile, assetDirName + "/" + Path.GetFileName(assetFile)); zip.CreateEntryFromFile(assetFile, assetDirName + "/" + Path.GetFileName(assetFile), CompressionLevel.SmallestSize);
} }
} }
@ -99,19 +87,39 @@ 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 transcriptDir + "/" + GetHTMLFilename(ticketNumber); return GetTranscriptDir() + "/" + GetHTMLFilename(ticketNumber);
} }
internal static string GetZipPath(uint ticketNumber) internal static string GetZipPath(uint ticketNumber)
{ {
return transcriptDir + "/" + GetZipFilename(ticketNumber); return GetTranscriptDir() + "/" + GetZipFilename(ticketNumber);
} }
internal static string GetAssetDirPath(uint ticketNumber) internal static string GetAssetDirPath(uint ticketNumber)
{ {
return transcriptDir + "/" + GetAssetDirName(ticketNumber); return GetTranscriptDir() + "/" + GetAssetDirName(ticketNumber);
} }
internal static string GetAssetDirName(uint ticketNumber) internal static string GetAssetDirName(uint ticketNumber)

View file

@ -1,5 +1,6 @@
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;
@ -14,6 +15,7 @@ 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);
@ -24,6 +26,12 @@ 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;
@ -95,7 +103,7 @@ public static class Utilities
public static DiscordColor StringToColor(string color) public static DiscordColor StringToColor(string color)
{ {
switch (color.ToLower()) switch (color.ToLower(CultureInfo.InvariantCulture))
{ {
case "black": case "black":
return DiscordColor.Black; return DiscordColor.Black;

View file

@ -39,6 +39,9 @@
# 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>Yes</td> <td>No</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>Yes</td> <td>No</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>Yes</td> <td>No</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>Yes</td> <td>No</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>Yes</td> <td>No</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>Yes</td> <td>No</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>Yes</td> <td>No</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`.