public async Task ExportAsync(string token, string channelId, string filePath, ExportFormat format, DateTime?from, DateTime?to) { // Get channel and guild var channel = await _dataService.GetChannelAsync(token, channelId); var guild = channel.GuildId == Guild.DirectMessages.Id ? Guild.DirectMessages : await _dataService.GetGuildAsync(token, channel.GuildId); // Generate file path if not set if (filePath.IsBlank()) { filePath = $"{guild.Name} - {channel.Name}.{format.GetFileExtension()}" .Replace(Path.GetInvalidFileNameChars(), '_'); } // Get messages var messages = await _dataService.GetChannelMessagesAsync(token, channelId, from, to); // Group them var messageGroups = _messageGroupService.GroupMessages(messages); // Create log var log = new ChannelChatLog(guild, channel, messageGroups, messages.Count); // Export await _exportService.ExportAsync(format, filePath, log); }
public async Task ExportAsync(ExportFormat format, string filePath, ChannelChatLog log) { using (var output = File.CreateText(filePath)) { var sharedCss = Assembly.GetExecutingAssembly() .GetManifestResourceString("DiscordChatExporter.Core.Resources.ExportService.Shared.css"); if (format == ExportFormat.PlainText) { await ExportAsPlainTextAsync(log, output); } else if (format == ExportFormat.HtmlDark) { var css = Assembly.GetExecutingAssembly() .GetManifestResourceString("DiscordChatExporter.Core.Resources.ExportService.DarkTheme.css"); await ExportAsHtmlAsync(log, output, $"{sharedCss}\n{css}"); } else if (format == ExportFormat.HtmlLight) { var css = Assembly.GetExecutingAssembly() .GetManifestResourceString("DiscordChatExporter.Core.Resources.ExportService.LightTheme.css"); await ExportAsHtmlAsync(log, output, $"{sharedCss}\n{css}"); } else if (format == ExportFormat.Csv) { await ExportAsCsvAsync(log, output); } else { throw new ArgumentOutOfRangeException(nameof(format)); } } }
private async Task ExportAsTextAsync(string filePath, ChannelChatLog log) { using (var writer = new StreamWriter(filePath, false, Encoding.UTF8, 128 * 1024)) { // Generation info await writer.WriteLineAsync("https://github.com/Tyrrrz/DiscordChatExporter"); await writer.WriteLineAsync(); // Guild and channel info await writer.WriteLineAsync('='.Repeat(48)); await writer.WriteLineAsync($"Guild: {log.Guild.Name}"); await writer.WriteLineAsync($"Channel: {log.Channel.Name}"); await writer.WriteLineAsync($"Topic: {log.Channel.Topic}"); await writer.WriteLineAsync($"Messages: {log.TotalMessageCount:N0}"); await writer.WriteLineAsync('='.Repeat(48)); await writer.WriteLineAsync(); // Chat log foreach (var group in log.MessageGroups) { var timeStampFormatted = group.TimeStamp.ToString(_settingsService.DateFormat); await writer.WriteLineAsync($"{group.Author.FullyQualifiedName} [{timeStampFormatted}]"); // Messages foreach (var message in group.Messages) { // Content if (message.Content.IsNotBlank()) { var contentFormatted = FormatMessageContentText(message); await writer.WriteLineAsync(contentFormatted); } // Attachments foreach (var attachment in message.Attachments) { await writer.WriteLineAsync(attachment.Url); } } await writer.WriteLineAsync(); } } }
private async Task ExportAsPlainTextAsync(ChannelChatLog log, TextWriter output) { // Generation info await output.WriteLineAsync("https://github.com/Tyrrrz/DiscordChatExporter"); await output.WriteLineAsync(); // Guild and channel info await output.WriteLineAsync('='.Repeat(48)); await output.WriteLineAsync($"Guild: {log.Guild.Name}"); await output.WriteLineAsync($"Channel: {log.Channel.Name}"); await output.WriteLineAsync($"Topic: {log.Channel.Topic}"); await output.WriteLineAsync($"Messages: {log.TotalMessageCount:N0}"); await output.WriteLineAsync('='.Repeat(48)); await output.WriteLineAsync(); // Chat log foreach (var group in log.MessageGroups) { var timeStampFormatted = group.TimeStamp.ToString(_settingsService.DateFormat); await output.WriteLineAsync($"{group.Author.FullName} [{timeStampFormatted}]"); // Messages foreach (var message in group.Messages) { // Content if (message.Content.IsNotBlank()) { var contentFormatted = FormatMessageContentPlainText(message); await output.WriteLineAsync(contentFormatted); } // Attachments foreach (var attachment in message.Attachments) { await output.WriteLineAsync(attachment.Url); } } await output.WriteLineAsync(); } }
public Task ExportAsync(ExportFormat format, string filePath, ChannelChatLog log) { if (format == ExportFormat.PlainText) { return(ExportAsTextAsync(filePath, log)); } if (format == ExportFormat.HtmlDark) { var css = AssemblyHelper.GetResourceString("DiscordChatExporter.Core.Resources.ExportService.DarkTheme.css"); return(ExportAsHtmlAsync(filePath, log, css)); } if (format == ExportFormat.HtmlLight) { var css = AssemblyHelper.GetResourceString("DiscordChatExporter.Core.Resources.ExportService.LightTheme.css"); return(ExportAsHtmlAsync(filePath, log, css)); } throw new ArgumentOutOfRangeException(nameof(format)); }
public Task ExportAsync(ExportFormat format, string filePath, ChannelChatLog log) { if (format == ExportFormat.PlainText) { return(ExportAsTextAsync(filePath, log)); } if (format == ExportFormat.HtmlDark) { var css = Program.GetResourceString("DiscordChatExporter.Resources.ExportService.DarkTheme.css"); return(ExportAsHtmlAsync(filePath, log, css)); } if (format == ExportFormat.HtmlLight) { var css = Program.GetResourceString("DiscordChatExporter.Resources.ExportService.LightTheme.css"); return(ExportAsHtmlAsync(filePath, log, css)); } throw new NotImplementedException(); }
private async Task ExportAsCsvAsync(ChannelChatLog log, TextWriter output) { using (var writer = new CsvWriter(output)) { // Headers writer.WriteField("Author"); writer.WriteField("Date"); writer.WriteField("Content"); writer.WriteField("Attachments"); await writer.NextRecordAsync(); // Chat log foreach (var group in log.MessageGroups) { // Messages foreach (var msg in group.Messages) { // Author writer.WriteField(msg.Author.FullName); // Date var timeStampFormatted = msg.TimeStamp.ToString(_settingsService.DateFormat); writer.WriteField(timeStampFormatted); // Content var contentFormatted = msg.Content.IsNotBlank() ? FormatMessageContentCsv(msg) : null; writer.WriteField(contentFormatted); // Attachments var attachmentsFormatted = msg.Attachments.Select(a => a.Url).JoinToString(","); writer.WriteField(attachmentsFormatted); await writer.NextRecordAsync(); } } } }
private async Task ExportAsHtmlAsync(ChannelChatLog log, TextWriter output, string css) { // Generation info await output.WriteLineAsync("<!-- https://github.com/Tyrrrz/DiscordChatExporter -->"); // Html start await output.WriteLineAsync("<!DOCTYPE html>"); await output.WriteLineAsync("<html lang=\"en\">"); // HEAD await output.WriteLineAsync("<head>"); await output.WriteLineAsync($"<title>{log.Guild.Name} - {log.Channel.Name}</title>"); await output.WriteLineAsync("<meta charset=\"utf-8\" />"); await output.WriteLineAsync("<meta name=\"viewport\" content=\"width=device-width\" />"); await output.WriteLineAsync($"<style>{css}</style>"); await output.WriteLineAsync("</head>"); // Body start await output.WriteLineAsync("<body>"); // Guild and channel info await output.WriteLineAsync("<div id=\"info\">"); await output.WriteLineAsync("<div class=\"info-left\">"); await output.WriteLineAsync($"<img class=\"guild-icon\" src=\"{log.Guild.IconUrl}\" />"); await output.WriteLineAsync("</div>"); // info-left await output.WriteLineAsync("<div class=\"info-right\">"); await output.WriteLineAsync($"<div class=\"guild-name\">{log.Guild.Name}</div>"); await output.WriteLineAsync($"<div class=\"channel-name\">{log.Channel.Name}</div>"); await output.WriteLineAsync($"<div class=\"channel-topic\">{log.Channel.Topic}</div>"); await output.WriteLineAsync( $"<div class=\"channel-messagecount\">{log.TotalMessageCount:N0} messages</div>"); await output.WriteLineAsync("</div>"); // info-right await output.WriteLineAsync("</div>"); // info // Chat log await output.WriteLineAsync("<div id=\"log\">"); foreach (var group in log.MessageGroups) { await output.WriteLineAsync("<div class=\"msg\">"); await output.WriteLineAsync("<div class=\"msg-left\">"); await output.WriteLineAsync($"<img class=\"msg-avatar\" src=\"{group.Author.AvatarUrl}\" />"); await output.WriteLineAsync("</div>"); await output.WriteLineAsync("<div class=\"msg-right\">"); await output.WriteAsync( $"<span class=\"msg-user\" title=\"{HtmlEncode(group.Author.FullName)}\">"); await output.WriteAsync(HtmlEncode(group.Author.Name)); await output.WriteLineAsync("</span>"); var timeStampFormatted = HtmlEncode(group.TimeStamp.ToString(_settingsService.DateFormat)); await output.WriteLineAsync($"<span class=\"msg-date\">{timeStampFormatted}</span>"); // Messages foreach (var message in group.Messages) { // Content if (message.Content.IsNotBlank()) { await output.WriteLineAsync("<div class=\"msg-content\">"); var contentFormatted = FormatMessageContentHtml(message); await output.WriteAsync(contentFormatted); // Edited timestamp if (message.EditedTimeStamp != null) { var editedTimeStampFormatted = HtmlEncode(message.EditedTimeStamp.Value.ToString(_settingsService.DateFormat)); await output.WriteAsync( $"<span class=\"msg-edited\" title=\"{editedTimeStampFormatted}\">(edited)</span>"); } await output.WriteLineAsync("</div>"); // msg-content } // Attachments foreach (var attachment in message.Attachments) { if (attachment.Type == AttachmentType.Image) { await output.WriteLineAsync("<div class=\"msg-attachment\">"); await output.WriteLineAsync($"<a href=\"{attachment.Url}\">"); await output.WriteLineAsync($"<img class=\"msg-attachment\" src=\"{attachment.Url}\" />"); await output.WriteLineAsync("</a>"); await output.WriteLineAsync("</div>"); } else { await output.WriteLineAsync("<div class=\"msg-attachment\">"); await output.WriteLineAsync($"<a href=\"{attachment.Url}\">"); var fileSizeFormatted = FormatFileSize(attachment.FileSize); await output.WriteLineAsync($"Attachment: {attachment.FileName} ({fileSizeFormatted})"); await output.WriteLineAsync("</a>"); await output.WriteLineAsync("</div>"); } } // Embeds foreach (var embed in message.Embeds) { var contentFormatted = FormatEmbedHtml(embed); await output.WriteAsync(contentFormatted); } } await output.WriteLineAsync("</div>"); // msg-right await output.WriteLineAsync("</div>"); // msg } await output.WriteLineAsync("</div>"); // log await output.WriteLineAsync("</body>"); await output.WriteLineAsync("</html>"); }
private static async Task ExportAll(ExportAllOptions options) { var token = options.Token; var format = options.Format; var year = options.Year; var month = options.Month; var allUpTo = options.AllUpTo; var settingsService = new SettingsService(); var dataService = new DataService(); var exportService = new ExportService(settingsService); var messageGroupService = new MessageGroupService(settingsService); DateTime?from = allUpTo ? null : (DateTime?)new DateTime(year, month, 1); DateTime to = new DateTime(year, month, 1).AddMonths(1); Console.WriteLine("Retrieving servers..."); var guilds = new List <Guild>(await dataService.GetUserGuildsAsync(token)); guilds.Add(Guild.DirectMessages); foreach (var guild in guilds) { Console.WriteLine("Retrieving channels for " + guild.Name + "..."); IReadOnlyList <Channel> channels; while (true) { try { if (guild == Guild.DirectMessages) { channels = await dataService.GetDirectMessageChannelsAsync(token); } else { channels = await dataService.GetGuildChannelsAsync(token, guild.Id); } break; } catch (Exception e) { Console.WriteLine(e); // If we get an error, it might be Discord asking us to kindly back off. // Give them a short break. Thread.Sleep(10000); throw; } } foreach (var channel in channels) { var filePath = "Export/" + guild.Name + "/" + channel.Name + "/" + year + "-" + month + (format == ExportFormat.PlainText ? ".txt" : ".html"); // Get messages Console.WriteLine("Retrieving messages for " + guild.Name + ":" + channel.Name); IReadOnlyList <Message> messages = null; while (true) { try { messages = await dataService.GetChannelMessagesAsync(token, channel.Id, from, to); break; } catch (HttpErrorStatusCodeException e) { // This indicates a channel we don't have access to. if (e.StatusCode == HttpStatusCode.Forbidden) { break; } Console.WriteLine(e); // If we get an error, it might be Discord asking us to kindly back off. // Give them a short break. Thread.Sleep(10000); } catch (Exception e) { Console.WriteLine(e); // If we get an error, it might be Discord asking us to kindly back off. // Give them a short break. Thread.Sleep(10000); throw; } } if (messages == null) { continue; } // Group them var messageGroups = messageGroupService.GroupMessages(messages); // Create log var log = new ChannelChatLog(guild, channel, messageGroups, messages.Count); // Export Console.WriteLine("Exporting messages..."); Directory.CreateDirectory("Export/" + guild.Name + "/" + channel.Name + "/"); await exportService.ExportAsync(format, filePath, log); } } }