diff --git a/Commands/CloseCommand.cs b/Commands/CloseCommand.cs index 0e13a84..4beb4b7 100644 --- a/Commands/CloseCommand.cs +++ b/Commands/CloseCommand.cs @@ -13,6 +13,7 @@ namespace SupportChild.Commands; public class CloseCommand { + // TODO: Refactor this class a whole lot private static Dictionary closeReasons = new Dictionary(); [RequireGuild] @@ -38,7 +39,7 @@ public class CloseCommand Color = DiscordColor.Cyan, Description = "Are you sure you wish to close this ticket? You cannot re-open it again later." }) - .AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Danger, "supportboi_closeconfirm", "Confirm")); + .AddComponents(new DiscordButtonComponent(DiscordButtonStyle.Danger, "supportchild_closeconfirm", "Confirm")); await command.RespondAsync(confirmation); closeReasons.Add(command.Channel.Id, reason); @@ -81,11 +82,52 @@ public class CloseCommand closeReason = "\nReason: " + cachedReason + "\n"; } + string fileName = Transcriber.GetZipFilename(ticket.id); + string filePath = Transcriber.GetZipPath(ticket.id); + long zipSize = 0; + + // If the zip transcript doesn't exist, use the html file. + try + { + FileInfo fi = new FileInfo(filePath); + if (!fi.Exists || fi.Length >= 26214400) + { + fileName = Transcriber.GetHTMLFilename(ticket.id); + filePath = Transcriber.GetHtmlPath(ticket.id); + } + zipSize = fi.Length; + } + catch (Exception e) + { + await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "ERROR: Could not find transcript file. Aborting..." + })); + Logger.Error("Failed to access transcript file:", e); + return; + } + + // Check if the chosen file path works. + if (!File.Exists(filePath)) + { + await interaction.EditOriginalResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "ERROR: Could not find transcript file. Aborting..." + })); + Logger.Error("Transcript file does not exist: \"" + filePath + "\""); + return; + } + try { // Log it if the log channel exists DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel); - DiscordEmbed embed = new DiscordEmbedBuilder + + await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read); + DiscordMessageBuilder message = new DiscordMessageBuilder(); + message.AddEmbed(new DiscordEmbedBuilder { Color = DiscordColor.Green, Description = "Ticket " + ticket.id.ToString("00000") + " closed by " + @@ -94,12 +136,8 @@ public class CloseCommand { Text = "Ticket: " + ticket.id.ToString("00000") } - }; - - await using FileStream file = new FileStream(Transcriber.GetPath(ticket.id), FileMode.Open, FileAccess.Read); - DiscordMessageBuilder message = new DiscordMessageBuilder(); - message.AddEmbed(embed); - message.AddFiles(new Dictionary { { Transcriber.GetFilename(ticket.id), file } }); + }); + message.AddFiles(new Dictionary { { fileName, file } }); await logChannel.SendMessageAsync(message); } @@ -110,25 +148,41 @@ public class CloseCommand if (Config.closingNotifications) { - DiscordEmbed embed = new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Ticket " + ticket.id.ToString("00000") + " which you opened has now been closed, " + - "check the transcript for more info.\n" + closeReason, - Footer = new DiscordEmbedBuilder.EmbedFooter - { - Text = "Ticket: " + ticket.id.ToString("00000") - } - }; - try { DiscordUser staffMember = await SupportChild.client.GetUserAsync(ticket.creatorID); - await using FileStream file = new FileStream(Transcriber.GetPath(ticket.id), FileMode.Open, FileAccess.Read); + await using FileStream file = new(filePath, FileMode.Open, FileAccess.Read); - DiscordMessageBuilder message = new DiscordMessageBuilder(); - message.AddEmbed(embed); - message.AddFiles(new Dictionary { { Transcriber.GetFilename(ticket.id), file } }); + DiscordMessageBuilder message = new(); + + if (zipSize >= 26214400) + { + message.AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Orange, + Description = "Ticket " + ticket.id.ToString("00000") + " which you opened has now been closed, check the transcript for more info.\n\n" + + "The zip file is too large, sending only the HTML file. Ask an administrator for the zip if you need it.\"\n" + closeReason, + Footer = new DiscordEmbedBuilder.EmbedFooter + { + Text = "Ticket: " + ticket.id.ToString("00000") + } + }); + } + else + { + message.AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Ticket " + ticket.id.ToString("00000") + " which you opened has now been closed, " + "check the transcript for more info.\n" + closeReason, + Footer = new DiscordEmbedBuilder.EmbedFooter + { + Text = "Ticket: " + ticket.id.ToString("00000") + } + }); + } + + + message.AddFiles(new Dictionary { { fileName, file } }); await staffMember.SendMessageAsync(message); } diff --git a/Commands/TranscriptCommand.cs b/Commands/TranscriptCommand.cs index b549531..150dcef 100644 --- a/Commands/TranscriptCommand.cs +++ b/Commands/TranscriptCommand.cs @@ -13,6 +13,7 @@ namespace SupportChild.Commands; public class TranscriptCommand { + // TODO: Refactor the hell out of this [RequireGuild] [Command("transcript")] [Description("Creates a transcript of a ticket.")] @@ -80,11 +81,49 @@ public class TranscriptCommand } } + string fileName = Transcriber.GetZipFilename(ticket.id); + string filePath = Transcriber.GetZipPath(ticket.id); + long zipSize = 0; + + // If the zip transcript doesn't exist, use the html file. + try + { + FileInfo fi = new FileInfo(filePath); + if (!fi.Exists || fi.Length >= 26214400) + { + fileName = Transcriber.GetHTMLFilename(ticket.id); + filePath = Transcriber.GetHtmlPath(ticket.id); + } + zipSize = fi.Length; + } + catch (Exception e) + { + await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "ERROR: Could not find transcript file. Aborting..." + })); + Logger.Error("Failed to access transcript file:", e); + return; + } + + // Check if the chosen file path works. + if (!File.Exists(filePath)) + { + await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Red, + Description = "ERROR: Could not find transcript file. Aborting..." + })); + Logger.Error("Transcript file does not exist: \"" + filePath + "\""); + return; + } + try { // Log it if the log channel exists DiscordChannel logChannel = await SupportChild.client.GetChannelAsync(Config.logChannel); - await using FileStream file = new FileStream(Transcriber.GetPath(ticket.id), FileMode.Open, FileAccess.Read); + await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read); DiscordMessageBuilder message = new DiscordMessageBuilder(); message.AddEmbed(new DiscordEmbedBuilder @@ -96,7 +135,7 @@ public class TranscriptCommand Text = "Ticket: " + ticket.id.ToString("00000") } }); - message.AddFiles(new Dictionary { { Transcriber.GetFilename(ticket.id), file } }); + message.AddFiles(new Dictionary { { fileName, file } }); await logChannel.SendMessageAsync(message); } @@ -105,20 +144,54 @@ public class TranscriptCommand Logger.Error("Could not send message in log channel."); } + 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 SendDirectMessage(SlashCommandContext command, string fileName, string filePath, long zipSize, uint ticketID) + { try { // Send transcript in a direct message - await using FileStream file = new FileStream(Transcriber.GetPath(ticket.id), FileMode.Open, FileAccess.Read); + await using FileStream file = new FileStream(filePath, FileMode.Open, FileAccess.Read); DiscordMessageBuilder directMessage = new DiscordMessageBuilder(); - directMessage.AddEmbed(new DiscordEmbedBuilder + + if (zipSize >= 26214400) { - Color = DiscordColor.Green, - Description = "Transcript generated!\n" - }); - directMessage.AddFiles(new Dictionary { { Transcriber.GetFilename(ticket.id), file } }); + directMessage.AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Orange, + Description = "Transcript generated.\n\nThe zip file is too large, sending only the HTML file. Ask an administrator for the zip if you need it.", + Footer = new DiscordEmbedBuilder.EmbedFooter + { + Text = "Ticket: " + ticketID.ToString("00000") + } + }); + } + else + { + directMessage.AddEmbed(new DiscordEmbedBuilder + { + Color = DiscordColor.Green, + Description = "Transcript generated!\n", + Footer = new DiscordEmbedBuilder.EmbedFooter + { + Text = "Ticket: " + ticketID.ToString("00000") + } + }); + } + + directMessage.AddFiles(new Dictionary { { fileName, file } }); await command.Member.SendMessageAsync(directMessage); + return true; } catch (UnauthorizedException) { @@ -127,13 +200,7 @@ public class TranscriptCommand Color = DiscordColor.Red, Description = "Not allowed to send direct message to you, please check your privacy settings.\n" })); - return; + return false; } - - await command.EditResponseAsync(new DiscordWebhookBuilder().AddEmbed(new DiscordEmbedBuilder - { - Color = DiscordColor.Green, - Description = "Transcript sent!\n" - })); } } \ No newline at end of file diff --git a/SupportChild.csproj b/SupportChild.csproj index 8287659..24081f6 100644 --- a/SupportChild.csproj +++ b/SupportChild.csproj @@ -30,6 +30,7 @@ + diff --git a/Transcriber.cs b/Transcriber.cs index 6967545..70a627e 100644 --- a/Transcriber.cs +++ b/Transcriber.cs @@ -1,4 +1,6 @@ -using System.IO; +using System; +using System.IO; +using System.IO.Compression; using System.Threading.Tasks; using DiscordChatExporter.Core.Discord; @@ -11,7 +13,7 @@ namespace SupportChild; internal static class Transcriber { - private static string transcriptDir = "./transcripts"; + private static string transcriptDir = "./transcripts"; // TODO: Should be local variable (or come from the config class) when added to config to avoid race conditions when reloading internal static async Task ExecuteAsync(ulong channelID, uint ticketID) { @@ -28,36 +30,103 @@ internal static class Transcriber Directory.CreateDirectory(transcriptDir); } + string htmlPath = GetHtmlPath(ticketID); + string zipPath = GetZipPath(ticketID); + string assetDirPath = GetAssetDirPath(ticketID); + + string assetDirName = GetAssetDirName(ticketID); + string htmlFilename = GetHTMLFilename(ticketID); + + // TODO: Delete existing files + if (File.Exists(htmlPath)) + { + File.Delete(htmlPath); + } + + if (File.Exists(zipPath)) + { + File.Delete(zipPath); + } + + if (Directory.Exists(assetDirPath)) + { + Directory.Delete(assetDirPath, true); + } + Channel channel = await discordClient.GetChannelAsync(new Snowflake(channelID)); Guild guild = await discordClient.GetGuildAsync(channel.GuildId); ExportRequest request = new( guild, channel, - GetPath(ticketID), - null, + htmlPath, + assetDirPath, ExportFormat.HtmlDark, null, null, PartitionLimit.Null, MessageFilter.Null, true, - false, - false, - "en-US", + true, + true, + "en-SE", true ); await exporter.ExportChannelAsync(request); + + string[] assetFiles; + try + { + assetFiles = Directory.GetFiles(assetDirPath); + } + catch (Exception) + { + assetFiles = []; + } + + if (assetFiles.Length > 0) + { + using (ZipArchive zip = ZipFile.Open(zipPath, ZipArchiveMode.Create)) + { + zip.CreateEntryFromFile(htmlPath, htmlFilename); + foreach (string assetFile in assetFiles) + { + zip.CreateEntryFromFile(assetFile, assetDirName + "/" + Path.GetFileName(assetFile)); + } + } + + Directory.Delete(assetDirPath, true); + } } - internal static string GetPath(uint ticketNumber) + internal static string GetHtmlPath(uint ticketNumber) { - return transcriptDir + "/" + GetFilename(ticketNumber); + return transcriptDir + "/" + GetHTMLFilename(ticketNumber); } - internal static string GetFilename(uint ticketNumber) + internal static string GetZipPath(uint ticketNumber) + { + return transcriptDir + "/" + GetZipFilename(ticketNumber); + } + + internal static string GetAssetDirPath(uint ticketNumber) + { + return transcriptDir + "/" + GetAssetDirName(ticketNumber); + } + + internal static string GetAssetDirName(uint ticketNumber) + { + return "ticket-" + ticketNumber.ToString("00000") + "-assets"; + } + + internal static string GetHTMLFilename(uint ticketNumber) { return "ticket-" + ticketNumber.ToString("00000") + ".html"; } + + internal static string GetZipFilename(uint ticketNumber) + { + return "ticket-" + ticketNumber.ToString("00000") + ".zip"; + } } \ No newline at end of file