Convert several types to records

This commit is contained in:
Tyrrrz 2021-11-09 23:15:56 +02:00
parent 2393a6a472
commit 7c88a21543
38 changed files with 182 additions and 826 deletions

View file

@ -1,9 +0,0 @@
// ReSharper disable CheckNamespace
// TODO: remove after moving to .NET 5
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit
{
}
}

View file

@ -1,9 +0,0 @@
// ReSharper disable CheckNamespace
// TODO: remove after moving to .NET 5
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit
{
}
}

View file

@ -1,27 +1,13 @@
using System.Diagnostics.CodeAnalysis; using System.Net.Http.Headers;
using System.Net.Http.Headers;
namespace DiscordChatExporter.Core.Discord namespace DiscordChatExporter.Core.Discord
{ {
public class AuthToken public record AuthToken(AuthTokenKind Kind, string Value)
{ {
public AuthTokenKind Kind { get; }
public string Value { get; }
public AuthToken(AuthTokenKind kind, string value)
{
Kind = kind;
Value = value;
}
public AuthenticationHeaderValue GetAuthenticationHeader() => Kind switch public AuthenticationHeaderValue GetAuthenticationHeader() => Kind switch
{ {
AuthTokenKind.Bot => new AuthenticationHeaderValue("Bot", Value), AuthTokenKind.Bot => new AuthenticationHeaderValue("Bot", Value),
_ => new AuthenticationHeaderValue(Value) _ => new AuthenticationHeaderValue(Value)
}; };
[ExcludeFromCodeCoverage]
public override string ToString() => Value;
} }
} }

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
@ -10,20 +9,16 @@ using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data namespace DiscordChatExporter.Core.Discord.Data
{ {
// https://discord.com/developers/docs/resources/channel#attachment-object // https://discord.com/developers/docs/resources/channel#attachment-object
public partial class Attachment : IHasId public partial record Attachment(
Snowflake Id,
string Url,
string FileName,
int? Width,
int? Height,
FileSize FileSize) : IHasId
{ {
public Snowflake Id { get; }
public string Url { get; }
public string FileName { get; }
public string FileExtension => Path.GetExtension(FileName); public string FileExtension => Path.GetExtension(FileName);
public int? Width { get; }
public int? Height { get; }
public bool IsImage => FileFormat.IsImage(FileExtension); public bool IsImage => FileFormat.IsImage(FileExtension);
public bool IsVideo => FileFormat.IsVideo(FileExtension); public bool IsVideo => FileFormat.IsVideo(FileExtension);
@ -31,30 +26,9 @@ namespace DiscordChatExporter.Core.Discord.Data
public bool IsAudio => FileFormat.IsAudio(FileExtension); public bool IsAudio => FileFormat.IsAudio(FileExtension);
public bool IsSpoiler => FileName.StartsWith("SPOILER_", StringComparison.Ordinal); public bool IsSpoiler => FileName.StartsWith("SPOILER_", StringComparison.Ordinal);
public FileSize FileSize { get; }
public Attachment(
Snowflake id,
string url,
string fileName,
int? width,
int? height,
FileSize fileSize)
{
Id = id;
Url = url;
FileName = fileName;
Width = width;
Height = height;
FileSize = fileSize;
}
[ExcludeFromCodeCoverage]
public override string ToString() => FileName;
} }
public partial class Attachment public partial record Attachment
{ {
public static Attachment Parse(JsonElement json) public static Attachment Parse(JsonElement json)
{ {

View file

@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis; using System.Linq;
using System.Linq;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
@ -9,12 +8,15 @@ using Tyrrrz.Extensions;
namespace DiscordChatExporter.Core.Discord.Data namespace DiscordChatExporter.Core.Discord.Data
{ {
// https://discord.com/developers/docs/resources/channel#channel-object // https://discord.com/developers/docs/resources/channel#channel-object
public partial class Channel : IHasId public partial record Channel(
Snowflake Id,
ChannelKind Kind,
Snowflake GuildId,
ChannelCategory Category,
string Name,
int? Position,
string? Topic) : IHasId
{ {
public Snowflake Id { get; }
public ChannelKind Kind { get; }
public bool IsTextChannel => Kind is public bool IsTextChannel => Kind is
ChannelKind.GuildTextChat or ChannelKind.GuildTextChat or
ChannelKind.DirectTextChat or ChannelKind.DirectTextChat or
@ -23,40 +25,9 @@ namespace DiscordChatExporter.Core.Discord.Data
ChannelKind.GuildStore; ChannelKind.GuildStore;
public bool IsVoiceChannel => !IsTextChannel; public bool IsVoiceChannel => !IsTextChannel;
public Snowflake GuildId { get; }
public ChannelCategory Category { get; }
public string Name { get; }
public int? Position { get; }
public string? Topic { get; }
public Channel(
Snowflake id,
ChannelKind kind,
Snowflake guildId,
ChannelCategory category,
string name,
int? position,
string? topic)
{
Id = id;
Kind = kind;
GuildId = guildId;
Category = category;
Name = name;
Position = position;
Topic = topic;
}
[ExcludeFromCodeCoverage]
public override string ToString() => Name;
} }
public partial class Channel public partial record Channel
{ {
private static ChannelCategory GetFallbackCategory(ChannelKind channelKind) => new( private static ChannelCategory GetFallbackCategory(ChannelKind channelKind) => new(
Snowflake.Zero, Snowflake.Zero,
@ -77,13 +48,14 @@ namespace DiscordChatExporter.Core.Discord.Data
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse); var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var guildId = json.GetPropertyOrNull("guild_id")?.GetNonWhiteSpaceString().Pipe(Snowflake.Parse); var guildId = json.GetPropertyOrNull("guild_id")?.GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var topic = json.GetPropertyOrNull("topic")?.GetStringOrNull(); var topic = json.GetPropertyOrNull("topic")?.GetStringOrNull();
var kind = (ChannelKind) json.GetProperty("type").GetInt32(); var kind = (ChannelKind)json.GetProperty("type").GetInt32();
var name = var name =
// Guild channel // Guild channel
json.GetPropertyOrNull("name")?.GetStringOrNull() ?? json.GetPropertyOrNull("name")?.GetStringOrNull() ??
// DM channel // DM channel
json.GetPropertyOrNull("recipients")?.EnumerateArray().Select(User.Parse).Select(u => u.Name).JoinToString(", ") ?? json.GetPropertyOrNull("recipients")?.EnumerateArray().Select(User.Parse).Select(u => u.Name)
.JoinToString(", ") ??
// Fallback // Fallback
id.ToString(); id.ToString();

View file

@ -1,31 +1,11 @@
using System.Diagnostics.CodeAnalysis; using System.Text.Json;
using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data namespace DiscordChatExporter.Core.Discord.Data
{ {
public partial class ChannelCategory : IHasId public record ChannelCategory(Snowflake Id, string Name, int? Position) : IHasId
{
public Snowflake Id { get; }
public string Name { get; }
public int? Position { get; }
public ChannelCategory(Snowflake id, string name, int? position)
{
Id = id;
Name = name;
Position = position;
}
[ExcludeFromCodeCoverage]
public override string ToString() => Name;
}
public partial class ChannelCategory
{ {
public static ChannelCategory Unknown { get; } = new(Snowflake.Zero, "<unknown category>", 0); public static ChannelCategory Unknown { get; } = new(Snowflake.Zero, "<unknown category>", 0);

View file

@ -4,16 +4,12 @@ using System.Diagnostics.CodeAnalysis;
namespace DiscordChatExporter.Core.Discord.Data.Common namespace DiscordChatExporter.Core.Discord.Data.Common
{ {
// Loosely based on https://github.com/omar/ByteSize (MIT license) // Loosely based on https://github.com/omar/ByteSize (MIT license)
public readonly partial struct FileSize public readonly partial record struct FileSize(long TotalBytes)
{ {
public long TotalBytes { get; }
public double TotalKiloBytes => TotalBytes / 1024.0; public double TotalKiloBytes => TotalBytes / 1024.0;
public double TotalMegaBytes => TotalKiloBytes / 1024.0; public double TotalMegaBytes => TotalKiloBytes / 1024.0;
public double TotalGigaBytes => TotalMegaBytes / 1024.0; public double TotalGigaBytes => TotalMegaBytes / 1024.0;
public FileSize(long bytes) => TotalBytes = bytes;
private double GetLargestWholeNumberValue() private double GetLargestWholeNumberValue()
{ {
if (Math.Abs(TotalGigaBytes) >= 1) if (Math.Abs(TotalGigaBytes) >= 1)
@ -46,7 +42,7 @@ namespace DiscordChatExporter.Core.Discord.Data.Common
public override string ToString() => $"{GetLargestWholeNumberValue():0.##} {GetLargestWholeNumberSymbol()}"; public override string ToString() => $"{GetLargestWholeNumberValue():0.##} {GetLargestWholeNumberSymbol()}";
} }
public partial struct FileSize public partial record struct FileSize
{ {
public static FileSize FromBytes(long bytes) => new(bytes); public static FileSize FromBytes(long bytes) => new(bytes);
} }

View file

@ -2,15 +2,12 @@
namespace DiscordChatExporter.Core.Discord.Data.Common namespace DiscordChatExporter.Core.Discord.Data.Common
{ {
public partial class IdBasedEqualityComparer : IEqualityComparer<IHasId> public class IdBasedEqualityComparer : IEqualityComparer<IHasId>
{ {
public static IdBasedEqualityComparer Instance { get; } = new();
public bool Equals(IHasId? x, IHasId? y) => x?.Id == y?.Id; public bool Equals(IHasId? x, IHasId? y) => x?.Id == y?.Id;
public int GetHashCode(IHasId obj) => obj.Id.GetHashCode(); public int GetHashCode(IHasId obj) => obj.Id.GetHashCode();
} }
public partial class IdBasedEqualityComparer
{
public static IdBasedEqualityComparer Instance { get; } = new();
}
} }

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
@ -10,63 +9,29 @@ using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data.Embeds namespace DiscordChatExporter.Core.Discord.Data.Embeds
{ {
// https://discord.com/developers/docs/resources/channel#embed-object // https://discord.com/developers/docs/resources/channel#embed-object
public partial class Embed public partial record Embed(
string? Title,
string? Url,
DateTimeOffset? Timestamp,
Color? Color,
EmbedAuthor? Author,
string? Description,
IReadOnlyList<EmbedField> Fields,
EmbedImage? Thumbnail,
EmbedImage? Image,
EmbedFooter? Footer)
{ {
public string? Title { get; } public PlainImageEmbedProjection? TryGetPlainImage() =>
PlainImageEmbedProjection.TryResolve(this);
public string? Url { get; } public SpotifyTrackEmbedProjection? TryGetSpotifyTrack() =>
SpotifyTrackEmbedProjection.TryResolve(this);
public DateTimeOffset? Timestamp { get; } public YouTubeVideoEmbedProjection? TryGetYouTubeVideo() =>
YouTubeVideoEmbedProjection.TryResolve(this);
public Color? Color { get; }
public EmbedAuthor? Author { get; }
public string? Description { get; }
public IReadOnlyList<EmbedField> Fields { get; }
public EmbedImage? Thumbnail { get; }
public EmbedImage? Image { 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)
{
Title = title;
Url = url;
Timestamp = timestamp;
Color = color;
Author = author;
Description = description;
Fields = fields;
Thumbnail = thumbnail;
Image = image;
Footer = footer;
}
public PlainImageEmbedProjection? TryGetPlainImage() => PlainImageEmbedProjection.TryResolve(this);
public SpotifyTrackEmbedProjection? TryGetSpotifyTrack() => SpotifyTrackEmbedProjection.TryResolve(this);
public YouTubeVideoEmbedProjection? TryGetYouTubeVideo() => YouTubeVideoEmbedProjection.TryResolve(this);
[ExcludeFromCodeCoverage]
public override string ToString() => Title ?? "<untitled embed>";
} }
public partial class Embed public partial record Embed
{ {
public static Embed Parse(JsonElement json) public static Embed Parse(JsonElement json)
{ {

View file

@ -1,33 +1,14 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json; using System.Text.Json;
using JsonExtensions.Reading; using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data.Embeds namespace DiscordChatExporter.Core.Discord.Data.Embeds
{ {
// https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure // https://discord.com/developers/docs/resources/channel#embed-object-embed-author-structure
public partial class EmbedAuthor public record EmbedAuthor(
{ string? Name,
public string? Name { get; } string? Url,
string? IconUrl,
public string? Url { get; } string? IconProxyUrl)
public string? IconUrl { get; }
public string? IconProxyUrl { get; }
public EmbedAuthor(string? name, string? url, string? iconUrl, string? iconProxyUrl)
{
Name = name;
Url = url;
IconUrl = iconUrl;
IconProxyUrl = iconProxyUrl;
}
[ExcludeFromCodeCoverage]
public override string ToString() => Name ?? "<unnamed author>";
}
public partial class EmbedAuthor
{ {
public static EmbedAuthor Parse(JsonElement json) public static EmbedAuthor Parse(JsonElement json)
{ {

View file

@ -1,4 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
@ -6,26 +5,10 @@ using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data.Embeds namespace DiscordChatExporter.Core.Discord.Data.Embeds
{ {
// https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure // https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure
public partial class EmbedField public record EmbedField(
{ string Name,
public string Name { get; } string Value,
bool IsInline)
public string Value { get; }
public bool IsInline { get; }
public EmbedField(string name, string value, bool isInline)
{
Name = name;
Value = value;
IsInline = isInline;
}
[ExcludeFromCodeCoverage]
public override string ToString() => $"{Name} | {Value}";
}
public partial class EmbedField
{ {
public static EmbedField Parse(JsonElement json) public static EmbedField Parse(JsonElement json)
{ {

View file

@ -1,4 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
@ -6,26 +5,10 @@ using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data.Embeds namespace DiscordChatExporter.Core.Discord.Data.Embeds
{ {
// https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure // https://discord.com/developers/docs/resources/channel#embed-object-embed-footer-structure
public partial class EmbedFooter public record EmbedFooter(
{ string Text,
public string Text { get; } string? IconUrl,
string? IconProxyUrl)
public string? IconUrl { get; }
public string? IconProxyUrl { get; }
public EmbedFooter(string text, string? iconUrl, string? iconProxyUrl)
{
Text = text;
IconUrl = iconUrl;
IconProxyUrl = iconProxyUrl;
}
[ExcludeFromCodeCoverage]
public override string ToString() => Text;
}
public partial class EmbedFooter
{ {
public static EmbedFooter Parse(JsonElement json) public static EmbedFooter Parse(JsonElement json)
{ {

View file

@ -4,26 +4,11 @@ using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data.Embeds namespace DiscordChatExporter.Core.Discord.Data.Embeds
{ {
// https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure // https://discord.com/developers/docs/resources/channel#embed-object-embed-image-structure
public partial class EmbedImage public record EmbedImage(
{ string? Url,
public string? Url { get; } string? ProxyUrl,
int? Width,
public string? ProxyUrl { get; } int? Height)
public int? Width { get; }
public int? Height { get; }
public EmbedImage(string? url, string? proxyUrl, int? width, int? height)
{
Url = url;
ProxyUrl = proxyUrl;
Height = height;
Width = width;
}
}
public partial class EmbedImage
{ {
public static EmbedImage Parse(JsonElement json) public static EmbedImage Parse(JsonElement json)
{ {

View file

@ -1,22 +1,11 @@
using System.Diagnostics.CodeAnalysis; using System.IO;
using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using DiscordChatExporter.Core.Utils; using DiscordChatExporter.Core.Utils;
namespace DiscordChatExporter.Core.Discord.Data.Embeds namespace DiscordChatExporter.Core.Discord.Data.Embeds
{ {
public partial class PlainImageEmbedProjection public record PlainImageEmbedProjection(string Url)
{
public string Url { get; }
public PlainImageEmbedProjection(string url) => Url = url;
[ExcludeFromCodeCoverage]
public override string ToString() => Url;
}
public partial class PlainImageEmbedProjection
{ {
public static PlainImageEmbedProjection? TryResolve(Embed embed) public static PlainImageEmbedProjection? TryResolve(Embed embed)
{ {

View file

@ -1,21 +1,13 @@
using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
namespace DiscordChatExporter.Core.Discord.Data.Embeds namespace DiscordChatExporter.Core.Discord.Data.Embeds
{ {
public partial class SpotifyTrackEmbedProjection public partial record SpotifyTrackEmbedProjection(string TrackId)
{ {
public string TrackId { get; }
public string Url => $"https://open.spotify.com/embed/track/{TrackId}"; public string Url => $"https://open.spotify.com/embed/track/{TrackId}";
public SpotifyTrackEmbedProjection(string trackId) => TrackId = trackId;
[ExcludeFromCodeCoverage]
public override string ToString() => Url;
} }
public partial class SpotifyTrackEmbedProjection public partial record SpotifyTrackEmbedProjection
{ {
private static string? TryParseTrackId(string embedUrl) private static string? TryParseTrackId(string embedUrl)
{ {

View file

@ -1,21 +1,13 @@
using System.Diagnostics.CodeAnalysis; using System.Text.RegularExpressions;
using System.Text.RegularExpressions;
namespace DiscordChatExporter.Core.Discord.Data.Embeds namespace DiscordChatExporter.Core.Discord.Data.Embeds
{ {
public partial class YouTubeVideoEmbedProjection public partial record YouTubeVideoEmbedProjection(string VideoId)
{ {
public string VideoId { get; }
public string Url => $"https://www.youtube.com/embed/{VideoId}"; public string Url => $"https://www.youtube.com/embed/{VideoId}";
public YouTubeVideoEmbedProjection(string videoId) => VideoId = videoId;
[ExcludeFromCodeCoverage]
public override string ToString() => Url;
} }
public partial class YouTubeVideoEmbedProjection public partial record YouTubeVideoEmbedProjection
{ {
// Adapted from YoutubeExplode // Adapted from YoutubeExplode
// https://github.com/Tyrrrz/YoutubeExplode/blob/5be164be20019783913f76fcc98f18c65aebe9f0/YoutubeExplode/Videos/VideoId.cs#L34-L64 // https://github.com/Tyrrrz/YoutubeExplode/blob/5be164be20019783913f76fcc98f18c65aebe9f0/YoutubeExplode/Videos/VideoId.cs#L34-L64

View file

@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis; using System.Linq;
using System.Linq;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Utils; using DiscordChatExporter.Core.Utils;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
@ -9,36 +8,21 @@ using Tyrrrz.Extensions;
namespace DiscordChatExporter.Core.Discord.Data namespace DiscordChatExporter.Core.Discord.Data
{ {
// https://discord.com/developers/docs/resources/emoji#emoji-object // https://discord.com/developers/docs/resources/emoji#emoji-object
public partial class Emoji public partial record Emoji(
{
// Only present on custom emoji // Only present on custom emoji
public string? Id { get; } string? Id,
// Name of custom emoji (e.g. LUL) or actual representation of standard emoji (e.g. 🙂) // Name of custom emoji (e.g. LUL) or actual representation of standard emoji (e.g. 🙂)
public string Name { get; } string Name,
bool IsAnimated,
string ImageUrl)
{
// Name of custom emoji (e.g. LUL) or name of standard emoji (e.g. slight_smile) // Name of custom emoji (e.g. LUL) or name of standard emoji (e.g. slight_smile)
public string Code => !string.IsNullOrWhiteSpace(Id) public string Code => !string.IsNullOrWhiteSpace(Id)
? Name ? Name
: EmojiIndex.TryGetCode(Name) ?? Name; : EmojiIndex.TryGetCode(Name) ?? Name;
public bool IsAnimated { get; }
public string ImageUrl { get; }
public Emoji(string? id, string name, bool isAnimated, string imageUrl)
{
Id = id;
Name = name;
IsAnimated = isAnimated;
ImageUrl = imageUrl;
}
[ExcludeFromCodeCoverage]
public override string ToString() => Name;
} }
public partial class Emoji public partial record Emoji
{ {
private static string GetTwemojiName(string name) => name private static string GetTwemojiName(string name) => name
.GetRunes() .GetRunes()

View file

@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis; using System.Text.Json;
using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
@ -7,26 +6,7 @@ using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data namespace DiscordChatExporter.Core.Discord.Data
{ {
// https://discord.com/developers/docs/resources/guild#guild-object // https://discord.com/developers/docs/resources/guild#guild-object
public partial class Guild : IHasId public record Guild(Snowflake Id, string Name, string IconUrl) : IHasId
{
public Snowflake Id { get; }
public string Name { get; }
public string IconUrl { get; }
public Guild(Snowflake id, string name, string iconUrl)
{
Id = id;
Name = name;
IconUrl = iconUrl;
}
[ExcludeFromCodeCoverage]
public override string ToString() => Name;
}
public partial class Guild
{ {
public static Guild DirectMessages { get; } = new( public static Guild DirectMessages { get; } = new(
Snowflake.Zero, Snowflake.Zero,

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
@ -10,28 +9,15 @@ using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data namespace DiscordChatExporter.Core.Discord.Data
{ {
// https://discord.com/developers/docs/resources/guild#guild-member-object // https://discord.com/developers/docs/resources/guild#guild-member-object
public partial class Member : IHasId public partial record Member(
User User,
string Nick,
IReadOnlyList<Snowflake> RoleIds) : IHasId
{ {
public Snowflake Id => User.Id; public Snowflake Id => User.Id;
public User User { get; }
public string Nick { get; }
public IReadOnlyList<Snowflake> RoleIds { get; }
public Member(User user, string nick, IReadOnlyList<Snowflake> roleIds)
{
User = user;
Nick = nick;
RoleIds = roleIds;
}
[ExcludeFromCodeCoverage]
public override string ToString() => Nick;
} }
public partial class Member public partial record Member
{ {
public static Member CreateForUser(User user) => new( public static Member CreateForUser(User user) => new(
user, user,
@ -44,9 +30,12 @@ namespace DiscordChatExporter.Core.Discord.Data
var user = json.GetProperty("user").Pipe(User.Parse); var user = json.GetProperty("user").Pipe(User.Parse);
var nick = json.GetPropertyOrNull("nick")?.GetStringOrNull(); var nick = json.GetPropertyOrNull("nick")?.GetStringOrNull();
var roleIds = var roleIds = json
json.GetPropertyOrNull("roles")?.EnumerateArray().Select(j => j.GetNonWhiteSpaceString()).Select(Snowflake.Parse).ToArray() ?? .GetPropertyOrNull("roles")?
Array.Empty<Snowflake>(); .EnumerateArray()
.Select(j => j.GetNonWhiteSpaceString())
.Select(Snowflake.Parse)
.ToArray() ?? Array.Empty<Snowflake>();
return new Member( return new Member(
user, user,

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
@ -11,73 +10,21 @@ using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data namespace DiscordChatExporter.Core.Discord.Data
{ {
// https://discord.com/developers/docs/resources/channel#message-object // https://discord.com/developers/docs/resources/channel#message-object
public partial class Message : IHasId public record Message(
{ Snowflake Id,
public Snowflake Id { get; } MessageKind Kind,
User Author,
public MessageKind Kind { get; } DateTimeOffset Timestamp,
DateTimeOffset? EditedTimestamp,
public User Author { get; } DateTimeOffset? CallEndedTimestamp,
bool IsPinned,
public DateTimeOffset Timestamp { get; } string Content,
IReadOnlyList<Attachment> Attachments,
public DateTimeOffset? EditedTimestamp { get; } IReadOnlyList<Embed> Embeds,
IReadOnlyList<Reaction> Reactions,
public DateTimeOffset? CallEndedTimestamp { get; } IReadOnlyList<User> MentionedUsers,
MessageReference? Reference,
public bool IsPinned { get; } Message? ReferencedMessage) : IHasId
public string Content { get; }
public IReadOnlyList<Attachment> Attachments { get; }
public IReadOnlyList<Embed> Embeds { get; }
public IReadOnlyList<Reaction> Reactions { get; }
public IReadOnlyList<User> MentionedUsers { get; }
public MessageReference? Reference { get; }
public Message? ReferencedMessage { get; }
public Message(
Snowflake id,
MessageKind kind,
User author,
DateTimeOffset timestamp,
DateTimeOffset? editedTimestamp,
DateTimeOffset? callEndedTimestamp,
bool isPinned,
string content,
IReadOnlyList<Attachment> attachments,
IReadOnlyList<Embed> embeds,
IReadOnlyList<Reaction> reactions,
IReadOnlyList<User> mentionedUsers,
MessageReference? messageReference,
Message? referencedMessage)
{
Id = id;
Kind = kind;
Author = author;
Timestamp = timestamp;
EditedTimestamp = editedTimestamp;
CallEndedTimestamp = callEndedTimestamp;
IsPinned = isPinned;
Content = content;
Attachments = attachments;
Embeds = embeds;
Reactions = reactions;
MentionedUsers = mentionedUsers;
Reference = messageReference;
ReferencedMessage = referencedMessage;
}
[ExcludeFromCodeCoverage]
public override string ToString() => Content;
}
public partial class Message
{ {
public static Message Parse(JsonElement json) public static Message Parse(JsonElement json)
{ {
@ -85,8 +32,9 @@ namespace DiscordChatExporter.Core.Discord.Data
var author = json.GetProperty("author").Pipe(User.Parse); var author = json.GetProperty("author").Pipe(User.Parse);
var timestamp = json.GetProperty("timestamp").GetDateTimeOffset(); var timestamp = json.GetProperty("timestamp").GetDateTimeOffset();
var editedTimestamp = json.GetPropertyOrNull("edited_timestamp")?.GetDateTimeOffset(); var editedTimestamp = json.GetPropertyOrNull("edited_timestamp")?.GetDateTimeOffset();
var callEndedTimestamp = json.GetPropertyOrNull("call")?.GetPropertyOrNull("ended_timestamp")?.GetDateTimeOffset(); var callEndedTimestamp = json.GetPropertyOrNull("call")?.GetPropertyOrNull("ended_timestamp")
var kind = (MessageKind) json.GetProperty("type").GetInt32(); ?.GetDateTimeOffset();
var kind = (MessageKind)json.GetProperty("type").GetInt32();
var isPinned = json.GetPropertyOrNull("pinned")?.GetBoolean() ?? false; var isPinned = json.GetPropertyOrNull("pinned")?.GetBoolean() ?? false;
var messageReference = json.GetPropertyOrNull("message_reference")?.Pipe(MessageReference.Parse); var messageReference = json.GetPropertyOrNull("message_reference")?.Pipe(MessageReference.Parse);
var referencedMessage = json.GetPropertyOrNull("referenced_message")?.Pipe(Parse); var referencedMessage = json.GetPropertyOrNull("referenced_message")?.Pipe(Parse);
@ -96,7 +44,7 @@ namespace DiscordChatExporter.Core.Discord.Data
MessageKind.RecipientAdd => "Added a recipient.", MessageKind.RecipientAdd => "Added a recipient.",
MessageKind.RecipientRemove => "Removed a recipient.", MessageKind.RecipientRemove => "Removed a recipient.",
MessageKind.Call => MessageKind.Call =>
$"Started a call that lasted {callEndedTimestamp?.Pipe(t => t - timestamp).Pipe(t => (int) t.TotalMinutes) ?? 0} minutes.", $"Started a call that lasted {callEndedTimestamp?.Pipe(t => t - timestamp).Pipe(t => (int)t.TotalMinutes) ?? 0} minutes.",
MessageKind.ChannelNameChange => "Changed the channel name.", MessageKind.ChannelNameChange => "Changed the channel name.",
MessageKind.ChannelIconChange => "Changed the channel icon.", MessageKind.ChannelIconChange => "Changed the channel icon.",
MessageKind.ChannelPinnedMessage => "Pinned a message.", MessageKind.ChannelPinnedMessage => "Pinned a message.",

View file

@ -1,4 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
using JsonExtensions.Reading; using JsonExtensions.Reading;
@ -6,26 +5,7 @@ using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data namespace DiscordChatExporter.Core.Discord.Data
{ {
// https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure // https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure
public partial class MessageReference public record MessageReference(Snowflake? MessageId, Snowflake? ChannelId, Snowflake? GuildId)
{
public Snowflake? MessageId { get; }
public Snowflake? ChannelId { get; }
public Snowflake? GuildId { get; }
public MessageReference(Snowflake? messageId, Snowflake? channelId, Snowflake? guildId)
{
MessageId = messageId;
ChannelId = channelId;
GuildId = guildId;
}
[ExcludeFromCodeCoverage]
public override string ToString() => MessageId?.ToString() ?? "<unknown reference>";
}
public partial class MessageReference
{ {
public static MessageReference Parse(JsonElement json) public static MessageReference Parse(JsonElement json)
{ {

View file

@ -1,27 +1,10 @@
using System.Diagnostics.CodeAnalysis; using System.Text.Json;
using System.Text.Json;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
namespace DiscordChatExporter.Core.Discord.Data namespace DiscordChatExporter.Core.Discord.Data
{ {
// https://discord.com/developers/docs/resources/channel#reaction-object // https://discord.com/developers/docs/resources/channel#reaction-object
public partial class Reaction public record Reaction(Emoji Emoji, int Count)
{
public Emoji Emoji { get; }
public int Count { get; }
public Reaction(Emoji emoji, int count)
{
Emoji = emoji;
Count = count;
}
[ExcludeFromCodeCoverage]
public override string ToString() => $"{Emoji} ({Count})";
}
public partial class Reaction
{ {
public static Reaction Parse(JsonElement json) public static Reaction Parse(JsonElement json)
{ {

View file

@ -1,5 +1,4 @@
using System.Diagnostics.CodeAnalysis; using System.Drawing;
using System.Drawing;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
@ -8,33 +7,7 @@ using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data namespace DiscordChatExporter.Core.Discord.Data
{ {
// https://discord.com/developers/docs/topics/permissions#role-object // https://discord.com/developers/docs/topics/permissions#role-object
public partial class Role : IHasId public record Role(Snowflake Id, string Name, int Position, Color? Color) : IHasId
{
public Snowflake Id { get; }
public string Name { get; }
public int Position { get; }
public Color? Color { get; }
public Role(
Snowflake id,
string name,
int position,
Color? color)
{
Id = id;
Name = name;
Position = position;
Color = color;
}
[ExcludeFromCodeCoverage]
public override string ToString() => Name;
}
public partial class Role
{ {
public static Role Parse(JsonElement json) public static Role Parse(JsonElement json)
{ {

View file

@ -1,5 +1,4 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json; using System.Text.Json;
using DiscordChatExporter.Core.Discord.Data.Common; using DiscordChatExporter.Core.Discord.Data.Common;
using DiscordChatExporter.Core.Utils.Extensions; using DiscordChatExporter.Core.Utils.Extensions;
@ -8,41 +7,19 @@ using JsonExtensions.Reading;
namespace DiscordChatExporter.Core.Discord.Data namespace DiscordChatExporter.Core.Discord.Data
{ {
// https://discord.com/developers/docs/resources/user#user-object // https://discord.com/developers/docs/resources/user#user-object
public partial class User : IHasId public partial record User(
Snowflake Id,
bool IsBot,
int Discriminator,
string Name,
string AvatarUrl) : IHasId
{ {
public Snowflake Id { get; }
public bool IsBot { get; }
public int Discriminator { get; }
public string DiscriminatorFormatted => $"{Discriminator:0000}"; public string DiscriminatorFormatted => $"{Discriminator:0000}";
public string Name { get; }
public string FullName => $"{Name}#{DiscriminatorFormatted}"; public string FullName => $"{Name}#{DiscriminatorFormatted}";
public string AvatarUrl { get; }
public User(
Snowflake id,
bool isBot,
int discriminator,
string name,
string avatarUrl)
{
Id = id;
IsBot = isBot;
Discriminator = discriminator;
Name = name;
AvatarUrl = avatarUrl;
}
[ExcludeFromCodeCoverage]
public override string ToString() => FullName;
} }
public partial class User public partial record User
{ {
private static string GetDefaultAvatarUrl(int discriminator) => private static string GetDefaultAvatarUrl(int discriminator) =>
$"https://cdn.discordapp.com/embed/avatars/{discriminator % 5}.png"; $"https://cdn.discordapp.com/embed/avatars/{discriminator % 5}.png";

View file

@ -5,29 +5,20 @@ using System.Text.RegularExpressions;
namespace DiscordChatExporter.Core.Discord namespace DiscordChatExporter.Core.Discord
{ {
public readonly partial struct Snowflake public readonly record struct Snowflake(ulong Value)
{ {
public ulong Value { get; }
public Snowflake(ulong value) => Value = value;
public DateTimeOffset ToDate() => DateTimeOffset.FromUnixTimeMilliseconds( public DateTimeOffset ToDate() => DateTimeOffset.FromUnixTimeMilliseconds(
(long) ((Value >> 22) + 1420070400000UL) (long)((Value >> 22) + 1420070400000UL)
).ToLocalTime(); ).ToLocalTime();
[ExcludeFromCodeCoverage] [ExcludeFromCodeCoverage]
public override string ToString() => Value.ToString(CultureInfo.InvariantCulture); public override string ToString() => Value.ToString(CultureInfo.InvariantCulture);
}
public partial struct Snowflake
{
public static Snowflake Zero { get; } = new(0); public static Snowflake Zero { get; } = new(0);
public static Snowflake FromDate(DateTimeOffset date) public static Snowflake FromDate(DateTimeOffset date) => new(
{ ((ulong)date.ToUnixTimeMilliseconds() - 1420070400000UL) << 22
var value = ((ulong) date.ToUnixTimeMilliseconds() - 1420070400000UL) << 22; );
return new Snowflake(value);
}
public static Snowflake? TryParse(string? str, IFormatProvider? formatProvider = null) public static Snowflake? TryParse(string? str, IFormatProvider? formatProvider = null)
{ {
@ -35,8 +26,7 @@ namespace DiscordChatExporter.Core.Discord
return null; return null;
// As number // As number
if (Regex.IsMatch(str, @"^\d+$") && if (Regex.IsMatch(str, @"^\d+$") && ulong.TryParse(str, NumberStyles.Number, formatProvider, out var value))
ulong.TryParse(str, NumberStyles.Number, formatProvider, out var value))
{ {
return new Snowflake(value); return new Snowflake(value);
} }
@ -55,19 +45,4 @@ namespace DiscordChatExporter.Core.Discord
public static Snowflake Parse(string str) => Parse(str, null); public static Snowflake Parse(string str) => Parse(str, null);
} }
public partial struct Snowflake : IComparable<Snowflake>, IEquatable<Snowflake>
{
public int CompareTo(Snowflake other) => Value.CompareTo(other.Value);
public bool Equals(Snowflake other) => CompareTo(other) == 0;
public override bool Equals(object? obj) => obj is Snowflake other && Equals(other);
public override int GetHashCode() => Value.GetHashCode();
public static bool operator ==(Snowflake left, Snowflake right) => left.Equals(right);
public static bool operator !=(Snowflake left, Snowflake right) => !(left == right);
}
} }

View file

@ -10,76 +10,35 @@ using DiscordChatExporter.Core.Utils;
namespace DiscordChatExporter.Core.Exporting namespace DiscordChatExporter.Core.Exporting
{ {
public partial class ExportRequest public partial record ExportRequest(
Guild Guild,
Channel Channel,
string OutputPath,
ExportFormat Format,
Snowflake? After,
Snowflake? Before,
PartitionLimit PartitionLimit,
MessageFilter MessageFilter,
bool ShouldDownloadMedia,
bool ShouldReuseMedia,
string DateFormat)
{ {
public Guild Guild { get; } private string? _outputBaseFilePath;
public string OutputBaseFilePath => _outputBaseFilePath ??= GetOutputBaseFilePath(
Guild,
Channel,
OutputPath,
Format,
After,
Before
);
public Channel Channel { get; } public string OutputBaseDirPath => Path.GetDirectoryName(OutputBaseFilePath) ?? OutputPath;
public string OutputPath { get; } public string OutputMediaDirPath => $"{OutputBaseFilePath}_Files{Path.DirectorySeparatorChar}";
public string OutputBaseFilePath { get; }
public string OutputBaseDirPath { get; }
public string OutputMediaDirPath { get; }
public ExportFormat Format { get; }
public Snowflake? After { get; }
public Snowflake? Before { get; }
public PartitionLimit PartitionLimit { get; }
public MessageFilter MessageFilter { get; }
public bool ShouldDownloadMedia { get; }
public bool ShouldReuseMedia { get; }
public string DateFormat { get; }
public ExportRequest(
Guild guild,
Channel channel,
string outputPath,
ExportFormat format,
Snowflake? after,
Snowflake? before,
PartitionLimit partitionLimit,
MessageFilter messageFilter,
bool shouldDownloadMedia,
bool shouldReuseMedia,
string dateFormat)
{
Guild = guild;
Channel = channel;
OutputPath = outputPath;
Format = format;
After = after;
Before = before;
PartitionLimit = partitionLimit;
MessageFilter = messageFilter;
ShouldDownloadMedia = shouldDownloadMedia;
ShouldReuseMedia = shouldReuseMedia;
DateFormat = dateFormat;
OutputBaseFilePath = GetOutputBaseFilePath(
guild,
channel,
outputPath,
format,
after,
before
);
OutputBaseDirPath = Path.GetDirectoryName(OutputBaseFilePath) ?? OutputPath;
OutputMediaDirPath = $"{OutputBaseFilePath}_Files{Path.DirectorySeparatorChar}";
}
} }
public partial class ExportRequest public partial record ExportRequest
{ {
private static string GetOutputBaseFilePath( private static string GetOutputBaseFilePath(
Guild guild, Guild guild,
@ -140,17 +99,17 @@ namespace DiscordChatExporter.Core.Exporting
// Both 'after' and 'before' are set // Both 'after' and 'before' are set
if (after is not null && before is not null) if (after is not null && before is not null)
{ {
buffer.Append($"{after?.ToDate():yyyy-MM-dd} to {before?.ToDate():yyyy-MM-dd}"); buffer.Append($"{after.Value.ToDate():yyyy-MM-dd} to {before.Value.ToDate():yyyy-MM-dd}");
} }
// Only 'after' is set // Only 'after' is set
else if (after is not null) else if (after is not null)
{ {
buffer.Append($"after {after?.ToDate():yyyy-MM-dd}"); buffer.Append($"after {after.Value.ToDate():yyyy-MM-dd}");
} }
// Only 'before' is set // Only 'before' is set
else else if (before is not null)
{ {
buffer.Append($"before {before?.ToDate():yyyy-MM-dd}"); buffer.Append($"before {before.Value.ToDate():yyyy-MM-dd}");
} }
buffer.Append(")"); buffer.Append(")");

View file

@ -1,38 +1,24 @@
using System.Diagnostics.CodeAnalysis; using DiscordChatExporter.Core.Utils;
using DiscordChatExporter.Core.Utils;
namespace DiscordChatExporter.Core.Markdown namespace DiscordChatExporter.Core.Markdown
{ {
internal class EmojiNode : MarkdownNode internal record EmojiNode(
{
// Only present on custom emoji // Only present on custom emoji
public string? Id { get; } string? Id,
// Name of custom emoji (e.g. LUL) or actual representation of standard emoji (e.g. 🙂) // Name of custom emoji (e.g. LUL) or actual representation of standard emoji (e.g. 🙂)
public string Name { get; } string Name,
bool IsAnimated) : MarkdownNode
{
// Name of custom emoji (e.g. LUL) or name of standard emoji (e.g. slight_smile) // Name of custom emoji (e.g. LUL) or name of standard emoji (e.g. slight_smile)
public string Code => !string.IsNullOrWhiteSpace(Id) public string Code => !string.IsNullOrWhiteSpace(Id)
? Name ? Name
: EmojiIndex.TryGetCode(Name) ?? Name; : EmojiIndex.TryGetCode(Name) ?? Name;
public bool IsAnimated { get; }
public bool IsCustomEmoji => !string.IsNullOrWhiteSpace(Id); public bool IsCustomEmoji => !string.IsNullOrWhiteSpace(Id);
public EmojiNode(string? id, string name, bool isAnimated)
{
Id = id;
Name = name;
IsAnimated = isAnimated;
}
public EmojiNode(string name) public EmojiNode(string name)
: this(null, name, false) : this(null, name, false)
{ {
} }
[ExcludeFromCodeCoverage]
public override string ToString() => $"<Emoji> {Name}";
} }
} }

View file

@ -1,29 +1,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace DiscordChatExporter.Core.Markdown namespace DiscordChatExporter.Core.Markdown
{ {
internal class FormattingNode : MarkdownNode internal record FormattingNode(FormattingKind Kind, IReadOnlyList<MarkdownNode> Children) : MarkdownNode;
{
public FormattingKind Kind { get; }
public IReadOnlyList<MarkdownNode> Children { get; }
public FormattingNode(FormattingKind kind, IReadOnlyList<MarkdownNode> children)
{
Kind = kind;
Children = children;
}
[ExcludeFromCodeCoverage]
public override string ToString()
{
var childrenFormatted = Children.Count == 1
? Children.Single().ToString()
: "+" + Children.Count;
return $"<{Kind}> ({childrenFormatted})";
}
}
} }

View file

@ -1,17 +1,4 @@
using System.Diagnostics.CodeAnalysis; namespace DiscordChatExporter.Core.Markdown
namespace DiscordChatExporter.Core.Markdown
{ {
internal class InlineCodeBlockNode : MarkdownNode internal record InlineCodeBlockNode(string Code) : MarkdownNode;
{
public string Code { get; }
public InlineCodeBlockNode(string code)
{
Code = code;
}
[ExcludeFromCodeCoverage]
public override string ToString() => $"<Code> {Code}";
}
} }

View file

@ -1,34 +1,14 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
namespace DiscordChatExporter.Core.Markdown namespace DiscordChatExporter.Core.Markdown
{ {
internal class LinkNode : MarkdownNode internal record LinkNode(
string Url,
IReadOnlyList<MarkdownNode> Children) : MarkdownNode
{ {
public string Url { get; }
public IReadOnlyList<MarkdownNode> Children { get; }
public LinkNode(string url, IReadOnlyList<MarkdownNode> children)
{
Url = url;
Children = children;
}
public LinkNode(string url) public LinkNode(string url)
: this(url, new[] {new TextNode(url)}) : this(url, new[] { new TextNode(url) })
{ {
} }
[ExcludeFromCodeCoverage]
public override string ToString()
{
var childrenFormatted = Children.Count == 1
? Children.Single().ToString()
: "+" + Children.Count;
return $"<Link> ({childrenFormatted})";
}
} }
} }

View file

@ -1,6 +1,4 @@
namespace DiscordChatExporter.Core.Markdown namespace DiscordChatExporter.Core.Markdown
{ {
internal abstract class MarkdownNode internal abstract record MarkdownNode;
{
}
} }

View file

@ -1,20 +1,4 @@
using System.Diagnostics.CodeAnalysis; namespace DiscordChatExporter.Core.Markdown
namespace DiscordChatExporter.Core.Markdown
{ {
internal class MentionNode : MarkdownNode internal record MentionNode(string Id, MentionKind Kind) : MarkdownNode;
{
public string Id { get; }
public MentionKind Kind { get; }
public MentionNode(string id, MentionKind kind)
{
Id = id;
Kind = kind;
}
[ExcludeFromCodeCoverage]
public override string ToString() => $"<{Kind} mention> {Id}";
}
} }

View file

@ -1,20 +1,4 @@
using System.Diagnostics.CodeAnalysis; namespace DiscordChatExporter.Core.Markdown
namespace DiscordChatExporter.Core.Markdown
{ {
internal class MultiLineCodeBlockNode : MarkdownNode internal record MultiLineCodeBlockNode(string Language, string Code) : MarkdownNode;
{
public string Language { get; }
public string Code { get; }
public MultiLineCodeBlockNode(string language, string code)
{
Language = language;
Code = code;
}
[ExcludeFromCodeCoverage]
public override string ToString() => $"<{Language}> {Code}";
}
} }

View file

@ -2,23 +2,10 @@
namespace DiscordChatExporter.Core.Markdown.Parsing namespace DiscordChatExporter.Core.Markdown.Parsing
{ {
internal readonly struct StringPart internal readonly record struct StringPart(string Target, int StartIndex, int Length)
{ {
public string Target { get; }
public int StartIndex { get; }
public int Length { get; }
public int EndIndex => StartIndex + Length; public int EndIndex => StartIndex + Length;
public StringPart(string target, int startIndex, int length)
{
Target = target;
StartIndex = startIndex;
Length = length;
}
public StringPart(string target) public StringPart(string target)
: this(target, 0, target.Length) : this(target, 0, target.Length)
{ {

View file

@ -1,17 +1,4 @@
using System.Diagnostics.CodeAnalysis; namespace DiscordChatExporter.Core.Markdown
namespace DiscordChatExporter.Core.Markdown
{ {
internal class TextNode : MarkdownNode internal record TextNode(string Text) : MarkdownNode;
{
public string Text { get; }
public TextNode(string text)
{
Text = text;
}
[ExcludeFromCodeCoverage]
public override string ToString() => Text;
}
} }

View file

@ -1,15 +1,6 @@
using System; using System;
using System.Diagnostics.CodeAnalysis;
namespace DiscordChatExporter.Core.Markdown namespace DiscordChatExporter.Core.Markdown
{ {
internal class UnixTimestampNode : MarkdownNode internal record UnixTimestampNode(DateTimeOffset Value) : MarkdownNode;
{
public DateTimeOffset Value { get; }
public UnixTimestampNode(DateTimeOffset value) => Value = value;
[ExcludeFromCodeCoverage]
public override string ToString() => Value.ToString();
}
} }

View file

@ -1,9 +0,0 @@
// ReSharper disable CheckNamespace
// TODO: remove after moving to .NET 5
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit
{
}
}

View file

@ -1,6 +1,5 @@
using DiscordChatExporter.Core.Discord; using DiscordChatExporter.Core.Discord;
using DiscordChatExporter.Core.Exporting; using DiscordChatExporter.Core.Exporting;
using DiscordChatExporter.Core.Exporting.Partitioning;
using Tyrrrz.Settings; using Tyrrrz.Settings;
namespace DiscordChatExporter.Gui.Services namespace DiscordChatExporter.Gui.Services