mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-06-07 01:51:18 -04:00
parent
ba5790e312
commit
b3e2dd3994
10 changed files with 130 additions and 41 deletions
|
@ -6,6 +6,8 @@
|
||||||
{
|
{
|
||||||
public string Id { get; }
|
public string Id { get; }
|
||||||
|
|
||||||
|
public string ParentId { get; }
|
||||||
|
|
||||||
public string GuildId { get; }
|
public string GuildId { get; }
|
||||||
|
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
@ -14,9 +16,10 @@
|
||||||
|
|
||||||
public ChannelType Type { 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;
|
Id = id;
|
||||||
|
ParentId = parentId;
|
||||||
GuildId = guildId;
|
GuildId = guildId;
|
||||||
Name = name;
|
Name = name;
|
||||||
Topic = topic;
|
Topic = topic;
|
||||||
|
@ -29,6 +32,6 @@
|
||||||
public partial class Channel
|
public partial class Channel
|
||||||
{
|
{
|
||||||
public static Channel CreateDeletedChannel(string id) =>
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -33,6 +33,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
{
|
{
|
||||||
// Get basic data
|
// Get basic data
|
||||||
var id = json["id"].Value<string>();
|
var id = json["id"].Value<string>();
|
||||||
|
var parentId = json["parent_id"]?.Value<string>();
|
||||||
var type = (ChannelType) json["type"].Value<int>();
|
var type = (ChannelType) json["type"].Value<int>();
|
||||||
var topic = json["topic"]?.Value<string>();
|
var topic = json["topic"]?.Value<string>();
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
if (name.IsBlank())
|
if (name.IsBlank())
|
||||||
name = json["recipients"].Select(ParseUser).Select(u => u.Name).JoinToString(", ");
|
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)
|
private Role ParseRole(JToken json)
|
||||||
|
|
|
@ -57,6 +57,8 @@
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Bootstrapper.cs" />
|
<Compile Include="Bootstrapper.cs" />
|
||||||
<Compile Include="Converters\ExportFormatToStringConverter.cs" />
|
<Compile Include="Converters\ExportFormatToStringConverter.cs" />
|
||||||
|
<Compile Include="ViewModels\Components\ChannelViewModel.cs" />
|
||||||
|
<Compile Include="ViewModels\Components\GuildViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Dialogs\ExportSetupViewModel.cs" />
|
<Compile Include="ViewModels\Dialogs\ExportSetupViewModel.cs" />
|
||||||
<Compile Include="ViewModels\Framework\DialogManager.cs" />
|
<Compile Include="ViewModels\Framework\DialogManager.cs" />
|
||||||
<Compile Include="ViewModels\Framework\DialogScreen.cs" />
|
<Compile Include="ViewModels\Framework\DialogScreen.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; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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<ChannelViewModel> Channels { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ using System.Linq;
|
||||||
using DiscordChatExporter.Core.Helpers;
|
using DiscordChatExporter.Core.Helpers;
|
||||||
using DiscordChatExporter.Core.Models;
|
using DiscordChatExporter.Core.Models;
|
||||||
using DiscordChatExporter.Core.Services;
|
using DiscordChatExporter.Core.Services;
|
||||||
|
using DiscordChatExporter.Gui.ViewModels.Components;
|
||||||
using DiscordChatExporter.Gui.ViewModels.Framework;
|
using DiscordChatExporter.Gui.ViewModels.Framework;
|
||||||
using Tyrrrz.Extensions;
|
using Tyrrrz.Extensions;
|
||||||
|
|
||||||
|
@ -14,9 +15,9 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
||||||
private readonly DialogManager _dialogManager;
|
private readonly DialogManager _dialogManager;
|
||||||
private readonly SettingsService _settingsService;
|
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; }
|
public string FilePath { get; set; }
|
||||||
|
|
||||||
|
@ -59,7 +60,7 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
||||||
To = From;
|
To = From;
|
||||||
|
|
||||||
// Generate default file name
|
// 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
|
// Prompt for output file path
|
||||||
var ext = SelectedFormat.GetFileExtension();
|
var ext = SelectedFormat.GetFileExtension();
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
using DiscordChatExporter.Gui.ViewModels.Components;
|
||||||
|
using DiscordChatExporter.Gui.ViewModels.Dialogs;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Gui.ViewModels.Framework
|
namespace DiscordChatExporter.Gui.ViewModels.Framework
|
||||||
{
|
{
|
||||||
// Used to instantiate new view models while making use of dependency injection
|
// Used to instantiate new view models while making use of dependency injection
|
||||||
public interface IViewModelFactory
|
public interface IViewModelFactory
|
||||||
{
|
{
|
||||||
|
ChannelViewModel CreateChannelViewModel();
|
||||||
|
|
||||||
|
GuildViewModel CreateGuildViewModel();
|
||||||
|
|
||||||
ExportSetupViewModel CreateExportSetupViewModel();
|
ExportSetupViewModel CreateExportSetupViewModel();
|
||||||
|
|
||||||
SettingsViewModel CreateSettingsViewModel();
|
SettingsViewModel CreateSettingsViewModel();
|
||||||
|
|
|
@ -6,6 +6,7 @@ using System.Reflection;
|
||||||
using DiscordChatExporter.Core.Exceptions;
|
using DiscordChatExporter.Core.Exceptions;
|
||||||
using DiscordChatExporter.Core.Models;
|
using DiscordChatExporter.Core.Models;
|
||||||
using DiscordChatExporter.Core.Services;
|
using DiscordChatExporter.Core.Services;
|
||||||
|
using DiscordChatExporter.Gui.ViewModels.Components;
|
||||||
using DiscordChatExporter.Gui.ViewModels.Framework;
|
using DiscordChatExporter.Gui.ViewModels.Framework;
|
||||||
using MaterialDesignThemes.Wpf;
|
using MaterialDesignThemes.Wpf;
|
||||||
using Stylet;
|
using Stylet;
|
||||||
|
@ -22,9 +23,6 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
private readonly DataService _dataService;
|
private readonly DataService _dataService;
|
||||||
private readonly ExportService _exportService;
|
private readonly ExportService _exportService;
|
||||||
|
|
||||||
private readonly Dictionary<Guild, IReadOnlyList<Channel>> _guildChannelsMap =
|
|
||||||
new Dictionary<Guild, IReadOnlyList<Channel>>();
|
|
||||||
|
|
||||||
public SnackbarMessageQueue Notifications { get; } = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
|
public SnackbarMessageQueue Notifications { get; } = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
public bool IsEnabled { get; private set; } = true;
|
public bool IsEnabled { get; private set; } = true;
|
||||||
|
@ -37,12 +35,9 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
|
|
||||||
public string TokenValue { get; set; }
|
public string TokenValue { get; set; }
|
||||||
|
|
||||||
public IReadOnlyList<Guild> AvailableGuilds { get; private set; }
|
public IReadOnlyList<GuildViewModel> AvailableGuilds { get; private set; }
|
||||||
|
|
||||||
public Guild SelectedGuild { get; set; }
|
public GuildViewModel SelectedGuild { get; set; }
|
||||||
|
|
||||||
public IReadOnlyList<Channel> AvailableChannels =>
|
|
||||||
SelectedGuild != null ? _guildChannelsMap[SelectedGuild] : Array.Empty<Channel>();
|
|
||||||
|
|
||||||
public RootViewModel(IViewModelFactory viewModelFactory, DialogManager dialogManager,
|
public RootViewModel(IViewModelFactory viewModelFactory, DialogManager dialogManager,
|
||||||
SettingsService settingsService, UpdateService updateService, DataService dataService,
|
SettingsService settingsService, UpdateService updateService, DataService dataService,
|
||||||
|
@ -136,38 +131,92 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
// Save token
|
// Save token
|
||||||
_settingsService.LastToken = token;
|
_settingsService.LastToken = token;
|
||||||
|
|
||||||
// Clear guild to channel map
|
// Prepare available guild list
|
||||||
_guildChannelsMap.Clear();
|
var availableGuilds = new List<GuildViewModel>();
|
||||||
|
|
||||||
// Get DM channels
|
// Direct Messages
|
||||||
{
|
{
|
||||||
|
// Get fake guild
|
||||||
var guild = Guild.DirectMessages;
|
var guild = Guild.DirectMessages;
|
||||||
|
|
||||||
|
// Get channels
|
||||||
var channels = await _dataService.GetDirectMessageChannelsAsync(token);
|
var channels = await _dataService.GetDirectMessageChannelsAsync(token);
|
||||||
|
|
||||||
// Order channels
|
// Create channel view models
|
||||||
channels = channels.OrderBy(c => c.Name).ToArray();
|
var channelViewModels = new List<ChannelViewModel>();
|
||||||
|
foreach (var channel in channels)
|
||||||
|
{
|
||||||
|
// Get fake category
|
||||||
|
var category = channel.Type == ChannelType.DirectTextChat ? "Private" : "Group";
|
||||||
|
|
||||||
_guildChannelsMap[guild] = channels;
|
// Create channel view model
|
||||||
|
var channelViewModel = _viewModelFactory.CreateChannelViewModel();
|
||||||
|
channelViewModel.Model = channel;
|
||||||
|
channelViewModel.Category = category;
|
||||||
|
|
||||||
|
// Add to list
|
||||||
|
channelViewModels.Add(channelViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get guild channels
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Guilds
|
||||||
{
|
{
|
||||||
|
// Get guilds
|
||||||
var guilds = await _dataService.GetUserGuildsAsync(token);
|
var guilds = await _dataService.GetUserGuildsAsync(token);
|
||||||
foreach (var guild in guilds)
|
foreach (var guild in guilds)
|
||||||
{
|
{
|
||||||
|
// Get channels
|
||||||
var channels = await _dataService.GetGuildChannelsAsync(token, guild.Id);
|
var channels = await _dataService.GetGuildChannelsAsync(token, guild.Id);
|
||||||
|
|
||||||
// Filter and order channels
|
// Get category channels
|
||||||
channels = channels.Where(c => c.Type == ChannelType.GuildTextChat).OrderBy(c => c.Name).ToArray();
|
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<ChannelViewModel>();
|
||||||
|
foreach (var channel in textChannels)
|
||||||
|
{
|
||||||
|
// Get category
|
||||||
|
var category = categoryChannels.FirstOrDefault(c => c.Id == channel.ParentId)?.Name ??
|
||||||
|
"<no category>";
|
||||||
|
|
||||||
|
// 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
|
// Update available guild list
|
||||||
AvailableGuilds = _guildChannelsMap.Keys.ToArray();
|
AvailableGuilds = availableGuilds;
|
||||||
|
|
||||||
// Select the first guild
|
// Pre-select first guild
|
||||||
SelectedGuild = AvailableGuilds.FirstOrDefault();
|
SelectedGuild = AvailableGuilds.FirstOrDefault();
|
||||||
}
|
}
|
||||||
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
|
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
|
||||||
|
@ -188,7 +237,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
|
|
||||||
public bool CanExportChannel => IsEnabled;
|
public bool CanExportChannel => IsEnabled;
|
||||||
|
|
||||||
public async void ExportChannel(Channel channel)
|
public async void ExportChannel(ChannelViewModel channel)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -212,7 +261,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
var progressHandler = new Progress<double>(p => Progress = p);
|
var progressHandler = new Progress<double>(p => Progress = p);
|
||||||
|
|
||||||
// Get chat log
|
// 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);
|
dialog.From, dialog.To, progressHandler);
|
||||||
|
|
||||||
// Export
|
// Export
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
Width="32"
|
Width="32"
|
||||||
Height="32">
|
Height="32">
|
||||||
<Ellipse.Fill>
|
<Ellipse.Fill>
|
||||||
<ImageBrush ImageSource="{Binding Guild.IconUrl}" />
|
<ImageBrush ImageSource="{Binding Guild.Model.IconUrl}" />
|
||||||
</Ellipse.Fill>
|
</Ellipse.Fill>
|
||||||
</Ellipse>
|
</Ellipse>
|
||||||
|
|
||||||
|
@ -39,13 +39,13 @@
|
||||||
TextTrimming="CharacterEllipsis">
|
TextTrimming="CharacterEllipsis">
|
||||||
<Run
|
<Run
|
||||||
Foreground="{DynamicResource SecondaryTextBrush}"
|
Foreground="{DynamicResource SecondaryTextBrush}"
|
||||||
Text="{Binding Guild.Name, Mode=OneWay}"
|
Text="{Binding Channel.Category, Mode=OneWay}"
|
||||||
ToolTip="{Binding Guild.Name, Mode=OneWay}" />
|
ToolTip="{Binding Channel.Category, Mode=OneWay}" />
|
||||||
<Run Text="/" />
|
<Run Text="/" />
|
||||||
<Run
|
<Run
|
||||||
Foreground="{DynamicResource PrimaryTextBrush}"
|
Foreground="{DynamicResource PrimaryTextBrush}"
|
||||||
Text="{Binding Channel.Name, Mode=OneWay}"
|
Text="{Binding Channel.Model.Name, Mode=OneWay}"
|
||||||
ToolTip="{Binding Channel.Name, Mode=OneWay}" />
|
ToolTip="{Binding Channel.Model.Name, Mode=OneWay}" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
|
|
@ -210,7 +210,7 @@
|
||||||
Margin="-8"
|
Margin="-8"
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
Cursor="Hand"
|
Cursor="Hand"
|
||||||
ToolTip="{Binding Name}">
|
ToolTip="{Binding Model.Name}">
|
||||||
<!-- Guild icon placeholder -->
|
<!-- Guild icon placeholder -->
|
||||||
<Ellipse
|
<Ellipse
|
||||||
Width="48"
|
Width="48"
|
||||||
|
@ -224,7 +224,7 @@
|
||||||
Height="48"
|
Height="48"
|
||||||
Margin="12,4,12,4">
|
Margin="12,4,12,4">
|
||||||
<Ellipse.Fill>
|
<Ellipse.Fill>
|
||||||
<ImageBrush ImageSource="{Binding IconUrl}" />
|
<ImageBrush ImageSource="{Binding Model.IconUrl}" />
|
||||||
</Ellipse.Fill>
|
</Ellipse.Fill>
|
||||||
</Ellipse>
|
</Ellipse>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -235,7 +235,7 @@
|
||||||
|
|
||||||
<!-- Channels -->
|
<!-- Channels -->
|
||||||
<Border Grid.Column="1">
|
<Border Grid.Column="1">
|
||||||
<ListBox HorizontalContentAlignment="Stretch" ItemsSource="{Binding AvailableChannels}">
|
<ListBox HorizontalContentAlignment="Stretch" ItemsSource="{Binding SelectedGuild.Channels}">
|
||||||
<ListBox.ItemTemplate>
|
<ListBox.ItemTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel
|
<StackPanel
|
||||||
|
@ -256,8 +256,11 @@
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="3,8,8,8"
|
Margin="3,8,8,8"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
FontSize="14"
|
FontSize="14">
|
||||||
Text="{Binding Name}" />
|
<Run Text="{Binding Category, Mode=OneWay}" Foreground="{DynamicResource SecondaryTextBrush}" />
|
||||||
|
<Run Text="/" Foreground="{DynamicResource SecondaryTextBrush}" />
|
||||||
|
<Run Text="{Binding Model.Name, Mode=OneWay}" Foreground="{DynamicResource PrimaryTextBrush}" />
|
||||||
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListBox.ItemTemplate>
|
</ListBox.ItemTemplate>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue