mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-23 19:26:57 -04:00
Use nullable
This commit is contained in:
parent
1bf9d9e2e2
commit
e5a2852165
42 changed files with 195 additions and 196 deletions
|
@ -7,7 +7,6 @@ using CliFx.Utilities;
|
|||
using DiscordChatExporter.Core.Models;
|
||||
using DiscordChatExporter.Core.Services;
|
||||
using DiscordChatExporter.Core.Services.Helpers;
|
||||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Cli.Commands
|
||||
{
|
||||
|
@ -22,7 +21,7 @@ namespace DiscordChatExporter.Cli.Commands
|
|||
public ExportFormat ExportFormat { get; set; } = ExportFormat.HtmlDark;
|
||||
|
||||
[CommandOption("output", 'o', Description = "Output file or directory path.")]
|
||||
public string OutputPath { get; set; }
|
||||
public string? OutputPath { get; set; }
|
||||
|
||||
[CommandOption("after",Description = "Limit to messages sent after this date.")]
|
||||
public DateTimeOffset? After { get; set; }
|
||||
|
@ -34,7 +33,7 @@ namespace DiscordChatExporter.Cli.Commands
|
|||
public int? PartitionLimit { get; set; }
|
||||
|
||||
[CommandOption("dateformat", Description = "Date format used in output.")]
|
||||
public string DateFormat { get; set; }
|
||||
public string? DateFormat { get; set; }
|
||||
|
||||
protected ExportCommandBase(SettingsService settingsService, DataService dataService, ExportService exportService)
|
||||
: base(dataService)
|
||||
|
@ -46,8 +45,8 @@ namespace DiscordChatExporter.Cli.Commands
|
|||
protected async Task ExportChannelAsync(IConsole console, Channel channel)
|
||||
{
|
||||
// Configure settings
|
||||
if (!DateFormat.IsNullOrWhiteSpace())
|
||||
SettingsService.DateFormat = DateFormat;
|
||||
if (!string.IsNullOrWhiteSpace(DateFormat))
|
||||
SettingsService.DateFormat = DateFormat!;
|
||||
|
||||
console.Output.Write($"Exporting channel [{channel.Name}]... ");
|
||||
var progress = console.CreateProgressTicker();
|
||||
|
@ -57,7 +56,7 @@ namespace DiscordChatExporter.Cli.Commands
|
|||
|
||||
// Generate file path if not set or is a directory
|
||||
var filePath = OutputPath;
|
||||
if (filePath.IsNullOrWhiteSpace() || ExportHelper.IsDirectoryPath(filePath))
|
||||
if (string.IsNullOrWhiteSpace(filePath) || ExportHelper.IsDirectoryPath(filePath))
|
||||
{
|
||||
// Generate default file name
|
||||
var fileName = ExportHelper.GetDefaultExportFileName(ExportFormat, chatLog.Guild,
|
||||
|
|
|
@ -6,13 +6,14 @@
|
|||
<Version>2.15</Version>
|
||||
<Company>Tyrrrz</Company>
|
||||
<Copyright>Copyright (c) Alexey Golub</Copyright>
|
||||
<Nullable>enable</Nullable>
|
||||
<ApplicationIcon>..\favicon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CliFx" Version="0.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" />
|
||||
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.3" />
|
||||
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.3" />
|
||||
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -16,9 +16,9 @@ namespace DiscordChatExporter.Core.Markdown.Internal
|
|||
{
|
||||
}
|
||||
|
||||
public ParsedMatch<T> Match(StringPart stringPart)
|
||||
public ParsedMatch<T>? Match(StringPart stringPart)
|
||||
{
|
||||
ParsedMatch<T> earliestMatch = null;
|
||||
ParsedMatch<T>? earliestMatch = null;
|
||||
|
||||
// Try to match the input with each matcher and get the match with the lowest start index
|
||||
foreach (var matcher in _matchers)
|
||||
|
|
|
@ -2,6 +2,6 @@
|
|||
{
|
||||
internal interface IMatcher<T>
|
||||
{
|
||||
ParsedMatch<T> Match(StringPart stringPart);
|
||||
ParsedMatch<T>? Match(StringPart stringPart);
|
||||
}
|
||||
}
|
|
@ -1,8 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Markdown.Internal
|
||||
|
@ -23,7 +19,7 @@ namespace DiscordChatExporter.Core.Markdown.Internal
|
|||
{
|
||||
}
|
||||
|
||||
public ParsedMatch<T> Match(StringPart stringPart)
|
||||
public ParsedMatch<T>? Match(StringPart stringPart)
|
||||
{
|
||||
var match = _regex.Match(stringPart.Target, stringPart.StartIndex, stringPart.Length);
|
||||
if (!match.Success)
|
||||
|
|
|
@ -20,7 +20,7 @@ namespace DiscordChatExporter.Core.Markdown.Internal
|
|||
{
|
||||
}
|
||||
|
||||
public ParsedMatch<T> Match(StringPart stringPart)
|
||||
public ParsedMatch<T>? Match(StringPart stringPart)
|
||||
{
|
||||
var index = stringPart.Target.IndexOf(_needle, stringPart.StartIndex, stringPart.Length, _comparison);
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ using System.Linq;
|
|||
using System.Text.RegularExpressions;
|
||||
using DiscordChatExporter.Core.Markdown.Internal;
|
||||
using DiscordChatExporter.Core.Markdown.Nodes;
|
||||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Markdown
|
||||
{
|
||||
|
@ -125,7 +124,7 @@ namespace DiscordChatExporter.Core.Markdown
|
|||
// Capture <:lul:123456> or <a:lul:123456>
|
||||
private static readonly IMatcher<Node> CustomEmojiNodeMatcher = new RegexMatcher<Node>(
|
||||
new Regex("<(a)?:(.+?):(\\d+?)>", DefaultRegexOptions),
|
||||
m => new EmojiNode(m.Groups[3].Value, m.Groups[2].Value, !m.Groups[1].Value.IsNullOrWhiteSpace()));
|
||||
m => new EmojiNode(m.Groups[3].Value, m.Groups[2].Value, !string.IsNullOrWhiteSpace(m.Groups[1].Value)));
|
||||
|
||||
/* Links */
|
||||
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Markdown.Nodes
|
||||
namespace DiscordChatExporter.Core.Markdown.Nodes
|
||||
{
|
||||
public class EmojiNode : Node
|
||||
{
|
||||
public string Id { get; }
|
||||
public string? Id { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public bool IsAnimated { get; }
|
||||
|
||||
public bool IsCustomEmoji => !Id.IsNullOrWhiteSpace();
|
||||
public bool IsCustomEmoji => !string.IsNullOrWhiteSpace(Id);
|
||||
|
||||
public EmojiNode(string id, string name, bool isAnimated)
|
||||
public EmojiNode(string? id, string name, bool isAnimated)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
{
|
||||
public string Id { get; }
|
||||
|
||||
public string ParentId { get; }
|
||||
public string? ParentId { get; }
|
||||
|
||||
public string GuildId { get; }
|
||||
public string? GuildId { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public string Topic { get; }
|
||||
public string? Topic { get; }
|
||||
|
||||
public ChannelType Type { get; }
|
||||
|
||||
public Channel(string id, string parentId, 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;
|
||||
|
|
|
@ -2,10 +2,11 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.3" />
|
||||
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -8,28 +8,29 @@ namespace DiscordChatExporter.Core.Models
|
|||
|
||||
public class Embed
|
||||
{
|
||||
public string Title { get; }
|
||||
public string? Title { get; }
|
||||
|
||||
public string Url { get; }
|
||||
public string? Url { get; }
|
||||
|
||||
public DateTimeOffset? Timestamp { get; }
|
||||
|
||||
// TODO: this should be nullable and default color should be set in CSS
|
||||
public Color Color { get; }
|
||||
|
||||
public EmbedAuthor Author { get; }
|
||||
public EmbedAuthor? Author { get; }
|
||||
|
||||
public string Description { get; }
|
||||
public string? Description { get; }
|
||||
|
||||
public IReadOnlyList<EmbedField> Fields { get; }
|
||||
|
||||
public EmbedImage Thumbnail { get; }
|
||||
public EmbedImage? Thumbnail { get; }
|
||||
|
||||
public EmbedImage Image { get; }
|
||||
public EmbedImage? Image { get; }
|
||||
|
||||
public EmbedFooter Footer { get; }
|
||||
public EmbedFooter? Footer { get; }
|
||||
|
||||
public Embed(string title, string url, DateTimeOffset? timestamp, Color color, EmbedAuthor author, string description,
|
||||
IReadOnlyList<EmbedField> fields, EmbedImage thumbnail, EmbedImage image, EmbedFooter footer)
|
||||
public Embed(string? title, string? url, DateTimeOffset? timestamp, Color color, EmbedAuthor? author, string? description,
|
||||
IReadOnlyList<EmbedField> fields, EmbedImage? thumbnail, EmbedImage? image, EmbedFooter? footer)
|
||||
{
|
||||
Title = title;
|
||||
Url = url;
|
||||
|
@ -43,6 +44,6 @@ namespace DiscordChatExporter.Core.Models
|
|||
Footer = footer;
|
||||
}
|
||||
|
||||
public override string ToString() => Title;
|
||||
public override string ToString() => Title ?? "<untitled embed>";
|
||||
}
|
||||
}
|
|
@ -4,19 +4,19 @@ namespace DiscordChatExporter.Core.Models
|
|||
|
||||
public class EmbedAuthor
|
||||
{
|
||||
public string Name { get; }
|
||||
public string? Name { get; }
|
||||
|
||||
public string Url { get; }
|
||||
public string? Url { get; }
|
||||
|
||||
public string IconUrl { get; }
|
||||
public string? IconUrl { get; }
|
||||
|
||||
public EmbedAuthor(string name, string url, string iconUrl)
|
||||
public EmbedAuthor(string? name, string? url, string? iconUrl)
|
||||
{
|
||||
Name = name;
|
||||
Url = url;
|
||||
IconUrl = iconUrl;
|
||||
}
|
||||
|
||||
public override string ToString() => Name;
|
||||
public override string ToString() => Name ?? "<unnamed author>";
|
||||
}
|
||||
}
|
|
@ -6,9 +6,9 @@ namespace DiscordChatExporter.Core.Models
|
|||
{
|
||||
public string Text { get; }
|
||||
|
||||
public string IconUrl { get; }
|
||||
public string? IconUrl { get; }
|
||||
|
||||
public EmbedFooter(string text, string iconUrl)
|
||||
public EmbedFooter(string text, string? iconUrl)
|
||||
{
|
||||
Text = text;
|
||||
IconUrl = iconUrl;
|
||||
|
|
|
@ -4,13 +4,13 @@ namespace DiscordChatExporter.Core.Models
|
|||
|
||||
public class EmbedImage
|
||||
{
|
||||
public string Url { get; }
|
||||
public string? Url { get; }
|
||||
|
||||
public int? Width { get; }
|
||||
|
||||
public int? Height { get; }
|
||||
|
||||
public EmbedImage(string url, int? width, int? height)
|
||||
public EmbedImage(string? url, int? width, int? height)
|
||||
{
|
||||
Url = url;
|
||||
Height = height;
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace DiscordChatExporter.Core.Models
|
|||
|
||||
public partial class Emoji
|
||||
{
|
||||
public string Id { get; }
|
||||
public string? Id { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
|
@ -16,7 +16,7 @@ namespace DiscordChatExporter.Core.Models
|
|||
|
||||
public string ImageUrl { get; }
|
||||
|
||||
public Emoji(string id, string name, bool isAnimated)
|
||||
public Emoji(string? id, string name, bool isAnimated)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
|
@ -37,10 +37,10 @@ namespace DiscordChatExporter.Core.Models
|
|||
private static string GetTwemojiName(string emoji) =>
|
||||
GetCodePoints(emoji).Select(i => i.ToString("x")).JoinToString("-");
|
||||
|
||||
public static string GetImageUrl(string id, string name, bool isAnimated)
|
||||
public static string GetImageUrl(string? id, string name, bool isAnimated)
|
||||
{
|
||||
// Custom emoji
|
||||
if (!id.IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(id))
|
||||
{
|
||||
// Animated
|
||||
if (isAnimated)
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Models
|
||||
namespace DiscordChatExporter.Core.Models
|
||||
{
|
||||
// https://discordapp.com/developers/docs/resources/guild#guild-object
|
||||
// https://discordapp.string.IsNullOrWhiteSpace(com/developers/docs/resources/guild#guild-object
|
||||
|
||||
public partial class Guild
|
||||
{
|
||||
|
@ -10,11 +8,11 @@ namespace DiscordChatExporter.Core.Models
|
|||
|
||||
public string Name { get; }
|
||||
|
||||
public string IconHash { get; }
|
||||
public string? IconHash { get; }
|
||||
|
||||
public string IconUrl { get; }
|
||||
|
||||
public Guild(string id, string name, string iconHash)
|
||||
public Guild(string id, string name, string? iconHash)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
|
@ -28,9 +26,9 @@ namespace DiscordChatExporter.Core.Models
|
|||
|
||||
public partial class Guild
|
||||
{
|
||||
public static string GetIconUrl(string id, string iconHash)
|
||||
public static string GetIconUrl(string id, string? iconHash)
|
||||
{
|
||||
return !iconHash.IsNullOrWhiteSpace()
|
||||
return !string.IsNullOrWhiteSpace(iconHash)
|
||||
? $"https://cdn.discordapp.com/icons/{id}/{iconHash}.png"
|
||||
: "https://cdn.discordapp.com/embed/avatars/0.png";
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace DiscordChatExporter.Core.Models
|
|||
|
||||
public DateTimeOffset? EditedTimestamp { get; }
|
||||
|
||||
public string Content { get; }
|
||||
public string? Content { get; }
|
||||
|
||||
public IReadOnlyList<Attachment> Attachments { get; }
|
||||
|
||||
|
@ -32,7 +32,7 @@ namespace DiscordChatExporter.Core.Models
|
|||
public bool IsPinned { get; }
|
||||
|
||||
public Message(string id, string channelId, MessageType type, User author, DateTimeOffset timestamp,
|
||||
DateTimeOffset? editedTimestamp, string content, IReadOnlyList<Attachment> attachments,
|
||||
DateTimeOffset? editedTimestamp, string? content, IReadOnlyList<Attachment> attachments,
|
||||
IReadOnlyList<Embed> embeds, IReadOnlyList<Reaction> reactions, IReadOnlyList<User> mentionedUsers,
|
||||
bool isPinned)
|
||||
{
|
||||
|
@ -50,6 +50,6 @@ namespace DiscordChatExporter.Core.Models
|
|||
IsPinned = isPinned;
|
||||
}
|
||||
|
||||
public override string ToString() => Content;
|
||||
public override string ToString() => Content ?? "<message without content>";
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Models
|
||||
{
|
||||
|
@ -15,13 +14,13 @@ namespace DiscordChatExporter.Core.Models
|
|||
|
||||
public string FullName { get; }
|
||||
|
||||
public string AvatarHash { get; }
|
||||
public string? AvatarHash { get; }
|
||||
|
||||
public string AvatarUrl { get; }
|
||||
|
||||
public bool IsBot { get; }
|
||||
|
||||
public User(string id, int discriminator, string name, string avatarHash, bool isBot)
|
||||
public User(string id, int discriminator, string name, string? avatarHash, bool isBot)
|
||||
{
|
||||
Id = id;
|
||||
Discriminator = discriminator;
|
||||
|
@ -40,10 +39,10 @@ namespace DiscordChatExporter.Core.Models
|
|||
{
|
||||
public static string GetFullName(string name, int discriminator) => $"{name}#{discriminator:0000}";
|
||||
|
||||
public static string GetAvatarUrl(string id, int discriminator, string avatarHash)
|
||||
public static string GetAvatarUrl(string id, int discriminator, string? avatarHash)
|
||||
{
|
||||
// Custom avatar
|
||||
if (!avatarHash.IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(avatarHash))
|
||||
{
|
||||
// Animated
|
||||
if (avatarHash.StartsWith("a_", StringComparison.Ordinal))
|
||||
|
|
|
@ -96,7 +96,7 @@ namespace DiscordChatExporter.Core.Rendering
|
|||
await RenderFieldAsync(writer, FormatDate(message.Timestamp));
|
||||
|
||||
// Content
|
||||
await RenderFieldAsync(writer, FormatMarkdown(message.Content));
|
||||
await RenderFieldAsync(writer, FormatMarkdown(message.Content ?? ""));
|
||||
|
||||
// Attachments
|
||||
var formattedAttachments = message.Attachments.Select(a => a.Url).JoinToString(",");
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -97,7 +97,7 @@ namespace DiscordChatExporter.Core.Rendering
|
|||
if (node is MultiLineCodeBlockNode multilineCodeBlockNode)
|
||||
{
|
||||
// Set CSS class for syntax highlighting
|
||||
var highlightCssClass = !multilineCodeBlockNode.Language.IsNullOrWhiteSpace()
|
||||
var highlightCssClass = !string.IsNullOrWhiteSpace(multilineCodeBlockNode.Language)
|
||||
? $"language-{multilineCodeBlockNode.Language}"
|
||||
: "nohighlight";
|
||||
|
||||
|
@ -153,7 +153,7 @@ namespace DiscordChatExporter.Core.Rendering
|
|||
// Extract message ID if the link points to a Discord message
|
||||
var linkedMessageId = Regex.Match(linkNode.Url, "^https?://discordapp.com/channels/.*?/(\\d+)/?$").Groups[1].Value;
|
||||
|
||||
return linkedMessageId.IsNullOrWhiteSpace()
|
||||
return string.IsNullOrWhiteSpace(linkedMessageId)
|
||||
? $"<a href=\"{Uri.EscapeUriString(linkNode.Url)}\">{HtmlEncode(linkNode.Title)}</a>"
|
||||
: $"<a href=\"{Uri.EscapeUriString(linkNode.Url)}\" onclick=\"scrollToMessage(event, '{linkedMessageId}')\">{HtmlEncode(linkNode.Title)}</a>";
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ namespace DiscordChatExporter.Core.Rendering
|
|||
private string FormatMarkdown(IReadOnlyList<Node> nodes, bool isTopLevel)
|
||||
{
|
||||
// Emojis are jumbo if all top-level nodes are emoji nodes or whitespace text nodes
|
||||
var isJumbo = isTopLevel && nodes.All(n => n is EmojiNode || n is TextNode textNode && textNode.Text.IsNullOrWhiteSpace());
|
||||
var isJumbo = isTopLevel && nodes.All(n => n is EmojiNode || n is TextNode textNode && string.IsNullOrWhiteSpace(textNode.Text));
|
||||
|
||||
return nodes.Select(n => FormatMarkdown(n, isJumbo)).JoinToString("");
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ namespace DiscordChatExporter.Core.Rendering
|
|||
return $"before {FormatDate(before.Value)}";
|
||||
|
||||
// Neither
|
||||
return null;
|
||||
return "";
|
||||
}
|
||||
|
||||
private string FormatMarkdown(Node node)
|
||||
|
@ -131,43 +131,43 @@ namespace DiscordChatExporter.Core.Rendering
|
|||
await writer.WriteLineAsync("{Embed}");
|
||||
|
||||
// Author name
|
||||
if (!(embed.Author?.Name).IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(embed.Author?.Name))
|
||||
await writer.WriteLineAsync(embed.Author?.Name);
|
||||
|
||||
// URL
|
||||
if (!embed.Url.IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(embed.Url))
|
||||
await writer.WriteLineAsync(embed.Url);
|
||||
|
||||
// Title
|
||||
if (!embed.Title.IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(embed.Title))
|
||||
await writer.WriteLineAsync(FormatMarkdown(embed.Title));
|
||||
|
||||
// Description
|
||||
if (!embed.Description.IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(embed.Description))
|
||||
await writer.WriteLineAsync(FormatMarkdown(embed.Description));
|
||||
|
||||
// Fields
|
||||
foreach (var field in embed.Fields)
|
||||
{
|
||||
// Name
|
||||
if (!field.Name.IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(field.Name))
|
||||
await writer.WriteLineAsync(field.Name);
|
||||
|
||||
// Value
|
||||
if (!field.Value.IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(field.Value))
|
||||
await writer.WriteLineAsync(field.Value);
|
||||
}
|
||||
|
||||
// Thumbnail URL
|
||||
if (!(embed.Thumbnail?.Url).IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(embed.Thumbnail?.Url))
|
||||
await writer.WriteLineAsync(embed.Thumbnail?.Url);
|
||||
|
||||
// Image URL
|
||||
if (!(embed.Image?.Url).IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(embed.Image?.Url))
|
||||
await writer.WriteLineAsync(embed.Image?.Url);
|
||||
|
||||
// Footer text
|
||||
if (!(embed.Footer?.Text).IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(embed.Footer?.Text))
|
||||
await writer.WriteLineAsync(embed.Footer?.Text);
|
||||
|
||||
await writer.WriteLineAsync();
|
||||
|
@ -201,6 +201,7 @@ namespace DiscordChatExporter.Core.Rendering
|
|||
await RenderMessageHeaderAsync(writer, message);
|
||||
|
||||
// Content
|
||||
if (!string.IsNullOrWhiteSpace(message.Content))
|
||||
await writer.WriteLineAsync(FormatMarkdown(message.Content));
|
||||
|
||||
// Separator
|
||||
|
|
|
@ -12,10 +12,10 @@ namespace DiscordChatExporter.Core.Services
|
|||
{
|
||||
private User ParseUser(JToken json)
|
||||
{
|
||||
var id = json["id"].Value<string>();
|
||||
var discriminator = json["discriminator"].Value<int>();
|
||||
var name = json["username"].Value<string>();
|
||||
var avatarHash = json["avatar"].Value<string>();
|
||||
var id = json["id"]!.Value<string>();
|
||||
var discriminator = json["discriminator"]!.Value<int>();
|
||||
var name = json["username"]!.Value<string>();
|
||||
var avatarHash = json["avatar"]!.Value<string>();
|
||||
var isBot = json["bot"]?.Value<bool>() ?? false;
|
||||
|
||||
return new User(id, discriminator, name, avatarHash, isBot);
|
||||
|
@ -23,9 +23,9 @@ namespace DiscordChatExporter.Core.Services
|
|||
|
||||
private Guild ParseGuild(JToken json)
|
||||
{
|
||||
var id = json["id"].Value<string>();
|
||||
var name = json["name"].Value<string>();
|
||||
var iconHash = json["icon"].Value<string>();
|
||||
var id = json["id"]!.Value<string>();
|
||||
var name = json["name"]!.Value<string>();
|
||||
var iconHash = json["icon"]!.Value<string>();
|
||||
|
||||
return new Guild(id, name, iconHash);
|
||||
}
|
||||
|
@ -33,23 +33,23 @@ namespace DiscordChatExporter.Core.Services
|
|||
private Channel ParseChannel(JToken json)
|
||||
{
|
||||
// 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>();
|
||||
|
||||
// Try to extract guild ID
|
||||
var guildId = json["guild_id"]?.Value<string>();
|
||||
|
||||
// If the guild ID is blank, it's direct messages
|
||||
if (guildId.IsNullOrWhiteSpace())
|
||||
if (string.IsNullOrWhiteSpace(guildId))
|
||||
guildId = Guild.DirectMessages.Id;
|
||||
|
||||
// Try to extract name
|
||||
var name = json["name"]?.Value<string>();
|
||||
|
||||
// If the name is blank, it's direct messages
|
||||
if (name.IsNullOrWhiteSpace())
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
name = json["recipients"].Select(ParseUser).Select(u => u.Name).JoinToString(", ");
|
||||
|
||||
return new Channel(id, parentId, guildId, name, topic, type);
|
||||
|
@ -57,20 +57,20 @@ namespace DiscordChatExporter.Core.Services
|
|||
|
||||
private Role ParseRole(JToken json)
|
||||
{
|
||||
var id = json["id"].Value<string>();
|
||||
var name = json["name"].Value<string>();
|
||||
var id = json["id"]!.Value<string>();
|
||||
var name = json["name"]!.Value<string>();
|
||||
|
||||
return new Role(id, name);
|
||||
}
|
||||
|
||||
private Attachment ParseAttachment(JToken json)
|
||||
{
|
||||
var id = json["id"].Value<string>();
|
||||
var url = json["url"].Value<string>();
|
||||
var id = json["id"]!.Value<string>();
|
||||
var url = json["url"]!.Value<string>();
|
||||
var width = json["width"]?.Value<int>();
|
||||
var height = json["height"]?.Value<int>();
|
||||
var fileName = json["filename"].Value<string>();
|
||||
var fileSizeBytes = json["size"].Value<long>();
|
||||
var fileName = json["filename"]!.Value<string>();
|
||||
var fileSizeBytes = json["size"]!.Value<long>();
|
||||
|
||||
var fileSize = new FileSize(fileSizeBytes);
|
||||
|
||||
|
@ -88,8 +88,8 @@ namespace DiscordChatExporter.Core.Services
|
|||
|
||||
private EmbedField ParseEmbedField(JToken json)
|
||||
{
|
||||
var name = json["name"].Value<string>();
|
||||
var value = json["value"].Value<string>();
|
||||
var name = json["name"]!.Value<string>();
|
||||
var value = json["value"]!.Value<string>();
|
||||
var isInline = json["inline"]?.Value<bool>() ?? false;
|
||||
|
||||
return new EmbedField(name, value, isInline);
|
||||
|
@ -106,7 +106,7 @@ namespace DiscordChatExporter.Core.Services
|
|||
|
||||
private EmbedFooter ParseEmbedFooter(JToken json)
|
||||
{
|
||||
var text = json["text"].Value<string>();
|
||||
var text = json["text"]!.Value<string>();
|
||||
var iconUrl = json["icon_url"]?.Value<string>();
|
||||
|
||||
return new EmbedFooter(text, iconUrl);
|
||||
|
@ -122,23 +122,23 @@ namespace DiscordChatExporter.Core.Services
|
|||
|
||||
// Get color
|
||||
var color = json["color"] != null
|
||||
? Color.FromArgb(json["color"].Value<int>()).ResetAlpha()
|
||||
? Color.FromArgb(json["color"]!.Value<int>()).ResetAlpha()
|
||||
: Color.FromArgb(79, 84, 92); // default color
|
||||
|
||||
// Get author
|
||||
var author = json["author"] != null ? ParseEmbedAuthor(json["author"]) : null;
|
||||
var author = json["author"] != null ? ParseEmbedAuthor(json["author"]!) : null;
|
||||
|
||||
// Get fields
|
||||
var fields = json["fields"].EmptyIfNull().Select(ParseEmbedField).ToArray();
|
||||
var fields = (json["fields"] ?? Enumerable.Empty<JToken>()).Select(ParseEmbedField).ToArray();
|
||||
|
||||
// Get thumbnail
|
||||
var thumbnail = json["thumbnail"] != null ? ParseEmbedImage(json["thumbnail"]) : null;
|
||||
var thumbnail = json["thumbnail"] != null ? ParseEmbedImage(json["thumbnail"]!) : null;
|
||||
|
||||
// Get image
|
||||
var image = json["image"] != null ? ParseEmbedImage(json["image"]) : null;
|
||||
var image = json["image"] != null ? ParseEmbedImage(json["image"]!) : null;
|
||||
|
||||
// Get footer
|
||||
var footer = json["footer"] != null ? ParseEmbedFooter(json["footer"]) : null;
|
||||
var footer = json["footer"] != null ? ParseEmbedFooter(json["footer"]!) : null;
|
||||
|
||||
return new Embed(title, url, timestamp, color, author, description, fields, thumbnail, image, footer);
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ namespace DiscordChatExporter.Core.Services
|
|||
private Emoji ParseEmoji(JToken json)
|
||||
{
|
||||
var id = json["id"]?.Value<string>();
|
||||
var name = json["name"]?.Value<string>();
|
||||
var name = json["name"]!.Value<string>();
|
||||
var isAnimated = json["animated"]?.Value<bool>() ?? false;
|
||||
|
||||
return new Emoji(id, name, isAnimated);
|
||||
|
@ -154,8 +154,8 @@ namespace DiscordChatExporter.Core.Services
|
|||
|
||||
private Reaction ParseReaction(JToken json)
|
||||
{
|
||||
var count = json["count"].Value<int>();
|
||||
var emoji = ParseEmoji(json["emoji"]);
|
||||
var count = json["count"]!.Value<int>();
|
||||
var emoji = ParseEmoji(json["emoji"]!);
|
||||
|
||||
return new Reaction(count, emoji);
|
||||
}
|
||||
|
@ -163,12 +163,12 @@ namespace DiscordChatExporter.Core.Services
|
|||
private Message ParseMessage(JToken json)
|
||||
{
|
||||
// Get basic data
|
||||
var id = json["id"].Value<string>();
|
||||
var channelId = json["channel_id"].Value<string>();
|
||||
var timestamp = json["timestamp"].Value<DateTime>().ToDateTimeOffset();
|
||||
var id = json["id"]!.Value<string>();
|
||||
var channelId = json["channel_id"]!.Value<string>();
|
||||
var timestamp = json["timestamp"]!.Value<DateTime>().ToDateTimeOffset();
|
||||
var editedTimestamp = json["edited_timestamp"]?.Value<DateTime?>()?.ToDateTimeOffset();
|
||||
var content = json["content"].Value<string>();
|
||||
var type = (MessageType) json["type"].Value<int>();
|
||||
var content = json["content"]!.Value<string>();
|
||||
var type = (MessageType) json["type"]!.Value<int>();
|
||||
|
||||
// Workarounds for non-default types
|
||||
if (type == MessageType.RecipientAdd)
|
||||
|
@ -187,22 +187,22 @@ namespace DiscordChatExporter.Core.Services
|
|||
content = "Joined the server.";
|
||||
|
||||
// Get author
|
||||
var author = ParseUser(json["author"]);
|
||||
var author = ParseUser(json["author"]!);
|
||||
|
||||
// Get attachments
|
||||
var attachments = json["attachments"].EmptyIfNull().Select(ParseAttachment).ToArray();
|
||||
var attachments = (json["attachments"] ?? Enumerable.Empty<JToken>()).Select(ParseAttachment).ToArray();
|
||||
|
||||
// Get embeds
|
||||
var embeds = json["embeds"].EmptyIfNull().Select(ParseEmbed).ToArray();
|
||||
var embeds = (json["embeds"] ?? Enumerable.Empty<JToken>()).Select(ParseEmbed).ToArray();
|
||||
|
||||
// Get reactions
|
||||
var reactions = json["reactions"].EmptyIfNull().Select(ParseReaction).ToArray();
|
||||
var reactions = (json["reactions"] ?? Enumerable.Empty<JToken>()).Select(ParseReaction).ToArray();
|
||||
|
||||
// Get mentioned users
|
||||
var mentionedUsers = json["mentions"].EmptyIfNull().Select(ParseUser).ToArray();
|
||||
var mentionedUsers = (json["mentions"] ?? Enumerable.Empty<JToken>()).Select(ParseUser).ToArray();
|
||||
|
||||
// Get whether this message is pinned
|
||||
var isPinned = json["pinned"].Value<bool>();
|
||||
var isPinned = json["pinned"]!.Value<bool>();
|
||||
|
||||
return new Message(id, channelId, type, author, timestamp, editedTimestamp, content, attachments, embeds,
|
||||
reactions, mentionedUsers, isPinned);
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace DiscordChatExporter.Core.Services
|
|||
var value = parameter.SubstringAfter("=");
|
||||
|
||||
// Skip empty values
|
||||
if (value.IsNullOrWhiteSpace())
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
continue;
|
||||
|
||||
request.RequestUri = request.RequestUri.SetQueryParameter(key, value);
|
||||
|
@ -53,6 +53,7 @@ namespace DiscordChatExporter.Core.Services
|
|||
|
||||
// Get response
|
||||
using var response = await _httpClient.SendAsync(request);
|
||||
|
||||
// Check status code
|
||||
// We throw our own exception here because default one doesn't have status code
|
||||
if (!response.IsSuccessStatusCode)
|
||||
|
@ -119,7 +120,7 @@ namespace DiscordChatExporter.Core.Services
|
|||
}
|
||||
|
||||
public async Task<IReadOnlyList<Message>> GetChannelMessagesAsync(AuthToken token, string channelId,
|
||||
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double> progress = null)
|
||||
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double>? progress = null)
|
||||
{
|
||||
var result = new List<Message>();
|
||||
|
||||
|
@ -211,7 +212,7 @@ namespace DiscordChatExporter.Core.Services
|
|||
}
|
||||
|
||||
public async Task<ChatLog> GetChatLogAsync(AuthToken token, Guild guild, Channel channel,
|
||||
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double> progress = null)
|
||||
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double>? progress = null)
|
||||
{
|
||||
// Get messages
|
||||
var messages = await GetChannelMessagesAsync(token, channel.Id, after, before, progress);
|
||||
|
@ -223,19 +224,19 @@ namespace DiscordChatExporter.Core.Services
|
|||
}
|
||||
|
||||
public async Task<ChatLog> GetChatLogAsync(AuthToken token, Channel channel,
|
||||
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double> progress = null)
|
||||
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double>? progress = null)
|
||||
{
|
||||
// Get guild
|
||||
var guild = channel.GuildId == Guild.DirectMessages.Id
|
||||
? Guild.DirectMessages
|
||||
: await GetGuildAsync(token, channel.GuildId);
|
||||
var guild = !string.IsNullOrWhiteSpace(channel.GuildId)
|
||||
? await GetGuildAsync(token, channel.GuildId)
|
||||
: Guild.DirectMessages;
|
||||
|
||||
// Get the chat log
|
||||
return await GetChatLogAsync(token, guild, channel, after, before, progress);
|
||||
}
|
||||
|
||||
public async Task<ChatLog> GetChatLogAsync(AuthToken token, string channelId,
|
||||
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double> progress = null)
|
||||
DateTimeOffset? after = null, DateTimeOffset? before = null, IProgress<double>? progress = null)
|
||||
{
|
||||
// Get channel
|
||||
var channel = await GetChannelAsync(token, channelId);
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Failsafe" Version="1.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="Onova" Version="2.4.5" />
|
||||
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Onova" Version="2.5.1" />
|
||||
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.5" />
|
||||
<PackageReference Include="Tyrrrz.Settings" Version="1.3.4" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ namespace DiscordChatExporter.Core.Services
|
|||
{
|
||||
// Create output directory
|
||||
var dirPath = Path.GetDirectoryName(filePath);
|
||||
if (!dirPath.IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(dirPath))
|
||||
Directory.CreateDirectory(dirPath);
|
||||
|
||||
// Render chat log to output file
|
||||
|
@ -74,7 +74,7 @@ namespace DiscordChatExporter.Core.Services
|
|||
var partitionFilePath = $"{fileNameWithoutExt} [{partitionNumber} of {partitions.Length}]{fileExt}";
|
||||
|
||||
// Compose full file path
|
||||
if (!dirPath.IsNullOrWhiteSpace())
|
||||
if (!string.IsNullOrWhiteSpace(dirPath))
|
||||
partitionFilePath = Path.Combine(dirPath, partitionFilePath);
|
||||
|
||||
// Export
|
||||
|
|
|
@ -3,7 +3,6 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Text;
|
||||
using DiscordChatExporter.Core.Models;
|
||||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Core.Services.Helpers
|
||||
{
|
||||
|
@ -12,7 +11,7 @@ namespace DiscordChatExporter.Core.Services.Helpers
|
|||
public static bool IsDirectoryPath(string path) =>
|
||||
path.Last() == Path.DirectorySeparatorChar ||
|
||||
path.Last() == Path.AltDirectorySeparatorChar ||
|
||||
Path.GetExtension(path).IsNullOrWhiteSpace() && !File.Exists(path);
|
||||
string.IsNullOrWhiteSpace(Path.GetExtension(path)) && !File.Exists(path);
|
||||
|
||||
public static string GetDefaultExportFileName(ExportFormat format, Guild guild, Channel channel,
|
||||
DateTimeOffset? after = null, DateTimeOffset? before = null)
|
||||
|
|
|
@ -9,8 +9,10 @@ namespace DiscordChatExporter.Core.Services
|
|||
|
||||
public string DateFormat { get; set; } = "dd-MMM-yy hh:mm tt";
|
||||
|
||||
public AuthToken LastToken { get; set; }
|
||||
public AuthToken? LastToken { get; set; }
|
||||
|
||||
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
|
||||
|
||||
public int? LastPartitionLimit { get; set; }
|
||||
|
||||
public SettingsService()
|
||||
|
|
|
@ -1,6 +1,16 @@
|
|||
namespace DiscordChatExporter.Gui
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace DiscordChatExporter.Gui
|
||||
{
|
||||
public partial class App
|
||||
{
|
||||
private static readonly Assembly Assembly = typeof(App).Assembly;
|
||||
|
||||
public static string Name => Assembly.GetName().Name!;
|
||||
|
||||
public static Version Version => Assembly.GetName().Version!;
|
||||
|
||||
public static string VersionString => Version.ToString(3);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
using DiscordChatExporter.Core.Services;
|
||||
using DiscordChatExporter.Core.Services;
|
||||
using DiscordChatExporter.Gui.ViewModels;
|
||||
using DiscordChatExporter.Gui.ViewModels.Framework;
|
||||
using Stylet;
|
||||
using StyletIoC;
|
||||
|
||||
#if !DEBUG
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
#endif
|
||||
|
||||
namespace DiscordChatExporter.Gui
|
||||
{
|
||||
public class Bootstrapper : Bootstrapper<RootViewModel>
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace DiscordChatExporter.Gui.Converters
|
|||
if (value is DateTimeOffset dateTimeOffsetValue)
|
||||
return dateTimeOffsetValue.DateTime;
|
||||
|
||||
return default;
|
||||
return default(DateTime);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
|
@ -22,7 +22,7 @@ namespace DiscordChatExporter.Gui.Converters
|
|||
if (value is DateTime dateTimeValue)
|
||||
return new DateTimeOffset(dateTimeValue);
|
||||
|
||||
return default;
|
||||
return default(DateTimeOffset);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,12 +10,12 @@ namespace DiscordChatExporter.Gui.Converters
|
|||
{
|
||||
public static ExportFormatToStringConverter Instance { get; } = new ExportFormatToStringConverter();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is ExportFormat exportFormatValue)
|
||||
return exportFormatValue.GetDisplayName();
|
||||
|
||||
return default;
|
||||
return default(string);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
|
|
|
@ -14,7 +14,7 @@ namespace DiscordChatExporter.Gui.Converters
|
|||
if (value is bool boolValue)
|
||||
return !boolValue;
|
||||
|
||||
return default;
|
||||
return default(bool);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
|
@ -22,7 +22,7 @@ namespace DiscordChatExporter.Gui.Converters
|
|||
if (value is bool boolValue)
|
||||
return !boolValue;
|
||||
|
||||
return default;
|
||||
return default(bool);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
<Version>2.15</Version>
|
||||
<Company>Tyrrrz</Company>
|
||||
<Copyright>Copyright (c) Alexey Golub</Copyright>
|
||||
<Nullable>enable</Nullable>
|
||||
<UseWPF>true</UseWPF>
|
||||
<ApplicationIcon>../favicon.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
|
@ -19,11 +20,11 @@
|
|||
<PackageReference Include="Gress" Version="1.1.1" />
|
||||
<PackageReference Include="MaterialDesignColors" Version="1.2.0" />
|
||||
<PackageReference Include="MaterialDesignThemes" Version="2.6.0" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.0.1" />
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.3" />
|
||||
<PackageReference Include="Ookii.Dialogs.Wpf" Version="1.1.0" />
|
||||
<PackageReference Include="PropertyChanged.Fody" Version="3.1.3" />
|
||||
<PackageReference Include="Stylet" Version="1.3.0" />
|
||||
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.3" />
|
||||
<PackageReference Include="Tyrrrz.Extensions" Version="1.6.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordChatExporter.Core.Services;
|
||||
using Onova;
|
||||
using Onova.Exceptions;
|
||||
using Onova.Services;
|
||||
|
@ -13,10 +12,10 @@ namespace DiscordChatExporter.Gui.Services
|
|||
new GithubPackageResolver("Tyrrrz", "DiscordChatExporter", "DiscordChatExporter.zip"),
|
||||
new ZipPackageExtractor());
|
||||
|
||||
private Version _updateVersion;
|
||||
private Version? _updateVersion;
|
||||
private bool _updaterLaunched;
|
||||
|
||||
public async Task<Version> CheckForUpdatesAsync()
|
||||
public async Task<Version?> CheckForUpdatesAsync()
|
||||
{
|
||||
var check = await _updateManager.CheckForUpdatesAsync();
|
||||
return check.CanUpdate ? check.LastVersion : null;
|
||||
|
|
|
@ -7,7 +7,7 @@ namespace DiscordChatExporter.Gui.ViewModels.Components
|
|||
{
|
||||
public Channel Model { get; set; }
|
||||
|
||||
public string Category { get; set; }
|
||||
public string? Category { get; set; }
|
||||
}
|
||||
|
||||
public partial class ChannelViewModel
|
||||
|
|
|
@ -6,7 +6,6 @@ using DiscordChatExporter.Core.Services;
|
|||
using DiscordChatExporter.Core.Services.Helpers;
|
||||
using DiscordChatExporter.Gui.ViewModels.Components;
|
||||
using DiscordChatExporter.Gui.ViewModels.Framework;
|
||||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
||||
{
|
||||
|
@ -21,12 +20,12 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
|||
|
||||
public bool IsSingleChannel => Channels.Count == 1;
|
||||
|
||||
public string OutputPath { get; set; }
|
||||
public string? OutputPath { get; set; }
|
||||
|
||||
public IReadOnlyList<ExportFormat> AvailableFormats =>
|
||||
Enum.GetValues(typeof(ExportFormat)).Cast<ExportFormat>().ToArray();
|
||||
|
||||
public ExportFormat SelectedFormat { get; set; } = ExportFormat.HtmlDark;
|
||||
public ExportFormat SelectedFormat { get; set; }
|
||||
|
||||
public DateTimeOffset? After { get; set; }
|
||||
|
||||
|
@ -38,11 +37,6 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
|||
{
|
||||
_dialogManager = dialogManager;
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
|
||||
protected override void OnViewLoaded()
|
||||
{
|
||||
base.OnViewLoaded();
|
||||
|
||||
// Persist preferences
|
||||
SelectedFormat = _settingsService.LastExportFormat;
|
||||
|
@ -85,7 +79,7 @@ namespace DiscordChatExporter.Gui.ViewModels.Dialogs
|
|||
}
|
||||
|
||||
// If canceled - return
|
||||
if (OutputPath.IsNullOrWhiteSpace())
|
||||
if (string.IsNullOrWhiteSpace(OutputPath))
|
||||
return;
|
||||
|
||||
// Close dialog
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using MaterialDesignThemes.Wpf;
|
||||
using Microsoft.Win32;
|
||||
|
@ -22,10 +23,10 @@ namespace DiscordChatExporter.Gui.ViewModels.Framework
|
|||
var view = _viewManager.CreateAndBindViewForModelIfNecessary(dialogScreen);
|
||||
|
||||
// Set up event routing that will close the view when called from viewmodel
|
||||
void OnDialogOpened(object sender, DialogOpenedEventArgs openArgs)
|
||||
void OnDialogOpened(object? sender, DialogOpenedEventArgs openArgs)
|
||||
{
|
||||
// Delegate to close the dialog and unregister event handler
|
||||
void OnScreenClosed(object o, CloseEventArgs closeArgs)
|
||||
void OnScreenClosed(object? o, EventArgs closeArgs)
|
||||
{
|
||||
openArgs.Session.Close();
|
||||
dialogScreen.Closed -= OnScreenClosed;
|
||||
|
@ -41,7 +42,7 @@ namespace DiscordChatExporter.Gui.ViewModels.Framework
|
|||
return dialogScreen.DialogResult;
|
||||
}
|
||||
|
||||
public string PromptSaveFilePath(string filter = "All files|*.*", string defaultFilePath = "")
|
||||
public string? PromptSaveFilePath(string filter = "All files|*.*", string defaultFilePath = "")
|
||||
{
|
||||
// Create dialog
|
||||
var dialog = new SaveFileDialog
|
||||
|
@ -56,7 +57,7 @@ namespace DiscordChatExporter.Gui.ViewModels.Framework
|
|||
return dialog.ShowDialog() == true ? dialog.FileName : null;
|
||||
}
|
||||
|
||||
public string PromptDirectoryPath(string defaultDirPath = "")
|
||||
public string? PromptDirectoryPath(string defaultDirPath = "")
|
||||
{
|
||||
// Create dialog
|
||||
var dialog = new VistaFolderBrowserDialog
|
||||
|
|
|
@ -1,22 +1,18 @@
|
|||
using Stylet;
|
||||
using System;
|
||||
using Stylet;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels.Framework
|
||||
{
|
||||
public abstract class DialogScreen<T> : Screen
|
||||
public abstract class DialogScreen<T> : PropertyChangedBase
|
||||
{
|
||||
public T DialogResult { get; private set; }
|
||||
|
||||
public event EventHandler? Closed;
|
||||
|
||||
public void Close(T dialogResult = default)
|
||||
{
|
||||
// Set the result
|
||||
DialogResult = dialogResult;
|
||||
|
||||
// If there is a parent - ask them to close this dialog
|
||||
if (Parent != null)
|
||||
RequestClose(Equals(dialogResult, default(T)));
|
||||
// Otherwise close ourselves
|
||||
else
|
||||
((IScreenState) this).Close();
|
||||
Closed?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,7 @@ namespace DiscordChatExporter.Gui.ViewModels.Framework
|
|||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static ChannelViewModel CreateChannelViewModel(this IViewModelFactory factory, Channel model,
|
||||
string category = null)
|
||||
public static ChannelViewModel CreateChannelViewModel(this IViewModelFactory factory, Channel model, string? category = null)
|
||||
{
|
||||
var viewModel = factory.CreateChannelViewModel();
|
||||
viewModel.Model = model;
|
||||
|
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
using DiscordChatExporter.Core.Models;
|
||||
using DiscordChatExporter.Core.Services;
|
||||
|
@ -38,13 +37,13 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||
|
||||
public bool IsBotToken { get; set; }
|
||||
|
||||
public string TokenValue { get; set; }
|
||||
public string? TokenValue { get; set; }
|
||||
|
||||
public IReadOnlyList<GuildViewModel> AvailableGuilds { get; private set; }
|
||||
public IReadOnlyList<GuildViewModel>? AvailableGuilds { get; private set; }
|
||||
|
||||
public GuildViewModel SelectedGuild { get; set; }
|
||||
public GuildViewModel? SelectedGuild { get; set; }
|
||||
|
||||
public IReadOnlyList<ChannelViewModel> SelectedChannels { get; set; }
|
||||
public IReadOnlyList<ChannelViewModel>? SelectedChannels { get; set; }
|
||||
|
||||
public RootViewModel(IViewModelFactory viewModelFactory, DialogManager dialogManager,
|
||||
SettingsService settingsService, UpdateService updateService, DataService dataService,
|
||||
|
@ -58,8 +57,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||
_exportService = exportService;
|
||||
|
||||
// Set title
|
||||
var version = Assembly.GetExecutingAssembly().GetName().Version.ToString(3);
|
||||
DisplayName = $"DiscordChatExporter v{version}";
|
||||
DisplayName = $"{App.Name} v{App.VersionString}";
|
||||
|
||||
// Update busy state when progress manager changes
|
||||
ProgressManager.Bind(o => o.IsActive, (sender, args) => IsBusy = ProgressManager.IsActive);
|
||||
|
@ -83,7 +81,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||
return;
|
||||
|
||||
// Notify user of an update and prepare it
|
||||
Notifications.Enqueue($"Downloading update to DiscordChatExporter v{updateVersion}...");
|
||||
Notifications.Enqueue($"Downloading update to {App.Name} v{updateVersion}...");
|
||||
await _updateService.PrepareUpdateAsync(updateVersion);
|
||||
|
||||
// Prompt user to install update (otherwise install it when application exits)
|
||||
|
@ -140,7 +138,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||
await _dialogManager.ShowDialogAsync(dialog);
|
||||
}
|
||||
|
||||
public bool CanPopulateGuildsAndChannels => !IsBusy && !TokenValue.IsNullOrWhiteSpace();
|
||||
public bool CanPopulateGuildsAndChannels => !IsBusy && !string.IsNullOrWhiteSpace(TokenValue);
|
||||
|
||||
public async void PopulateGuildsAndChannels()
|
||||
{
|
||||
|
@ -150,7 +148,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||
try
|
||||
{
|
||||
// Sanitize token
|
||||
TokenValue = TokenValue.Trim('"');
|
||||
TokenValue = TokenValue!.Trim('"');
|
||||
|
||||
// Create token
|
||||
var token = new AuthToken(
|
||||
|
@ -253,15 +251,15 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||
}
|
||||
}
|
||||
|
||||
public bool CanExportChannels => !IsBusy && !SelectedChannels.IsNullOrEmpty();
|
||||
public bool CanExportChannels => !IsBusy && SelectedGuild != null && SelectedChannels != null && SelectedChannels.Any();
|
||||
|
||||
public async void ExportChannels()
|
||||
{
|
||||
// Get last used token
|
||||
var token = _settingsService.LastToken;
|
||||
var token = _settingsService.LastToken!;
|
||||
|
||||
// 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)
|
||||
|
@ -281,7 +279,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
|||
try
|
||||
{
|
||||
// Generate file path if necessary
|
||||
var filePath = dialog.OutputPath;
|
||||
var filePath = dialog.OutputPath!;
|
||||
if (ExportHelper.IsDirectoryPath(filePath))
|
||||
{
|
||||
// Generate default file name
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue