mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-23 19:26:57 -04:00
More refactoring
This commit is contained in:
parent
b2a48d338a
commit
9d0d7cd5dd
8 changed files with 197 additions and 127 deletions
|
@ -16,7 +16,7 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
Value = value;
|
Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthenticationHeaderValue GetAuthenticationHeader() => Type == AuthTokenType.User
|
public AuthenticationHeaderValue GetAuthorizationHeader() => Type == AuthTokenType.User
|
||||||
? new AuthenticationHeaderValue(Value)
|
? new AuthenticationHeaderValue(Value)
|
||||||
: new AuthenticationHeaderValue("Bot", Value);
|
: new AuthenticationHeaderValue("Bot", Value);
|
||||||
|
|
||||||
|
|
|
@ -29,10 +29,12 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
{
|
{
|
||||||
var userId = json.GetProperty("user").Pipe(ParseId);
|
var userId = json.GetProperty("user").Pipe(ParseId);
|
||||||
var nick = json.GetPropertyOrNull("nick")?.GetString();
|
var nick = json.GetPropertyOrNull("nick")?.GetString();
|
||||||
var roles = json.GetPropertyOrNull("roles")?.EnumerateArray().Select(j => j.GetString()).ToArray() ??
|
|
||||||
|
var roleIds =
|
||||||
|
json.GetPropertyOrNull("roles")?.EnumerateArray().Select(j => j.GetString()).ToArray() ??
|
||||||
Array.Empty<string>();
|
Array.Empty<string>();
|
||||||
|
|
||||||
return new Member(userId, nick, roles);
|
return new Member(userId, nick, roleIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Guild ParseGuild(JsonElement json)
|
private Guild ParseGuild(JsonElement json)
|
||||||
|
@ -40,7 +42,9 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
var id = ParseId(json);
|
var id = ParseId(json);
|
||||||
var name = json.GetProperty("name").GetString();
|
var name = json.GetProperty("name").GetString();
|
||||||
var iconHash = json.GetProperty("icon").GetString();
|
var iconHash = json.GetProperty("icon").GetString();
|
||||||
var roles = json.GetPropertyOrNull("roles")?.EnumerateArray().Select(ParseRole).ToArray() ??
|
|
||||||
|
var roles =
|
||||||
|
json.GetPropertyOrNull("roles")?.EnumerateArray().Select(ParseRole).ToArray() ??
|
||||||
Array.Empty<Role>();
|
Array.Empty<Role>();
|
||||||
|
|
||||||
return new Guild(id, name, iconHash, roles);
|
return new Guild(id, name, iconHash, roles);
|
||||||
|
@ -53,7 +57,8 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
var type = (ChannelType) json.GetProperty("type").GetInt32();
|
var type = (ChannelType) json.GetProperty("type").GetInt32();
|
||||||
var topic = json.GetPropertyOrNull("topic")?.GetString();
|
var topic = json.GetPropertyOrNull("topic")?.GetString();
|
||||||
|
|
||||||
var guildId = json.GetPropertyOrNull("guild_id")?.GetString() ??
|
var guildId =
|
||||||
|
json.GetPropertyOrNull("guild_id")?.GetString() ??
|
||||||
Guild.DirectMessages.Id;
|
Guild.DirectMessages.Id;
|
||||||
|
|
||||||
var name =
|
var name =
|
||||||
|
@ -134,10 +139,22 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
var image = json.GetPropertyOrNull("image")?.Pipe(ParseEmbedImage);
|
var image = json.GetPropertyOrNull("image")?.Pipe(ParseEmbedImage);
|
||||||
var footer = json.GetPropertyOrNull("footer")?.Pipe(ParseEmbedFooter);
|
var footer = json.GetPropertyOrNull("footer")?.Pipe(ParseEmbedFooter);
|
||||||
|
|
||||||
var fields = json.GetPropertyOrNull("fields")?.EnumerateArray().Select(ParseEmbedField).ToArray() ??
|
var fields =
|
||||||
|
json.GetPropertyOrNull("fields")?.EnumerateArray().Select(ParseEmbedField).ToArray() ??
|
||||||
Array.Empty<EmbedField>();
|
Array.Empty<EmbedField>();
|
||||||
|
|
||||||
return new Embed(title, url, timestamp, color, author, description, fields, thumbnail, image, footer);
|
return new Embed(
|
||||||
|
title,
|
||||||
|
url,
|
||||||
|
timestamp,
|
||||||
|
color,
|
||||||
|
author,
|
||||||
|
description,
|
||||||
|
fields,
|
||||||
|
thumbnail,
|
||||||
|
image,
|
||||||
|
footer
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Emoji ParseEmoji(JsonElement json)
|
private Emoji ParseEmoji(JsonElement json)
|
||||||
|
@ -180,20 +197,36 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
|
|
||||||
var author = json.GetProperty("author").Pipe(ParseUser);
|
var author = json.GetProperty("author").Pipe(ParseUser);
|
||||||
|
|
||||||
var attachments = json.GetPropertyOrNull("attachments")?.EnumerateArray().Select(ParseAttachment).ToArray() ??
|
var attachments =
|
||||||
|
json.GetPropertyOrNull("attachments")?.EnumerateArray().Select(ParseAttachment).ToArray() ??
|
||||||
Array.Empty<Attachment>();
|
Array.Empty<Attachment>();
|
||||||
|
|
||||||
var embeds = json.GetPropertyOrNull("embeds")?.EnumerateArray().Select(ParseEmbed).ToArray() ??
|
var embeds =
|
||||||
|
json.GetPropertyOrNull("embeds")?.EnumerateArray().Select(ParseEmbed).ToArray() ??
|
||||||
Array.Empty<Embed>();
|
Array.Empty<Embed>();
|
||||||
|
|
||||||
var reactions = json.GetPropertyOrNull("reactions")?.EnumerateArray().Select(ParseReaction).ToArray() ??
|
var reactions =
|
||||||
|
json.GetPropertyOrNull("reactions")?.EnumerateArray().Select(ParseReaction).ToArray() ??
|
||||||
Array.Empty<Reaction>();
|
Array.Empty<Reaction>();
|
||||||
|
|
||||||
var mentionedUsers = json.GetPropertyOrNull("mentions")?.EnumerateArray().Select(ParseUser).ToArray() ??
|
var mentionedUsers =
|
||||||
|
json.GetPropertyOrNull("mentions")?.EnumerateArray().Select(ParseUser).ToArray() ??
|
||||||
Array.Empty<User>();
|
Array.Empty<User>();
|
||||||
|
|
||||||
return new Message(id, channelId, type, author, timestamp, editedTimestamp, isPinned, content, attachments, embeds,
|
return new Message(
|
||||||
reactions, mentionedUsers);
|
id,
|
||||||
|
channelId,
|
||||||
|
type,
|
||||||
|
author,
|
||||||
|
timestamp,
|
||||||
|
editedTimestamp,
|
||||||
|
isPinned,
|
||||||
|
content,
|
||||||
|
attachments,
|
||||||
|
embeds,
|
||||||
|
reactions,
|
||||||
|
mentionedUsers
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,6 +18,8 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
private readonly IAsyncPolicy<HttpResponseMessage> _httpRequestPolicy;
|
private readonly IAsyncPolicy<HttpResponseMessage> _httpRequestPolicy;
|
||||||
|
|
||||||
|
private readonly Uri _baseUri = new Uri("https://discordapp.com/api/v6/", UriKind.Absolute);
|
||||||
|
|
||||||
public DiscordClient(AuthToken token, HttpClient httpClient)
|
public DiscordClient(AuthToken token, HttpClient httpClient)
|
||||||
{
|
{
|
||||||
_token = token;
|
_token = token;
|
||||||
|
@ -51,10 +53,8 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
{
|
{
|
||||||
using var response = await _httpRequestPolicy.ExecuteAsync(async () =>
|
using var response = await _httpRequestPolicy.ExecuteAsync(async () =>
|
||||||
{
|
{
|
||||||
var uri = new Uri(new Uri("https://discordapp.com/api/v6"), url);
|
using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_baseUri, url));
|
||||||
|
request.Headers.Authorization = _token.GetAuthorizationHeader();
|
||||||
using var request = new HttpRequestMessage(HttpMethod.Get, uri);
|
|
||||||
request.Headers.Authorization = _token.GetAuthenticationHeader();
|
|
||||||
|
|
||||||
return await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
return await _httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||||
});
|
});
|
||||||
|
@ -113,11 +113,13 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var route = "users/@me/guilds?limit=100";
|
var url = new UrlBuilder()
|
||||||
if (!string.IsNullOrWhiteSpace(afterId))
|
.SetPath("users/@me/guilds")
|
||||||
route += $"&after={afterId}";
|
.SetQueryParameter("limit", "100")
|
||||||
|
.SetQueryParameterIfNotNullOrWhiteSpace("after", afterId)
|
||||||
|
.Build();
|
||||||
|
|
||||||
var response = await GetApiResponseAsync(route);
|
var response = await GetApiResponseAsync(url);
|
||||||
|
|
||||||
var isEmpty = true;
|
var isEmpty = true;
|
||||||
|
|
||||||
|
@ -147,7 +149,7 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
|
|
||||||
public async Task<IReadOnlyList<Channel>> GetGuildChannelsAsync(string guildId)
|
public async Task<IReadOnlyList<Channel>> GetGuildChannelsAsync(string guildId)
|
||||||
{
|
{
|
||||||
// Special case for direct messages pseudo-guild
|
// Direct messages pseudo-guild
|
||||||
if (guildId == Guild.DirectMessages.Id)
|
if (guildId == Guild.DirectMessages.Id)
|
||||||
return Array.Empty<Channel>();
|
return Array.Empty<Channel>();
|
||||||
|
|
||||||
|
@ -159,38 +161,42 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
|
|
||||||
private async Task<Message> GetLastMessageAsync(string channelId, DateTimeOffset? before = null)
|
private async Task<Message> GetLastMessageAsync(string channelId, DateTimeOffset? before = null)
|
||||||
{
|
{
|
||||||
var route = $"channels/{channelId}/messages?limit=1";
|
var url = new UrlBuilder()
|
||||||
if (before != null)
|
.SetPath($"channels/{channelId}/messages")
|
||||||
route += $"&before={before.Value.ToSnowflake()}";
|
.SetQueryParameter("limit", "1")
|
||||||
|
.SetQueryParameterIfNotNullOrWhiteSpace("before", before?.ToSnowflake())
|
||||||
|
.Build();
|
||||||
|
|
||||||
var response = await GetApiResponseAsync(route);
|
var response = await GetApiResponseAsync(url);
|
||||||
|
|
||||||
return response.EnumerateArray().Select(ParseMessage).FirstOrDefault();
|
return response.EnumerateArray().Select(ParseMessage).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async IAsyncEnumerable<Message> GetMessagesAsync(string channelId,
|
public async IAsyncEnumerable<Message> GetMessagesAsync(
|
||||||
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double>? progress = null)
|
string channelId,
|
||||||
|
DateTimeOffset? after = null,
|
||||||
|
DateTimeOffset? before = null,
|
||||||
|
IProgress<double>? progress = null)
|
||||||
{
|
{
|
||||||
// Get the last message
|
|
||||||
var lastMessage = await GetLastMessageAsync(channelId, before);
|
var lastMessage = await GetLastMessageAsync(channelId, before);
|
||||||
|
|
||||||
// If the last message doesn't exist or it's outside of range - return
|
// If the last message doesn't exist or it's outside of range - return
|
||||||
if (lastMessage == null || lastMessage.Timestamp < after)
|
if (lastMessage == null || lastMessage.Timestamp < after)
|
||||||
{
|
|
||||||
progress?.Report(1);
|
|
||||||
yield break;
|
yield break;
|
||||||
}
|
|
||||||
|
|
||||||
// Get other messages
|
|
||||||
var firstMessage = default(Message);
|
var firstMessage = default(Message);
|
||||||
var afterId = after?.ToSnowflake() ?? "0";
|
var afterId = after?.ToSnowflake() ?? "0";
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// Get message batch
|
var url = new UrlBuilder()
|
||||||
var route = $"channels/{channelId}/messages?limit=100&after={afterId}";
|
.SetPath($"channels/{channelId}/messages")
|
||||||
var response = await GetApiResponseAsync(route);
|
.SetQueryParameter("limit", "100")
|
||||||
|
.SetQueryParameter("after", afterId)
|
||||||
|
.Build();
|
||||||
|
|
||||||
|
var response = await GetApiResponseAsync(url);
|
||||||
|
|
||||||
// Parse
|
|
||||||
var messages = response
|
var messages = response
|
||||||
.EnumerateArray()
|
.EnumerateArray()
|
||||||
.Select(ParseMessage)
|
.Select(ParseMessage)
|
||||||
|
@ -201,33 +207,28 @@ namespace DiscordChatExporter.Domain.Discord
|
||||||
if (!messages.Any())
|
if (!messages.Any())
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Trim messages to range (until last message)
|
foreach (var message in messages)
|
||||||
var messagesInRange = messages
|
|
||||||
.TakeWhile(m => m.Id != lastMessage.Id && m.Timestamp < lastMessage.Timestamp)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
// Yield messages
|
|
||||||
foreach (var message in messagesInRange)
|
|
||||||
{
|
{
|
||||||
// Set first message if it's not set
|
|
||||||
firstMessage ??= message;
|
firstMessage ??= message;
|
||||||
|
|
||||||
// Report progress (based on the time range of parsed messages compared to total)
|
// Ensure messages are in range (take into account that last message could have been deleted)
|
||||||
progress?.Report((message.Timestamp - firstMessage.Timestamp).TotalSeconds /
|
if (message.Timestamp > lastMessage.Timestamp)
|
||||||
(lastMessage.Timestamp - firstMessage.Timestamp).TotalSeconds);
|
yield break;
|
||||||
|
|
||||||
|
// Report progress based on the duration of parsed messages divided by total
|
||||||
|
progress?.Report(
|
||||||
|
(message.Timestamp - firstMessage.Timestamp) /
|
||||||
|
(lastMessage.Timestamp - firstMessage.Timestamp)
|
||||||
|
);
|
||||||
|
|
||||||
yield return message;
|
yield return message;
|
||||||
afterId = message.Id;
|
afterId = message.Id;
|
||||||
}
|
|
||||||
|
|
||||||
// Break if messages were trimmed (which means the last message was encountered)
|
// Yielded last message - break loop
|
||||||
if (messagesInRange.Length != messages.Length)
|
if (message.Id == lastMessage.Id)
|
||||||
break;
|
yield break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yield last message
|
|
||||||
yield return lastMessage;
|
|
||||||
progress?.Report(1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
50
DiscordChatExporter.Domain/Discord/UrlBuilder.cs
Normal file
50
DiscordChatExporter.Domain/Discord/UrlBuilder.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Domain.Discord
|
||||||
|
{
|
||||||
|
internal class UrlBuilder
|
||||||
|
{
|
||||||
|
private string _path = "";
|
||||||
|
|
||||||
|
private readonly Dictionary<string, string?> _queryParameters =
|
||||||
|
new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
public UrlBuilder SetPath(string path)
|
||||||
|
{
|
||||||
|
_path = path;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UrlBuilder SetQueryParameter(string key, string? value)
|
||||||
|
{
|
||||||
|
var keyEncoded = WebUtility.UrlEncode(key);
|
||||||
|
var valueEncoded = WebUtility.UrlEncode(value);
|
||||||
|
_queryParameters[keyEncoded] = valueEncoded;
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UrlBuilder SetQueryParameterIfNotNullOrWhiteSpace(string key, string? value) =>
|
||||||
|
!string.IsNullOrWhiteSpace(value)
|
||||||
|
? SetQueryParameter(key, value)
|
||||||
|
: this;
|
||||||
|
|
||||||
|
public string Build()
|
||||||
|
{
|
||||||
|
var buffer = new StringBuilder();
|
||||||
|
|
||||||
|
buffer.Append(_path);
|
||||||
|
|
||||||
|
if (_queryParameters.Any())
|
||||||
|
buffer.Append('?');
|
||||||
|
|
||||||
|
buffer.AppendJoin('&', _queryParameters.Select(kvp => $"{kvp.Key}={kvp.Value}"));
|
||||||
|
|
||||||
|
return buffer.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,8 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
|
|
||||||
public ChannelExporter(DiscordClient discord) => _discord = discord;
|
public ChannelExporter(DiscordClient discord) => _discord = discord;
|
||||||
|
|
||||||
|
public ChannelExporter(AuthToken token) : this(new DiscordClient(token)) {}
|
||||||
|
|
||||||
public async Task ExportAsync(
|
public async Task ExportAsync(
|
||||||
Guild guild,
|
Guild guild,
|
||||||
Channel channel,
|
Channel channel,
|
||||||
|
@ -28,13 +30,12 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
DateTimeOffset? before = null,
|
DateTimeOffset? before = null,
|
||||||
IProgress<double>? progress = null)
|
IProgress<double>? progress = null)
|
||||||
{
|
{
|
||||||
// Get base file path from output path
|
|
||||||
var baseFilePath = GetFilePathFromOutputPath(guild, channel, outputPath, format, after, before);
|
var baseFilePath = GetFilePathFromOutputPath(guild, channel, outputPath, format, after, before);
|
||||||
|
|
||||||
// Create options
|
// Options
|
||||||
var options = new ExportOptions(baseFilePath, format, partitionLimit);
|
var options = new ExportOptions(baseFilePath, format, partitionLimit);
|
||||||
|
|
||||||
// Create context
|
// Context
|
||||||
var mentionableUsers = new HashSet<User>(IdBasedEqualityComparer.Instance);
|
var mentionableUsers = new HashSet<User>(IdBasedEqualityComparer.Instance);
|
||||||
var mentionableChannels = await _discord.GetGuildChannelsAsync(guild.Id);
|
var mentionableChannels = await _discord.GetGuildChannelsAsync(guild.Id);
|
||||||
var mentionableRoles = guild.Roles;
|
var mentionableRoles = guild.Roles;
|
||||||
|
@ -44,11 +45,9 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
mentionableUsers, mentionableChannels, mentionableRoles
|
mentionableUsers, mentionableChannels, mentionableRoles
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create renderer
|
await using var messageExporter = new MessageExporter(options, context);
|
||||||
await using var renderer = new MessageExporter(options, context);
|
|
||||||
|
|
||||||
// Render messages
|
var exportedAnything = false;
|
||||||
var renderedAnything = false;
|
|
||||||
await foreach (var message in _discord.GetMessagesAsync(channel.Id, after, before, progress))
|
await foreach (var message in _discord.GetMessagesAsync(channel.Id, after, before, progress))
|
||||||
{
|
{
|
||||||
// Add encountered users to the list of mentionable users
|
// Add encountered users to the list of mentionable users
|
||||||
|
@ -68,12 +67,12 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render message
|
// Render message
|
||||||
await renderer.RenderMessageAsync(message);
|
await messageExporter.ExportMessageAsync(message);
|
||||||
renderedAnything = true;
|
exportedAnything = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Throw if no messages were rendered
|
// Throw if no messages were exported
|
||||||
if (!renderedAnything)
|
if (!exportedAnything)
|
||||||
throw DiscordChatExporterException.ChannelEmpty(channel);
|
throw DiscordChatExporterException.ChannelEmpty(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ namespace DiscordChatExporter.Domain.Exporting
|
||||||
return _writer = writer;
|
return _writer = writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RenderMessageAsync(Message message)
|
public async Task ExportMessageAsync(Message message)
|
||||||
{
|
{
|
||||||
var writer = await GetWriterAsync();
|
var writer = await GetWriterAsync();
|
||||||
await writer.WriteMessageAsync(message);
|
await writer.WriteMessageAsync(message);
|
||||||
|
|
|
@ -31,6 +31,16 @@
|
||||||
<xs:documentation>Used to control if equality checks should use the static Equals method resolved from the base class.</xs:documentation>
|
<xs:documentation>Used to control if equality checks should use the static Equals method resolved from the base class.</xs:documentation>
|
||||||
</xs:annotation>
|
</xs:annotation>
|
||||||
</xs:attribute>
|
</xs:attribute>
|
||||||
|
<xs:attribute name="SuppressWarnings" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to turn off build warnings from this weaver.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
|
<xs:attribute name="SuppressOnPropertyNameChangedWarning" type="xs:boolean">
|
||||||
|
<xs:annotation>
|
||||||
|
<xs:documentation>Used to turn off build warnings about mismatched On_PropertyName_Changed methods.</xs:documentation>
|
||||||
|
</xs:annotation>
|
||||||
|
</xs:attribute>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
</xs:all>
|
</xs:all>
|
||||||
|
|
|
@ -54,35 +54,28 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
_updateService = updateService;
|
_updateService = updateService;
|
||||||
|
|
||||||
// Set title
|
|
||||||
DisplayName = $"{App.Name} v{App.VersionString}";
|
DisplayName = $"{App.Name} v{App.VersionString}";
|
||||||
|
|
||||||
// Update busy state when progress manager changes
|
// Update busy state when progress manager changes
|
||||||
ProgressManager.Bind(o => o.IsActive, (sender, args) => IsBusy = ProgressManager.IsActive);
|
ProgressManager.Bind(o => o.IsActive,
|
||||||
|
(sender, args) => IsBusy = ProgressManager.IsActive);
|
||||||
ProgressManager.Bind(o => o.IsActive,
|
ProgressManager.Bind(o => o.IsActive,
|
||||||
(sender, args) => IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress.IsEither(0, 1));
|
(sender, args) => IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress.IsEither(0, 1));
|
||||||
ProgressManager.Bind(o => o.Progress,
|
ProgressManager.Bind(o => o.Progress,
|
||||||
(sender, args) => IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress.IsEither(0, 1));
|
(sender, args) => IsProgressIndeterminate = ProgressManager.IsActive && ProgressManager.Progress.IsEither(0, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
private DiscordClient GetDiscordClient(AuthToken token) => new DiscordClient(token);
|
|
||||||
|
|
||||||
private ChannelExporter GetChannelExporter(AuthToken token) => new ChannelExporter(GetDiscordClient(token));
|
|
||||||
|
|
||||||
private async Task HandleAutoUpdateAsync()
|
private async Task HandleAutoUpdateAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Check for updates
|
|
||||||
var updateVersion = await _updateService.CheckForUpdatesAsync();
|
var updateVersion = await _updateService.CheckForUpdatesAsync();
|
||||||
if (updateVersion == null)
|
if (updateVersion == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Notify user of an update and prepare it
|
|
||||||
Notifications.Enqueue($"Downloading update to {App.Name} v{updateVersion}...");
|
Notifications.Enqueue($"Downloading update to {App.Name} v{updateVersion}...");
|
||||||
await _updateService.PrepareUpdateAsync(updateVersion);
|
await _updateService.PrepareUpdateAsync(updateVersion);
|
||||||
|
|
||||||
// Prompt user to install update (otherwise install it when application exits)
|
|
||||||
Notifications.Enqueue(
|
Notifications.Enqueue(
|
||||||
"Update has been downloaded and will be installed when you exit",
|
"Update has been downloaded and will be installed when you exit",
|
||||||
"INSTALL NOW", () =>
|
"INSTALL NOW", () =>
|
||||||
|
@ -102,17 +95,14 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
{
|
{
|
||||||
base.OnViewLoaded();
|
base.OnViewLoaded();
|
||||||
|
|
||||||
// Load settings
|
|
||||||
_settingsService.Load();
|
_settingsService.Load();
|
||||||
|
|
||||||
// Get last token
|
|
||||||
if (_settingsService.LastToken != null)
|
if (_settingsService.LastToken != null)
|
||||||
{
|
{
|
||||||
IsBotToken = _settingsService.LastToken.Type == AuthTokenType.Bot;
|
IsBotToken = _settingsService.LastToken.Type == AuthTokenType.Bot;
|
||||||
TokenValue = _settingsService.LastToken.Value;
|
TokenValue = _settingsService.LastToken.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check and prepare update
|
|
||||||
await HandleAutoUpdateAsync();
|
await HandleAutoUpdateAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,49 +110,44 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
{
|
{
|
||||||
base.OnClose();
|
base.OnClose();
|
||||||
|
|
||||||
// Save settings
|
|
||||||
_settingsService.Save();
|
_settingsService.Save();
|
||||||
|
|
||||||
// Finalize updates if necessary
|
|
||||||
_updateService.FinalizeUpdate(false);
|
_updateService.FinalizeUpdate(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void ShowSettings()
|
public async void ShowSettings()
|
||||||
{
|
{
|
||||||
// Create dialog
|
|
||||||
var dialog = _viewModelFactory.CreateSettingsViewModel();
|
var dialog = _viewModelFactory.CreateSettingsViewModel();
|
||||||
|
|
||||||
// Show dialog
|
|
||||||
await _dialogManager.ShowDialogAsync(dialog);
|
await _dialogManager.ShowDialogAsync(dialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanPopulateGuildsAndChannels => !IsBusy && !string.IsNullOrWhiteSpace(TokenValue);
|
public bool CanPopulateGuildsAndChannels =>
|
||||||
|
!IsBusy && !string.IsNullOrWhiteSpace(TokenValue);
|
||||||
|
|
||||||
public async void PopulateGuildsAndChannels()
|
public async void PopulateGuildsAndChannels()
|
||||||
{
|
{
|
||||||
// Create progress operation
|
using var operation = ProgressManager.CreateOperation();
|
||||||
var operation = ProgressManager.CreateOperation();
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Sanitize token
|
var tokenValue = TokenValue?.Trim('"');
|
||||||
TokenValue = TokenValue!.Trim('"');
|
if (string.IsNullOrWhiteSpace(tokenValue))
|
||||||
|
return;
|
||||||
|
|
||||||
// Create token
|
|
||||||
var token = new AuthToken(
|
var token = new AuthToken(
|
||||||
IsBotToken ? AuthTokenType.Bot : AuthTokenType.User,
|
IsBotToken ? AuthTokenType.Bot : AuthTokenType.User,
|
||||||
TokenValue);
|
tokenValue
|
||||||
|
);
|
||||||
|
|
||||||
// Save token
|
|
||||||
_settingsService.LastToken = token;
|
_settingsService.LastToken = token;
|
||||||
|
|
||||||
// Prepare available guild list
|
var discord = new DiscordClient(token);
|
||||||
|
|
||||||
var availableGuilds = new List<GuildViewModel>();
|
var availableGuilds = new List<GuildViewModel>();
|
||||||
|
|
||||||
// Get direct messages
|
// Direct messages
|
||||||
{
|
{
|
||||||
var guild = Guild.DirectMessages;
|
var guild = Guild.DirectMessages;
|
||||||
var channels = await GetDiscordClient(token).GetDirectMessageChannelsAsync();
|
var channels = await discord.GetDirectMessageChannelsAsync();
|
||||||
|
|
||||||
// Create channel view models
|
// Create channel view models
|
||||||
var channelViewModels = new List<ChannelViewModel>();
|
var channelViewModels = new List<ChannelViewModel>();
|
||||||
|
@ -188,11 +173,11 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
availableGuilds.Add(guildViewModel);
|
availableGuilds.Add(guildViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get guilds
|
// Guilds
|
||||||
var guilds = await GetDiscordClient(token).GetUserGuildsAsync();
|
var guilds = await discord.GetUserGuildsAsync();
|
||||||
foreach (var guild in guilds)
|
foreach (var guild in guilds)
|
||||||
{
|
{
|
||||||
var channels = await GetDiscordClient(token).GetGuildChannelsAsync(guild.Id);
|
var channels = await discord.GetGuildChannelsAsync(guild.Id);
|
||||||
var categoryChannels = channels.Where(c => c.Type == ChannelType.GuildCategory).ToArray();
|
var categoryChannels = channels.Where(c => c.Type == ChannelType.GuildCategory).ToArray();
|
||||||
var exportableChannels = channels.Where(c => c.IsTextChannel).ToArray();
|
var exportableChannels = channels.Where(c => c.IsTextChannel).ToArray();
|
||||||
|
|
||||||
|
@ -220,40 +205,32 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
availableGuilds.Add(guildViewModel);
|
availableGuilds.Add(guildViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update available guild list
|
|
||||||
AvailableGuilds = availableGuilds;
|
AvailableGuilds = availableGuilds;
|
||||||
|
|
||||||
// Pre-select first guild
|
|
||||||
SelectedGuild = AvailableGuilds.FirstOrDefault();
|
SelectedGuild = AvailableGuilds.FirstOrDefault();
|
||||||
}
|
}
|
||||||
catch (DiscordChatExporterException ex) when (!ex.IsCritical)
|
catch (DiscordChatExporterException ex) when (!ex.IsCritical)
|
||||||
{
|
{
|
||||||
Notifications.Enqueue(ex.Message);
|
Notifications.Enqueue(ex.Message.TrimEnd('.'));
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
operation.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanExportChannels => !IsBusy && SelectedGuild != null && SelectedChannels != null && SelectedChannels.Any();
|
public bool CanExportChannels =>
|
||||||
|
!IsBusy && SelectedGuild != null && SelectedChannels != null && SelectedChannels.Any();
|
||||||
|
|
||||||
public async void ExportChannels()
|
public async void ExportChannels()
|
||||||
{
|
{
|
||||||
// Get last used token
|
var token = _settingsService.LastToken;
|
||||||
var token = _settingsService.LastToken!;
|
if (token == null || SelectedGuild == null || SelectedChannels == null || !SelectedChannels.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
// Create dialog
|
var dialog = _viewModelFactory.CreateExportSetupViewModel(SelectedGuild, SelectedChannels);
|
||||||
var dialog = _viewModelFactory.CreateExportSetupViewModel(SelectedGuild!, SelectedChannels!);
|
|
||||||
|
|
||||||
// Show dialog, if canceled - return
|
|
||||||
if (await _dialogManager.ShowDialogAsync(dialog) != true)
|
if (await _dialogManager.ShowDialogAsync(dialog) != true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Create a progress operation for each channel to export
|
var exporter = new ChannelExporter(token);
|
||||||
|
|
||||||
var operations = ProgressManager.CreateOperations(dialog.Channels!.Count);
|
var operations = ProgressManager.CreateOperations(dialog.Channels!.Count);
|
||||||
|
|
||||||
// Export channels
|
|
||||||
var successfulExportCount = 0;
|
var successfulExportCount = 0;
|
||||||
await dialog.Channels.Zip(operations).ParallelForEachAsync(async tuple =>
|
await dialog.Channels.Zip(operations).ParallelForEachAsync(async tuple =>
|
||||||
{
|
{
|
||||||
|
@ -261,7 +238,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await GetChannelExporter(token).ExportAsync(dialog.Guild!, channel!,
|
await exporter.ExportAsync(dialog.Guild!, channel!,
|
||||||
dialog.OutputPath!, dialog.SelectedFormat, _settingsService.DateFormat,
|
dialog.OutputPath!, dialog.SelectedFormat, _settingsService.DateFormat,
|
||||||
dialog.PartitionLimit, dialog.After, dialog.Before, operation);
|
dialog.PartitionLimit, dialog.After, dialog.Before, operation);
|
||||||
|
|
||||||
|
@ -269,7 +246,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
}
|
}
|
||||||
catch (DiscordChatExporterException ex) when (!ex.IsCritical)
|
catch (DiscordChatExporterException ex) when (!ex.IsCritical)
|
||||||
{
|
{
|
||||||
Notifications.Enqueue(ex.Message);
|
Notifications.Enqueue(ex.Message.TrimEnd('.'));
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue