Introduce the foundation for the new username system

pull/1098/head
Tyrrrz 1 year ago
parent bf0d8ab31e
commit 3d2d197904

@ -42,7 +42,7 @@ public partial record Channel
json.GetPropertyOrNull("recipients")? json.GetPropertyOrNull("recipients")?
.EnumerateArrayOrNull()? .EnumerateArrayOrNull()?
.Select(User.Parse) .Select(User.Parse)
.Select(u => u.Name) .Select(u => u.DisplayName)
.Pipe(s => string.Join(", ", s)) ?? .Pipe(s => string.Join(", ", s)) ??
// Fallback // Fallback

@ -11,7 +11,7 @@ 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 record Member( public partial record Member(
User User, User User,
string? Nick, string? DisplayName,
string? AvatarUrl, string? AvatarUrl,
IReadOnlyList<Snowflake> RoleIds) : IHasId IReadOnlyList<Snowflake> RoleIds) : IHasId
{ {
@ -25,7 +25,7 @@ public partial record Member
public static Member Parse(JsonElement json, Snowflake? guildId = null) public static Member Parse(JsonElement json, Snowflake? guildId = null)
{ {
var user = json.GetProperty("user").Pipe(User.Parse); var user = json.GetProperty("user").Pipe(User.Parse);
var nick = json.GetPropertyOrNull("nick")?.GetNonWhiteSpaceStringOrNull(); var displayName = json.GetPropertyOrNull("nick")?.GetNonWhiteSpaceStringOrNull();
var roleIds = json var roleIds = json
.GetPropertyOrNull("roles")? .GetPropertyOrNull("roles")?
@ -43,7 +43,7 @@ public partial record Member
return new Member( return new Member(
user, user,
nick, displayName,
avatarUrl, avatarUrl,
roleIds roleIds
); );

@ -9,13 +9,24 @@ namespace DiscordChatExporter.Core.Discord.Data;
public partial record User( public partial record User(
Snowflake Id, Snowflake Id,
bool IsBot, bool IsBot,
int Discriminator, // Remove after Discord migrates all accounts to the new system.
// With that, also remove the DiscriminatorFormatted and FullName properties.
// Replace existing calls to FullName with Name (not DisplayName).
int? Discriminator,
string Name, string Name,
string DisplayName,
string AvatarUrl) : IHasId string AvatarUrl) : IHasId
{ {
public string DiscriminatorFormatted => $"{Discriminator:0000}"; public string DiscriminatorFormatted => Discriminator is not null
? $"{Discriminator:0000}"
: "0000";
public string FullName => $"{Name}#{DiscriminatorFormatted}"; // This effectively represents the user's true identity.
// In the old system, this is formed from the username and discriminator.
// In the new system, the username is already the user's unique identifier.
public string FullName => Discriminator is not null
? $"{Name}#{DiscriminatorFormatted}"
: Name;
} }
public partial record User public partial record User
@ -24,16 +35,23 @@ public partial record User
{ {
var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse); var id = json.GetProperty("id").GetNonWhiteSpaceString().Pipe(Snowflake.Parse);
var isBot = json.GetPropertyOrNull("bot")?.GetBooleanOrNull() ?? false; var isBot = json.GetPropertyOrNull("bot")?.GetBooleanOrNull() ?? false;
var discriminator = json.GetProperty("discriminator").GetNonWhiteSpaceString().Pipe(int.Parse);
var discriminator = json
.GetPropertyOrNull("discriminator")?
.GetNonWhiteSpaceStringOrNull()?
.Pipe(int.Parse)
.NullIfDefault();
var name = json.GetProperty("username").GetNonNullString(); var name = json.GetProperty("username").GetNonNullString();
var displayName = json.GetPropertyOrNull("global_name")?.GetNonWhiteSpaceStringOrNull() ?? name;
var avatarUrl = var avatarUrl =
json json
.GetPropertyOrNull("avatar")? .GetPropertyOrNull("avatar")?
.GetNonWhiteSpaceStringOrNull()? .GetNonWhiteSpaceStringOrNull()?
.Pipe(h => ImageCdn.GetUserAvatarUrl(id, h)) ?? .Pipe(h => ImageCdn.GetUserAvatarUrl(id, h)) ??
ImageCdn.GetFallbackUserAvatarUrl(discriminator); ImageCdn.GetFallbackUserAvatarUrl(discriminator ?? 0);
return new User(id, isBot, discriminator, name, avatarUrl); return new User(id, isBot, discriminator, name, displayName, avatarUrl);
} }
} }

@ -11,6 +11,7 @@ internal class FromMessageFilter : MessageFilter
public override bool IsMatch(Message message) => public override bool IsMatch(Message message) =>
string.Equals(_value, message.Author.Name, StringComparison.OrdinalIgnoreCase) || string.Equals(_value, message.Author.Name, StringComparison.OrdinalIgnoreCase) ||
string.Equals(_value, message.Author.DisplayName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(_value, message.Author.FullName, StringComparison.OrdinalIgnoreCase) || string.Equals(_value, message.Author.FullName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(_value, message.Author.Id.ToString(), StringComparison.OrdinalIgnoreCase); string.Equals(_value, message.Author.Id.ToString(), StringComparison.OrdinalIgnoreCase);
} }

@ -12,6 +12,7 @@ internal class MentionsMessageFilter : MessageFilter
public override bool IsMatch(Message message) => message.MentionedUsers.Any(user => public override bool IsMatch(Message message) => message.MentionedUsers.Any(user =>
string.Equals(_value, user.Name, StringComparison.OrdinalIgnoreCase) || string.Equals(_value, user.Name, StringComparison.OrdinalIgnoreCase) ||
string.Equals(_value, user.DisplayName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(_value, user.FullName, StringComparison.OrdinalIgnoreCase) || string.Equals(_value, user.FullName, StringComparison.OrdinalIgnoreCase) ||
string.Equals(_value, user.Id.ToString(), StringComparison.OrdinalIgnoreCase) string.Equals(_value, user.Id.ToString(), StringComparison.OrdinalIgnoreCase)
); );

@ -249,12 +249,12 @@ internal partial class HtmlMarkdownVisitor : MarkdownVisitor
var member = mention.TargetId?.Pipe(_context.TryGetMember); var member = mention.TargetId?.Pipe(_context.TryGetMember);
var fullName = member?.User.FullName ?? "Unknown"; var fullName = member?.User.FullName ?? "Unknown";
var nick = member?.Nick ?? member?.User.Name ?? "Unknown"; var displayName = member?.DisplayName ?? member?.User.DisplayName ?? "Unknown";
_buffer.Append( _buffer.Append(
// lang=html // lang=html
$""" $"""
<span class="chatlog__markdown-mention" title="{HtmlEncode(fullName)}">@{HtmlEncode(nick)}</span> <span class="chatlog__markdown-mention" title="{HtmlEncode(fullName)}">@{HtmlEncode(displayName)}</span>
""" """
); );
} }

@ -44,7 +44,7 @@ internal class JsonMessageWriter : MessageWriter
_writer.WriteString("id", user.Id.ToString()); _writer.WriteString("id", user.Id.ToString());
_writer.WriteString("name", user.Name); _writer.WriteString("name", user.Name);
_writer.WriteString("discriminator", user.DiscriminatorFormatted); _writer.WriteString("discriminator", user.DiscriminatorFormatted);
_writer.WriteString("nickname", Context.TryGetMember(user.Id)?.Nick ?? user.Name); _writer.WriteString("nickname", Context.TryGetMember(user.Id)?.DisplayName ?? user.DisplayName);
_writer.WriteString("color", Context.TryGetUserColor(user.Id)?.ToHex()); _writer.WriteString("color", Context.TryGetUserColor(user.Id)?.ToHex());
_writer.WriteBoolean("isBot", user.IsBot); _writer.WriteBoolean("isBot", user.IsBot);

@ -40,9 +40,9 @@
var authorMember = Context.TryGetMember(message.Author.Id); var authorMember = Context.TryGetMember(message.Author.Id);
var authorColor = Context.TryGetUserColor(message.Author.Id); var authorColor = Context.TryGetUserColor(message.Author.Id);
var authorNick = message.Author.IsBot var authorDisplayName = message.Author.IsBot
? message.Author.Name ? message.Author.DisplayName
: authorMember?.Nick ?? message.Author.Name; : authorMember?.DisplayName ?? message.Author.DisplayName;
<div id="chatlog__message-container-@message.Id" class="chatlog__message-container @(message.IsPinned ? "chatlog__message-container--pinned" : null)" data-message-id="@message.Id"> <div id="chatlog__message-container-@message.Id" class="chatlog__message-container @(message.IsPinned ? "chatlog__message-container--pinned" : null)" data-message-id="@message.Id">
<div class="chatlog__message"> <div class="chatlog__message">
@ -71,7 +71,7 @@
<div class="chatlog__message-primary"> <div class="chatlog__message-primary">
@* Author name *@ @* Author name *@
<span class="chatlog__system-notification-author" style="@(authorColor is not null ? $"color: rgb({authorColor.Value.R}, {authorColor.Value.G}, {authorColor.Value.B})" : null)" title="@message.Author.FullName" data-user-id="@message.Author.Id">@authorNick</span> <span class="chatlog__system-notification-author" style="@(authorColor is not null ? $"color: rgb({authorColor.Value.R}, {authorColor.Value.G}, {authorColor.Value.B})" : null)" title="@message.Author.FullName" data-user-id="@message.Author.Id">@authorDisplayName</span>
@* Space out the content *@ @* Space out the content *@
<span> </span> <span> </span>
@ -81,7 +81,7 @@
@if (message.Kind == MessageKind.RecipientAdd && message.MentionedUsers.Any()) @if (message.Kind == MessageKind.RecipientAdd && message.MentionedUsers.Any())
{ {
<span>added </span> <span>added </span>
<a class="chatlog__system-notification-link" title="@message.MentionedUsers.First().FullName">@message.MentionedUsers.First().Name</a> <a class="chatlog__system-notification-link" title="@message.MentionedUsers.First().FullName">@message.MentionedUsers.First().DisplayName</a>
<span> to the group.</span> <span> to the group.</span>
} }
else if (message.Kind == MessageKind.RecipientRemove && message.MentionedUsers.Any()) else if (message.Kind == MessageKind.RecipientRemove && message.MentionedUsers.Any())
@ -93,7 +93,7 @@
else else
{ {
<span>removed </span> <span>removed </span>
<a class="chatlog__system-notification-link" title="@message.MentionedUsers.First().FullName">@message.MentionedUsers.First().Name</a> <a class="chatlog__system-notification-link" title="@message.MentionedUsers.First().FullName">@message.MentionedUsers.First().DisplayName</a>
<span> from the group.</span> <span> from the group.</span>
} }
} }
@ -168,12 +168,12 @@
{ {
var referencedUserMember = Context.TryGetMember(message.ReferencedMessage.Author.Id); var referencedUserMember = Context.TryGetMember(message.ReferencedMessage.Author.Id);
var referencedUserColor = Context.TryGetUserColor(message.ReferencedMessage.Author.Id); var referencedUserColor = Context.TryGetUserColor(message.ReferencedMessage.Author.Id);
var referencedUserNick = message.ReferencedMessage.Author.IsBot var referencedUserDisplayName = message.ReferencedMessage.Author.IsBot
? message.ReferencedMessage.Author.Name ? message.ReferencedMessage.Author.DisplayName
: referencedUserMember?.Nick ?? message.ReferencedMessage.Author.Name; : referencedUserMember?.DisplayName ?? message.ReferencedMessage.Author.DisplayName;
<img class="chatlog__reply-avatar" src="@await ResolveAssetUrlAsync(referencedUserMember?.AvatarUrl ?? message.ReferencedMessage.Author.AvatarUrl)" alt="Avatar" loading="lazy"> <img class="chatlog__reply-avatar" src="@await ResolveAssetUrlAsync(referencedUserMember?.AvatarUrl ?? message.ReferencedMessage.Author.AvatarUrl)" alt="Avatar" loading="lazy">
<div class="chatlog__reply-author" style="@(referencedUserColor is not null ? $"color: rgb({referencedUserColor.Value.R}, {referencedUserColor.Value.G}, {referencedUserColor.Value.B})" : null)" title="@message.ReferencedMessage.Author.FullName">@referencedUserNick</div> <div class="chatlog__reply-author" style="@(referencedUserColor is not null ? $"color: rgb({referencedUserColor.Value.R}, {referencedUserColor.Value.G}, {referencedUserColor.Value.B})" : null)" title="@message.ReferencedMessage.Author.FullName">@referencedUserDisplayName</div>
<div class="chatlog__reply-content"> <div class="chatlog__reply-content">
<span class="chatlog__reply-link" onclick="scrollToMessage(event, '@message.ReferencedMessage.Id')"> <span class="chatlog__reply-link" onclick="scrollToMessage(event, '@message.ReferencedMessage.Id')">
@if (!string.IsNullOrWhiteSpace(message.ReferencedMessage.Content) && !message.ReferencedMessage.IsContentHidden()) @if (!string.IsNullOrWhiteSpace(message.ReferencedMessage.Content) && !message.ReferencedMessage.IsContentHidden())
@ -201,12 +201,12 @@
{ {
var interactionUserMember = Context.TryGetMember(message.Interaction.User.Id); var interactionUserMember = Context.TryGetMember(message.Interaction.User.Id);
var interactionUserColor = Context.TryGetUserColor(message.Interaction.User.Id); var interactionUserColor = Context.TryGetUserColor(message.Interaction.User.Id);
var interactionUserNick = message.Interaction.User.IsBot var interactionUserDisplayName = message.Interaction.User.IsBot
? message.Interaction.User.Name ? message.Interaction.User.DisplayName
: interactionUserMember?.Nick ?? message.Interaction.User.Name; : interactionUserMember?.DisplayName ?? message.Interaction.User.DisplayName;
<img class="chatlog__reply-avatar" src="@await ResolveAssetUrlAsync(interactionUserMember?.AvatarUrl ?? message.Interaction.User.AvatarUrl)" alt="Avatar" loading="lazy"> <img class="chatlog__reply-avatar" src="@await ResolveAssetUrlAsync(interactionUserMember?.AvatarUrl ?? message.Interaction.User.AvatarUrl)" alt="Avatar" loading="lazy">
<div class="chatlog__reply-author" style="@(interactionUserColor is not null ? $"color: rgb({interactionUserColor.Value.R}, {interactionUserColor.Value.G}, {interactionUserColor.Value.B})" : null)" title="@message.Interaction.User.FullName">@interactionUserNick</div> <div class="chatlog__reply-author" style="@(interactionUserColor is not null ? $"color: rgb({interactionUserColor.Value.R}, {interactionUserColor.Value.G}, {interactionUserColor.Value.B})" : null)" title="@message.Interaction.User.FullName">@interactionUserDisplayName</div>
<div class="chatlog__reply-content"> <div class="chatlog__reply-content">
used /@message.Interaction.Name used /@message.Interaction.Name
</div> </div>
@ -223,7 +223,7 @@
// Header // Header
<div class="chatlog__header"> <div class="chatlog__header">
@* Author name *@ @* Author name *@
<span class="chatlog__author" style="@(authorColor is not null ? $"color: rgb({authorColor.Value.R}, {authorColor.Value.G}, {authorColor.Value.B})" : null)" title="@message.Author.FullName" data-user-id="@message.Author.Id">@authorNick</span> <span class="chatlog__author" style="@(authorColor is not null ? $"color: rgb({authorColor.Value.R}, {authorColor.Value.G}, {authorColor.Value.B})" : null)" title="@message.Author.FullName" data-user-id="@message.Author.Id">@authorDisplayName</span>
@* Bot tag *@ @* Bot tag *@
@if (message.Author.IsBot) @if (message.Author.IsBot)

@ -59,9 +59,9 @@ internal partial class PlainTextMarkdownVisitor : MarkdownVisitor
await _context.PopulateMemberAsync(mention.TargetId.Value, cancellationToken); await _context.PopulateMemberAsync(mention.TargetId.Value, cancellationToken);
var member = mention.TargetId?.Pipe(_context.TryGetMember); var member = mention.TargetId?.Pipe(_context.TryGetMember);
var name = member?.User.Name ?? "Unknown"; var displayName = member?.DisplayName ?? member?.User.DisplayName ?? "Unknown";
_buffer.Append($"@{name}"); _buffer.Append($"@{displayName}");
} }
else if (mention.Kind == MentionKind.Channel) else if (mention.Kind == MentionKind.Channel)
{ {

@ -9,13 +9,13 @@ internal static class PlainTextMessageExtensions
public static string GetFallbackContent(this Message message) => message.Kind switch public static string GetFallbackContent(this Message message) => message.Kind switch
{ {
MessageKind.RecipientAdd => message.MentionedUsers.Any() MessageKind.RecipientAdd => message.MentionedUsers.Any()
? $"Added {message.MentionedUsers.First().Name} to the group." ? $"Added {message.MentionedUsers.First().DisplayName} to the group."
: "Added a recipient.", : "Added a recipient.",
MessageKind.RecipientRemove => message.MentionedUsers.Any() MessageKind.RecipientRemove => message.MentionedUsers.Any()
? message.Author.Id == message.MentionedUsers.First().Id ? message.Author.Id == message.MentionedUsers.First().Id
? "Left the group." ? "Left the group."
: $"Removed {message.MentionedUsers.First().Name} from the group." : $"Removed {message.MentionedUsers.First().DisplayName} from the group."
: "Removed a recipient.", : "Removed a recipient.",
MessageKind.Call => MessageKind.Call =>

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
namespace DiscordChatExporter.Core.Utils.Extensions; namespace DiscordChatExporter.Core.Utils.Extensions;
@ -10,4 +11,7 @@ public static class GenericExtensions
!predicate(value) !predicate(value)
? value ? value
: null; : null;
public static T? NullIfDefault<T>(this T value) where T : struct =>
value.NullIf(v => EqualityComparer<T>.Default.Equals(v, default));
} }
Loading…
Cancel
Save