Compare commits

...

23 commits
4.0.0 ... main

Author SHA1 Message Date
f84f767b1a
Updated README.md with the right links to the build server. 2025-03-27 20:36:38 +13:00
f61b40fbfb
Removed fedora package from the Jenkinsfile due to it not compiling correctly. 2025-03-27 20:21:57 +13:00
141c52dd58
I have no idea why this keeps on breaking and I am fast getting to the point of removing the fedora package until I can wrap my head around it. 2025-03-27 20:12:45 +13:00
d2ac490181
Fixed shit 2025-03-27 15:29:07 +13:00
70f76041c9
Removed some unnecessary stuff in the Jenkinsfile 2025-03-27 15:22:24 +13:00
9c6316d857
Add systemd-rpm-macros to docker containers 2025-03-27 15:08:22 +13:00
6a5be484a0
Added Fedora package (this will most likely fail but meh). 2025-03-27 15:02:01 +13:00
c569c64abc
Trying to fix the jenkins build. 2025-03-27 14:37:01 +13:00
6d2cbde816
Improve handling of standard and dev builds of the rpm, set transcript dir in bot service 2025-03-27 13:26:57 +13:00
f2cf4d01a9
Added RPM packaging for RHEL 8 and 9 based distros 2025-03-27 11:59:21 +13:00
e130c96cb2
Updated badge in readme file. 2025-03-27 11:17:35 +13:00
0cead82b09
I put the wrong link 2025-02-12 00:02:14 +13:00
9f846ea729
Update README.md 2025-02-06 15:30:21 +13:00
63276257d2
Fixed transcript command not being able to send old html transcripts 2025-02-06 15:22:19 +13:00
df2f2e63c9
Don't cache empty close reasons 2025-02-06 15:20:58 +13:00
ff2a9e54e4
Update README.md 2025-02-06 15:18:37 +13:00
4cbcf0b65e
Refactored and split up the database class 2025-02-06 15:15:18 +13:00
8445fefaf9
Merged add and remove message into set message 2025-02-04 22:05:30 +13:00
7f1d3a9cec
Fix formatting in /say log 2025-02-04 21:57:55 +13:00
787bd1193e
Update README.md 2025-02-04 21:56:48 +13:00
679796a910
Catch NRE when user can be found but a dm channel cannot be created
Regardless of DM permission
2025-02-04 21:51:52 +13:00
f70a3ab044
Version bump 2025-02-04 21:49:46 +13:00
d1691e2377
Fix typo 2025-02-04 21:48:50 +13:00
51 changed files with 1625 additions and 1300 deletions

7
.gitignore vendored
View file

@ -7,4 +7,9 @@
/config.yml /config.yml
/transcripts /transcripts
/Linux-x64 /Linux-x64
/Windows-x64 /Windows-x64
Folder.DotSettings.user
packaging/rpmbuild-nightly/
packaging/rpmbuild/

View file

@ -37,7 +37,7 @@ public class AddCategoryCommand
return; return;
} }
if (Database.TryGetCategory(category.Id, out Database.Category _)) if (Database.Category.TryGetCategory(category.Id, out Database.Category _))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -47,7 +47,7 @@ public class AddCategoryCommand
return; return;
} }
if (Database.TryGetCategory(title, out Database.Category _)) if (Database.Category.TryGetCategory(title, out Database.Category _))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -57,7 +57,7 @@ public class AddCategoryCommand
return; return;
} }
if (Database.AddCategory(title, category.Id)) if (Database.Category.AddCategory(title, category.Id))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -18,7 +18,7 @@ public class AddCommand
[Parameter("user")][Description("User to add to ticket.")] DiscordUser user) [Parameter("user")][Description("User to add to ticket.")] DiscordUser user)
{ {
// Check if ticket exists in the database // Check if ticket exists in the database
if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -1,60 +0,0 @@
using System.ComponentModel;
using System.Globalization;
using DSharpPlus.Entities;
using System.Threading.Tasks;
using DSharpPlus.Commands;
using DSharpPlus.Commands.ContextChecks;
using DSharpPlus.Commands.Processors.SlashCommands;
using DSharpPlus.Exceptions;
namespace SupportChild.Commands;
public class AddMessageCommand
{
[RequireGuild]
[Command("addmessage")]
[Description("Adds a new message for the 'say' command.")]
public async Task OnExecute(SlashCommandContext command,
[Parameter("identifier")][Description("The identifier word used in the /say command.")] string identifier,
[Parameter("message")][Description("The message the /say command will return.")] string message)
{
if (string.IsNullOrEmpty(message))
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "No message specified."
}, true);
return;
}
if (Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _))
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "There is already a message with that identifier."
}, true);
return;
}
if (Database.AddMessage(identifier, command.Member.Id, message))
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = "Message added."
}, true);
}
else
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "Error: Failed adding the message to the database."
}, true);
}
await LogChannel.Success(command.User.Mention + " added or updated `" + identifier + "` in the /say command.\n\nContent:\n\n" + message);
}
}

View file

@ -18,7 +18,7 @@ public class AddStaffCommand
public async Task OnExecute(SlashCommandContext command, public async Task OnExecute(SlashCommandContext command,
[Parameter("user")][Description("User to add to staff.")] DiscordUser user) [Parameter("user")][Description("User to add to staff.")] DiscordUser user)
{ {
DiscordMember staffMember = null; DiscordMember staffMember;
try try
{ {
staffMember = user == null ? command.Member : await command.Guild.GetMemberAsync(user.Id); staffMember = user == null ? command.Member : await command.Guild.GetMemberAsync(user.Id);
@ -43,16 +43,15 @@ public class AddStaffCommand
return; return;
} }
bool alreadyStaff = Database.IsStaff(staffMember.Id); bool alreadyStaff = Database.StaffMember.IsStaff(staffMember.Id);
if (!Database.StaffMember.AddStaff(staffMember.DisplayName, staffMember.Id))
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); await command.RespondAsync(new DiscordEmbedBuilder
{
c.Open(); Color = DiscordColor.Red,
cmd.Parameters.AddWithValue("@user_id", staffMember.Id); Description = "Error: Failed to add staff member to database."
cmd.Parameters.AddWithValue("@name", staffMember.DisplayName); }, true);
cmd.ExecuteNonQuery(); }
cmd.Dispose();
if (alreadyStaff) if (alreadyStaff)
{ {

View file

@ -21,7 +21,7 @@ public class AdminCommands
[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.Ticket.IsOpenTicket(command.Channel.Id))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -33,7 +33,7 @@ public class AdminCommands
DiscordUser ticketUser = user == null ? command.User : user; DiscordUser ticketUser = user == null ? command.User : user;
long id = Database.NewTicket(ticketUser.Id, 0, command.Channel.Id); long id = Database.Ticket.NewTicket(ticketUser.Id, 0, command.Channel.Id);
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
Color = DiscordColor.Green, Color = DiscordColor.Green,
@ -57,7 +57,7 @@ public class AdminCommands
if (ticketID == 0) if (ticketID == 0)
{ {
// Check if ticket exists in the database // Check if ticket exists in the database
if (!Database.TryGetOpenTicket(command.Channel.Id, out ticket)) if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out ticket))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -71,7 +71,7 @@ public class AdminCommands
else else
{ {
// Check if ticket exists in the database // Check if ticket exists in the database
if (!Database.TryGetOpenTicketByID((uint)ticketID, out ticket)) if (!Database.Ticket.TryGetOpenTicketByID((uint)ticketID, out ticket))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -96,7 +96,7 @@ public class AdminCommands
} }
// Delete the ticket from the database and respond to command // Delete the ticket from the database and respond to command
if (Database.DeleteOpenTicket(ticket.id)) if (Database.Ticket.DeleteOpenTicket(ticket.id))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -43,7 +43,7 @@ public class AssignCommand
} }
// Check if ticket exists in the database // Check if ticket exists in the database
if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -53,7 +53,7 @@ public class AssignCommand
return; return;
} }
if (!Database.IsStaff(member.Id)) if (!Database.StaffMember.IsStaff(member.Id))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -63,7 +63,7 @@ public class AssignCommand
return; return;
} }
if (!Database.AssignStaff(ticket, member.Id)) if (!Database.StaffMember.AssignStaff(ticket, member.Id))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -19,7 +19,7 @@ public class BlacklistCommand
{ {
try try
{ {
if (!Database.Blacklist(user.Id, command.User.Id)) if (!Database.Blacklist.Ban(user.Id, command.User.Id))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -26,7 +26,7 @@ public class CloseCommand
[Parameter("reason")][Description("(Optional) The reason for closing this ticket.")] string reason = "") [Parameter("reason")][Description("(Optional) The reason for closing this ticket.")] string reason = "")
{ {
// Check if ticket exists in the database // Check if ticket exists in the database
if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket _)) if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket _))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -42,17 +42,20 @@ public class CloseCommand
Color = DiscordColor.Cyan, Color = DiscordColor.Cyan,
Description = "Are you sure you wish to close this ticket? You cannot re-open it again later." Description = "Are you sure you wish to close this ticket? You cannot re-open it again later."
}) })
.AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Danger, "supportchild_closeconfirm", "Confirm")); .AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Danger, "supportboi_closeconfirm", "Confirm"));
await command.RespondAsync(confirmation); await command.RespondAsync(confirmation);
if (closeReasons.TryGetValue(command.Channel.Id, out _)) if (!string.IsNullOrWhiteSpace(reason))
{ {
closeReasons[command.Channel.Id] = reason; if (closeReasons.TryGetValue(command.Channel.Id, out _))
} {
else closeReasons[command.Channel.Id] = reason;
{ }
closeReasons.Add(command.Channel.Id, reason); else
{
closeReasons.Add(command.Channel.Id, reason);
}
} }
} }
@ -75,7 +78,7 @@ public class CloseCommand
await interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredMessageUpdate); await interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredMessageUpdate);
// Check if ticket exists in the database // Check if ticket exists in the database
if (!Database.TryGetOpenTicket(interaction.Channel.Id, out Database.Ticket ticket)) if (!Database.Ticket.TryGetOpenTicket(interaction.Channel.Id, out Database.Ticket ticket))
{ {
currentlyClosingTickets.Remove(interaction.Channel.Id); currentlyClosingTickets.Remove(interaction.Channel.Id);
await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder
@ -165,7 +168,7 @@ public class CloseCommand
{ {
try try
{ {
DiscordUser staffMember = await SupportChild.client.GetUserAsync(ticket.creatorID); DiscordUser ticketCreator = await SupportChild.client.GetUserAsync(ticket.creatorID);
await using FileStream file = new(filePath, FileMode.Open, FileAccess.Read); await using FileStream file = new(filePath, FileMode.Open, FileAccess.Read);
DiscordMessageBuilder message = new(); DiscordMessageBuilder message = new();
@ -198,14 +201,15 @@ public class CloseCommand
message.AddFiles(new Dictionary<string, Stream> { { fileName, file } }); message.AddFiles(new Dictionary<string, Stream> { { fileName, file } });
await staffMember.SendMessageAsync(message); await ticketCreator.SendMessageAsync(message);
} }
catch (NotFoundException) { /* ignore */ } catch (NotFoundException) { /* ignore */ }
catch (UnauthorizedException) { /* ignore */ } catch (UnauthorizedException) { /* ignore */ }
catch (NullReferenceException) { /* ignore */ }
} }
Database.ArchiveTicket(ticket); Database.Ticket.ArchiveTicket(ticket);
Database.TryDeleteInterview(interaction.Channel.Id); Database.Interviews.TryDeleteInterview(interaction.Channel.Id);
await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder
{ {
@ -218,7 +222,7 @@ public class CloseCommand
// Delete the channel and database entry // Delete the channel and database entry
await interaction.Channel.DeleteAsync("Ticket closed."); await interaction.Channel.DeleteAsync("Ticket closed.");
Database.DeleteOpenTicket(ticket.id); Database.Ticket.DeleteOpenTicket(ticket.id);
closeReasons.Remove(interaction.Channel.Id); closeReasons.Remove(interaction.Channel.Id);
currentlyClosingTickets.Remove(interaction.Channel.Id); currentlyClosingTickets.Remove(interaction.Channel.Id);

View file

@ -16,7 +16,7 @@ public class InterviewCommands
[Description("Restarts the interview in this ticket, using an updated template if available.")] [Description("Restarts the interview in this ticket, using an updated template if available.")]
public async Task Restart(SlashCommandContext command) public async Task Restart(SlashCommandContext command)
{ {
if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -52,7 +52,7 @@ public class InterviewCommands
[Description("Stops the interview in this ticket.")] [Description("Stops the interview in this ticket.")]
public async Task Stop(SlashCommandContext command) public async Task Stop(SlashCommandContext command)
{ {
if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -62,7 +62,7 @@ public class InterviewCommands
return; return;
} }
if (!Database.TryGetInterview(command.Channel.Id, out Interview _)) if (!Database.Interviews.TryGetInterview(command.Channel.Id, out Interview _))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -39,7 +39,7 @@ public class InterviewTemplateCommands
return; return;
} }
if (Database.TryGetInterviewTemplateJSON(category.Id, out string templateJSON)) if (Database.Interviews.TryGetInterviewTemplateJSON(category.Id, out string templateJSON))
{ {
MemoryStream stream = new(Encoding.UTF8.GetBytes(templateJSON)); MemoryStream stream = new(Encoding.UTF8.GetBytes(templateJSON));
await command.RespondAsync(new DiscordInteractionResponseBuilder() await command.RespondAsync(new DiscordInteractionResponseBuilder()
@ -172,7 +172,7 @@ public class InterviewTemplateCommands
return; return;
} }
if (!Database.SetInterviewTemplate(template)) if (!Database.Interviews.SetInterviewTemplate(template))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -207,7 +207,7 @@ public class InterviewTemplateCommands
try try
{ {
if (Database.TryGetInterviewTemplateJSON(template.categoryID, out string templateJSON)) if (Database.Interviews.TryGetInterviewTemplateJSON(template.categoryID, out string templateJSON))
{ {
MemoryStream memStream = new(Encoding.UTF8.GetBytes(templateJSON)); MemoryStream memStream = new(Encoding.UTF8.GetBytes(templateJSON));
await LogChannel.Success(command.User.Mention + " uploaded a new interview template for the `" + category.Name + "` category.", 0, await LogChannel.Success(command.User.Mention + " uploaded a new interview template for the `" + category.Name + "` category.", 0,
@ -256,7 +256,7 @@ public class InterviewTemplateCommands
return; return;
} }
if (!Database.TryGetInterviewFromTemplate(category.Id, 0, out Interview _)) if (!Database.Interviews.TryGetInterviewFromTemplate(category.Id, 0, out Interview _))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -266,7 +266,7 @@ public class InterviewTemplateCommands
return; return;
} }
if (!Database.TryGetInterviewTemplateJSON(category.Id, out string templateJSON)) if (!Database.Interviews.TryGetInterviewTemplateJSON(category.Id, out string templateJSON))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -277,7 +277,7 @@ public class InterviewTemplateCommands
} }
MemoryStream memStream = new(Encoding.UTF8.GetBytes(templateJSON)); MemoryStream memStream = new(Encoding.UTF8.GetBytes(templateJSON));
if (!Database.TryDeleteInterviewTemplate(category.Id)) if (!Database.Interviews.TryDeleteInterviewTemplate(category.Id))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -20,7 +20,7 @@ public class ListAssignedCommand
{ {
DiscordUser listUser = user == null ? command.User : user; DiscordUser listUser = user == null ? command.User : user;
if (!Database.TryGetAssignedTickets(listUser.Id, out List<Database.Ticket> assignedTickets)) if (!Database.Ticket.TryGetAssignedTickets(listUser.Id, out List<Database.Ticket> assignedTickets))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -21,7 +21,7 @@ public class ListCommand
DiscordUser listUser = user == null ? command.User : user; DiscordUser listUser = user == null ? command.User : user;
List<DiscordEmbedBuilder> openEmbeds = new List<DiscordEmbedBuilder>(); List<DiscordEmbedBuilder> openEmbeds = new List<DiscordEmbedBuilder>();
if (Database.TryGetOpenTickets(listUser.Id, out List<Database.Ticket> openTickets)) if (Database.Ticket.TryGetOpenTickets(listUser.Id, out List<Database.Ticket> openTickets))
{ {
List<string> listItems = new List<string>(); List<string> listItems = new List<string>();
foreach (Database.Ticket ticket in openTickets) foreach (Database.Ticket ticket in openTickets)
@ -46,7 +46,7 @@ public class ListCommand
} }
List<DiscordEmbedBuilder> closedEmbeds = new List<DiscordEmbedBuilder>(); List<DiscordEmbedBuilder> closedEmbeds = new List<DiscordEmbedBuilder>();
if (Database.TryGetClosedTickets(listUser.Id, out List<Database.Ticket> closedTickets)) if (Database.Ticket.TryGetClosedTickets(listUser.Id, out List<Database.Ticket> closedTickets))
{ {
List<string> listItems = new List<string>(); List<string> listItems = new List<string>();
foreach (Database.Ticket ticket in closedTickets) foreach (Database.Ticket ticket in closedTickets)

View file

@ -20,7 +20,7 @@ public class ListInvalidCommand
[Description("List tickets which channels have been deleted. Use /admin unsetticket <id> to remove them.")] [Description("List tickets which channels have been deleted. Use /admin unsetticket <id> to remove them.")]
public async Task ListInvalid(SlashCommandContext command) public async Task ListInvalid(SlashCommandContext command)
{ {
if (!Database.TryGetOpenTickets(out List<Database.Ticket> openTickets)) if (!Database.Ticket.TryGetOpenTickets(out List<Database.Ticket> openTickets))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -20,7 +20,7 @@ public class ListOpen
[Description("Lists all open tickets, oldest first.")] [Description("Lists all open tickets, oldest first.")]
public async Task OnExecute(SlashCommandContext command) public async Task OnExecute(SlashCommandContext command)
{ {
if (!Database.TryGetOpenTickets(out List<Database.Ticket> openTickets)) if (!Database.Ticket.TryGetOpenTickets(out List<Database.Ticket> openTickets))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -20,7 +20,7 @@ public class ListUnassignedCommand
[Description("Lists unassigned tickets.")] [Description("Lists unassigned tickets.")]
public async Task OnExecute(SlashCommandContext command) public async Task OnExecute(SlashCommandContext command)
{ {
if (!Database.TryGetAssignedTickets(0, out List<Database.Ticket> unassignedTickets)) if (!Database.Ticket.TryGetAssignedTickets(0, out List<Database.Ticket> unassignedTickets))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -20,7 +20,7 @@ public class MoveCommand
[Parameter("category")][Description("The category to move the ticket to. Only has to be the beginning of the name.")] string category) [Parameter("category")][Description("The category to move the ticket to. Only has to be the beginning of the name.")] string category)
{ {
// Check if ticket exists in the database // Check if ticket exists in the database
if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

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]
@ -73,7 +74,7 @@ public class NewCommand
for (; nrOfButtons < 5 * (nrOfButtonRows + 1) && nrOfButtons < verifiedCategories.Count; nrOfButtons++) for (; nrOfButtons < 5 * (nrOfButtonRows + 1) && nrOfButtons < verifiedCategories.Count; nrOfButtons++)
{ {
buttonRow.Add(new DiscordButtonComponent(DiscordButtonStyle.Primary, "supportchild_newcommandbutton " + verifiedCategories[nrOfButtons].id, verifiedCategories[nrOfButtons].name)); buttonRow.Add(new DiscordButtonComponent(DiscordButtonStyle.Primary, "supportboi_newcommandbutton " + verifiedCategories[nrOfButtons].id, verifiedCategories[nrOfButtons].name));
} }
builder.AddComponents(buttonRow); builder.AddComponents(buttonRow);
} }
@ -95,7 +96,7 @@ public class NewCommand
{ {
categoryOptions.Add(new DiscordSelectComponentOption(verifiedCategories[selectionOptions].name, verifiedCategories[selectionOptions].id.ToString())); categoryOptions.Add(new DiscordSelectComponentOption(verifiedCategories[selectionOptions].name, verifiedCategories[selectionOptions].id.ToString()));
} }
selectionComponents.Add(new DiscordSelectComponent("supportchild_newcommandselector" + selectionBoxes, "Open new ticket...", categoryOptions, false, 0, 1)); selectionComponents.Add(new DiscordSelectComponent("supportboi_newcommandselector" + selectionBoxes, "Open new ticket...", categoryOptions, false, 0, 1));
} }
await command.RespondAsync(new DiscordInteractionResponseBuilder().AddComponents(selectionComponents).AsEphemeral()); await command.RespondAsync(new DiscordInteractionResponseBuilder().AddComponents(selectionComponents).AsEphemeral());
@ -104,18 +105,18 @@ public class NewCommand
public static async Task<(bool, string)> OpenNewTicket(ulong userID, ulong commandChannelID, ulong categoryID) public static async Task<(bool, string)> OpenNewTicket(ulong userID, ulong commandChannelID, ulong categoryID)
{ {
// Check if user is blacklisted // Check if user is blacklisted
if (Database.IsBlacklisted(userID)) if (Database.Blacklist.IsBanned(userID))
{ {
return (false, "You are banned from opening tickets."); return (false, "You are banned from opening tickets.");
} }
if (Database.IsOpenTicket(commandChannelID)) if (Database.Ticket.IsOpenTicket(commandChannelID))
{ {
return (false, "You cannot use this command in a ticket channel."); return (false, "You cannot use this command in a ticket channel.");
} }
if (!Database.IsStaff(userID) if (!Database.StaffMember.IsStaff(userID)
&& Database.TryGetOpenTickets(userID, out List<Database.Ticket> ownTickets) && Database.Ticket.TryGetOpenTickets(userID, out List<Database.Ticket> ownTickets)
&& (ownTickets.Count >= Config.ticketLimit && Config.ticketLimit != 0)) && (ownTickets.Count >= Config.ticketLimit && Config.ticketLimit != 0))
{ {
return (false, "You have reached the limit for maximum open tickets."); return (false, "You have reached the limit for maximum open tickets.");
@ -173,7 +174,7 @@ public class NewCommand
assignedStaff = await RandomAssignCommand.GetRandomVerifiedStaffMember(ticketChannel, userID, 0, null); assignedStaff = await RandomAssignCommand.GetRandomVerifiedStaffMember(ticketChannel, userID, 0, null);
} }
long id = Database.NewTicket(member.Id, assignedStaff?.Id ?? 0, ticketChannel.Id); long id = Database.Ticket.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"));

View file

@ -20,7 +20,7 @@ public class RandomAssignCommand
public async Task OnExecute(SlashCommandContext command, [Parameter("role")][Description("(Optional) Limit the random assignment to a specific role.")] DiscordRole role = null) public async Task OnExecute(SlashCommandContext command, [Parameter("role")][Description("(Optional) Limit the random assignment to a specific role.")] DiscordRole role = null)
{ {
// Check if ticket exists in the database // Check if ticket exists in the database
if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -43,7 +43,7 @@ public class RandomAssignCommand
} }
// Attempt to assign the staff member to the ticket // Attempt to assign the staff member to the ticket
if (!Database.AssignStaff(ticket, staffMember.Id)) if (!Database.StaffMember.AssignStaff(ticket, staffMember.Id))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -85,14 +85,14 @@ public class RandomAssignCommand
if (targetRole == null) if (targetRole == null)
{ {
// No role was specified, any active staff will be picked // No role was specified, any active staff will be picked
staffMembers = Database.GetActiveStaff(ignoredUserIDs); staffMembers = Database.StaffMember.GetActiveStaff(ignoredUserIDs);
} }
else else
{ {
// Check if role rassign should override staff's active status // Check if role rassign should override staff's active status
staffMembers = Config.randomAssignRoleOverride staffMembers = Config.randomAssignRoleOverride
? Database.GetAllStaff(ignoredUserIDs) ? Database.StaffMember.GetAllStaff(ignoredUserIDs)
: Database.GetActiveStaff(ignoredUserIDs); : Database.StaffMember.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

View file

@ -16,7 +16,7 @@ public class RemoveCategoryCommand
public async Task OnExecute(SlashCommandContext command, public async Task OnExecute(SlashCommandContext command,
[Parameter("category")][Description("The category to remove.")] DiscordChannel category) [Parameter("category")][Description("The category to remove.")] DiscordChannel category)
{ {
if (!Database.TryGetCategory(category.Id, out Database.Category _)) if (!Database.Category.TryGetCategory(category.Id, out Database.Category _))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -26,7 +26,7 @@ public class RemoveCategoryCommand
return; return;
} }
if (Database.RemoveCategory(category.Id)) if (Database.Category.RemoveCategory(category.Id))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -1,49 +0,0 @@
using System.ComponentModel;
using System.Globalization;
using DSharpPlus.Entities;
using System.Threading.Tasks;
using DSharpPlus.Commands;
using DSharpPlus.Commands.ContextChecks;
using DSharpPlus.Commands.Processors.SlashCommands;
using DSharpPlus.Exceptions;
namespace SupportChild.Commands;
public class RemoveMessageCommand
{
[RequireGuild]
[Command("removemessage")]
[Description("Removes a message from the 'say' command.")]
public async Task OnExecute(SlashCommandContext command,
[Parameter("identifier")][Description("The identifier word used in the /say command.")] string identifier)
{
if (!Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _))
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "There is no message with that identifier."
}, true);
return;
}
if (Database.RemoveMessage(identifier))
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = "Message removed."
}, true);
}
else
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "Error: Failed removing the message from the database."
}, true);
}
await LogChannel.Success("`" + identifier + "` was removed from the /say command by " + command.User.Mention + ".");
}
}

View file

@ -18,7 +18,7 @@ public class RemoveStaffCommand
public async Task OnExecute(SlashCommandContext command, public async Task OnExecute(SlashCommandContext command,
[Parameter("user")][Description("User to remove from staff.")] DiscordUser user) [Parameter("user")][Description("User to remove from staff.")] DiscordUser user)
{ {
if (!Database.IsStaff(user.Id)) if (!Database.StaffMember.IsStaff(user.Id))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -30,11 +30,11 @@ public class RemoveStaffCommand
await command.DeferResponseAsync(true); await command.DeferResponseAsync(true);
if (Database.TryGetAssignedTickets(user.Id, out List<Database.Ticket> assignedTickets)) if (Database.Ticket.TryGetAssignedTickets(user.Id, out List<Database.Ticket> assignedTickets))
{ {
foreach (Database.Ticket assignedTicket in assignedTickets) foreach (Database.Ticket assignedTicket in assignedTickets)
{ {
Database.UnassignStaff(assignedTicket); Database.StaffMember.UnassignStaff(assignedTicket);
try try
{ {
DiscordChannel ticketChannel = await SupportChild.client.GetChannelAsync(assignedTicket.channelID); DiscordChannel ticketChannel = await SupportChild.client.GetChannelAsync(assignedTicket.channelID);
@ -53,19 +53,23 @@ public class RemoveStaffCommand
} }
} }
await using MySqlConnection c = Database.GetConnection(); if (Database.StaffMember.RemoveStaff(user.Id))
c.Open();
MySqlCommand deletion = new MySqlCommand(@"DELETE FROM staff WHERE user_id=@user_id", c);
deletion.Parameters.AddWithValue("@user_id", user.Id);
await deletion.PrepareAsync();
deletion.ExecuteNonQuery();
await command.RespondAsync(new DiscordEmbedBuilder
{ {
Color = DiscordColor.Green, await command.RespondAsync(new DiscordEmbedBuilder
Description = "User was removed from staff." {
}, true); Color = DiscordColor.Green,
Description = "User was removed from staff."
}, true);
await LogChannel.Success(user.Mention + " was removed from staff by " + command.User.Mention + "."); await LogChannel.Success(user.Mention + " was removed from staff by " + command.User.Mention + ".");
}
else
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "Error: Failed to remove user from staff."
}, true);
}
} }
} }

View file

@ -27,7 +27,7 @@ public class SayCommand
return; return;
} }
if (!Database.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), if (!Database.Message.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture),
out Database.Message message)) out Database.Message message))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
@ -44,12 +44,12 @@ 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 + "."); await LogChannel.Success(command.User.Mention + " posted the `" + message.identifier + "` message in " + command.Channel.Mention + ".");
} }
private static async Task SendMessageList(SlashCommandContext command) private static async Task SendMessageList(SlashCommandContext command)
{ {
List<Database.Message> messages = Database.GetAllMessages(); List<Database.Message> messages = Database.Message.GetAllMessages();
if (messages.Count == 0) if (messages.Count == 0)
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder

View file

@ -0,0 +1,199 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using DSharpPlus.Entities;
using System.Threading.Tasks;
using DSharpPlus.Commands;
using DSharpPlus.Commands.ContextChecks;
using DSharpPlus.Commands.Processors.SlashCommands;
namespace SupportChild.Commands;
public class SetMessageCommand
{
private readonly struct CommandInfo(string identifier, string message)
{
public string identifier { get; } = identifier;
public string message { get; } = message;
}
private static Dictionary<ulong, CommandInfo> waitingCommands = new();
[RequireGuild]
[Command("setmessage")]
[Description("Adds or updates message for the 'say' command.")]
public async Task OnExecute(SlashCommandContext command,
[Parameter("identifier")][Description("The identifier word used in the /say command.")] string identifier,
[Parameter("message")][Description("The message the /say command will return. Empty to delete message.")] string message = "")
{
if (Database.Message.TryGetMessage(identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message oldMessage))
{
if (string.IsNullOrEmpty(message))
{
await command.RespondAsync(new DiscordInteractionResponseBuilder()
.AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Orange,
Description = "Are you sure you want to delete the `" + identifier + "` message?"
})
.AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Danger, "supportboi_confirmmessagedelete " + command.Interaction.Id, "Confirm"),
new DiscordButtonComponent(DiscordButtonStyle.Secondary, "supportboi_cancelmessagedelete " + command.Interaction.Id, "Cancel")));
}
else
{
await command.RespondAsync(new DiscordInteractionResponseBuilder()
.AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Cyan,
Title = "Replace the `" + identifier + "` message?"
}
.AddField("Old message:", oldMessage.message.Truncate(1024)).AddField("New message:", message.Truncate(1024)))
.AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Success, "supportboi_confirmmessageupdate " + command.Interaction.Id, "Confirm"),
new DiscordButtonComponent(DiscordButtonStyle.Secondary, "supportboi_cancelmessageupdate " + command.Interaction.Id, "Cancel")));
}
waitingCommands.Add(command.Interaction.Id, new CommandInfo(identifier, message));
}
else
{
if (string.IsNullOrEmpty(message))
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "Cannot delete that message, it does not exist."
}, true);
}
else
{
await AddNewMessage(command, identifier, message);
}
}
}
private static async Task AddNewMessage(SlashCommandContext command, string identifier, string message)
{
if (Database.Message.AddMessage(identifier, command.Member.Id, message))
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = "Message added."
}, true);
}
else
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "Error: Failed adding the message to the database."
}, true);
}
await LogChannel.Success(command.User.Mention + " added the `" + identifier + "` message for the /say command.\n\n**Content:**\n\n" + message);
}
public static async Task ConfirmMessageDeletion(DiscordInteraction interaction, ulong previousInteractionID)
{
if (!waitingCommands.Remove(previousInteractionID, out CommandInfo command))
{
await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "I don't remember which message you wanted to delete, has the bot been restarted since the command was used?"
}));
return;
}
if (!Database.Message.TryGetMessage(command.identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _))
{
await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "There is no message with that identifier."
}));
return;
}
if (Database.Message.RemoveMessage(command.identifier))
{
await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = "Message removed."
}));
}
else
{
await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "Error: Failed removing the message from the database."
}));
}
await LogChannel.Success("`" + command.identifier + "` was removed from the /say command by " + interaction.User.Mention + ".");
}
public static async Task CancelMessageDeletion(DiscordInteraction interaction, ulong previousInteractionID)
{
waitingCommands.Remove(previousInteractionID);
if (interaction.Message != null)
{
await interaction.Message.DeleteAsync();
}
}
public static async Task ConfirmMessageUpdate(DiscordInteraction interaction, ulong previousInteractionID)
{
if (!waitingCommands.Remove(previousInteractionID, out CommandInfo command))
{
await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "I don't remember which message you wanted to update, has the bot been restarted since the command was used?"
}));
return;
}
if (!Database.Message.TryGetMessage(command.identifier.ToLower(CultureInfo.InvariantCulture), out Database.Message _))
{
await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "There is no message with that identifier."
}));
return;
}
if (Database.Message.UpdateMessage(command.identifier, interaction.User.Id, command.message))
{
await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = "Message updated."
}));
}
else
{
await interaction.CreateResponseAsync(DiscordInteractionResponseType.UpdateMessage, new DiscordInteractionResponseBuilder().AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "Error: Failed updating the message in the database."
}));
}
await LogChannel.Success("`" + command.identifier + "` was updated for the /say command by " + interaction.User.Mention + ".");
}
public static async Task CancelMessageUpdate(DiscordInteraction interaction, ulong previousInteractionID)
{
waitingCommands.Remove(previousInteractionID);
if (interaction.Message != null)
{
await interaction.Message.DeleteAsync();
}
}
}

View file

@ -16,9 +16,8 @@ public class SetSummaryCommand
[Description("Sets a ticket's summary for the summary command.")] [Description("Sets a ticket's summary for the summary command.")]
public async Task OnExecute(SlashCommandContext command, [Parameter("Summary")][Description("The ticket summary text.")] string summary) public async Task OnExecute(SlashCommandContext command, [Parameter("Summary")][Description("The ticket summary text.")] string summary)
{ {
ulong channelID = command.Channel.Id;
// Check if ticket exists in the database // Check if ticket exists in the database
if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -28,21 +27,23 @@ public class SetSummaryCommand
return; return;
} }
await using MySqlConnection c = Database.GetConnection(); if (Database.Ticket.SetSummary(command.Channel.Id, summary))
c.Open();
MySqlCommand update = new MySqlCommand(@"UPDATE tickets SET summary = @summary WHERE channel_id = @channel_id", c);
update.Parameters.AddWithValue("@summary", summary);
update.Parameters.AddWithValue("@channel_id", channelID);
await update.PrepareAsync();
update.ExecuteNonQuery();
update.Dispose();
await command.RespondAsync(new DiscordEmbedBuilder
{ {
Color = DiscordColor.Green, await command.RespondAsync(new DiscordEmbedBuilder
Description = "Summary set." {
}, true); Color = DiscordColor.Green,
Description = "Summary set."
}, true);
await LogChannel.Success(command.User.Mention + " set the summary for " + command.Channel.Mention + " to:\n\n" + summary, ticket.id); await LogChannel.Success(command.User.Mention + " set the summary for " + command.Channel.Mention + " to:\n\n" + summary, ticket.id);
}
else
{
await command.RespondAsync(new DiscordEmbedBuilder
{
Color = DiscordColor.Red,
Description = "Error: Failed setting the summary of a ticket in database."
}, true);
}
} }
} }

View file

@ -14,11 +14,11 @@ public class StatusCommand
[Description("Shows bot status and information.")] [Description("Shows bot status and information.")]
public async Task OnExecute(SlashCommandContext command) public async Task OnExecute(SlashCommandContext command)
{ {
long openTickets = Database.GetNumberOfTickets(); long openTickets = Database.Ticket.GetNumberOfTickets();
long closedTickets = Database.GetNumberOfClosedTickets(); long closedTickets = Database.Ticket.GetNumberOfClosedTickets();
DiscordEmbed botInfo = new DiscordEmbedBuilder() DiscordEmbed botInfo = new DiscordEmbedBuilder()
.WithAuthor("Emotion-stuff/supportchild @ Toastielab", "https://toastielab.dev/toastie-stuff/SupportChild", "https://cdn.discordapp.com/attachments/765441543100170271/914327948667011132/Ellie_Concept_2_transparent_ver.png") .WithAuthor("toastie-stuff/supportchild @ Toastielab", "https://toastielab.dev/toastie-stuff/SupportChild", "https://cdn.discordapp.com/attachments/765441543100170271/914327948667011132/Ellie_Concept_2_transparent_ver.png")
.WithTitle("Bot information") .WithTitle("Bot information")
.WithColor(DiscordColor.Cyan) .WithColor(DiscordColor.Cyan)
.AddField("Version:", SupportChild.GetVersion(), true) .AddField("Version:", SupportChild.GetVersion(), true)

View file

@ -14,7 +14,7 @@ public class SummaryCommand
[Description("Shows ticket information and summary if there is one.")] [Description("Shows ticket information and summary if there is one.")]
public async Task OnExecute(SlashCommandContext command) public async Task OnExecute(SlashCommandContext command)
{ {
if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -18,7 +18,7 @@ public class ToggleActiveCommand
DiscordUser staffUser = user == null ? command.User : user; DiscordUser staffUser = user == null ? command.User : user;
// Check if ticket exists in the database // Check if ticket exists in the database
if (!Database.TryGetStaff(staffUser.Id, out Database.StaffMember staffMember)) if (!Database.StaffMember.TryGetStaff(staffUser.Id, out Database.StaffMember staffMember))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -28,7 +28,7 @@ public class ToggleActiveCommand
return; return;
} }
if (Database.SetStaffActive(staffUser.Id, !staffMember.active)) if (Database.StaffMember.SetStaffActive(staffUser.Id, !staffMember.active))
{ {
if (user != null && user.Id != command.User.Id) if (user != null && user.Id != command.User.Id)
{ {

View file

@ -23,7 +23,7 @@ public class TranscriptCommand
Database.Ticket ticket; Database.Ticket ticket;
if (ticketID == 0) // If there are no arguments use current channel if (ticketID == 0) // If there are no arguments use current channel
{ {
if (Database.TryGetOpenTicket(command.Channel.Id, out ticket)) if (Database.Ticket.TryGetOpenTicket(command.Channel.Id, out ticket))
{ {
try try
{ {
@ -52,7 +52,7 @@ public class TranscriptCommand
else else
{ {
// If the ticket is still open, generate a new fresh transcript // If the ticket is still open, generate a new fresh transcript
if (Database.TryGetOpenTicketByID((uint)ticketID, out ticket) && ticket?.creatorID == command.Member.Id) if (Database.Ticket.TryGetOpenTicketByID((uint)ticketID, out ticket) && ticket?.creatorID == command.Member.Id)
{ {
try try
{ {
@ -70,7 +70,7 @@ public class TranscriptCommand
} }
// If there is no open or closed ticket, send an error. If there is a closed ticket we will simply use the old transcript from when the ticket was closed. // If there is no open or closed ticket, send an error. If there is a closed ticket we will simply use the old transcript from when the ticket was closed.
else if (!Database.TryGetClosedTicket((uint)ticketID, out ticket) || (ticket?.creatorID != command.Member.Id && !Database.IsStaff(command.Member.Id))) else if (!Database.Ticket.TryGetClosedTicket((uint)ticketID, out ticket) || (ticket?.creatorID != command.Member.Id && !Database.StaffMember.IsStaff(command.Member.Id)))
{ {
await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder
{ {
@ -83,18 +83,22 @@ public class TranscriptCommand
string fileName = Transcriber.GetZipFilename(ticket.id); string fileName = Transcriber.GetZipFilename(ticket.id);
string filePath = Transcriber.GetZipPath(ticket.id); string filePath = Transcriber.GetZipPath(ticket.id);
long zipSize = 0; bool zipTooLarge = false;
// If the zip transcript doesn't exist, use the html file. // If the zip transcript doesn't exist or is too large, use the html file.
try try
{ {
FileInfo fileInfo = new FileInfo(filePath); FileInfo zipFile = new(filePath);
if (!fileInfo.Exists || fileInfo.Length >= 26214400) if (!zipFile.Exists || zipFile.Length >= 26214400)
{ {
fileName = Transcriber.GetHTMLFilename(ticket.id); fileName = Transcriber.GetHTMLFilename(ticket.id);
filePath = Transcriber.GetHtmlPath(ticket.id); filePath = Transcriber.GetHtmlPath(ticket.id);
} }
zipSize = fileInfo.Length;
if (zipFile.Exists && zipFile.Length >= 26214400)
{
zipTooLarge = true;
}
} }
catch (Exception e) catch (Exception e)
{ {
@ -121,7 +125,7 @@ public class TranscriptCommand
try try
{ {
await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read); await using FileStream file = new(filePath, FileMode.Open, FileAccess.Read);
await LogChannel.Success("Transcript generated by " + command.User.Mention + ".", ticket.id, new Utilities.File(fileName, file)); await LogChannel.Success("Transcript generated by " + command.User.Mention + ".", ticket.id, new Utilities.File(fileName, file));
} }
catch (Exception e) catch (Exception e)
@ -129,26 +133,14 @@ public class TranscriptCommand
Logger.Error("Failed to log transcript generation.", e); Logger.Error("Failed to log transcript generation.", e);
} }
if (await SendDirectMessage(command, fileName, filePath, zipSize, ticket.id))
{
await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = "Transcript sent!\n"
}));
}
}
private static async Task<bool> SendDirectMessage(SlashCommandContext command, string fileName, string filePath, long zipSize, uint ticketID)
{
try try
{ {
// Send transcript in a direct message // Send transcript in a direct message
await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read); await using FileStream file = new(filePath, FileMode.Open, FileAccess.Read);
DiscordMessageBuilder directMessage = new DiscordMessageBuilder(); DiscordMessageBuilder directMessage = new();
if (zipSize >= 26214400) if (zipTooLarge)
{ {
directMessage.AddEmbed(new DiscordEmbedBuilder directMessage.AddEmbed(new DiscordEmbedBuilder
{ {
@ -176,7 +168,12 @@ public class TranscriptCommand
directMessage.AddFiles(new Dictionary<string, Stream> { { fileName, file } }); directMessage.AddFiles(new Dictionary<string, Stream> { { fileName, file } });
await command.Member.SendMessageAsync(directMessage); await command.Member.SendMessageAsync(directMessage);
return true;
await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder
{
Color = DiscordColor.Green,
Description = "Transcript sent!\n"
}));
} }
catch (UnauthorizedException) catch (UnauthorizedException)
{ {
@ -185,7 +182,6 @@ public class TranscriptCommand
Color = DiscordColor.Red, Color = DiscordColor.Red,
Description = "Not allowed to send direct message to you, please check your privacy settings.\n" Description = "Not allowed to send direct message to you, please check your privacy settings.\n"
})); }));
return false;
} }
} }
} }

View file

@ -16,7 +16,7 @@ public class UnassignCommand
public async Task OnExecute(SlashCommandContext command) public async Task OnExecute(SlashCommandContext command)
{ {
// Check if ticket exists in the database // Check if ticket exists in the database
if (!Database.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket)) if (!Database.Ticket.TryGetOpenTicket(command.Channel.Id, out Database.Ticket ticket))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {
@ -26,7 +26,7 @@ public class UnassignCommand
return; return;
} }
if (!Database.UnassignStaff(ticket)) if (!Database.StaffMember.UnassignStaff(ticket))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -18,7 +18,7 @@ public class UnblacklistCommand
{ {
try try
{ {
if (!Database.Unblacklist(user.Id)) if (!Database.Blacklist.Unban(user.Id))
{ {
await command.RespondAsync(new DiscordEmbedBuilder await command.RespondAsync(new DiscordEmbedBuilder
{ {

View file

@ -1,983 +0,0 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Security.Cryptography;
using DSharpPlus;
using MySqlConnector;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using SupportChild.Interviews;
namespace SupportChild;
public static class Database
{
private static string connectionString = "";
public static void SetConnectionString(string host, int port, string database, string username, string password)
{
connectionString = "server=" + host +
";database=" + database +
";port=" + port +
";userid=" + username +
";password=" + password;
}
public static MySqlConnection GetConnection()
{
return new MySqlConnection(connectionString);
}
public static long GetNumberOfTickets()
{
try
{
using MySqlConnection c = GetConnection();
using MySqlCommand countTickets = new MySqlCommand("SELECT COUNT(*) FROM tickets", c);
c.Open();
return (long)countTickets.ExecuteScalar();
}
catch (MySqlException e)
{
Logger.Error("Error occured when attempting to count number of open tickets.", e);
}
return -1;
}
public static long GetNumberOfClosedTickets()
{
try
{
using MySqlConnection c = GetConnection();
using MySqlCommand countTickets = new MySqlCommand("SELECT COUNT(*) FROM ticket_history", c);
c.Open();
return (long)countTickets.ExecuteScalar();
}
catch (MySqlException e)
{
Logger.Error("Error occured when attempting to count number of open tickets.", e);
}
return -1;
}
public static void SetupTables()
{
using MySqlConnection c = GetConnection();
using MySqlCommand createTickets = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS tickets(" +
"id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT," +
"created_time DATETIME NOT NULL," +
"creator_id BIGINT UNSIGNED NOT NULL," +
"assigned_staff_id BIGINT UNSIGNED NOT NULL DEFAULT 0," +
"summary VARCHAR(5000) NOT NULL," +
"channel_id BIGINT UNSIGNED NOT NULL UNIQUE," +
"INDEX(created_time, assigned_staff_id, channel_id))",
c);
using MySqlCommand createTicketHistory = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS ticket_history(" +
"id INT UNSIGNED NOT NULL PRIMARY KEY," +
"created_time DATETIME NOT NULL," +
"closed_time DATETIME NOT NULL," +
"creator_id BIGINT UNSIGNED NOT NULL," +
"assigned_staff_id BIGINT UNSIGNED NOT NULL DEFAULT 0," +
"summary VARCHAR(5000) NOT NULL," +
"channel_id BIGINT UNSIGNED NOT NULL UNIQUE," +
"INDEX(created_time, closed_time, channel_id))",
c);
using MySqlCommand createBlacklisted = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS blacklisted_users(" +
"user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
"time DATETIME NOT NULL," +
"moderator_id BIGINT UNSIGNED NOT NULL," +
"INDEX(user_id, time))",
c);
using MySqlCommand createStaffList = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS staff(" +
"user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
"name VARCHAR(256) NOT NULL," +
"active BOOLEAN NOT NULL DEFAULT true)",
c);
using MySqlCommand createMessages = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS messages(" +
"identifier VARCHAR(256) NOT NULL PRIMARY KEY," +
"user_id BIGINT UNSIGNED NOT NULL," +
"message VARCHAR(5000) NOT NULL)",
c);
using MySqlCommand createCategories = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS categories(" +
"name VARCHAR(256) NOT NULL UNIQUE," +
"category_id BIGINT UNSIGNED NOT NULL PRIMARY KEY)",
c);
using MySqlCommand createInterviews = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS interviews(" +
"channel_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
"interview JSON NOT NULL," +
"definitions JSON NOT NULL)",
c);
using MySqlCommand createInterviewTemplates = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS interview_templates(" +
"category_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
"template JSON NOT NULL)",
c);
c.Open();
createTickets.ExecuteNonQuery();
createBlacklisted.ExecuteNonQuery();
createTicketHistory.ExecuteNonQuery();
createStaffList.ExecuteNonQuery();
createMessages.ExecuteNonQuery();
createCategories.ExecuteNonQuery();
createInterviews.ExecuteNonQuery();
createInterviewTemplates.ExecuteNonQuery();
}
public static bool IsOpenTicket(ulong channelID)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE channel_id=@channel_id", c);
selection.Parameters.AddWithValue("@channel_id", channelID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if ticket exists in the database
if (!results.Read())
{
return false;
}
results.Close();
return true;
}
public static bool TryGetOpenTicket(ulong channelID, out Ticket ticket)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE channel_id=@channel_id", c);
selection.Parameters.AddWithValue("@channel_id", channelID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if ticket exists in the database
if (!results.Read())
{
ticket = null;
return false;
}
ticket = new Ticket(results);
results.Close();
return true;
}
public static bool TryGetOpenTicketByID(uint id, out Ticket ticket)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE id=@id", c);
selection.Parameters.AddWithValue("@id", id);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if open ticket exists in the database
if (results.Read())
{
ticket = new Ticket(results);
results.Close();
return true;
}
results.Close();
ticket = null;
return false;
}
public static bool TryGetClosedTicket(uint id, out Ticket ticket)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM ticket_history WHERE id=@id", c);
selection.Parameters.AddWithValue("@id", id);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if closed ticket exists in the database
if (results.Read())
{
ticket = new Ticket(results);
results.Close();
return true;
}
ticket = null;
results.Close();
return false;
}
public static bool TryGetOpenTickets(ulong userID, out List<Ticket> tickets)
{
tickets = null;
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE creator_id=@creator_id", c);
selection.Parameters.AddWithValue("@creator_id", userID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return false;
}
tickets = new List<Ticket> { new Ticket(results) };
while (results.Read())
{
tickets.Add(new Ticket(results));
}
results.Close();
return true;
}
public static bool TryGetOpenTickets(out List<Ticket> tickets)
{
tickets = null;
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets ORDER BY channel_id ASC", c);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return false;
}
tickets = new List<Ticket> { new Ticket(results) };
while (results.Read())
{
tickets.Add(new Ticket(results));
}
results.Close();
return true;
}
public static bool TryGetClosedTickets(ulong userID, out List<Ticket> tickets)
{
tickets = null;
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM ticket_history WHERE creator_id=@creator_id", c);
selection.Parameters.AddWithValue("@creator_id", userID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return false;
}
tickets = new List<Ticket> { new Ticket(results) };
while (results.Read())
{
tickets.Add(new Ticket(results));
}
results.Close();
return true;
}
public static bool TryGetAssignedTickets(ulong staffID, out List<Ticket> tickets)
{
tickets = null;
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM tickets WHERE assigned_staff_id=@assigned_staff_id", c);
selection.Parameters.AddWithValue("@assigned_staff_id", staffID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return false;
}
tickets = new List<Ticket> { new Ticket(results) };
while (results.Read())
{
tickets.Add(new Ticket(results));
}
results.Close();
return true;
}
public static long NewTicket(ulong memberID, ulong staffID, ulong ticketID)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO tickets (created_time, creator_id, assigned_staff_id, summary, channel_id) VALUES (UTC_TIMESTAMP(), @creator_id, @assigned_staff_id, @summary, @channel_id);", c);
cmd.Parameters.AddWithValue("@creator_id", memberID);
cmd.Parameters.AddWithValue("@assigned_staff_id", staffID);
cmd.Parameters.AddWithValue("@summary", "");
cmd.Parameters.AddWithValue("@channel_id", ticketID);
cmd.ExecuteNonQuery();
return cmd.LastInsertedId;
}
public static void ArchiveTicket(Ticket ticket)
{
// Check if ticket already exists in the archive
if (TryGetClosedTicket(ticket.id, out Ticket _))
{
using MySqlConnection c = GetConnection();
using MySqlCommand deleteTicket = new MySqlCommand(@"DELETE FROM ticket_history WHERE id=@id OR channel_id=@channel_id", c);
deleteTicket.Parameters.AddWithValue("@id", ticket.id);
deleteTicket.Parameters.AddWithValue("@channel_id", ticket.channelID);
c.Open();
deleteTicket.Prepare();
deleteTicket.ExecuteNonQuery();
}
// Create an entry in the ticket history database
using MySqlConnection conn = GetConnection();
using MySqlCommand archiveTicket = new MySqlCommand(@"INSERT INTO ticket_history (id, created_time, closed_time, creator_id, assigned_staff_id, summary, channel_id) VALUES (@id, @created_time, UTC_TIMESTAMP(), @creator_id, @assigned_staff_id, @summary, @channel_id);", conn);
archiveTicket.Parameters.AddWithValue("@id", ticket.id);
archiveTicket.Parameters.AddWithValue("@created_time", ticket.channelID.GetSnowflakeTime());
archiveTicket.Parameters.AddWithValue("@creator_id", ticket.creatorID);
archiveTicket.Parameters.AddWithValue("@assigned_staff_id", ticket.assignedStaffID);
archiveTicket.Parameters.AddWithValue("@summary", ticket.summary);
archiveTicket.Parameters.AddWithValue("@channel_id", ticket.channelID);
conn.Open();
archiveTicket.Prepare();
archiveTicket.ExecuteNonQuery();
}
public static bool DeleteOpenTicket(uint ticketID)
{
try
{
using MySqlConnection c = GetConnection();
using MySqlCommand deletion = new MySqlCommand(@"DELETE FROM tickets WHERE id=@id", c);
deletion.Parameters.AddWithValue("@id", ticketID);
c.Open();
deletion.Prepare();
return deletion.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not delete open ticket in database.", e);
return false;
}
}
public static bool IsBlacklisted(ulong userID)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM blacklisted_users WHERE user_id=@user_id", c);
selection.Parameters.AddWithValue("@user_id", userID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if user is blacklisted
if (results.Read())
{
results.Close();
return true;
}
results.Close();
return false;
}
public static bool Blacklist(ulong blacklistedID, ulong staffID)
{
try
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO blacklisted_users (user_id,time,moderator_id) VALUES (@user_id, UTC_TIMESTAMP(), @moderator_id);", c);
cmd.Parameters.AddWithValue("@user_id", blacklistedID);
cmd.Parameters.AddWithValue("@moderator_id", staffID);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not blacklist user in database.", e);
return false;
}
}
public static bool Unblacklist(ulong blacklistedID)
{
try
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM blacklisted_users WHERE user_id=@user_id", c);
cmd.Parameters.AddWithValue("@user_id", blacklistedID);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not unblacklist user in database.", e);
return false;
}
}
public static bool AssignStaff(Ticket ticket, ulong staffID)
{
try
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand update = new MySqlCommand(@"UPDATE tickets SET assigned_staff_id = @assigned_staff_id WHERE id = @id", c);
update.Parameters.AddWithValue("@assigned_staff_id", staffID);
update.Parameters.AddWithValue("@id", ticket.id);
update.Prepare();
return update.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not add staff to ticket in database.", e);
return false;
}
}
public static bool UnassignStaff(Ticket ticket)
{
return AssignStaff(ticket, 0);
}
public static bool SetStaffActive(ulong staffID, bool active)
{
try
{
using MySqlConnection c = GetConnection();
c.Open();
MySqlCommand update = new MySqlCommand(@"UPDATE staff SET active = @active WHERE user_id = @user_id", c);
update.Parameters.AddWithValue("@user_id", staffID);
update.Parameters.AddWithValue("@active", active);
update.Prepare();
return update.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not set staff member as active in database.", e);
return false;
}
}
public static List<StaffMember> GetActiveStaff(params ulong[] ignoredUserIDs)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE active = true AND user_id NOT IN (@user_ids)", c);
selection.Parameters.AddWithValue("@user_ids", string.Join(",", ignoredUserIDs));
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return new List<StaffMember>();
}
List<StaffMember> staffMembers = new List<StaffMember> { new StaffMember(results) };
while (results.Read())
{
staffMembers.Add(new StaffMember(results));
}
results.Close();
return staffMembers;
}
public static List<StaffMember> GetAllStaff(params ulong[] ignoredUserIDs)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE user_id NOT IN (@user_ids)", c);
selection.Parameters.AddWithValue("@user_ids", string.Join(",", ignoredUserIDs));
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return new List<StaffMember>();
}
List<StaffMember> staffMembers = new List<StaffMember> { new StaffMember(results) };
while (results.Read())
{
staffMembers.Add(new StaffMember(results));
}
results.Close();
return staffMembers;
}
public static bool IsStaff(ulong staffID)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE user_id=@user_id", c);
selection.Parameters.AddWithValue("@user_id", staffID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return false;
}
results.Close();
return true;
}
public static bool TryGetStaff(ulong staffID, out StaffMember staffMember)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM staff WHERE user_id=@user_id", c);
selection.Parameters.AddWithValue("@user_id", staffID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
staffMember = null;
return false;
}
staffMember = new StaffMember(results);
results.Close();
return true;
}
public static List<Message> GetAllMessages()
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM messages", c);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return new List<Message>();
}
List<Message> messages = new List<Message> { new Message(results) };
while (results.Read())
{
messages.Add(new Message(results));
}
results.Close();
return messages;
}
public static bool TryGetMessage(string identifier, out Message message)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM messages WHERE identifier=@identifier", c);
selection.Parameters.AddWithValue("@identifier", identifier);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if ticket exists in the database
if (!results.Read())
{
message = null;
return false;
}
message = new Message(results);
results.Close();
return true;
}
public static bool AddMessage(string identifier, ulong userID, string message)
{
try
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO messages (identifier,user_id,message) VALUES (@identifier, @user_id, @message);", c);
cmd.Parameters.AddWithValue("@identifier", identifier);
cmd.Parameters.AddWithValue("@user_id", userID);
cmd.Parameters.AddWithValue("@message", message);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not add message to database.", e);
return false;
}
}
public static bool RemoveMessage(string identifier)
{
try
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM messages WHERE identifier=@identifier", c);
cmd.Parameters.AddWithValue("@identifier", identifier);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not remove message from database.", e);
return false;
}
}
public static List<Category> GetAllCategories()
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories", c);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return new List<Category>();
}
List<Category> categories = new List<Category> { new Category(results) };
while (results.Read())
{
categories.Add(new Category(results));
}
results.Close();
return categories;
}
public static bool TryGetCategory(ulong categoryID, out Category message)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories WHERE category_id=@category_id", c);
selection.Parameters.AddWithValue("@category_id", categoryID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
message = null;
return false;
}
message = new Category(results);
results.Close();
return true;
}
public static bool TryGetCategory(string name, out Category message)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories WHERE name=@name", c);
selection.Parameters.AddWithValue("@name", name);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
message = null;
return false;
}
message = new Category(results);
results.Close();
return true;
}
public static bool AddCategory(string name, ulong categoryID)
{
try
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO categories (name,category_id) VALUES (@name, @category_id);", c);
cmd.Parameters.AddWithValue("@name", name);
cmd.Parameters.AddWithValue("@category_id", categoryID);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not add category to database.", e);
return false;
}
}
public static bool RemoveCategory(ulong categoryID)
{
try
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM categories WHERE category_id=@category_id", c);
cmd.Parameters.AddWithValue("@category_id", categoryID);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not remove category from database.", e);
return false;
}
}
public static bool TryGetInterviewTemplateJSON(ulong categoryID, out string templateJSON)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand("SELECT * FROM interview_templates WHERE category_id=@category_id", c);
selection.Parameters.AddWithValue("@category_id", categoryID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
templateJSON = null;
return false;
}
templateJSON = results.GetString("template");
results.Close();
return true;
}
public static bool TryGetInterviewFromTemplate(ulong categoryID, ulong channelID, out Interview interview)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand("SELECT * FROM interview_templates WHERE category_id=@category_id", c);
selection.Parameters.AddWithValue("@category_id", categoryID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if messages exist in the database
if (!results.Read())
{
interview = null;
return false;
}
string templateString = results.GetString("template");
results.Close();
try
{
Template template = JsonConvert.DeserializeObject<Template>(templateString, new JsonSerializerSettings
{
Error = delegate (object sender, ErrorEventArgs args)
{
Logger.Error("Error occured when trying to read interview template '" + categoryID + "' from database: " + args.ErrorContext.Error.Message);
Logger.Debug("Detailed exception:", args.ErrorContext.Error);
args.ErrorContext.Handled = false;
}
});
interview = new Interview(channelID, template.interview, template.definitions);
return true;
}
catch (Exception e)
{
Logger.Warn("Unable to create interview object from the current template for category '" + categoryID + "' in the database.", e);
interview = null;
return false;
}
}
public static bool SetInterviewTemplate(Template template)
{
try
{
string templateString = JsonConvert.SerializeObject(template, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Error,
Formatting = Formatting.Indented,
ContractResolver = new InterviewStep.StripInternalPropertiesResolver()
});
string query;
if (TryGetInterviewTemplateJSON(template.categoryID, out _))
{
query = "UPDATE interview_templates SET template = @template WHERE category_id=@category_id";
}
else
{
query = "INSERT INTO interview_templates (category_id,template) VALUES (@category_id, @template)";
}
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(query, c);
cmd.Parameters.AddWithValue("@category_id", template.categoryID);
cmd.Parameters.AddWithValue("@template", templateString);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not set interview template in database.", e);
return false;
}
}
public static bool TryDeleteInterviewTemplate(ulong categoryID)
{
try
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand deletion = new MySqlCommand(@"DELETE FROM interview_templates WHERE category_id=@category_id", c);
deletion.Parameters.AddWithValue("@category_id", categoryID);
deletion.Prepare();
return deletion.ExecuteNonQuery() > 0;
}
catch (MySqlException)
{
return false;
}
}
public static bool SaveInterview(Interview interview)
{
try
{
string query;
if (TryGetInterview(interview.channelID, out _))
{
query = "UPDATE interviews SET interview = @interview, definitions = @definitions WHERE channel_id = @channel_id";
}
else
{
query = "INSERT INTO interviews (channel_id,interview, definitions) VALUES (@channel_id, @interview, @definitions)";
}
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(query, c);
cmd.Parameters.AddWithValue("@channel_id", interview.channelID);
cmd.Parameters.AddWithValue("@interview", JsonConvert.SerializeObject(interview.interviewRoot, Formatting.Indented));
cmd.Parameters.AddWithValue("@definitions", JsonConvert.SerializeObject(interview.definitions, Formatting.Indented));
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not save interview to database.", e);
return false;
}
}
public static bool TryGetInterview(ulong channelID, out Interview interview)
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM interviews WHERE channel_id=@channel_id", c);
selection.Parameters.AddWithValue("@channel_id", channelID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if ticket exists in the database
if (!results.Read())
{
interview = null;
return false;
}
interview = new Interview(channelID,
JsonConvert.DeserializeObject<InterviewStep>(results.GetString("interview")),
JsonConvert.DeserializeObject<Dictionary<string, InterviewStep>>(results.GetString("definitions")));
results.Close();
return true;
}
public static bool TryDeleteInterview(ulong channelID)
{
try
{
using MySqlConnection c = GetConnection();
c.Open();
using MySqlCommand deletion = new MySqlCommand(@"DELETE FROM interviews WHERE channel_id=@channel_id", c);
deletion.Parameters.AddWithValue("@channel_id", channelID);
deletion.Prepare();
return deletion.ExecuteNonQuery() > 0;
}
catch (MySqlException)
{
return false;
}
}
public class Ticket
{
public uint id;
public ulong creatorID;
public ulong assignedStaffID;
public string summary;
public ulong channelID;
public Ticket(MySqlDataReader reader)
{
id = reader.GetUInt32("id");
creatorID = reader.GetUInt64("creator_id");
assignedStaffID = reader.GetUInt64("assigned_staff_id");
summary = reader.GetString("summary");
channelID = reader.GetUInt64("channel_id");
}
public string DiscordRelativeTime()
{
return Formatter.Timestamp(channelID.GetSnowflakeTime(), Config.timestampFormat);
}
}
public class StaffMember
{
public ulong userID;
public string name;
public bool active;
public StaffMember(MySqlDataReader reader)
{
userID = reader.GetUInt64("user_id");
name = reader.GetString("name");
active = reader.GetBoolean("active");
}
}
public class Message
{
public string identifier;
public ulong userID;
public string message;
public Message(MySqlDataReader reader)
{
identifier = reader.GetString("identifier");
userID = reader.GetUInt64("user_id");
message = reader.GetString("message");
}
}
public class Category
{
public string name;
public ulong id;
public Category(MySqlDataReader reader)
{
name = reader.GetString("name");
id = reader.GetUInt64("category_id");
}
}
}

63
Database/Blacklist.cs Normal file
View file

@ -0,0 +1,63 @@
using MySqlConnector;
namespace SupportChild.Database;
public class Blacklist
{
public static bool IsBanned(ulong userID)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM blacklisted_users WHERE user_id=@user_id", c);
selection.Parameters.AddWithValue("@user_id", userID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if user is blacklisted
if (results.Read())
{
results.Close();
return true;
}
results.Close();
return false;
}
public static bool Ban(ulong blacklistedID, ulong staffID)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO blacklisted_users (user_id,time,moderator_id) VALUES (@user_id, UTC_TIMESTAMP(), @moderator_id);", c);
cmd.Parameters.AddWithValue("@user_id", blacklistedID);
cmd.Parameters.AddWithValue("@moderator_id", staffID);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not blacklist user in database.", e);
return false;
}
}
public static bool Unban(ulong blacklistedID)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM blacklisted_users WHERE user_id=@user_id", c);
cmd.Parameters.AddWithValue("@user_id", blacklistedID);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not unblacklist user in database.", e);
return false;
}
}
}

108
Database/Category.cs Normal file
View file

@ -0,0 +1,108 @@
using System.Collections.Generic;
using MySqlConnector;
namespace SupportChild.Database;
public class Category(MySqlDataReader reader)
{
public readonly string name = reader.GetString("name");
public readonly ulong id = reader.GetUInt64("category_id");
public static List<Category> GetAllCategories()
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories", c);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return [];
}
List<Category> categories = [new(results)];
while (results.Read())
{
categories.Add(new Category(results));
}
results.Close();
return categories;
}
public static bool TryGetCategory(ulong categoryID, out Category message)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories WHERE category_id=@category_id", c);
selection.Parameters.AddWithValue("@category_id", categoryID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
message = null;
return false;
}
message = new Category(results);
results.Close();
return true;
}
public static bool TryGetCategory(string name, out Category message)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new MySqlCommand(@"SELECT * FROM categories WHERE name=@name", c);
selection.Parameters.AddWithValue("@name", name);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
message = null;
return false;
}
message = new Category(results);
results.Close();
return true;
}
public static bool AddCategory(string name, ulong categoryID)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"INSERT INTO categories (name,category_id) VALUES (@name, @category_id);", c);
cmd.Parameters.AddWithValue("@name", name);
cmd.Parameters.AddWithValue("@category_id", categoryID);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not add category to database.", e);
return false;
}
}
public static bool RemoveCategory(ulong categoryID)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand cmd = new MySqlCommand(@"DELETE FROM categories WHERE category_id=@category_id", c);
cmd.Parameters.AddWithValue("@category_id", categoryID);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not remove category from database.", e);
return false;
}
}
}

92
Database/Database.cs Normal file
View file

@ -0,0 +1,92 @@
using MySqlConnector;
namespace SupportChild.Database;
public static class Connection
{
private static string connectionString = "";
public static void SetConnectionString(string host, int port, string database, string username, string password)
{
connectionString = "server=" + host +
";database=" + database +
";port=" + port +
";userid=" + username +
";password=" + password;
}
public static MySqlConnection GetConnection()
{
return new MySqlConnection(connectionString);
}
public static void SetupTables()
{
using MySqlConnection c = GetConnection();
using MySqlCommand createTickets = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS tickets(" +
"id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT," +
"created_time DATETIME NOT NULL," +
"creator_id BIGINT UNSIGNED NOT NULL," +
"assigned_staff_id BIGINT UNSIGNED NOT NULL DEFAULT 0," +
"summary VARCHAR(5000) NOT NULL," +
"channel_id BIGINT UNSIGNED NOT NULL UNIQUE," +
"INDEX(created_time, assigned_staff_id, channel_id))",
c);
using MySqlCommand createTicketHistory = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS ticket_history(" +
"id INT UNSIGNED NOT NULL PRIMARY KEY," +
"created_time DATETIME NOT NULL," +
"closed_time DATETIME NOT NULL," +
"creator_id BIGINT UNSIGNED NOT NULL," +
"assigned_staff_id BIGINT UNSIGNED NOT NULL DEFAULT 0," +
"summary VARCHAR(5000) NOT NULL," +
"channel_id BIGINT UNSIGNED NOT NULL UNIQUE," +
"INDEX(created_time, closed_time, channel_id))",
c);
using MySqlCommand createBlacklisted = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS blacklisted_users(" +
"user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
"time DATETIME NOT NULL," +
"moderator_id BIGINT UNSIGNED NOT NULL," +
"INDEX(user_id, time))",
c);
using MySqlCommand createStaffList = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS staff(" +
"user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
"name VARCHAR(256) NOT NULL," +
"active BOOLEAN NOT NULL DEFAULT true)",
c);
using MySqlCommand createMessages = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS messages(" +
"identifier VARCHAR(256) NOT NULL PRIMARY KEY," +
"user_id BIGINT UNSIGNED NOT NULL," +
"message VARCHAR(5000) NOT NULL)",
c);
using MySqlCommand createCategories = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS categories(" +
"name VARCHAR(256) NOT NULL UNIQUE," +
"category_id BIGINT UNSIGNED NOT NULL PRIMARY KEY)",
c);
using MySqlCommand createInterviews = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS interviews(" +
"channel_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
"interview JSON NOT NULL," +
"definitions JSON NOT NULL)",
c);
using MySqlCommand createInterviewTemplates = new MySqlCommand(
"CREATE TABLE IF NOT EXISTS interview_templates(" +
"category_id BIGINT UNSIGNED NOT NULL PRIMARY KEY," +
"template JSON NOT NULL)",
c);
c.Open();
createTickets.ExecuteNonQuery();
createBlacklisted.ExecuteNonQuery();
createTicketHistory.ExecuteNonQuery();
createStaffList.ExecuteNonQuery();
createMessages.ExecuteNonQuery();
createCategories.ExecuteNonQuery();
createInterviews.ExecuteNonQuery();
createInterviewTemplates.ExecuteNonQuery();
}
}

195
Database/Interviews.cs Normal file
View file

@ -0,0 +1,195 @@
using System;
using System.Collections.Generic;
using MySqlConnector;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using SupportChild.Interviews;
namespace SupportChild.Database;
public class Interviews
{
public static bool TryGetInterviewTemplateJSON(ulong categoryID, out string templateJSON)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM interview_templates WHERE category_id=@category_id", c);
selection.Parameters.AddWithValue("@category_id", categoryID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
templateJSON = null;
return false;
}
templateJSON = results.GetString("template");
results.Close();
return true;
}
public static bool TryGetInterviewFromTemplate(ulong categoryID, ulong channelID, out Interview interview)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM interview_templates WHERE category_id=@category_id", c);
selection.Parameters.AddWithValue("@category_id", categoryID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if messages exist in the database
if (!results.Read())
{
interview = null;
return false;
}
string templateString = results.GetString("template");
results.Close();
try
{
Template template = JsonConvert.DeserializeObject<Template>(templateString, new JsonSerializerSettings
{
Error = delegate (object _, ErrorEventArgs args)
{
Logger.Error("Error occured when trying to read interview template '" + categoryID + "' from database: " + args.ErrorContext.Error.Message);
Logger.Debug("Detailed exception:", args.ErrorContext.Error);
args.ErrorContext.Handled = false;
}
});
interview = new Interview(channelID, template.interview, template.definitions);
return true;
}
catch (Exception e)
{
Logger.Warn("Unable to create interview object from the current template for category '" + categoryID + "' in the database.", e);
interview = null;
return false;
}
}
public static bool SetInterviewTemplate(Template template)
{
try
{
string templateString = JsonConvert.SerializeObject(template, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
MissingMemberHandling = MissingMemberHandling.Error,
Formatting = Formatting.Indented,
ContractResolver = new InterviewStep.StripInternalPropertiesResolver()
});
string query;
if (TryGetInterviewTemplateJSON(template.categoryID, out _))
{
query = "UPDATE interview_templates SET template = @template WHERE category_id=@category_id";
}
else
{
query = "INSERT INTO interview_templates (category_id,template) VALUES (@category_id, @template)";
}
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand cmd = new(query, c);
cmd.Parameters.AddWithValue("@category_id", template.categoryID);
cmd.Parameters.AddWithValue("@template", templateString);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not set interview template in database.", e);
return false;
}
}
public static bool TryDeleteInterviewTemplate(ulong categoryID)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand deletion = new("DELETE FROM interview_templates WHERE category_id=@category_id", c);
deletion.Parameters.AddWithValue("@category_id", categoryID);
deletion.Prepare();
return deletion.ExecuteNonQuery() > 0;
}
catch (MySqlException)
{
return false;
}
}
public static bool SaveInterview(Interview interview)
{
try
{
string query;
if (TryGetInterview(interview.channelID, out _))
{
query = "UPDATE interviews SET interview = @interview, definitions = @definitions WHERE channel_id = @channel_id";
}
else
{
query = "INSERT INTO interviews (channel_id,interview, definitions) VALUES (@channel_id, @interview, @definitions)";
}
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand cmd = new(query, c);
cmd.Parameters.AddWithValue("@channel_id", interview.channelID);
cmd.Parameters.AddWithValue("@interview", JsonConvert.SerializeObject(interview.interviewRoot, Formatting.Indented));
cmd.Parameters.AddWithValue("@definitions", JsonConvert.SerializeObject(interview.definitions, Formatting.Indented));
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not save interview to database.", e);
return false;
}
}
public static bool TryGetInterview(ulong channelID, out Interview interview)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM interviews WHERE channel_id=@channel_id", c);
selection.Parameters.AddWithValue("@channel_id", channelID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if ticket exists in the database
if (!results.Read())
{
interview = null;
return false;
}
interview = new Interview(channelID,
JsonConvert.DeserializeObject<InterviewStep>(results.GetString("interview")),
JsonConvert.DeserializeObject<Dictionary<string, InterviewStep>>(results.GetString("definitions")));
results.Close();
return true;
}
public static bool TryDeleteInterview(ulong channelID)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand deletion = new("DELETE FROM interviews WHERE channel_id=@channel_id", c);
deletion.Parameters.AddWithValue("@channel_id", channelID);
deletion.Prepare();
return deletion.ExecuteNonQuery() > 0;
}
catch (MySqlException)
{
return false;
}
}
}

112
Database/Message.cs Normal file
View file

@ -0,0 +1,112 @@
using System.Collections.Generic;
using MySqlConnector;
namespace SupportChild.Database;
public class Message(MySqlDataReader reader)
{
public readonly string identifier = reader.GetString("identifier");
public readonly ulong userID = reader.GetUInt64("user_id");
public readonly string message = reader.GetString("message");
public static List<Message> GetAllMessages()
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM messages", c);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return [];
}
List<Message> messages = [new(results)];
while (results.Read())
{
messages.Add(new Message(results));
}
results.Close();
return messages;
}
public static bool TryGetMessage(string identifier, out Message message)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM messages WHERE identifier=@identifier", c);
selection.Parameters.AddWithValue("@identifier", identifier);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if ticket exists in the database
if (!results.Read())
{
message = null;
return false;
}
message = new Message(results);
results.Close();
return true;
}
public static bool AddMessage(string identifier, ulong userID, string message)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand cmd = new("INSERT INTO messages (identifier,user_id,message) VALUES (@identifier, @user_id, @message);", c);
cmd.Parameters.AddWithValue("@identifier", identifier);
cmd.Parameters.AddWithValue("@user_id", userID);
cmd.Parameters.AddWithValue("@message", message);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not add message to database.", e);
return false;
}
}
public static bool UpdateMessage(string identifier, ulong userID, string message)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand cmd = new("UPDATE messages SET message = @message, user_id = @user_id WHERE identifier=@identifier", c);
cmd.Parameters.AddWithValue("@identifier", identifier);
cmd.Parameters.AddWithValue("@user_id", userID);
cmd.Parameters.AddWithValue("@message", message);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not add message to database.", e);
return false;
}
}
public static bool RemoveMessage(string identifier)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand cmd = new("DELETE FROM messages WHERE identifier=@identifier", c);
cmd.Parameters.AddWithValue("@identifier", identifier);
cmd.Prepare();
return cmd.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Error("Could not remove message from database.", e);
return false;
}
}
}

176
Database/StaffMember.cs Normal file
View file

@ -0,0 +1,176 @@
using System.Collections.Generic;
using MySqlConnector;
namespace SupportChild.Database;
public class StaffMember(MySqlDataReader reader)
{
public readonly ulong userID = reader.GetUInt64("user_id");
public string name = reader.GetString("name");
public readonly bool active = reader.GetBoolean("active");
public static bool AddStaff(string name, ulong userID)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand update = IsStaff(userID) ? new MySqlCommand("UPDATE staff SET name = @name WHERE user_id = @user_id", c)
: new MySqlCommand("INSERT INTO staff (user_id, name) VALUES (@user_id, @name);", c);
update.Parameters.AddWithValue("@name", name);
update.Parameters.AddWithValue("@user_id", userID);
update.Prepare();
return update.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not add staff to database.", e);
return false;
}
}
public static bool RemoveStaff(ulong userID)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand update = new("DELETE FROM staff WHERE user_id = @user_id", c);
update.Parameters.AddWithValue("@user_id", userID);
update.Prepare();
return update.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not remove staff from database.", e);
return false;
}
}
public static bool AssignStaff(Ticket ticket, ulong staffID)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand update = new("UPDATE tickets SET assigned_staff_id = @assigned_staff_id WHERE id = @id", c);
update.Parameters.AddWithValue("@assigned_staff_id", staffID);
update.Parameters.AddWithValue("@id", ticket.id);
update.Prepare();
return update.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not add staff to ticket in database.", e);
return false;
}
}
public static bool UnassignStaff(Ticket ticket)
{
return AssignStaff(ticket, 0);
}
public static bool SetStaffActive(ulong staffID, bool active)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
MySqlCommand update = new("UPDATE staff SET active = @active WHERE user_id = @user_id", c);
update.Parameters.AddWithValue("@user_id", staffID);
update.Parameters.AddWithValue("@active", active);
update.Prepare();
return update.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not set staff member as active in database.", e);
return false;
}
}
public static List<StaffMember> GetActiveStaff(params ulong[] ignoredUserIDs)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM staff WHERE active = true AND user_id NOT IN (@user_ids)", c);
selection.Parameters.AddWithValue("@user_ids", string.Join(",", ignoredUserIDs));
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return [];
}
List<StaffMember> staffMembers = [new(results)];
while (results.Read())
{
staffMembers.Add(new StaffMember(results));
}
results.Close();
return staffMembers;
}
public static List<StaffMember> GetAllStaff(params ulong[] ignoredUserIDs)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM staff WHERE user_id NOT IN (@user_ids)", c);
selection.Parameters.AddWithValue("@user_ids", string.Join(",", ignoredUserIDs));
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return [];
}
List<StaffMember> staffMembers = [new(results)];
while (results.Read())
{
staffMembers.Add(new StaffMember(results));
}
results.Close();
return staffMembers;
}
public static bool IsStaff(ulong staffID)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM staff WHERE user_id=@user_id", c);
selection.Parameters.AddWithValue("@user_id", staffID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return false;
}
results.Close();
return true;
}
public static bool TryGetStaff(ulong staffID, out StaffMember staffMember)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM staff WHERE user_id=@user_id", c);
selection.Parameters.AddWithValue("@user_id", staffID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
staffMember = null;
return false;
}
staffMember = new StaffMember(results);
results.Close();
return true;
}
}

312
Database/Ticket.cs Normal file
View file

@ -0,0 +1,312 @@
using System.Collections.Generic;
using DSharpPlus;
using MySqlConnector;
namespace SupportChild.Database;
public class Ticket(MySqlDataReader reader)
{
public readonly uint id = reader.GetUInt32("id");
public readonly ulong creatorID = reader.GetUInt64("creator_id");
public readonly ulong assignedStaffID = reader.GetUInt64("assigned_staff_id");
public readonly string summary = reader.GetString("summary");
public readonly ulong channelID = reader.GetUInt64("channel_id");
public string DiscordRelativeTime()
{
return Formatter.Timestamp(channelID.GetSnowflakeTime(), Config.timestampFormat);
}
public static long GetNumberOfTickets()
{
try
{
using MySqlConnection c = Connection.GetConnection();
using MySqlCommand countTickets = new MySqlCommand("SELECT COUNT(*) FROM tickets", c);
c.Open();
return (long)(countTickets?.ExecuteScalar() ?? 0);
}
catch (MySqlException e)
{
Logger.Error("Error occured when attempting to count number of open tickets.", e);
}
return -1;
}
public static long GetNumberOfClosedTickets()
{
try
{
using MySqlConnection c = Connection.GetConnection();
using MySqlCommand countTickets = new MySqlCommand("SELECT COUNT(*) FROM ticket_history", c);
c.Open();
return (long)(countTickets?.ExecuteScalar() ?? 0);
}
catch (MySqlException e)
{
Logger.Error("Error occured when attempting to count number of open tickets.", e);
}
return -1;
}
public static bool IsOpenTicket(ulong channelID)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM tickets WHERE channel_id=@channel_id", c);
selection.Parameters.AddWithValue("@channel_id", channelID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if ticket exists in the database
if (!results.Read())
{
return false;
}
results.Close();
return true;
}
public static bool TryGetOpenTicket(ulong channelID, out Ticket ticket)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM tickets WHERE channel_id=@channel_id", c);
selection.Parameters.AddWithValue("@channel_id", channelID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if ticket exists in the database
if (!results.Read())
{
ticket = null;
return false;
}
ticket = new Ticket(results);
results.Close();
return true;
}
public static bool TryGetOpenTicketByID(uint id, out Ticket ticket)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM tickets WHERE id=@id", c);
selection.Parameters.AddWithValue("@id", id);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if open ticket exists in the database
if (results.Read())
{
ticket = new Ticket(results);
results.Close();
return true;
}
results.Close();
ticket = null;
return false;
}
public static bool TryGetClosedTicket(uint id, out Ticket ticket)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM ticket_history WHERE id=@id", c);
selection.Parameters.AddWithValue("@id", id);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
// Check if closed ticket exists in the database
if (results.Read())
{
ticket = new Ticket(results);
results.Close();
return true;
}
ticket = null;
results.Close();
return false;
}
public static bool TryGetOpenTickets(ulong userID, out List<Ticket> tickets)
{
tickets = null;
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM tickets WHERE creator_id=@creator_id", c);
selection.Parameters.AddWithValue("@creator_id", userID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return false;
}
tickets = [new Ticket(results)];
while (results.Read())
{
tickets.Add(new Ticket(results));
}
results.Close();
return true;
}
public static bool TryGetOpenTickets(out List<Ticket> tickets)
{
tickets = null;
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM tickets ORDER BY channel_id", c);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return false;
}
tickets = [new Ticket(results)];
while (results.Read())
{
tickets.Add(new Ticket(results));
}
results.Close();
return true;
}
public static bool TryGetClosedTickets(ulong userID, out List<Ticket> tickets)
{
tickets = null;
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM ticket_history WHERE creator_id=@creator_id", c);
selection.Parameters.AddWithValue("@creator_id", userID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return false;
}
tickets = [new Ticket(results)];
while (results.Read())
{
tickets.Add(new Ticket(results));
}
results.Close();
return true;
}
public static bool TryGetAssignedTickets(ulong staffID, out List<Ticket> tickets)
{
tickets = null;
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand selection = new("SELECT * FROM tickets WHERE assigned_staff_id=@assigned_staff_id", c);
selection.Parameters.AddWithValue("@assigned_staff_id", staffID);
selection.Prepare();
MySqlDataReader results = selection.ExecuteReader();
if (!results.Read())
{
return false;
}
tickets = [new Ticket(results)];
while (results.Read())
{
tickets.Add(new Ticket(results));
}
results.Close();
return true;
}
public static long NewTicket(ulong memberID, ulong staffID, ulong ticketID)
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand cmd = new("INSERT INTO tickets (created_time, creator_id, assigned_staff_id, summary, channel_id) VALUES (UTC_TIMESTAMP(), @creator_id, @assigned_staff_id, @summary, @channel_id);", c);
cmd.Parameters.AddWithValue("@creator_id", memberID);
cmd.Parameters.AddWithValue("@assigned_staff_id", staffID);
cmd.Parameters.AddWithValue("@summary", "");
cmd.Parameters.AddWithValue("@channel_id", ticketID);
cmd.ExecuteNonQuery();
return cmd.LastInsertedId;
}
public static void ArchiveTicket(Ticket ticket)
{
// Check if ticket already exists in the archive
if (TryGetClosedTicket(ticket.id, out Ticket _))
{
using MySqlConnection c = Connection.GetConnection();
using MySqlCommand deleteTicket = new("DELETE FROM ticket_history WHERE id=@id OR channel_id=@channel_id", c);
deleteTicket.Parameters.AddWithValue("@id", ticket.id);
deleteTicket.Parameters.AddWithValue("@channel_id", ticket.channelID);
c.Open();
deleteTicket.Prepare();
deleteTicket.ExecuteNonQuery();
}
// Create an entry in the ticket history database
using MySqlConnection conn = Connection.GetConnection();
using MySqlCommand archiveTicket = new("INSERT INTO ticket_history (id, created_time, closed_time, creator_id, assigned_staff_id, summary, channel_id) VALUES (@id, @created_time, UTC_TIMESTAMP(), @creator_id, @assigned_staff_id, @summary, @channel_id);", conn);
archiveTicket.Parameters.AddWithValue("@id", ticket.id);
archiveTicket.Parameters.AddWithValue("@created_time", ticket.channelID.GetSnowflakeTime());
archiveTicket.Parameters.AddWithValue("@creator_id", ticket.creatorID);
archiveTicket.Parameters.AddWithValue("@assigned_staff_id", ticket.assignedStaffID);
archiveTicket.Parameters.AddWithValue("@summary", ticket.summary);
archiveTicket.Parameters.AddWithValue("@channel_id", ticket.channelID);
conn.Open();
archiveTicket.Prepare();
archiveTicket.ExecuteNonQuery();
}
public static bool DeleteOpenTicket(uint ticketID)
{
try
{
using MySqlConnection c = Connection.GetConnection();
using MySqlCommand deletion = new("DELETE FROM tickets WHERE id=@id", c);
deletion.Parameters.AddWithValue("@id", ticketID);
c.Open();
deletion.Prepare();
return deletion.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not delete open ticket in database.", e);
return false;
}
}
public static bool SetSummary(ulong channelID, string summary)
{
try
{
using MySqlConnection c = Connection.GetConnection();
c.Open();
using MySqlCommand update = new("UPDATE tickets SET summary = @summary WHERE channel_id = @channel_id", c);
update.Parameters.AddWithValue("@summary", summary);
update.Parameters.AddWithValue("@channel_id", channelID);
update.Prepare();
return update.ExecuteNonQuery() > 0;
}
catch (MySqlException e)
{
Logger.Warn("Could not set summary in database.", e);
return false;
}
}
}

View file

@ -66,7 +66,7 @@ public static class EventHandler
} }
// Ignore messages outside of tickets. // Ignore messages outside of tickets.
if (!Database.TryGetOpenTicket(e.Channel.Id, out Database.Ticket ticket)) if (!Database.Ticket.TryGetOpenTicket(e.Channel.Id, out Database.Ticket ticket))
{ {
return; return;
} }
@ -87,7 +87,7 @@ public static class EventHandler
private static async Task SendTicketUpdatedMessage(MessageCreatedEventArgs e, Database.Ticket ticket) private static async Task SendTicketUpdatedMessage(MessageCreatedEventArgs e, Database.Ticket ticket)
{ {
// Ignore staff messages // Ignore staff messages
if (Database.IsStaff(e.Author.Id)) if (Database.StaffMember.IsStaff(e.Author.Id))
{ {
return; return;
} }
@ -112,7 +112,7 @@ public static class EventHandler
public static async Task OnMemberAdded(DiscordClient client, GuildMemberAddedEventArgs e) public static async Task OnMemberAdded(DiscordClient client, GuildMemberAddedEventArgs e)
{ {
if (!Database.TryGetOpenTickets(e.Member.Id, out List<Database.Ticket> ownTickets)) if (!Database.Ticket.TryGetOpenTickets(e.Member.Id, out List<Database.Ticket> ownTickets))
{ {
return; return;
} }
@ -147,7 +147,7 @@ public static class EventHandler
public static async Task OnMemberRemoved(DiscordClient client, GuildMemberRemovedEventArgs e) public static async Task OnMemberRemoved(DiscordClient client, GuildMemberRemovedEventArgs e)
{ {
if (Database.TryGetOpenTickets(e.Member.Id, out List<Database.Ticket> ownTickets)) if (Database.Ticket.TryGetOpenTickets(e.Member.Id, out List<Database.Ticket> ownTickets))
{ {
foreach (Database.Ticket ticket in ownTickets) foreach (Database.Ticket ticket in ownTickets)
{ {
@ -167,7 +167,7 @@ public static class EventHandler
} }
} }
if (LogChannel.IsEnabled && Database.TryGetAssignedTickets(e.Member.Id, out List<Database.Ticket> assignedTickets)) if (LogChannel.IsEnabled && Database.Ticket.TryGetAssignedTickets(e.Member.Id, out List<Database.Ticket> assignedTickets))
{ {
foreach (Database.Ticket ticket in assignedTickets) foreach (Database.Ticket ticket in assignedTickets)
{ {
@ -205,6 +205,18 @@ public static class EventHandler
case not null when e.Id.StartsWith("supportchild_interviewbutton"): case not null when e.Id.StartsWith("supportchild_interviewbutton"):
await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction); await Interviewer.ProcessButtonOrSelectorResponse(e.Interaction);
return; return;
case not null when e.Id.StartsWith("supportchild_confirmmessagedelete"):
await SetMessageCommand.ConfirmMessageDeletion(e.Interaction, GetIDFromCustomID(e.Id));
return;
case not null when e.Id.StartsWith("supportchild_cancelmessagedelete"):
await SetMessageCommand.CancelMessageDeletion(e.Interaction, GetIDFromCustomID(e.Id));
return;
case not null when e.Id.StartsWith("supportchild_confirmmessageupdate"):
await SetMessageCommand.ConfirmMessageUpdate(e.Interaction, GetIDFromCustomID(e.Id));
return;
case not null when e.Id.StartsWith("supportchild_cancelmessageupdate"):
await SetMessageCommand.CancelMessageUpdate(e.Interaction, GetIDFromCustomID(e.Id));
return;
case "right": case "right":
case "left": case "left":
case "rightskip": case "rightskip":
@ -289,6 +301,17 @@ public static class EventHandler
} }
} }
private static ulong GetIDFromCustomID(string customID)
{
List<string> values = customID.Split(' ').ToList();
if (values.Count < 2 || !ulong.TryParse(values[1], out ulong id))
{
Logger.Warn("Got an invalid button/selector ID: " + customID);
return 0;
}
return id;
}
private static async Task OnNewTicketButtonUsed(DiscordInteraction interaction) private static async Task OnNewTicketButtonUsed(DiscordInteraction interaction)
{ {
await interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral()); await interaction.CreateResponseAsync(DiscordInteractionResponseType.DeferredChannelMessageWithSource, new DiscordInteractionResponseBuilder().AsEphemeral());

View file

@ -13,7 +13,7 @@ public static class Interviewer
{ {
public static async Task<bool> StartInterview(DiscordChannel channel) public static async Task<bool> StartInterview(DiscordChannel channel)
{ {
if (!Database.TryGetInterviewFromTemplate(channel.Parent.Id, channel.Id, out Interview interview)) if (!Database.Interviews.TryGetInterviewFromTemplate(channel.Parent.Id, channel.Id, out Interview interview))
{ {
return false; return false;
} }
@ -26,12 +26,12 @@ public static class Interviewer
Color = DiscordColor.Red Color = DiscordColor.Red
}); });
interview.interviewRoot.AddRelatedMessageIDs(errorMessage.Id); interview.interviewRoot.AddRelatedMessageIDs(errorMessage.Id);
Database.SaveInterview(interview); Database.Interviews.SaveInterview(interview);
return false; return false;
} }
await SendNextMessage(interview, channel, interview.interviewRoot); await SendNextMessage(interview, channel, interview.interviewRoot);
return Database.SaveInterview(interview); return Database.Interviews.SaveInterview(interview);
} }
public static async Task<bool> RestartInterview(DiscordChannel channel) public static async Task<bool> RestartInterview(DiscordChannel channel)
@ -47,14 +47,14 @@ public static class Interviewer
public static async Task<bool> StopInterview(DiscordChannel channel) public static async Task<bool> StopInterview(DiscordChannel channel)
{ {
if (Database.TryGetInterview(channel.Id, out Interview interview)) if (Database.Interviews.TryGetInterview(channel.Id, out Interview interview))
{ {
if (Config.deleteMessagesAfterInterviewEnd) if (Config.deleteMessagesAfterInterviewEnd)
{ {
await DeletePreviousMessages(interview, channel); await DeletePreviousMessages(interview, channel);
} }
if (!Database.TryDeleteInterview(channel.Id)) if (!Database.Interviews.TryDeleteInterview(channel.Id))
{ {
Logger.Warn("Could not delete interview from database. Channel ID: " + channel.Id); Logger.Warn("Could not delete interview from database. Channel ID: " + channel.Id);
} }
@ -70,7 +70,7 @@ public static class Interviewer
return; return;
} }
if (!Database.TryGetOpenTicket(interaction.Channel.Id, out Database.Ticket ticket)) if (!Database.Ticket.TryGetOpenTicket(interaction.Channel.Id, out Database.Ticket ticket))
{ {
await interaction.CreateResponseAsync(DiscordInteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder() await interaction.CreateResponseAsync(DiscordInteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
.AddEmbed(new DiscordEmbedBuilder() .AddEmbed(new DiscordEmbedBuilder()
@ -98,7 +98,7 @@ public static class Interviewer
} }
// Return if there is no active interview in this channel // Return if there is no active interview in this channel
if (!Database.TryGetInterview(interaction.Channel.Id, out Interview interview)) if (!Database.Interviews.TryGetInterview(interaction.Channel.Id, out Interview interview))
{ {
await interaction.CreateResponseAsync(DiscordInteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder() await interaction.CreateResponseAsync(DiscordInteractionResponseType.ChannelMessageWithSource, new DiscordInteractionResponseBuilder()
.AddEmbed(new DiscordEmbedBuilder() .AddEmbed(new DiscordEmbedBuilder()
@ -171,7 +171,7 @@ public static class Interviewer
componentID = interaction.Data.Values[0]; componentID = interaction.Data.Values[0];
break; break;
case DiscordComponentType.Button: case DiscordComponentType.Button:
componentID = interaction.Data.CustomId.Replace("supportchild_interviewbutton ", ""); componentID = interaction.Data.CustomId.Replace("supportboi_interviewbutton ", "");
break; break;
case DiscordComponentType.ActionRow: case DiscordComponentType.ActionRow:
case DiscordComponentType.FormInput: case DiscordComponentType.FormInput:
@ -214,7 +214,7 @@ public static class Interviewer
Description = "Error: Could not determine the next question based on your answer. Check your response and ask an admin to check the bot logs if this seems incorrect." Description = "Error: Could not determine the next question based on your answer. Check your response and ask an admin to check the bot logs if this seems incorrect."
}).AsEphemeral()); }).AsEphemeral());
currentStep.AddRelatedMessageIDs(followupMessage.Id); currentStep.AddRelatedMessageIDs(followupMessage.Id);
Database.SaveInterview(interview); Database.Interviews.SaveInterview(interview);
} }
else else
{ {
@ -265,7 +265,7 @@ public static class Interviewer
} }
// The channel does not have an active interview. // The channel does not have an active interview.
if (!Database.TryGetInterview(answerMessage.ReferencedMessage.Channel.Id, if (!Database.Interviews.TryGetInterview(answerMessage.ReferencedMessage.Channel.Id,
out Interview interview)) out Interview interview))
{ {
return; return;
@ -299,7 +299,7 @@ public static class Interviewer
Color = DiscordColor.Red Color = DiscordColor.Red
}); });
currentStep.AddRelatedMessageIDs(answerMessage.Id, lengthMessage.Id); currentStep.AddRelatedMessageIDs(answerMessage.Id, lengthMessage.Id);
Database.SaveInterview(interview); Database.Interviews.SaveInterview(interview);
return; return;
} }
@ -311,7 +311,7 @@ public static class Interviewer
Color = DiscordColor.Red Color = DiscordColor.Red
}); });
currentStep.AddRelatedMessageIDs(answerMessage.Id, lengthMessage.Id); currentStep.AddRelatedMessageIDs(answerMessage.Id, lengthMessage.Id);
Database.SaveInterview(interview); Database.Interviews.SaveInterview(interview);
return; return;
} }
@ -334,7 +334,7 @@ public static class Interviewer
Color = DiscordColor.Red Color = DiscordColor.Red
}); });
currentStep.AddRelatedMessageIDs(answerMessage.Id, errorMessage.Id); currentStep.AddRelatedMessageIDs(answerMessage.Id, errorMessage.Id);
Database.SaveInterview(interview); Database.Interviews.SaveInterview(interview);
} }
private static async Task HandleAnswer(string answer, private static async Task HandleAnswer(string answer,
@ -375,13 +375,13 @@ public static class Interviewer
nextStep.AddRelatedMessageIDs(answerMessage.Id, errorMessage.Id); nextStep.AddRelatedMessageIDs(answerMessage.Id, errorMessage.Id);
previousStep.answer = null; previousStep.answer = null;
previousStep.answerID = 0; previousStep.answerID = 0;
Database.SaveInterview(interview); Database.Interviews.SaveInterview(interview);
} }
return; return;
} }
await SendNextMessage(interview, channel, nextStep); await SendNextMessage(interview, channel, nextStep);
Database.SaveInterview(interview); Database.Interviews.SaveInterview(interview);
break; break;
case StepType.INTERVIEW_END: case StepType.INTERVIEW_END:
DiscordEmbedBuilder endEmbed = new() DiscordEmbedBuilder endEmbed = new()
@ -403,7 +403,7 @@ public static class Interviewer
await DeletePreviousMessages(interview, channel); await DeletePreviousMessages(interview, channel);
} }
if (!Database.TryDeleteInterview(channel.Id)) if (!Database.Interviews.TryDeleteInterview(channel.Id))
{ {
Logger.Error("Could not delete interview from database. Channel ID: " + channel.Id); Logger.Error("Could not delete interview from database. Channel ID: " + channel.Id);
} }
@ -451,7 +451,7 @@ public static class Interviewer
previousStep.AddRelatedMessageIDs(errorMessage.Id, answerMessage.Id); previousStep.AddRelatedMessageIDs(errorMessage.Id, answerMessage.Id);
} }
Database.SaveInterview(interview); Database.Interviews.SaveInterview(interview);
Logger.Error("Could not find a step to return to after a reference step in channel " + channel.Id); Logger.Error("Could not find a step to return to after a reference step in channel " + channel.Id);
return; return;
@ -480,7 +480,7 @@ public static class Interviewer
previousStep.AddRelatedMessageIDs(errorMessage.Id, answerMessage.Id); previousStep.AddRelatedMessageIDs(errorMessage.Id, answerMessage.Id);
} }
Database.SaveInterview(interview); Database.Interviews.SaveInterview(interview);
return; return;
} }
} }
@ -556,7 +556,7 @@ public static class Interviewer
for (; nrOfButtons < 5 * (nrOfButtonRows + 1) && nrOfButtons < step.steps.Count; nrOfButtons++) for (; nrOfButtons < 5 * (nrOfButtonRows + 1) && nrOfButtons < step.steps.Count; nrOfButtons++)
{ {
(string stepPattern, InterviewStep nextStep) = step.steps.ToArray()[nrOfButtons]; (string stepPattern, InterviewStep nextStep) = step.steps.ToArray()[nrOfButtons];
buttonRow.Add(new DiscordButtonComponent(nextStep.GetButtonStyle(), "supportchild_interviewbutton " + nrOfButtons, stepPattern)); buttonRow.Add(new DiscordButtonComponent(nextStep.GetButtonStyle(), "supportboi_interviewbutton " + nrOfButtons, stepPattern));
} }
msgBuilder.AddComponents(buttonRow); msgBuilder.AddComponents(buttonRow);
} }
@ -574,26 +574,26 @@ public static class Interviewer
categoryOptions.Add(new DiscordSelectComponentOption(stepPattern, selectionOptions.ToString(), nextStep.selectorDescription)); categoryOptions.Add(new DiscordSelectComponentOption(stepPattern, selectionOptions.ToString(), nextStep.selectorDescription));
} }
selectionComponents.Add(new DiscordSelectComponent("supportchild_interviewselector " + selectionBoxes, selectionComponents.Add(new DiscordSelectComponent("supportboi_interviewselector " + selectionBoxes,
string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select an option..." : step.selectorPlaceholder, categoryOptions)); string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select an option..." : step.selectorPlaceholder, categoryOptions));
} }
msgBuilder.AddComponents(selectionComponents); msgBuilder.AddComponents(selectionComponents);
break; break;
case StepType.ROLE_SELECTOR: case StepType.ROLE_SELECTOR:
msgBuilder.AddComponents(new DiscordRoleSelectComponent("supportchild_interviewroleselector", msgBuilder.AddComponents(new DiscordRoleSelectComponent("supportboi_interviewroleselector",
string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a role..." : step.selectorPlaceholder)); string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a role..." : step.selectorPlaceholder));
break; break;
case StepType.USER_SELECTOR: case StepType.USER_SELECTOR:
msgBuilder.AddComponents(new DiscordUserSelectComponent("supportchild_interviewuserselector", msgBuilder.AddComponents(new DiscordUserSelectComponent("supportboi_interviewuserselector",
string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a user..." : step.selectorPlaceholder)); string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a user..." : step.selectorPlaceholder));
break; break;
case StepType.CHANNEL_SELECTOR: case StepType.CHANNEL_SELECTOR:
msgBuilder.AddComponents(new DiscordChannelSelectComponent("supportchild_interviewchannelselector", msgBuilder.AddComponents(new DiscordChannelSelectComponent("supportboi_interviewchannelselector",
string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a channel..." : step.selectorPlaceholder)); string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a channel..." : step.selectorPlaceholder));
break; break;
case StepType.MENTIONABLE_SELECTOR: case StepType.MENTIONABLE_SELECTOR:
msgBuilder.AddComponents(new DiscordMentionableSelectComponent("supportchild_interviewmentionableselector", msgBuilder.AddComponents(new DiscordMentionableSelectComponent("supportboi_interviewmentionableselector",
string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a user or role..." : step.selectorPlaceholder)); string.IsNullOrWhiteSpace(step.selectorPlaceholder) ? "Select a user or role..." : step.selectorPlaceholder));
break; break;
case StepType.TEXT_INPUT: case StepType.TEXT_INPUT:
@ -604,7 +604,7 @@ public static class Interviewer
} }
else else
{ {
lengthInfo = " (Maximum " + (step?.maxLength ?? InterviewStep.DEFAULT_MAX_FIELD_LENGTH) + " characters)"; lengthInfo = " (Maximum " + (step.maxLength ?? InterviewStep.DEFAULT_MAX_FIELD_LENGTH) + " characters)";
} }
embed.WithFooter("Reply to this message with your answer" + lengthInfo + ". You cannot include images or files."); embed.WithFooter("Reply to this message with your answer" + lengthInfo + ". You cannot include images or files.");
break; break;

82
Jenkinsfile vendored
View file

@ -1,41 +1,67 @@
pipeline { pipeline
{
agent any agent any
stages { stages
stage('Setup Dependencies') { {
steps { stage('Setup Dependencies')
{
steps
{
sh 'dotnet restore' sh 'dotnet restore'
} }
} }
stage('Build') { stage('Build')
parallel { {
stage('Linux') { parallel
steps { {
sh 'dotnet publish -r linux-x64 -c Release -p:PublishTrimmed=true --self-contained true --no-restore --output Linux-x64/' stage('Linux')
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/' steps
{
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/'
archiveArtifacts(artifacts: 'linux-x64/supportchild', caseSensitive: true)
archiveArtifacts(artifacts: 'linux-x64/supportchild-sc', caseSensitive: true)
} }
} }
stage('Windows') { stage('Windows')
steps { {
sh 'dotnet publish -r win-x64 -c Release -p:PublishTrimmed=true --self-contained true --no-restore --output Windows-x64/' steps
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/' 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/'
archiveArtifacts(artifacts: 'windows-x64/supportchild.exe', caseSensitive: true)
archiveArtifacts(artifacts: 'windows-x64/supportchild-sc.exe', caseSensitive: true)
} }
} }
} stage('RHEL9')
} {
stage('Archive') { agent
parallel { {
stage('Linux') { dockerfile { filename 'packaging/RHEL9.Dockerfile' }
steps { }
archiveArtifacts(artifacts: 'Linux-x64/SupportChild', caseSensitive: true) environment { DOTNET_CLI_HOME = "/tmp/.dotnet" }
archiveArtifacts(artifacts: 'Linux-x64/SupportChild-SC', caseSensitive: true) steps
{
sh 'rpmbuild -bb packaging/supportchild.spec --define "_topdir $PWD/.rpmbuild-el9" --define "dev_build true"'
sh 'cp .rpmbuild-el9/RPMS/x86_64/supportchild-dev-*.el9.x86_64.rpm linux-x64/'
archiveArtifacts(artifacts: 'linux-x64/supportchild-dev-*.el9.x86_64.rpm', caseSensitive: true)
} }
} }
stage('Windows') { stage('RHEL8')
steps { {
archiveArtifacts(artifacts: 'Windows-x64/SupportChild.exe', caseSensitive: true) agent
archiveArtifacts(artifacts: 'Windows-x64/SupportChild-SC.exe', caseSensitive: true) {
dockerfile { filename 'packaging/RHEL8.Dockerfile' }
}
environment { DOTNET_CLI_HOME = "/tmp/.dotnet" }
steps
{
sh 'rpmbuild -bb packaging/supportchild.spec --define "_topdir $PWD/.rpmbuild-el8" --define "dev_build true"'
sh 'cp .rpmbuild-el8/RPMS/x86_64/supportchild-dev-*.el8.x86_64.rpm linux-x64/'
archiveArtifacts(artifacts: 'linux-x64/supportchild-dev-*.el8.x86_64.rpm', caseSensitive: true)
} }
} }
} }

View file

@ -1,3 +1,4 @@
[![Build Status](https://build.toastiet0ast.com/buildStatus/icon?job=toastie-stuff%2FSupportChild%2Fmain)](https://build.toastiet0ast.com/job/toastie-stuff/job/SupportChild/job/main/)
# SupportChild # SupportChild
A support ticket Discord bot. Uses a MySQL database for storage of ticket information. Creates formatted HTML ticket transcripts when tickets are closed. A support ticket Discord bot. Uses a MySQL database for storage of ticket information. Creates formatted HTML ticket transcripts when tickets are closed.
@ -8,11 +9,11 @@ A support ticket Discord bot. Uses a MySQL database for storage of ticket inform
1. Set up a mysql-compatible server, create a user and empty database for the bot to use. 1. Set up a mysql-compatible server, create a user and empty database for the bot to use.
2. (Optional) Install .NET 8 if it doesn't already exist on your system. 2. (Optional) Install .NET 9 if it doesn't already exist on your system.
3. Create a new bot application and invite it to your server. 3. Create a new bot application and invite it to your server.
4. Download the bot for your operating system, either a [release version](https://toastielab.dev/toastie-stuff/SupportChild/releases). Get the normal version if you have installed .NET 8 on your system, get the self contained version otherwise. 4. Download the bot for your operating system, either a [release version](https://toastielab.dev/toastie-stuff/SupportChild/releases) or a [dev build](https://build.toastiet0ast.com/job/toastie-stuff/job/SupportChild/job/main/). Get the normal version if you have installed .NET 8 on your system, get the self contained version otherwise.
| Application | Description | | Application | Description |
|-----------------------------|-----------------------------------------------------------------------| |-----------------------------|-----------------------------------------------------------------------|
@ -22,7 +23,7 @@ A support ticket Discord bot. Uses a MySQL database for storage of ticket inform
| `SupportChild-Windows.exe` | Larger Windows version which does not require .NET 9 to be installed. | | `SupportChild-Windows.exe` | Larger Windows version which does not require .NET 9 to be installed. |
5. Run `./SupportChild_Linux` on Linux or `./SupportChild_Windows.exe` on Windows, this creates a config file in the current directory. 5. Run the bot application, `./SupportChild_<version>.exe`, this creates a config file in the current directory.
6. Set up the config, there are instructions inside. If you need more help either contact me in Discord or through an issue here. 6. Set up the config, there are instructions inside. If you need more help either contact me in Discord or through an issue here.

View file

@ -147,8 +147,8 @@ internal static class SupportChild
try try
{ {
Logger.Log("Connecting to database. (" + Config.hostName + ":" + Config.port + ")"); Logger.Log("Connecting to database. (" + Config.hostName + ":" + Config.port + ")");
Database.SetConnectionString(Config.hostName, Config.port, Config.database, Config.username, Config.password); Database.Connection.SetConnectionString(Config.hostName, Config.port, Config.database, Config.username, Config.password);
Database.SetupTables(); Database.Connection.SetupTables();
} }
catch (Exception e) catch (Exception e)
{ {
@ -193,7 +193,6 @@ internal static class SupportChild
[ [
typeof(AddCategoryCommand), typeof(AddCategoryCommand),
typeof(AddCommand), typeof(AddCommand),
typeof(AddMessageCommand),
typeof(AddStaffCommand), typeof(AddStaffCommand),
typeof(AdminCommands), typeof(AdminCommands),
typeof(AssignCommand), typeof(AssignCommand),
@ -201,6 +200,7 @@ internal static class SupportChild
typeof(CloseCommand), typeof(CloseCommand),
typeof(CreateButtonPanelCommand), typeof(CreateButtonPanelCommand),
typeof(CreateSelectionBoxPanelCommand), typeof(CreateSelectionBoxPanelCommand),
typeof(InterviewCommands),
typeof(InterviewTemplateCommands), typeof(InterviewTemplateCommands),
typeof(ListAssignedCommand), typeof(ListAssignedCommand),
typeof(ListCommand), typeof(ListCommand),
@ -211,10 +211,9 @@ internal static class SupportChild
typeof(NewCommand), typeof(NewCommand),
typeof(RandomAssignCommand), typeof(RandomAssignCommand),
typeof(RemoveCategoryCommand), typeof(RemoveCategoryCommand),
typeof(RemoveMessageCommand),
typeof(RemoveStaffCommand), typeof(RemoveStaffCommand),
typeof(InterviewCommands),
typeof(SayCommand), typeof(SayCommand),
typeof(SetMessageCommand),
typeof(SetSummaryCommand), typeof(SetSummaryCommand),
typeof(StatusCommand), typeof(StatusCommand),
typeof(SummaryCommand), typeof(SummaryCommand),

View file

@ -2,9 +2,10 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>4.0.0</Version> <Version>4.0.1</Version>
<ApplicationIcon>ellie_icon.ico</ApplicationIcon> <ApplicationIcon>ellie_icon.ico</ApplicationIcon>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<AssemblyName>supportchild</AssemblyName>
<StartupObject>SupportChild.SupportChild</StartupObject> <StartupObject>SupportChild.SupportChild</StartupObject>
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
<PublishSingleFile>true</PublishSingleFile> <PublishSingleFile>true</PublishSingleFile>
@ -19,7 +20,7 @@
<PackageIconUrl>https://cdn.elliebot.net/Ellie.png</PackageIconUrl> <PackageIconUrl>https://cdn.elliebot.net/Ellie.png</PackageIconUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageProjectUrl>https://toastielab.dev/toastie-stuff/SupportChild</PackageProjectUrl> <PackageProjectUrl>https://toastielab.dev/toastie-stuff/SupportChild</PackageProjectUrl>
<PackageVersion>4.0.0</PackageVersion> <PackageVersion>4.0.1</PackageVersion>
<LangVersion>default</LangVersion> <LangVersion>default</LangVersion>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<GitVersion>false</GitVersion> <GitVersion>false</GitVersion>

View file

@ -4,6 +4,7 @@ using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using DSharpPlus.Entities; using DSharpPlus.Entities;
@ -20,6 +21,11 @@ public static class Extensions
{ {
return needles.Any(haystack.Contains); return needles.Any(haystack.Contains);
} }
public static string Truncate(this string value, int maxChars)
{
return value.Length <= maxChars ? value : string.Concat(value.AsSpan(0, maxChars - 3), "...");
}
} }
public static class Utilities public static class Utilities
@ -65,7 +71,7 @@ public static class Utilities
public static async Task<List<Database.Category>> GetVerifiedCategories() public static async Task<List<Database.Category>> GetVerifiedCategories()
{ {
List<Database.Category> verifiedCategories = new List<Database.Category>(); List<Database.Category> verifiedCategories = new List<Database.Category>();
foreach (Database.Category category in Database.GetAllCategories()) foreach (Database.Category category in Database.Category.GetAllCategories())
{ {
DiscordChannel channel = null; DiscordChannel channel = null;
try try
@ -97,7 +103,7 @@ public static class Utilities
throw new InvalidOperationException("Could not load manifest resource stream."); throw new InvalidOperationException("Could not load manifest resource stream.");
} }
using StreamReader reader = new StreamReader(stream); using StreamReader reader = new StreamReader(stream, Encoding.Unicode);
return reader.ReadToEnd(); return reader.ReadToEnd();
} }

View file

@ -0,0 +1,2 @@
FROM fedora:latest
RUN dnf install dotnet-sdk-9.0 rpm-build git systemd-rpm-macros -y

View file

@ -0,0 +1,2 @@
FROM redhat/ubi8:latest
RUN dnf install dotnet-sdk-9.0 rpm-build git systemd-rpm-macros -y

View file

@ -0,0 +1,2 @@
FROM redhat/ubi9:latest
RUN dnf install dotnet-sdk-9.0 rpm-build git systemd-rpm-macros -y

View file

@ -0,0 +1,14 @@
[Unit]
Description=SupportChild Ticket Discord Bot
Documentation=https://toastielab.dev/toastie-stuff/SupportChild
After=network.target
Wants=network.target
[Service]
User=supportchild
ExecStart=/usr/bin/supportchild --config /etc/supportchild/config.yml --transcripts /var/lib/supportboi/transcripts
Restart=no
Type=exec
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,74 @@
%global debug_package %{nil}
%global repo_root %{_topdir}/..
%global base_version %(echo "$(sed -ne '/Version/{s/.*<Version>\\(.*\\)<\\/Version>.*/\\1/p;q;}' < SupportChild.csproj)")
%if %{defined dev_build}
Name: supportchild-dev
Summary: A support ticket Discord bot (dev build)
Version: %{base_version}~%(date "+%%Y%%m%%d%%H%%M%%S")git%(git rev-parse --short HEAD)
Source: https://toastielab.dev/toastie-stuff/SupportChild/archive/%(git rev-parse HEAD).zip
Provides: supportchild
%else
Name: supportchild
Summary: A support ticket Discord bot
Version: %{base_version}
Source: https://toastielab.dev/toastie-stuff/SupportChild/archive/%{base_version}.zip
%endif
Release: 1%{?dist}
License: GPLv3
URL: https://toastielab.dev/toastie-stuff/SupportChild
Packager: Toastie_t0ast
BuildRequires: systemd-rpm-macros
Requires: dotnet-runtime-9.0
%{?systemd_requires}
%description
A support ticket Discord bot. Uses a MySQL database for storage of ticket
information. Creates formatted HTML ticket transcripts when tickets are closed.
%prep
%setup -T -c
%build
dotnet publish %{repo_root}/SupportChild.csproj -p:PublishSingleFile=true -r linux-x64 -c Release --self-contained false --output %{_builddir}/out
%install
if [[ -d %{_rpmdir}/%{_arch} ]]; then
%{__rm} %{_rpmdir}/%{_arch}/*
fi
%{__install} -d %{buildroot}/usr/bin
# rpmbuild post-processing using the strip command breaks dotnet binaries, remove the executable bit to avoid it
%{__install} -m 644 %{_builddir}/out/supportchild %{buildroot}/usr/bin/supportchild
%{__install} -d %{buildroot}/usr/lib/systemd/system
%{__install} -m 644 %{repo_root}/packaging/supportchild.service %{buildroot}/usr/lib/systemd/system/
%{__install} -d %{buildroot}/etc/supportchild/
%{__install} -m 600 %{repo_root}/default_config.yml %{buildroot}/etc/supportchild/config.yml
%{__install} -d %{buildroot}/var/lib/supportchild/transcripts
%pre
getent group supportchild > /dev/null || groupadd supportchild
getent passwd supportchild > /dev/null || useradd -r -m -d /var/lib/supportchild -s /sbin/nologin -g supportchild supportchild
%post
%systemd_post supportchild.service
%preun
%systemd_preun supportchild.service
%postun
%systemd_postun_with_restart supportchild.service
if [[ "$1" == "0" ]]; then
getent passwd supportchild > /dev/null && userdel supportchild
fi
%files
%attr(0755,root,root) /usr/bin/supportchild
%attr(0644,root,root) /usr/lib/systemd/system/supportchild.service
%config %attr(0600, supportchild, supportchild) /etc/supportchild/config.yml
%dir %attr(0700, supportchild, supportchild) /var/lib/supportchild
%dir %attr(0755, supportchild, supportchild) /var/lib/supportchild/transcripts