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