[Feature] Support for the new username system (#2696)

* update a lot of stuff

* update CDN for new avatar calculation

* whoops, forgot to commit

* handle `null` values

* Remove duplicate line

Co-authored-by: Dmitry <dimson-n@users.noreply.github.com>

* updates

* Apply suggestions from code review

Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com>

* Apply suggestion

Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com>

* update `SocketThreadUSer`

---------

Co-authored-by: Dmitry <dimson-n@users.noreply.github.com>
Co-authored-by: Quin Lynch <49576606+quinchs@users.noreply.github.com>
This commit is contained in:
Misha133
2023-06-27 17:11:32 +03:00
committed by GitHub
parent 9ddd922d2f
commit df717e6b2c
22 changed files with 113 additions and 39 deletions

View File

@@ -59,6 +59,17 @@ namespace Discord.Commands
}
}
//By global (display) name
{
await channelUsers
.Where(x => string.Equals(input, x.GlobalName, StringComparison.OrdinalIgnoreCase))
.ForEachAsync(channelUser => AddResult(results, channelUser as T, channelUser.GlobalName == input ? 0.65f : 0.55f))
.ConfigureAwait(false);
foreach (var guildUser in guildUsers.Where(x => string.Equals(input, x.GlobalName, StringComparison.OrdinalIgnoreCase)))
AddResult(results, guildUser as T, guildUser.GlobalName == input ? 0.60f : 0.50f);
}
//By Username (0.5-0.6)
{
await channelUsers

View File

@@ -72,6 +72,7 @@ namespace Discord
string extension = FormatToExtension(format, bannerId);
return $"{DiscordConfig.CDNUrl}banners/{userId}/{bannerId}.{extension}?size={size}";
}
/// <summary>
/// Returns the default user avatar URL.
/// </summary>
@@ -83,6 +84,19 @@ namespace Discord
{
return $"{DiscordConfig.CDNUrl}embed/avatars/{discriminator % 5}.png";
}
/// <summary>
/// Returns the default user avatar URL.
/// </summary>
/// <param name="userId">The Id of a user.</param>
/// <returns>
/// A URL pointing to the user's default avatar when one isn't set.
/// </returns>
public static string GetDefaultUserAvatarUrl(ulong userId)
{
return $"{DiscordConfig.CDNUrl}embed/avatars/{(userId >> 22) % 6}.png";
}
/// <summary>
/// Returns an icon URL.
/// </summary>

View File

@@ -47,11 +47,11 @@ namespace Discord
/// </returns>
string GetDefaultAvatarUrl();
/// <summary>
/// Gets the per-username unique ID for this user.
/// Gets the per-username unique ID for this user. This will return "0000" for users who have migrated to new username system.
/// </summary>
string Discriminator { get; }
/// <summary>
/// Gets the per-username unique ID for this user.
/// Gets the per-username unique ID for this user. This will return 0 for users who have migrated to new username system.
/// </summary>
ushort DiscriminatorValue { get; }
/// <summary>
@@ -87,6 +87,14 @@ namespace Discord
/// </returns>
UserProperties? PublicFlags { get; }
/// <summary>
/// Gets the user's display name, if it is set. For bots, this will get the application name.
/// </summary>
/// <remarks>
/// This property will be <see langword="null"/> if user has no display name set.
/// </remarks>
string GlobalName { get; }
/// <summary>
/// Creates the direct message channel of this user.
/// </summary>

View File

@@ -27,7 +27,7 @@ namespace Discord
/// <summary> Fills the embed author field with the provided user's full username and avatar URL. </summary>
public static EmbedBuilder WithAuthor(this EmbedBuilder builder, IUser user) =>
builder.WithAuthor($"{user.Username}#{user.Discriminator}", user.GetAvatarUrl() ?? user.GetDefaultAvatarUrl());
builder.WithAuthor(user.DiscriminatorValue != 0 ? $"{user.Username}#{user.Discriminator}" : user.Username, user.GetAvatarUrl() ?? user.GetDefaultAvatarUrl());
/// <summary> Converts a <see cref="EmbedType.Rich"/> <see cref="IEmbed"/> object to a <see cref="EmbedBuilder"/>. </summary>
/// <exception cref="InvalidOperationException">The embed type is not <see cref="EmbedType.Rich"/>.</exception>

View File

@@ -108,16 +108,18 @@ namespace Discord
}
/// <summary>
/// Formats a user's username + discriminator.
/// Formats a user's username and optional discriminator.
/// </summary>
/// <param name="doBidirectional">To format the string in bidirectional unicode or not</param>
/// <param name="user">The user whose username and discriminator to format</param>
/// <returns>The username + discriminator</returns>
/// <returns>The username + optional discriminator.</returns>
public static string UsernameAndDiscriminator(IUser user, bool doBidirectional)
{
return doBidirectional
? $"\u2066{user.Username}\u2069#{user.Discriminator}"
: $"{user.Username}#{user.Discriminator}";
if (user.DiscriminatorValue != 0)
return doBidirectional
? $"\u2066{user.Username}\u2069#{user.Discriminator}"
: $"{user.Username}#{user.Discriminator}";
return user.Username;
}
}
}

View File

@@ -12,14 +12,14 @@ namespace Discord
private const char SanitizeChar = '\x200b';
//If the system can't be positive a user doesn't have a nickname, assume useNickname = true (source: Jake)
internal static string MentionUser(string id, bool useNickname = true) => useNickname ? $"<@!{id}>" : $"<@{id}>";
internal static string MentionUser(string id) => $"<@{id}>";
/// <summary>
/// Returns a mention string based on the user ID.
/// </summary>
/// <returns>
/// A user mention string (e.g. &lt;@80351110224678912&gt;).
/// </returns>
public static string MentionUser(ulong id) => MentionUser(id.ToString(), true);
public static string MentionUser(ulong id) => MentionUser(id.ToString());
internal static string MentionChannel(string id) => $"<#{id}>";
/// <summary>
/// Returns a mention string based on the channel ID.
@@ -192,19 +192,19 @@ namespace Discord
return "";
case TagHandling.FullName:
if (user != null)
return $"@{user.Username}#{user.Discriminator}";
return user.DiscriminatorValue != 0 ? $"@{user.Username}#{user.Discriminator}" : user.Username;
else
return "";
case TagHandling.FullNameNoPrefix:
if (user != null)
return $"{user.Username}#{user.Discriminator}";
return user.DiscriminatorValue != 0 ? $"@{user.Username}#{user.Discriminator}" : user.Username;
else
return "";
case TagHandling.Sanitize:
if (guildUser != null && guildUser.Nickname == null)
return MentionUser($"{SanitizeChar}{tag.Key}", false);
return MentionUser($"{SanitizeChar}{tag.Key}");
else
return MentionUser($"{SanitizeChar}{tag.Key}", true);
return MentionUser($"{SanitizeChar}{tag.Key}");
}
}
return "";

View File

@@ -19,6 +19,9 @@ namespace Discord.API
[JsonProperty("accent_color")]
public Optional<uint?> AccentColor { get; set; }
[JsonProperty("global_name")]
public Optional<string> GlobalName { get; set; }
//CurrentUser
[JsonProperty("verified")]
public Optional<bool> Verified { get; set; }

View File

@@ -19,8 +19,10 @@ namespace Discord.Rest
private long? _timedOutTicks;
private long? _joinedAtTicks;
private ImmutableArray<ulong> _roleIds;
/// <inheritdoc />
public string DisplayName => Nickname ?? Username;
/// <inheritdoc cref="IGuildUser.DisplayName"/>
public string DisplayName => Nickname ?? GlobalName ?? Username;
/// <inheritdoc />
public string Nickname { get; private set; }
/// <inheritdoc/>

View File

@@ -30,6 +30,8 @@ namespace Discord.Rest
public Color? AccentColor { get; private set; }
/// <inheritdoc />
public UserProperties? PublicFlags { get; private set; }
/// <inheritdoc />
public string GlobalName { get; internal set; }
/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
@@ -85,13 +87,15 @@ namespace Discord.Rest
if (model.AccentColor.IsSpecified)
AccentColor = model.AccentColor.Value;
if (model.Discriminator.IsSpecified)
DiscriminatorValue = ushort.Parse(model.Discriminator.Value, NumberStyles.None, CultureInfo.InvariantCulture);
DiscriminatorValue = ushort.Parse(model.Discriminator.GetValueOrDefault(null) ?? "0", NumberStyles.None, CultureInfo.InvariantCulture);
if (model.Bot.IsSpecified)
IsBot = model.Bot.Value;
if (model.Username.IsSpecified)
Username = model.Username.Value;
if (model.PublicFlags.IsSpecified)
PublicFlags = model.PublicFlags.Value;
if (model.GlobalName.IsSpecified)
GlobalName = model.GlobalName.Value;
}
/// <inheritdoc />
@@ -121,7 +125,9 @@ namespace Discord.Rest
/// <inheritdoc />
public string GetDefaultAvatarUrl()
=> CDN.GetDefaultUserAvatarUrl(DiscriminatorValue);
=> DiscriminatorValue != 0
? CDN.GetDefaultUserAvatarUrl(DiscriminatorValue)
: CDN.GetDefaultUserAvatarUrl(Id);
/// <summary>
/// Gets the Username#Discriminator of the user.

View File

@@ -201,9 +201,10 @@ namespace Discord.Rest
Username = command.User.Username,
Avatar = command.User.AvatarId,
Bot = command.User.IsBot,
Discriminator = command.User.Discriminator,
Discriminator = command.User.Discriminator == "0000" ? Optional<string>.Unspecified : command.User.Discriminator,
PublicFlags = command.User.PublicFlags.HasValue ? command.User.PublicFlags.Value : Optional<UserProperties>.Unspecified,
Id = command.User.Id,
GlobalName = command.User.GlobalName,
}
};
}

View File

@@ -140,7 +140,7 @@ namespace Discord.WebSocket
/// <returns>
/// A generic WebSocket-based user; <see langword="null" /> when the user cannot be found.
/// </returns>
public abstract SocketUser GetUser(string username, string discriminator);
public abstract SocketUser GetUser(string username, string discriminator = null);
/// <summary>
/// Gets a channel.
/// </summary>

View File

@@ -343,7 +343,7 @@ namespace Discord.WebSocket
return null;
}
/// <inheritdoc />
public override SocketUser GetUser(string username, string discriminator)
public override SocketUser GetUser(string username, string discriminator = null)
{
for (int i = 0; i < _shards.Length; i++)
{

View File

@@ -425,8 +425,8 @@ namespace Discord.WebSocket
public override SocketUser GetUser(ulong id)
=> State.GetUser(id);
/// <inheritdoc />
public override SocketUser GetUser(string username, string discriminator)
=> State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username);
public override SocketUser GetUser(string username, string discriminator = null)
=> State.Users.FirstOrDefault(x => (discriminator is null || x.Discriminator == discriminator) && x.Username == username);
/// <summary>
/// Gets a global application command.

View File

@@ -48,7 +48,10 @@ namespace Discord.WebSocket
}
}
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Global)";
private string DebuggerDisplay => DiscriminatorValue != 0
? $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Guild)"
: $"{Username} ({Id}{(IsBot ? ", Bot" : "")}, Guild)";
internal new SocketGlobalUser Clone() => MemberwiseClone() as SocketGlobalUser;
}
}

View File

@@ -48,7 +48,10 @@ namespace Discord.WebSocket
return entity;
}
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Group)";
private string DebuggerDisplay => DiscriminatorValue != 0
? $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Group)"
: $"{Username} ({Id}{(IsBot ? ", Bot" : "")}, Group)";
internal new SocketGroupUser Clone() => MemberwiseClone() as SocketGroupUser;
#endregion

View File

@@ -29,8 +29,10 @@ namespace Discord.WebSocket
/// Gets the guild the user is in.
/// </summary>
public SocketGuild Guild { get; }
/// <inheritdoc />
public string DisplayName => Nickname ?? Username;
/// <inheritdoc cref="IGuildUser.DisplayName"/>
public string DisplayName => Nickname ?? GlobalName ?? Username;
/// <inheritdoc />
public string Nickname { get; private set; }
/// <inheritdoc/>
@@ -265,7 +267,9 @@ namespace Discord.WebSocket
public string GetGuildAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128)
=> CDN.GetGuildUserAvatarUrl(Id, Guild.Id, GuildAvatarId, size, format);
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Guild)";
private string DebuggerDisplay => DiscriminatorValue != 0
? $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Guild)"
: $"{Username} ({Id}{(IsBot ? ", Bot" : "")}, Guild)";
internal new SocketGuildUser Clone()
{

View File

@@ -91,7 +91,10 @@ namespace Discord.WebSocket
public Task ModifyAsync(Action<SelfUserProperties> func, RequestOptions options = null)
=> UserHelper.ModifyAsync(this, Discord, func, options);
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Self)";
private string DebuggerDisplay => DiscriminatorValue != 0
? $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Self)"
: $"{Username} ({Id}{(IsBot ? ", Bot" : "")}, Self)";
internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser;
}
}

View File

@@ -31,7 +31,7 @@ namespace Discord.WebSocket
/// <inheritdoc/>
public string DisplayName
=> GuildUser.Nickname ?? GuildUser.Username;
=> GuildUser.Nickname ?? GuildUser.GlobalName ?? GuildUser.Username;
/// <inheritdoc/>
public string Nickname

View File

@@ -42,7 +42,10 @@ namespace Discord.WebSocket
return entity;
}
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Unknown)";
private string DebuggerDisplay => DiscriminatorValue != 0
? $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Unknown)"
: $"{Username} ({Id}{(IsBot ? ", Bot" : "")}, Unknown)";
internal new SocketUnknownUser Clone() => MemberwiseClone() as SocketUnknownUser;
}
}

View File

@@ -32,6 +32,9 @@ namespace Discord.WebSocket
internal abstract SocketGlobalUser GlobalUser { get; set; }
internal abstract SocketPresence Presence { get; set; }
/// <inheritdoc />
public string GlobalName { get; internal set; }
/// <inheritdoc />
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
/// <inheritdoc />
@@ -68,10 +71,10 @@ namespace Discord.WebSocket
}
if (model.Discriminator.IsSpecified)
{
var newVal = ushort.Parse(model.Discriminator.Value, NumberStyles.None, CultureInfo.InvariantCulture);
var newVal = ushort.Parse(model.Discriminator.GetValueOrDefault(null) ?? "0", NumberStyles.None, CultureInfo.InvariantCulture);
if (newVal != DiscriminatorValue)
{
DiscriminatorValue = ushort.Parse(model.Discriminator.Value, NumberStyles.None, CultureInfo.InvariantCulture);
DiscriminatorValue = ushort.Parse(model.Discriminator.GetValueOrDefault(null) ?? "0", NumberStyles.None, CultureInfo.InvariantCulture);
hasChanges = true;
}
}
@@ -90,6 +93,11 @@ namespace Discord.WebSocket
PublicFlags = model.PublicFlags.Value;
hasChanges = true;
}
if (model.GlobalName.IsSpecified)
{
GlobalName = model.GlobalName.Value;
hasChanges = true;
}
return hasChanges;
}
@@ -109,7 +117,9 @@ namespace Discord.WebSocket
/// <inheritdoc />
public string GetDefaultAvatarUrl()
=> CDN.GetDefaultUserAvatarUrl(DiscriminatorValue);
=> DiscriminatorValue != 0
? CDN.GetDefaultUserAvatarUrl(DiscriminatorValue)
: CDN.GetDefaultUserAvatarUrl(Id);
/// <summary>
/// Gets the full name of the user (e.g. Example#0001).

View File

@@ -49,7 +49,10 @@ namespace Discord.WebSocket
return entity;
}
private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Webhook)";
private string DebuggerDisplay => DiscriminatorValue != 0
? $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")}, Webhook)"
: $"{Username} ({Id}{(IsBot ? ", Bot" : "")}, Webhook)";
internal new SocketWebhookUser Clone() => MemberwiseClone() as SocketWebhookUser;
#endregion

View File

@@ -16,10 +16,8 @@ namespace Discord
[Fact]
public void MentionUser()
{
Assert.Equal("<@!123>", MentionUtils.MentionUser(123u));
Assert.Equal("<@!123>", MentionUtils.MentionUser("123"));
Assert.Equal("<@!123>", MentionUtils.MentionUser("123", true));
Assert.Equal("<@123>", MentionUtils.MentionUser("123", false));
Assert.Equal("<@123>", MentionUtils.MentionUser(123u));
Assert.Equal("<@123>", MentionUtils.MentionUser("123"));
}
/// <summary>
/// Tests <see cref="MentionUtils.MentionChannel(string)"/>