diff --git a/DiscordChatExporter/DiscordChatExporter.csproj b/DiscordChatExporter/DiscordChatExporter.csproj index d76983d..724651e 100644 --- a/DiscordChatExporter/DiscordChatExporter.csproj +++ b/DiscordChatExporter/DiscordChatExporter.csproj @@ -148,7 +148,6 @@ - diff --git a/DiscordChatExporter/Models/ExportFormat.cs b/DiscordChatExporter/Models/ExportFormat.cs index b144788..acac9cb 100644 --- a/DiscordChatExporter/Models/ExportFormat.cs +++ b/DiscordChatExporter/Models/ExportFormat.cs @@ -2,7 +2,8 @@ { public enum ExportFormat { - Text, - Html + PlainText, + HtmlDark, + HtmlLight } } \ No newline at end of file diff --git a/DiscordChatExporter/Models/Extensions.cs b/DiscordChatExporter/Models/Extensions.cs index 83d0ff3..ef0893b 100644 --- a/DiscordChatExporter/Models/Extensions.cs +++ b/DiscordChatExporter/Models/Extensions.cs @@ -1,14 +1,31 @@ -namespace DiscordChatExporter.Models +using System; + +namespace DiscordChatExporter.Models { public static class Extensions { public static string GetFileExtension(this ExportFormat format) { - if (format == ExportFormat.Text) + if (format == ExportFormat.PlainText) return "txt"; - if (format == ExportFormat.Html) + if (format == ExportFormat.HtmlDark) return "html"; - return null; + if (format == ExportFormat.HtmlLight) + return "html"; + + throw new NotImplementedException(); + } + + public static string GetDisplayName(this ExportFormat format) + { + if (format == ExportFormat.PlainText) + return "Plain Text"; + if (format == ExportFormat.HtmlDark) + return "HTML (Dark)"; + if (format == ExportFormat.HtmlLight) + return "HTML (Light)"; + + throw new NotImplementedException(); } } } \ No newline at end of file diff --git a/DiscordChatExporter/Models/Theme.cs b/DiscordChatExporter/Models/Theme.cs deleted file mode 100644 index 54db5c5..0000000 --- a/DiscordChatExporter/Models/Theme.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace DiscordChatExporter.Models -{ - public enum Theme - { - Dark, - Light - } -} \ No newline at end of file diff --git a/DiscordChatExporter/Program.cs b/DiscordChatExporter/Program.cs index 6cead56..27f8439 100644 --- a/DiscordChatExporter/Program.cs +++ b/DiscordChatExporter/Program.cs @@ -1,4 +1,7 @@ using System; +using System.IO; +using System.Reflection; +using System.Resources; using AmmySidekick; namespace DiscordChatExporter @@ -15,5 +18,19 @@ namespace DiscordChatExporter app.Run(); } + + public static string GetResourceString(string resourcePath) + { + var assembly = Assembly.GetExecutingAssembly(); + var stream = assembly.GetManifestResourceStream(resourcePath); + if (stream == null) + throw new MissingManifestResourceException("Could not find resource"); + + using (stream) + using (var reader = new StreamReader(stream)) + { + return reader.ReadToEnd(); + } + } } } \ No newline at end of file diff --git a/DiscordChatExporter/Services/DataService.cs b/DiscordChatExporter/Services/DataService.cs index e40c8de..fbd3128 100644 --- a/DiscordChatExporter/Services/DataService.cs +++ b/DiscordChatExporter/Services/DataService.cs @@ -71,7 +71,8 @@ namespace DiscordChatExporter.Services return channels; } - public async Task> GetChannelMessagesAsync(string token, string channelId, DateTime? from, DateTime? to) + public async Task> GetChannelMessagesAsync(string token, string channelId, + DateTime? from, DateTime? to) { var result = new List(); @@ -100,10 +101,12 @@ namespace DiscordChatExporter.Services } // If no messages - break - if (currentMessageId == null) break; + if (currentMessageId == null) + break; // If last message is older than from date - break - if (from != null && result.Last().TimeStamp < from) break; + if (from != null && result.Last().TimeStamp < from) + break; // Otherwise offset the next request beforeId = currentMessageId; diff --git a/DiscordChatExporter/Services/ExportService.cs b/DiscordChatExporter/Services/ExportService.cs index 0cae5de..eb75abc 100644 --- a/DiscordChatExporter/Services/ExportService.cs +++ b/DiscordChatExporter/Services/ExportService.cs @@ -1,8 +1,6 @@ using System; using System.IO; using System.Net; -using System.Reflection; -using System.Resources; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -20,7 +18,7 @@ namespace DiscordChatExporter.Services _settingsService = settingsService; } - public async Task ExportAsTextAsync(string filePath, ChannelChatLog log) + private async Task ExportAsTextAsync(string filePath, ChannelChatLog log) { var dateFormat = _settingsService.DateFormat; @@ -66,9 +64,8 @@ namespace DiscordChatExporter.Services } } - public async Task ExportAsHtmlAsync(string filePath, ChannelChatLog log, Theme theme) + private async Task ExportAsHtmlAsync(string filePath, ChannelChatLog log, string css) { - var themeCss = GetThemeCss(theme); var dateFormat = _settingsService.DateFormat; using (var writer = new StreamWriter(filePath, false, Encoding.UTF8, 128 * 1024)) @@ -85,7 +82,7 @@ namespace DiscordChatExporter.Services await writer.WriteLineAsync($"{log.Guild} - {log.Channel}"); await writer.WriteLineAsync(""); await writer.WriteLineAsync(""); - await writer.WriteLineAsync($""); + await writer.WriteLineAsync($""); await writer.WriteLineAsync(""); // Body start @@ -173,26 +170,30 @@ namespace DiscordChatExporter.Services await writer.WriteLineAsync(""); } } - } - public partial class ExportService - { - private static string GetThemeCss(Theme theme) + public Task ExportAsync(ExportFormat format, string filePath, ChannelChatLog log) { - var resourcePath = $"DiscordChatExporter.Resources.ExportService.{theme}Theme.css"; - - var assembly = Assembly.GetExecutingAssembly(); - var stream = assembly.GetManifestResourceStream(resourcePath); - if (stream == null) - throw new MissingManifestResourceException("Could not find style resource"); - - using (stream) - using (var reader = new StreamReader(stream)) + if (format == ExportFormat.PlainText) + { + return ExportAsTextAsync(filePath, log); + } + if (format == ExportFormat.HtmlDark) { - return reader.ReadToEnd(); + 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(); } + } + public partial class ExportService + { private static string HtmlEncode(string str) { return WebUtility.HtmlEncode(str); diff --git a/DiscordChatExporter/Services/IExportService.cs b/DiscordChatExporter/Services/IExportService.cs index 7983d2f..e405aa0 100644 --- a/DiscordChatExporter/Services/IExportService.cs +++ b/DiscordChatExporter/Services/IExportService.cs @@ -5,7 +5,6 @@ namespace DiscordChatExporter.Services { public interface IExportService { - Task ExportAsTextAsync(string filePath, ChannelChatLog log); - Task ExportAsHtmlAsync(string filePath, ChannelChatLog log, Theme theme); + Task ExportAsync(ExportFormat format, string filePath, ChannelChatLog log); } } \ No newline at end of file diff --git a/DiscordChatExporter/Services/ISettingsService.cs b/DiscordChatExporter/Services/ISettingsService.cs index 46f2370..3f04e73 100644 --- a/DiscordChatExporter/Services/ISettingsService.cs +++ b/DiscordChatExporter/Services/ISettingsService.cs @@ -4,7 +4,6 @@ namespace DiscordChatExporter.Services { public interface ISettingsService { - Theme Theme { get; set; } string DateFormat { get; set; } int MessageGroupLimit { get; set; } diff --git a/DiscordChatExporter/Services/SettingsService.cs b/DiscordChatExporter/Services/SettingsService.cs index 12ccf5e..5e0e021 100644 --- a/DiscordChatExporter/Services/SettingsService.cs +++ b/DiscordChatExporter/Services/SettingsService.cs @@ -5,12 +5,11 @@ namespace DiscordChatExporter.Services { public class SettingsService : SettingsManager, ISettingsService { - public Theme Theme { get; set; } public string DateFormat { get; set; } = "dd-MMM-yy hh:mm tt"; public int MessageGroupLimit { get; set; } = 20; public string LastToken { get; set; } - public ExportFormat LastExportFormat { get; set; } = ExportFormat.Html; + public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark; public SettingsService() { diff --git a/DiscordChatExporter/ViewModels/ExportSetupViewModel.cs b/DiscordChatExporter/ViewModels/ExportSetupViewModel.cs index f343beb..a9bdef6 100644 --- a/DiscordChatExporter/ViewModels/ExportSetupViewModel.cs +++ b/DiscordChatExporter/ViewModels/ExportSetupViewModel.cs @@ -39,7 +39,15 @@ namespace DiscordChatExporter.ViewModels public ExportFormat SelectedFormat { get => _format; - set => Set(ref _format, value); + set + { + Set(ref _format, value); + + // Replace extension in path + var newExt = value.GetFileExtension(); + if (FilePath != null && !FilePath.EndsWith(newExt)) + FilePath = FilePath.SubstringUntilLast(".") + "." + newExt; + } } public DateTime? From @@ -82,7 +90,10 @@ namespace DiscordChatExporter.ViewModels private void Export() { + // Save format _settingsService.LastExportFormat = SelectedFormat; + + // Start export MessengerInstance.Send(new StartExportMessage(Channel, FilePath, SelectedFormat, From, To)); } } diff --git a/DiscordChatExporter/ViewModels/ISettingsViewModel.cs b/DiscordChatExporter/ViewModels/ISettingsViewModel.cs index 49000d1..fc331ce 100644 --- a/DiscordChatExporter/ViewModels/ISettingsViewModel.cs +++ b/DiscordChatExporter/ViewModels/ISettingsViewModel.cs @@ -1,12 +1,7 @@ -using System.Collections.Generic; -using DiscordChatExporter.Models; - -namespace DiscordChatExporter.ViewModels +namespace DiscordChatExporter.ViewModels { public interface ISettingsViewModel { - IReadOnlyList AvailableThemes { get; } - Theme Theme { get; set; } string DateFormat { get; set; } int MessageGroupLimit { get; set; } } diff --git a/DiscordChatExporter/ViewModels/MainViewModel.cs b/DiscordChatExporter/ViewModels/MainViewModel.cs index b8d66b7..45cc58b 100644 --- a/DiscordChatExporter/ViewModels/MainViewModel.cs +++ b/DiscordChatExporter/ViewModels/MainViewModel.cs @@ -23,10 +23,10 @@ namespace DiscordChatExporter.ViewModels private readonly Dictionary> _guildChannelsMap; private bool _isBusy; + private string _token; private IReadOnlyList _availableGuilds; private Guild _selectedGuild; private IReadOnlyList _availableChannels; - private string _cachedToken; public bool IsBusy { @@ -43,13 +43,13 @@ namespace DiscordChatExporter.ViewModels public string Token { - get => _settingsService.LastToken; + get => _token; set { // Remove invalid chars value = value?.Trim('"'); - _settingsService.LastToken = value; + Set(ref _token, value); PullDataCommand.RaiseCanExecuteChanged(); } } @@ -107,12 +107,20 @@ namespace DiscordChatExporter.ViewModels { Export(m.Channel, m.FilePath, m.Format, m.From, m.To); }); + + // Defaults + _token = _settingsService.LastToken; } private async void PullData() { IsBusy = true; - _cachedToken = Token; + + // Copy token so it doesn't get mutated + var token = Token; + + // Save token + _settingsService.LastToken = token; // Clear existing _guildChannelsMap.Clear(); @@ -121,17 +129,17 @@ namespace DiscordChatExporter.ViewModels { // Get DM channels { - var channels = await _dataService.GetDirectMessageChannelsAsync(_cachedToken); + var channels = await _dataService.GetDirectMessageChannelsAsync(token); var guild = new Guild("@me", "Direct Messages", null); _guildChannelsMap[guild] = channels.ToArray(); } // Get guild channels { - var guilds = await _dataService.GetGuildsAsync(_cachedToken); + var guilds = await _dataService.GetGuildsAsync(token); foreach (var guild in guilds) { - var channels = await _dataService.GetGuildChannelsAsync(_cachedToken, guild.Id); + var channels = await _dataService.GetGuildChannelsAsync(token, guild.Id); _guildChannelsMap[guild] = channels.Where(c => c.Type == ChannelType.GuildTextChat).ToArray(); } } @@ -171,22 +179,22 @@ namespace DiscordChatExporter.ViewModels { IsBusy = true; + // Get last used token + var token = _settingsService.LastToken; + try { // Get messages - var messages = await _dataService.GetChannelMessagesAsync(_cachedToken, channel.Id, from, to); + var messages = await _dataService.GetChannelMessagesAsync(token, channel.Id, from, to); // Group them var messageGroups = _messageGroupService.GroupMessages(messages); // Create log - var chatLog = new ChannelChatLog(SelectedGuild, channel, messageGroups, messages.Count); + var log = new ChannelChatLog(SelectedGuild, channel, messageGroups, messages.Count); // Export - if (format == ExportFormat.Text) - await _exportService.ExportAsTextAsync(filePath, chatLog); - else if (format == ExportFormat.Html) - await _exportService.ExportAsHtmlAsync(filePath, chatLog, _settingsService.Theme); + await _exportService.ExportAsync(format, filePath, log); // Notify completion MessengerInstance.Send(new ShowExportDoneMessage(filePath)); diff --git a/DiscordChatExporter/ViewModels/SettingsViewModel.cs b/DiscordChatExporter/ViewModels/SettingsViewModel.cs index 51add4d..585ab09 100644 --- a/DiscordChatExporter/ViewModels/SettingsViewModel.cs +++ b/DiscordChatExporter/ViewModels/SettingsViewModel.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using DiscordChatExporter.Models; -using DiscordChatExporter.Services; +using DiscordChatExporter.Services; using GalaSoft.MvvmLight; using Tyrrrz.Extensions; @@ -12,14 +8,6 @@ namespace DiscordChatExporter.ViewModels { private readonly ISettingsService _settingsService; - public IReadOnlyList AvailableThemes { get; } - - public Theme Theme - { - get => _settingsService.Theme; - set => _settingsService.Theme = value; - } - public string DateFormat { get => _settingsService.DateFormat; @@ -35,9 +23,6 @@ namespace DiscordChatExporter.ViewModels public SettingsViewModel(ISettingsService settingsService) { _settingsService = settingsService; - - // Defaults - AvailableThemes = Enum.GetValues(typeof(Theme)).Cast().ToArray(); } } } \ No newline at end of file diff --git a/DiscordChatExporter/Views/ExportSetupDialog.ammy b/DiscordChatExporter/Views/ExportSetupDialog.ammy index b539056..ea13a1b 100644 --- a/DiscordChatExporter/Views/ExportSetupDialog.ammy +++ b/DiscordChatExporter/Views/ExportSetupDialog.ammy @@ -1,8 +1,9 @@ -using MaterialDesignThemes.Wpf +using DiscordChatExporter.Models +using MaterialDesignThemes.Wpf UserControl "DiscordChatExporter.Views.ExportSetupDialog" { DataContext: bind ExportSetupViewModel from $resource Container - Width: 350 + Width: 325 StackPanel { // File path @@ -15,13 +16,29 @@ UserControl "DiscordChatExporter.Views.ExportSetupDialog" { set [ UpdateSourceTrigger: PropertyChanged ] } + // Format + ComboBox { + Margin: "16 8 16 8" + HintAssist.Hint: "Export format" + HintAssist.IsFloating: true + IsReadOnly: true + ItemsSource: bind AvailableFormats + ItemTemplate: DataTemplate { + TextBlock { + Text: bind + convert (ExportFormat f) => Extensions.GetDisplayName(f) + } + } + SelectedItem: bind SelectedFormat + } + // Date range Grid { #TwoColumns("*", "*") DatePicker { Grid.Column: 0 - Margin: "16 16 8 8" + Margin: "16 20 8 8" HintAssist.Hint: "From" HintAssist.IsFloating: true SelectedDate: bind From @@ -29,7 +46,7 @@ UserControl "DiscordChatExporter.Views.ExportSetupDialog" { DatePicker { Grid.Column: 1 - Margin: "8 16 16 8" + Margin: "8 20 16 8" HintAssist.Hint: "To" HintAssist.IsFloating: true SelectedDate: bind To @@ -40,6 +57,14 @@ UserControl "DiscordChatExporter.Views.ExportSetupDialog" { @StackPanelHorizontal { HorizontalAlignment: Right + // Browse + Button "BrowseButton" { + Click: BrowseButton_Click + Content: "BROWSE" + Margin: 8 + Style: resource dyn "MaterialDesignFlatButton" + } + // Export Button "ExportButton" { Click: ExportButton_Click @@ -49,14 +74,6 @@ UserControl "DiscordChatExporter.Views.ExportSetupDialog" { Style: resource dyn "MaterialDesignFlatButton" } - // Browse - Button "BrowseButton" { - Click: BrowseButton_Click - Content: "BROWSE" - Margin: 8 - Style: resource dyn "MaterialDesignFlatButton" - } - // Cancel Button { Command: DialogHost.CloseDialogCommand diff --git a/DiscordChatExporter/Views/ExportSetupDialog.ammy.cs b/DiscordChatExporter/Views/ExportSetupDialog.ammy.cs index 53065c8..7f0d629 100644 --- a/DiscordChatExporter/Views/ExportSetupDialog.ammy.cs +++ b/DiscordChatExporter/Views/ExportSetupDialog.ammy.cs @@ -1,10 +1,8 @@ -using System.Collections.Generic; -using System.Windows; +using System.Windows; using DiscordChatExporter.Models; using DiscordChatExporter.ViewModels; using MaterialDesignThemes.Wpf; using Microsoft.Win32; -using Tyrrrz.Extensions; namespace DiscordChatExporter.Views { @@ -17,39 +15,30 @@ namespace DiscordChatExporter.Views InitializeComponent(); } - private string GetFilter() - { - var filters = new List(); - foreach (var format in ViewModel.AvailableFormats) - { - var ext = format.GetFileExtension(); - filters.Add($"{format} (*.{ext})|*.{ext}"); - } - - return filters.JoinToString("|"); - } - - public void ExportButton_Click(object sender, RoutedEventArgs args) - { - DialogHost.CloseDialogCommand.Execute(null, null); - } - public void BrowseButton_Click(object sender, RoutedEventArgs args) { + // Get file extension of the selected format + var ext = ViewModel.SelectedFormat.GetFileExtension(); + + // Open dialog var sfd = new SaveFileDialog { FileName = ViewModel.FilePath, - Filter = GetFilter(), - FilterIndex = ViewModel.AvailableFormats.IndexOf(ViewModel.SelectedFormat) + 1, + Filter = $"{ext.ToUpperInvariant()} Files|*.{ext}|All Files|*.*", AddExtension = true, Title = "Select output file" }; + // Assign new file path if dialog was successful if (sfd.ShowDialog() == true) { ViewModel.FilePath = sfd.FileName; - ViewModel.SelectedFormat = ViewModel.AvailableFormats[sfd.FilterIndex - 1]; } } + + public void ExportButton_Click(object sender, RoutedEventArgs args) + { + DialogHost.CloseDialogCommand.Execute(null, null); + } } } \ No newline at end of file diff --git a/DiscordChatExporter/Views/MainWindow.ammy b/DiscordChatExporter/Views/MainWindow.ammy index 458a18f..f2ae3af 100644 --- a/DiscordChatExporter/Views/MainWindow.ammy +++ b/DiscordChatExporter/Views/MainWindow.ammy @@ -46,6 +46,8 @@ Window "DiscordChatExporter.Views.MainWindow" { FontSize: 16 Text: bind Token set [ UpdateSourceTrigger: PropertyChanged ] + TextFieldAssist.DecorationVisibility: Hidden + TextFieldAssist.TextBoxViewMargin: "0 0 2 0" } // Submit diff --git a/DiscordChatExporter/Views/SettingsDialog.ammy b/DiscordChatExporter/Views/SettingsDialog.ammy index 52bf77c..531ad0e 100644 --- a/DiscordChatExporter/Views/SettingsDialog.ammy +++ b/DiscordChatExporter/Views/SettingsDialog.ammy @@ -5,19 +5,9 @@ UserControl "DiscordChatExporter.Views.SettingsDialog" { Width: 250 StackPanel { - // Theme - ComboBox { - Margin: "16 16 16 8" - HintAssist.Hint: "Theme" - HintAssist.IsFloating: true - IsReadOnly: true - ItemsSource: bind AvailableThemes - SelectedItem: bind Theme - } - // Date format TextBox { - Margin: "16 8 16 8" + Margin: "16 16 16 8" HintAssist.Hint: "Date format" HintAssist.IsFloating: true Text: bind DateFormat