diff --git a/DiscordChatExporter.Core/Models/Channel.cs b/DiscordChatExporter.Core/Models/Channel.cs index 66bc3f5..a02316c 100644 --- a/DiscordChatExporter.Core/Models/Channel.cs +++ b/DiscordChatExporter.Core/Models/Channel.cs @@ -6,6 +6,8 @@ { public string Id { get; } + public string ParentId { get; } + public string GuildId { get; } public string Name { get; } @@ -13,10 +15,11 @@ public string Topic { get; } public ChannelType Type { get; } - - public Channel(string id, string guildId, string name, string topic, ChannelType type) + + public Channel(string id, string parentId, string guildId, string name, string topic, ChannelType type) { Id = id; + ParentId = parentId; GuildId = guildId; Name = name; Topic = topic; @@ -29,6 +32,6 @@ public partial class Channel { public static Channel CreateDeletedChannel(string id) => - new Channel(id, null, "deleted-channel", null, ChannelType.GuildTextChat); + new Channel(id, null, null, "deleted-channel", null, ChannelType.GuildTextChat); } } \ No newline at end of file diff --git a/DiscordChatExporter.Core/Services/DataService.Parsers.cs b/DiscordChatExporter.Core/Services/DataService.Parsers.cs index 35d2367..5fa44bb 100644 --- a/DiscordChatExporter.Core/Services/DataService.Parsers.cs +++ b/DiscordChatExporter.Core/Services/DataService.Parsers.cs @@ -33,6 +33,7 @@ namespace DiscordChatExporter.Core.Services { // Get basic data var id = json["id"].Value(); + var parentId = json["parent_id"]?.Value(); var type = (ChannelType) json["type"].Value(); var topic = json["topic"]?.Value(); @@ -50,7 +51,7 @@ namespace DiscordChatExporter.Core.Services if (name.IsBlank()) name = json["recipients"].Select(ParseUser).Select(u => u.Name).JoinToString(", "); - return new Channel(id, guildId, name, topic, type); + return new Channel(id, parentId, guildId, name, topic, type); } private Role ParseRole(JToken json) diff --git a/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj b/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj index 6e60dcb..e42ca76 100644 --- a/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj +++ b/DiscordChatExporter.Gui/DiscordChatExporter.Gui.csproj @@ -57,6 +57,8 @@ + + diff --git a/DiscordChatExporter.Gui/ViewModels/Components/ChannelViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Components/ChannelViewModel.cs new file mode 100644 index 0000000..dd0230f --- /dev/null +++ b/DiscordChatExporter.Gui/ViewModels/Components/ChannelViewModel.cs @@ -0,0 +1,12 @@ +using DiscordChatExporter.Core.Models; +using Stylet; + +namespace DiscordChatExporter.Gui.ViewModels.Components +{ + public class ChannelViewModel : PropertyChangedBase + { + public Channel Model { get; set; } + + public string Category { get; set; } + } +} \ No newline at end of file diff --git a/DiscordChatExporter.Gui/ViewModels/Components/GuildViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Components/GuildViewModel.cs new file mode 100644 index 0000000..40949c0 --- /dev/null +++ b/DiscordChatExporter.Gui/ViewModels/Components/GuildViewModel.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using DiscordChatExporter.Core.Models; +using Stylet; + +namespace DiscordChatExporter.Gui.ViewModels.Components +{ + public class GuildViewModel : PropertyChangedBase + { + public Guild Model { get; set; } + + public IReadOnlyList Channels { get; set; } + } +} \ No newline at end of file diff --git a/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs b/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs index 707deca..8f3ecc8 100644 --- a/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs +++ b/DiscordChatExporter.Gui/ViewModels/Dialogs/ExportSetupViewModel.cs @@ -4,6 +4,7 @@ using System.Linq; using DiscordChatExporter.Core.Helpers; using DiscordChatExporter.Core.Models; using DiscordChatExporter.Core.Services; +using DiscordChatExporter.Gui.ViewModels.Components; using DiscordChatExporter.Gui.ViewModels.Framework; using Tyrrrz.Extensions; @@ -14,9 +15,9 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs private readonly DialogManager _dialogManager; private readonly SettingsService _settingsService; - public Guild Guild { get; set; } + public GuildViewModel Guild { get; set; } - public Channel Channel { get; set; } + public ChannelViewModel Channel { get; set; } public string FilePath { get; set; } @@ -59,7 +60,7 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs To = From; // Generate default file name - var defaultFileName = ExportHelper.GetDefaultExportFileName(SelectedFormat, Guild, Channel, From, To); + var defaultFileName = ExportHelper.GetDefaultExportFileName(SelectedFormat, Guild.Model, Channel.Model, From, To); // Prompt for output file path var ext = SelectedFormat.GetFileExtension(); diff --git a/DiscordChatExporter.Gui/ViewModels/Framework/IViewModelFactory.cs b/DiscordChatExporter.Gui/ViewModels/Framework/IViewModelFactory.cs index e741b6d..f02c801 100644 --- a/DiscordChatExporter.Gui/ViewModels/Framework/IViewModelFactory.cs +++ b/DiscordChatExporter.Gui/ViewModels/Framework/IViewModelFactory.cs @@ -1,10 +1,15 @@ -using DiscordChatExporter.Gui.ViewModels.Dialogs; +using DiscordChatExporter.Gui.ViewModels.Components; +using DiscordChatExporter.Gui.ViewModels.Dialogs; namespace DiscordChatExporter.Gui.ViewModels.Framework { // Used to instantiate new view models while making use of dependency injection public interface IViewModelFactory { + ChannelViewModel CreateChannelViewModel(); + + GuildViewModel CreateGuildViewModel(); + ExportSetupViewModel CreateExportSetupViewModel(); SettingsViewModel CreateSettingsViewModel(); diff --git a/DiscordChatExporter.Gui/ViewModels/RootViewModel.cs b/DiscordChatExporter.Gui/ViewModels/RootViewModel.cs index 50ec8eb..d802217 100644 --- a/DiscordChatExporter.Gui/ViewModels/RootViewModel.cs +++ b/DiscordChatExporter.Gui/ViewModels/RootViewModel.cs @@ -6,6 +6,7 @@ using System.Reflection; using DiscordChatExporter.Core.Exceptions; using DiscordChatExporter.Core.Models; using DiscordChatExporter.Core.Services; +using DiscordChatExporter.Gui.ViewModels.Components; using DiscordChatExporter.Gui.ViewModels.Framework; using MaterialDesignThemes.Wpf; using Stylet; @@ -22,9 +23,6 @@ namespace DiscordChatExporter.Gui.ViewModels private readonly DataService _dataService; private readonly ExportService _exportService; - private readonly Dictionary> _guildChannelsMap = - new Dictionary>(); - public SnackbarMessageQueue Notifications { get; } = new SnackbarMessageQueue(TimeSpan.FromSeconds(5)); public bool IsEnabled { get; private set; } = true; @@ -37,12 +35,9 @@ namespace DiscordChatExporter.Gui.ViewModels public string TokenValue { get; set; } - public IReadOnlyList AvailableGuilds { get; private set; } - - public Guild SelectedGuild { get; set; } + public IReadOnlyList AvailableGuilds { get; private set; } - public IReadOnlyList AvailableChannels => - SelectedGuild != null ? _guildChannelsMap[SelectedGuild] : Array.Empty(); + public GuildViewModel SelectedGuild { get; set; } public RootViewModel(IViewModelFactory viewModelFactory, DialogManager dialogManager, SettingsService settingsService, UpdateService updateService, DataService dataService, @@ -136,38 +131,92 @@ namespace DiscordChatExporter.Gui.ViewModels // Save token _settingsService.LastToken = token; - // Clear guild to channel map - _guildChannelsMap.Clear(); + // Prepare available guild list + var availableGuilds = new List(); - // Get DM channels + // Direct Messages { + // Get fake guild var guild = Guild.DirectMessages; + + // Get channels var channels = await _dataService.GetDirectMessageChannelsAsync(token); - // Order channels - channels = channels.OrderBy(c => c.Name).ToArray(); + // Create channel view models + var channelViewModels = new List(); + foreach (var channel in channels) + { + // Get fake category + var category = channel.Type == ChannelType.DirectTextChat ? "Private" : "Group"; + + // Create channel view model + var channelViewModel = _viewModelFactory.CreateChannelViewModel(); + channelViewModel.Model = channel; + channelViewModel.Category = category; - _guildChannelsMap[guild] = channels; + // Add to list + channelViewModels.Add(channelViewModel); + } + + // Create guild view model + var guildViewModel = _viewModelFactory.CreateGuildViewModel(); + guildViewModel.Model = guild; + guildViewModel.Channels = channelViewModels.OrderBy(c => c.Category) + .ThenBy(c => c.Model.Name) + .ToArray(); + + // Add to list + availableGuilds.Add(guildViewModel); } - // Get guild channels + // Guilds { + // Get guilds var guilds = await _dataService.GetUserGuildsAsync(token); foreach (var guild in guilds) { + // Get channels var channels = await _dataService.GetGuildChannelsAsync(token, guild.Id); - // Filter and order channels - channels = channels.Where(c => c.Type == ChannelType.GuildTextChat).OrderBy(c => c.Name).ToArray(); + // Get category channels + var categoryChannels = channels.Where(c => c.Type == ChannelType.Category).ToArray(); - _guildChannelsMap[guild] = channels; + // Get text channels + var textChannels = channels.Where(c => c.Type == ChannelType.GuildTextChat).ToArray(); + + // Create channel view models + var channelViewModels = new List(); + foreach (var channel in textChannels) + { + // Get category + var category = categoryChannels.FirstOrDefault(c => c.Id == channel.ParentId)?.Name ?? + ""; + + // Create channel view model + var channelViewModel = _viewModelFactory.CreateChannelViewModel(); + channelViewModel.Model = channel; + channelViewModel.Category = category; + + // Add to list + channelViewModels.Add(channelViewModel); + } + + // Create guild view model + var guildViewModel = _viewModelFactory.CreateGuildViewModel(); + guildViewModel.Model = guild; + guildViewModel.Channels = channelViewModels.OrderBy(c => c.Category) + .ThenBy(c => c.Model.Name) + .ToArray(); + + // Add to list + availableGuilds.Add(guildViewModel); } } - // Update available guilds - AvailableGuilds = _guildChannelsMap.Keys.ToArray(); + // Update available guild list + AvailableGuilds = availableGuilds; - // Select the first guild + // Pre-select first guild SelectedGuild = AvailableGuilds.FirstOrDefault(); } catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized) @@ -188,7 +237,7 @@ namespace DiscordChatExporter.Gui.ViewModels public bool CanExportChannel => IsEnabled; - public async void ExportChannel(Channel channel) + public async void ExportChannel(ChannelViewModel channel) { try { @@ -212,7 +261,7 @@ namespace DiscordChatExporter.Gui.ViewModels var progressHandler = new Progress(p => Progress = p); // Get chat log - var chatLog = await _dataService.GetChatLogAsync(token, dialog.Guild, dialog.Channel, + var chatLog = await _dataService.GetChatLogAsync(token, dialog.Guild.Model, dialog.Channel.Model, dialog.From, dialog.To, progressHandler); // Export diff --git a/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.xaml b/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.xaml index 5b417fa..5274090 100644 --- a/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.xaml +++ b/DiscordChatExporter.Gui/Views/Dialogs/ExportSetupView.xaml @@ -26,7 +26,7 @@ Width="32" Height="32"> - + @@ -39,13 +39,13 @@ TextTrimming="CharacterEllipsis"> + Text="{Binding Channel.Category, Mode=OneWay}" + ToolTip="{Binding Channel.Category, Mode=OneWay}" /> + Text="{Binding Channel.Model.Name, Mode=OneWay}" + ToolTip="{Binding Channel.Model.Name, Mode=OneWay}" /> diff --git a/DiscordChatExporter.Gui/Views/RootView.xaml b/DiscordChatExporter.Gui/Views/RootView.xaml index 361c982..0a8f9b2 100644 --- a/DiscordChatExporter.Gui/Views/RootView.xaml +++ b/DiscordChatExporter.Gui/Views/RootView.xaml @@ -210,7 +210,7 @@ Margin="-8" Background="Transparent" Cursor="Hand" - ToolTip="{Binding Name}"> + ToolTip="{Binding Model.Name}"> - + @@ -235,7 +235,7 @@ - + + FontSize="14"> + + + +