diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs
index 8befa9d8..741aa6ff 100644
--- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs
+++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs
@@ -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
diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs
index b1ba99c4..95f20833 100644
--- a/src/Discord.Net.Core/CDN.cs
+++ b/src/Discord.Net.Core/CDN.cs
@@ -72,6 +72,7 @@ namespace Discord
string extension = FormatToExtension(format, bannerId);
return $"{DiscordConfig.CDNUrl}banners/{userId}/{bannerId}.{extension}?size={size}";
}
+
///
/// Returns the default user avatar URL.
///
@@ -83,6 +84,19 @@ namespace Discord
{
return $"{DiscordConfig.CDNUrl}embed/avatars/{discriminator % 5}.png";
}
+
+ ///
+ /// Returns the default user avatar URL.
+ ///
+ /// The Id of a user.
+ ///
+ /// A URL pointing to the user's default avatar when one isn't set.
+ ///
+ public static string GetDefaultUserAvatarUrl(ulong userId)
+ {
+ return $"{DiscordConfig.CDNUrl}embed/avatars/{(userId >> 22) % 6}.png";
+ }
+
///
/// Returns an icon URL.
///
diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs
index 683f6e6f..a07e73e9 100644
--- a/src/Discord.Net.Core/Entities/Users/IUser.cs
+++ b/src/Discord.Net.Core/Entities/Users/IUser.cs
@@ -47,11 +47,11 @@ namespace Discord
///
string GetDefaultAvatarUrl();
///
- /// 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.
///
string Discriminator { get; }
///
- /// 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.
///
ushort DiscriminatorValue { get; }
///
@@ -87,6 +87,14 @@ namespace Discord
///
UserProperties? PublicFlags { get; }
+ ///
+ /// Gets the user's display name, if it is set. For bots, this will get the application name.
+ ///
+ ///
+ /// This property will be if user has no display name set.
+ ///
+ string GlobalName { get; }
+
///
/// Creates the direct message channel of this user.
///
diff --git a/src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs b/src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs
index c05df7cb..8cc3f1e1 100644
--- a/src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs
+++ b/src/Discord.Net.Core/Extensions/EmbedBuilderExtensions.cs
@@ -27,7 +27,7 @@ namespace Discord
/// Fills the embed author field with the provided user's full username and avatar URL.
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());
/// Converts a object to a .
/// The embed type is not .
diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs
index 3e39f8c1..04e811c7 100644
--- a/src/Discord.Net.Core/Format.cs
+++ b/src/Discord.Net.Core/Format.cs
@@ -108,16 +108,18 @@ namespace Discord
}
///
- /// Formats a user's username + discriminator.
+ /// Formats a user's username and optional discriminator.
///
/// To format the string in bidirectional unicode or not
/// The user whose username and discriminator to format
- /// The username + discriminator
+ /// The username + optional discriminator.
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;
}
}
}
diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs
index b17f9c33..2210ddc8 100644
--- a/src/Discord.Net.Core/Utils/MentionUtils.cs
+++ b/src/Discord.Net.Core/Utils/MentionUtils.cs
@@ -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}>";
///
/// Returns a mention string based on the user ID.
///
///
/// A user mention string (e.g. <@80351110224678912>).
///
- 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}>";
///
/// 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 "";
diff --git a/src/Discord.Net.Rest/API/Common/User.cs b/src/Discord.Net.Rest/API/Common/User.cs
index 08fe88cb..31235981 100644
--- a/src/Discord.Net.Rest/API/Common/User.cs
+++ b/src/Discord.Net.Rest/API/Common/User.cs
@@ -19,6 +19,9 @@ namespace Discord.API
[JsonProperty("accent_color")]
public Optional AccentColor { get; set; }
+ [JsonProperty("global_name")]
+ public Optional GlobalName { get; set; }
+
//CurrentUser
[JsonProperty("verified")]
public Optional Verified { get; set; }
diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
index b26e1b4d..e6f14134 100644
--- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
+++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
@@ -19,8 +19,10 @@ namespace Discord.Rest
private long? _timedOutTicks;
private long? _joinedAtTicks;
private ImmutableArray _roleIds;
- ///
- public string DisplayName => Nickname ?? Username;
+
+ ///
+ public string DisplayName => Nickname ?? GlobalName ?? Username;
+
///
public string Nickname { get; private set; }
///
diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs
index 812ae77d..cdba3e2d 100644
--- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs
+++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs
@@ -30,6 +30,8 @@ namespace Discord.Rest
public Color? AccentColor { get; private set; }
///
public UserProperties? PublicFlags { get; private set; }
+ ///
+ public string GlobalName { get; internal set; }
///
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;
}
///
@@ -121,7 +125,9 @@ namespace Discord.Rest
///
public string GetDefaultAvatarUrl()
- => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue);
+ => DiscriminatorValue != 0
+ ? CDN.GetDefaultUserAvatarUrl(DiscriminatorValue)
+ : CDN.GetDefaultUserAvatarUrl(Id);
///
/// Gets the Username#Discriminator of the user.
diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
index f6ed5824..34d279ec 100644
--- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
+++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs
@@ -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.Unspecified : command.User.Discriminator,
PublicFlags = command.User.PublicFlags.HasValue ? command.User.PublicFlags.Value : Optional.Unspecified,
Id = command.User.Id,
+ GlobalName = command.User.GlobalName,
}
};
}
diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs
index 3cad7201..4d7b08c1 100644
--- a/src/Discord.Net.WebSocket/BaseSocketClient.cs
+++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs
@@ -140,7 +140,7 @@ namespace Discord.WebSocket
///
/// A generic WebSocket-based user; when the user cannot be found.
///
- public abstract SocketUser GetUser(string username, string discriminator);
+ public abstract SocketUser GetUser(string username, string discriminator = null);
///
/// Gets a channel.
///
diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
index c3809ba6..7b58b914 100644
--- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs
@@ -343,7 +343,7 @@ namespace Discord.WebSocket
return null;
}
///
- 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++)
{
diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
index 1f881c0b..1de03ecc 100644
--- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs
+++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs
@@ -425,8 +425,8 @@ namespace Discord.WebSocket
public override SocketUser GetUser(ulong id)
=> State.GetUser(id);
///
- 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);
///
/// Gets a global application command.
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
index 236e7d43..773fec38 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs
@@ -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;
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs
index a40ae59b..7156bc45 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs
@@ -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
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
index 7ad46575..4b212ef5 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
@@ -29,8 +29,10 @@ namespace Discord.WebSocket
/// Gets the guild the user is in.
///
public SocketGuild Guild { get; }
- ///
- public string DisplayName => Nickname ?? Username;
+
+ ///
+ public string DisplayName => Nickname ?? GlobalName ?? Username;
+
///
public string Nickname { get; private set; }
///
@@ -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()
{
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs
index 3bde1bea..3dbc2f9e 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs
@@ -91,7 +91,10 @@ namespace Discord.WebSocket
public Task ModifyAsync(Action 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;
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
index 37a86f98..b45991e8 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
@@ -31,7 +31,7 @@ namespace Discord.WebSocket
///
public string DisplayName
- => GuildUser.Nickname ?? GuildUser.Username;
+ => GuildUser.Nickname ?? GuildUser.GlobalName ?? GuildUser.Username;
///
public string Nickname
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs
index 99c47696..32560f64 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs
@@ -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;
}
}
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
index b01b10ff..f4cdf414 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs
@@ -32,6 +32,9 @@ namespace Discord.WebSocket
internal abstract SocketGlobalUser GlobalUser { get; set; }
internal abstract SocketPresence Presence { get; set; }
+ ///
+ public string GlobalName { get; internal set; }
+
///
public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id);
///
@@ -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
///
public string GetDefaultAvatarUrl()
- => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue);
+ => DiscriminatorValue != 0
+ ? CDN.GetDefaultUserAvatarUrl(DiscriminatorValue)
+ : CDN.GetDefaultUserAvatarUrl(Id);
///
/// Gets the full name of the user (e.g. Example#0001).
diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
index 434c5afd..583e9a74 100644
--- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
+++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
@@ -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
diff --git a/test/Discord.Net.Tests.Unit/MentionUtilsTests.cs b/test/Discord.Net.Tests.Unit/MentionUtilsTests.cs
index abd1191c..b59b9f39 100644
--- a/test/Discord.Net.Tests.Unit/MentionUtilsTests.cs
+++ b/test/Discord.Net.Tests.Unit/MentionUtilsTests.cs
@@ -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"));
}
///
/// Tests